From 23727004835556c836ee2cdc03ae4172f4495a32 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 27 Apr 2021 17:31:16 +0300 Subject: [PATCH 01/70] [Docs] TSVB supports url drilldowns on 7.13+ (#98460) --- docs/user/dashboard/drilldowns.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/dashboard/drilldowns.asciidoc b/docs/user/dashboard/drilldowns.asciidoc index cbe47f23fcbaf..d74f88babb5ce 100644 --- a/docs/user/dashboard/drilldowns.asciidoc +++ b/docs/user/dashboard/drilldowns.asciidoc @@ -138,7 +138,7 @@ The following panels support dashboard and URL drilldowns. | TSVB ^| X -^| +^| X | Tag Cloud ^| X From 6c46e4107cf72a9b11bfd5b9dd106b016e361805 Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Tue, 27 Apr 2021 09:32:02 -0500 Subject: [PATCH 02/70] [DOCS] Drilldown docs changes for 7.13 (#98390) --- docs/settings/url-drilldown-settings.asciidoc | 31 +++++ docs/setup/settings.asciidoc | 1 + docs/user/dashboard/drilldowns.asciidoc | 125 +++++++----------- 3 files changed, 83 insertions(+), 74 deletions(-) create mode 100644 docs/settings/url-drilldown-settings.asciidoc diff --git a/docs/settings/url-drilldown-settings.asciidoc b/docs/settings/url-drilldown-settings.asciidoc new file mode 100644 index 0000000000000..8be3a21bfbffc --- /dev/null +++ b/docs/settings/url-drilldown-settings.asciidoc @@ -0,0 +1,31 @@ +[[url-drilldown-settings-kb]] +=== URL drilldown settings in {kib} +++++ +URL drilldown settings +++++ + +Configure the URL drilldown settings in your `kibana.yml` configuration file. + +[cols="2*<"] +|=== +| [[url-drilldown-enabled]] `url_drilldown.enabled` + | When `true`, enables URL drilldowns on your {kib} instance. + +| [[external-URL-policy]] `externalUrl.policy` + | Configures the external URL policies. URL drilldowns respect the global *External URL* service, which you can use to deny or allow external URLs. +By default all external URLs are allowed. +|=== + +For example, to allow external URLs only to the `example.com` domain with the `https` scheme, except for the `danger.example.com` sub-domain, +which is denied even when `https` scheme is used: + +["source","yml"] +----------- +externalUrl.policy: + - allow: false + host: danger.example.com + - allow: true + host: example.com + protocol: https +----------- + diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 1b027739169ad..0aab86fb5a9e2 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -756,3 +756,4 @@ include::{kib-repo-dir}/settings/security-settings.asciidoc[] include::{kib-repo-dir}/settings/spaces-settings.asciidoc[] include::{kib-repo-dir}/settings/task-manager-settings.asciidoc[] include::{kib-repo-dir}/settings/telemetry-settings.asciidoc[] +include::{kib-repo-dir}/settings/url-drilldown-settings.asciidoc[] diff --git a/docs/user/dashboard/drilldowns.asciidoc b/docs/user/dashboard/drilldowns.asciidoc index d74f88babb5ce..fc25f84030ee2 100644 --- a/docs/user/dashboard/drilldowns.asciidoc +++ b/docs/user/dashboard/drilldowns.asciidoc @@ -2,8 +2,8 @@ [[drilldowns]] == Create custom dashboard actions -Custom dashboard actions, also known as drilldowns, allow you to create -workflows for analyzing and troubleshooting your data. Drilldowns apply only to the panel that you created the drilldown from, and are not shared across all of the panels. Each panel can have multiple drilldowns. +Custom dashboard actions, or _drilldowns_, allow you to create workflows for analyzing and troubleshooting your data. +Drilldowns apply only to the panel that you created the drilldown from, and are not shared across all panels. Each panel can have multiple drilldowns. Third-party developers can create drilldowns. To learn how to code drilldowns, refer to {kib-repo}blob/{branch}/x-pack/examples/ui_actions_enhanced_examples[this example plugin]. @@ -11,27 +11,23 @@ Third-party developers can create drilldowns. To learn how to code drilldowns, r [[supported-drilldowns]] === Supported drilldowns -{kib} supports two types of drilldowns. - -[NOTE] -===================================== -Some drilldowns are paid subscription features, while others are free. -For a comparison of the Elastic subscription levels, -refer https://www.elastic.co/subscriptions[the subscription page]. -===================================== +{kib} supports dashboard and URL drilldowns. [float] [[dashboard-drilldowns]] ==== Dashboard drilldowns Dashboard drilldowns enable you to open a dashboard from another dashboard, -taking the time range, filters, and other parameters with you, +taking the time range, filters, and other parameters with you so the context remains the same. Dashboard drilldowns help you to continue your analysis from a new perspective. For example, if you have a dashboard that shows the overall status of multiple data center, you can create a drilldown that navigates from the overall status dashboard to a dashboard that shows a single data center or server. +[role="screenshot"] +image:images/drilldown_on_piechart.gif[Drilldown on pie chart that navigates to another dashboard] + [float] [[url-drilldowns]] ==== URL drilldowns @@ -39,45 +35,25 @@ that shows a single data center or server. URL drilldowns enable you to navigate from a dashboard to internal or external URLs. Destination URLs can be dynamic, depending on the dashboard context or user interaction with a panel. For example, if you have a dashboard that shows data from a Github repository, you can create a URL drilldown -that opens Github from the dashboard. +that opens Github from the dashboard panel. + +[role="screenshot"] +image:images/url_drilldown_go_to_github.gif[Drilldown on pie chart that navigates to Github] Some panels support multiple interactions, also known as triggers. The <> you use to create a <> depends on the trigger you choose. URL drilldowns support these types of triggers: -* *Single click* — A single data point in the visualization. +* *Single click* — A single data point in the panel. -* *Range selection* — A range of values in a visualization. +* *Range selection* — A range of values in a panel. For example, *Single click* has `{{event.value}}` and *Range selection* has `{{event.from}}` and `{{event.to}}`. -To disable URL drilldowns on your {kib} instance, add the following line to `kibana.yml` config file: - -["source","yml"] ------------ -url_drilldown.enabled: false ------------ - -URL drilldown also respects the global *External URL* service, which can be used to deny/allow external URLs. -By default all external URLs are allowed. To configure external URL policies you need to use `externalUrl.policy` setting in `kibana.yml`, for example: - -["source","yml"] ------------ -externalUrl.policy: - - allow: false - host: danger.example.com - - allow: true - host: example.com - protocol: https ------------ - -The above rules allow external URLs only to `example.com` domain with `https` scheme, except for `danger.example.com` sub-domain, -which is denied even when `https` scheme is used. - [float] [[dashboard-drilldown-supported-panels]] -=== Supported panels +=== Supported panel types -The following panels support dashboard and URL drilldowns. +The following panel types support drilldowns. [options="header"] |=== @@ -160,25 +136,23 @@ The following panels support dashboard and URL drilldowns. [float] [[drilldowns-example]] -=== Try it: Create a dashboard drilldown +=== Create a dashboard drilldown To create dashboard drilldowns, you create or locate the dashboards you want to connect, then configure the drilldown that allows you to easily open one dashboard from the other dashboard. -image:images/drilldown_on_piechart.gif[Drilldown on pie chart that navigates to another dashboard] - [float] ==== Create the dashboard . Add the *Sample web logs* data. -. Create a new dashboard, then add the following panels: +. Create a new dashboard, then add the following panels from the *Visualize Library*: * *[Logs] Heatmap* * *[Logs] Host, Visits, and Bytes Table* * *[Logs] Total Requests and Bytes* * *[Logs] Visitors by OS* + -If you don’t see data for a panel, try changing the <>. +If you don’t see the data on a panel, try changing the <>. . Save the dashboard. In the *Title* field, enter `Host Overview`. @@ -197,79 +171,82 @@ Filter: `geo.src: CN` . Open the *[Logs] Visitors by OS* panel menu, then select *Create drilldown*. -. Give the drilldown a name, then select *Go to dashboard*. +. Click *Go to dashboard*. -. From the *Choose a destination dashboard* dropdown, select *Host Overview*. +.. Give the drilldown a name. For example, `My Drilldown`. -. To carry over the filter, query, and date range, make sure that *Use filters and query from origin dashboard* and *Use date range from origin dashboard* are selected. -+ -[role="screenshot"] -image::images/drilldown_create.png[Create drilldown with entries for drilldown name and destination] +.. From the *Choose a destination dashboard* dropdown, select *Host Overview*. -. Click *Create drilldown*. -+ -The drilldown is stored as dashboard metadata. +.. To use the geo.src filter, KQL query, and time filter, select *Use filters and query from origin dashboard* and *Use date range from origin dashboard*. + +.. Click *Create drilldown*. . Save the dashboard. -+ -If you fail to save the dashboard, the drilldown is lost when you navigate away from the dashboard. -. In the *[Logs] Visitors by OS* panel, click *win 8*, then select the drilldown. +. In the *[Logs] Visitors by OS* panel, click *win 8*, then select `My Drilldown`. + [role="screenshot"] image::images/drilldown_on_panel.png[Drilldown on pie chart that navigates to another dashboard] -. On the *Host Overview* dashboard, verify that the search query, filters, -and date range are carried over. +. On the *Host Overview* dashboard, verify that the geo.src filter, KQL query, and time filter are applied. [float] [[create-a-url-drilldown]] -=== Try it: Create a URL drilldown +=== Create a URL drilldown To create URL drilldowns, you add <> to a URL template, which configures the behavior of the drilldown. -image:images/url_drilldown_go_to_github.gif[Drilldown on pie chart that navigates to Github] - . Add the *Sample web logs* data. -. Open the *[Logs] Web traffic* dashboard. This isn’t data from Github, but works for demonstration purposes. +. Open the *[Logs] Web traffic* dashboard. . In the toolbar, click *Edit*. . Open the *[Logs] Visitors by OS* panel menu, then select *Create drilldown*. -.. In the *Name* field, enter `Show on Github`. +. Click *Go to URL*. -.. Select *Go to URL*. +.. Give the drilldown a name. For example, `Show on Github`. -.. Enter the URL template: +.. For the *Trigger*, select *Single click*. + +.. To navigate to the {kib} repository Github issues, enter the following in the *Enter URL* field: + [source, bash] ---- https://github.com/elastic/kibana/issues?q=is:issue+is:open+{{event.value}} ---- + -The example URL navigates to {kib} issues on Github. `{{event.value}}` is substituted with a value associated with a selected pie slice. -+ -[role="screenshot"] -image:images/url_drilldown_url_template.png[URL template input] +`{{event.value}}` is substituted with a value associated with a selected pie slice. .. Click *Create drilldown*. -+ -The drilldown is stored as dashboard metadata. . Save the dashboard. -+ -If you fail to save the dashboard, the drilldown is lost when you navigate away from the dashboard. . On the *[Logs] Visitors by OS* panel, click any chart slice, then select *Show on Github*. + [role="screenshot"] image:images/url_drilldown_popup.png[URL drilldown popup] -. On the page that lists the issues in the {kib} repository, verify the slice value appears in Github. +. In the list of {kib} repository issues, verify that the slice value appears. + [role="screenshot"] image:images/url_drilldown_github.png[Github] +[float] +[[manage-drilldowns]] +=== Manage drilldowns + +Make changes to your drilldowns, make a copy of your drilldowns for another panel, and delete drilldowns. + +. Open the panel menu that includes the drilldown, then click *Manage drilldowns*. + +. On the *Manage* tab, use the following options: + +* To change drilldowns, click *Edit* next to the drilldown you want to change, make your changes, then click *Save*. + +* To make a copy, click *Copy* next to the drilldown you want to change, enter the drilldown name, then click *Create drilldown*. + +* To delete a drilldown, select the drilldown you want to delete, then click *Delete*. + include::url-drilldown.asciidoc[] From 18d9d435afe76b3a04b1977ea070655a365a2474 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 27 Apr 2021 16:49:20 +0200 Subject: [PATCH 03/70] [ML] Transforms: Adds a link to discover from the transform list to the actions menu. (#97805) Adds a link to discover from the transform list to the actions menu. Conditions for the link to be enabled: - Kibana index pattern must be available - Transform must have been started once and done some progress so there's the destination index available --- x-pack/plugins/transform/kibana.json | 3 +- .../public/app/__mocks__/app_dependencies.tsx | 19 +++- .../transform/public/app/app_dependencies.tsx | 13 ++- .../transform/public/app/common/index.ts | 1 - .../public/app/common/navigation.test.tsx | 16 --- .../public/app/common/navigation.tsx | 19 ---- .../public/app/mount_management_section.ts | 6 +- .../step_create/step_create_form.tsx | 43 +++++++- .../discover_action_name.test.tsx | 88 +++++++++++++++++ .../action_discover/discover_action_name.tsx | 97 ++++++++++++++++++ .../components/action_discover/index.ts | 9 ++ .../action_discover/use_action_discover.tsx | 99 +++++++++++++++++++ .../transform_list/expanded_row.test.tsx | 30 +++--- .../transform_list/use_actions.test.tsx | 14 ++- .../components/transform_list/use_actions.tsx | 3 + .../transform_list/use_columns.test.tsx | 7 +- x-pack/plugins/transform/public/plugin.ts | 12 ++- .../apps/transform/creation_index_pattern.ts | 26 +++++ .../test/functional/apps/transform/index.ts | 1 + .../functional/services/transform/discover.ts | 65 ++++++++++++ .../functional/services/transform/index.ts | 3 + .../services/transform/transform_table.ts | 9 +- 22 files changed, 508 insertions(+), 75 deletions(-) delete mode 100644 x-pack/plugins/transform/public/app/common/navigation.test.tsx create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.test.tsx create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.tsx create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/index.ts create mode 100644 x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/use_action_discover.tsx create mode 100644 x-pack/test/functional/services/transform/discover.ts diff --git a/x-pack/plugins/transform/kibana.json b/x-pack/plugins/transform/kibana.json index d5da9377ed870..4216ac9761e86 100644 --- a/x-pack/plugins/transform/kibana.json +++ b/x-pack/plugins/transform/kibana.json @@ -9,7 +9,8 @@ "licensing", "management", "features", - "savedObjects" + "savedObjects", + "share" ], "optionalPlugins": [ "security", diff --git a/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx b/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx index 41b7482c4c0f8..5a6f8cf72e36d 100644 --- a/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx +++ b/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx @@ -7,17 +7,28 @@ import { useContext } from 'react'; +import type { ScopedHistory } from 'kibana/public'; + import { coreMock } from '../../../../../../src/core/public/mocks'; import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; +import { savedObjectsPluginMock } from '../../../../../../src/plugins/saved_objects/public/mocks'; +import { SharePluginStart } from '../../../../../../src/plugins/share/public'; + import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; +import type { AppDependencies } from '../app_dependencies'; import { MlSharedContext } from './shared_context'; +import type { GetMlSharedImportsReturnType } from '../../shared_imports'; const coreSetup = coreMock.createSetup(); const coreStart = coreMock.createStart(); const dataStart = dataPluginMock.createStartContract(); -const appDependencies = { +// Replace mock to support syntax using `.then()` as used in transform code. +coreStart.savedObjects.client.find = jest.fn().mockResolvedValue({ savedObjects: [] }); + +const appDependencies: AppDependencies = { + application: coreStart.application, chrome: coreStart.chrome, data: dataStart, docLinks: coreStart.docLinks, @@ -28,11 +39,15 @@ const appDependencies = { storage: ({ get: jest.fn() } as unknown) as Storage, overlays: coreStart.overlays, http: coreSetup.http, + history: {} as ScopedHistory, + savedObjectsPlugin: savedObjectsPluginMock.createStartContract(), + share: ({ urlGenerators: { getUrlGenerator: jest.fn() } } as unknown) as SharePluginStart, + ml: {} as GetMlSharedImportsReturnType, }; export const useAppDependencies = () => { const ml = useContext(MlSharedContext); - return { ...appDependencies, ml, savedObjects: jest.fn() }; + return { ...appDependencies, ml }; }; export const useToastNotifications = () => { diff --git a/x-pack/plugins/transform/public/app/app_dependencies.tsx b/x-pack/plugins/transform/public/app/app_dependencies.tsx index c49ab8183521f..c39aa5a49e5e9 100644 --- a/x-pack/plugins/transform/public/app/app_dependencies.tsx +++ b/x-pack/plugins/transform/public/app/app_dependencies.tsx @@ -5,17 +5,19 @@ * 2.0. */ -import { CoreSetup, CoreStart } from 'src/core/public'; -import { DataPublicPluginStart } from 'src/plugins/data/public'; -import { SavedObjectsStart } from 'src/plugins/saved_objects/public'; -import { ScopedHistory } from 'kibana/public'; +import type { CoreSetup, CoreStart } from 'src/core/public'; +import type { DataPublicPluginStart } from 'src/plugins/data/public'; +import type { SavedObjectsStart } from 'src/plugins/saved_objects/public'; +import type { ScopedHistory } from 'kibana/public'; +import type { SharePluginStart } from 'src/plugins/share/public'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; -import { Storage } from '../../../../../src/plugins/kibana_utils/public'; +import type { Storage } from '../../../../../src/plugins/kibana_utils/public'; import type { GetMlSharedImportsReturnType } from '../shared_imports'; export interface AppDependencies { + application: CoreStart['application']; chrome: CoreStart['chrome']; data: DataPublicPluginStart; docLinks: CoreStart['docLinks']; @@ -28,6 +30,7 @@ export interface AppDependencies { overlays: CoreStart['overlays']; history: ScopedHistory; savedObjectsPlugin: SavedObjectsStart; + share: SharePluginStart; ml: GetMlSharedImportsReturnType; } diff --git a/x-pack/plugins/transform/public/app/common/index.ts b/x-pack/plugins/transform/public/app/common/index.ts index 8fa97139ab967..ccd90f8759358 100644 --- a/x-pack/plugins/transform/public/app/common/index.ts +++ b/x-pack/plugins/transform/public/app/common/index.ts @@ -28,7 +28,6 @@ export { } from './transform'; export { TRANSFORM_LIST_COLUMN, TransformListAction, TransformListRow } from './transform_list'; export { getTransformProgress, isCompletedBatchTransform } from './transform_stats'; -export { getDiscoverUrl } from './navigation'; export { getEsAggFromAggConfig, isPivotAggsConfigWithUiSupport, diff --git a/x-pack/plugins/transform/public/app/common/navigation.test.tsx b/x-pack/plugins/transform/public/app/common/navigation.test.tsx deleted file mode 100644 index af2f586873961..0000000000000 --- a/x-pack/plugins/transform/public/app/common/navigation.test.tsx +++ /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. - */ - -import { getDiscoverUrl } from './navigation'; - -describe('navigation', () => { - test('getDiscoverUrl should provide encoded url to Discover page', () => { - expect(getDiscoverUrl('farequote-airline', 'http://example.com')).toBe( - 'http://example.com/app/discover#?_g=()&_a=(index:farequote-airline)' - ); - }); -}); diff --git a/x-pack/plugins/transform/public/app/common/navigation.tsx b/x-pack/plugins/transform/public/app/common/navigation.tsx index 9daaf8c840755..b847ac66de58e 100644 --- a/x-pack/plugins/transform/public/app/common/navigation.tsx +++ b/x-pack/plugins/transform/public/app/common/navigation.tsx @@ -7,28 +7,9 @@ import React, { FC } from 'react'; import { Redirect } from 'react-router-dom'; -import rison from 'rison-node'; import { SECTION_SLUG } from '../constants'; -/** - * Gets a url for navigating to Discover page. - * @param indexPatternId Index pattern ID. - * @param baseUrl Base url. - */ -export function getDiscoverUrl(indexPatternId: string, baseUrl: string): string { - const _g = rison.encode({}); - - // Add the index pattern ID to the appState part of the URL. - const _a = rison.encode({ - index: indexPatternId, - }); - - const hash = `/discover#?_g=${_g}&_a=${_a}`; - - return `${baseUrl}/app${hash}`; -} - export const RedirectToTransformManagement: FC = () => ; export const RedirectToCreateTransform: FC<{ savedObjectId: string }> = ({ savedObjectId }) => ( diff --git a/x-pack/plugins/transform/public/app/mount_management_section.ts b/x-pack/plugins/transform/public/app/mount_management_section.ts index 019e1f56cee06..1d39d233f8284 100644 --- a/x-pack/plugins/transform/public/app/mount_management_section.ts +++ b/x-pack/plugins/transform/public/app/mount_management_section.ts @@ -28,8 +28,8 @@ export async function mountManagementSection( const { http, notifications, getStartServices } = coreSetup; const startServices = await getStartServices(); const [core, plugins] = startServices; - const { chrome, docLinks, i18n, overlays, savedObjects, uiSettings } = core; - const { data } = plugins; + const { application, chrome, docLinks, i18n, overlays, savedObjects, uiSettings } = core; + const { data, share } = plugins; const { docTitle } = chrome; // Initialize services @@ -39,6 +39,7 @@ export async function mountManagementSection( // AppCore/AppPlugins to be passed on as React context const appDependencies: AppDependencies = { + application, chrome, data, docLinks, @@ -51,6 +52,7 @@ export async function mountManagementSection( uiSettings, history, savedObjectsPlugin: plugins.savedObjects, + share, ml: await getMlSharedImports(), }; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx index 36bdca7921622..526f59e7dad41 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx @@ -26,6 +26,11 @@ import { import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public'; +import { + DISCOVER_APP_URL_GENERATOR, + DiscoverUrlGeneratorState, +} from '../../../../../../../../../src/plugins/discover/public'; + import type { PutTransformsResponseSchema } from '../../../../../../common/api_schemas/transforms'; import { isGetTransformsStatsResponseSchema, @@ -36,7 +41,7 @@ import { PROGRESS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants import { getErrorMessage } from '../../../../../../common/utils/errors'; -import { getTransformProgress, getDiscoverUrl } from '../../../../common'; +import { getTransformProgress } from '../../../../common'; import { useApi } from '../../../../hooks/use_api'; import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; import { RedirectToTransformManagement } from '../../../../common/navigation'; @@ -86,13 +91,45 @@ export const StepCreateForm: FC = React.memo( const [progressPercentComplete, setProgressPercentComplete] = useState( undefined ); + const [discoverLink, setDiscoverLink] = useState(); const deps = useAppDependencies(); const indexPatterns = deps.data.indexPatterns; const toastNotifications = useToastNotifications(); + const { getUrlGenerator } = deps.share.urlGenerators; + const isDiscoverAvailable = deps.application.capabilities.discover?.show ?? false; useEffect(() => { + let unmounted = false; + onChange({ created, started, indexPatternId }); + + const getDiscoverUrl = async (): Promise => { + const state: DiscoverUrlGeneratorState = { + indexPatternId, + }; + + let discoverUrlGenerator; + try { + discoverUrlGenerator = getUrlGenerator(DISCOVER_APP_URL_GENERATOR); + } catch (error) { + // ignore error thrown when url generator is not available + return; + } + + const discoverUrl = await discoverUrlGenerator.createUrl(state); + if (!unmounted) { + setDiscoverLink(discoverUrl); + } + }; + + if (started === true && indexPatternId !== undefined && isDiscoverAvailable) { + getDiscoverUrl(); + } + + return () => { + unmounted = true; + }; // custom comparison // eslint-disable-next-line react-hooks/exhaustive-deps }, [created, started, indexPatternId]); @@ -477,7 +514,7 @@ export const StepCreateForm: FC = React.memo( )} - {started === true && indexPatternId !== undefined && ( + {isDiscoverAvailable && discoverLink !== undefined && ( } @@ -490,7 +527,7 @@ export const StepCreateForm: FC = React.memo( defaultMessage: 'Use Discover to explore the transform.', } )} - href={getDiscoverUrl(indexPatternId, deps.http.basePath.get())} + href={discoverLink} data-test-subj="transformWizardCardDiscover" /> diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.test.tsx new file mode 100644 index 0000000000000..8dba93399792c --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.test.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 { cloneDeep } from 'lodash'; +import React from 'react'; +import { IntlProvider } from 'react-intl'; + +import { render, waitFor, screen } from '@testing-library/react'; + +import { TransformListRow } from '../../../../common'; +import { isDiscoverActionDisabled, DiscoverActionName } from './discover_action_name'; + +import transformListRow from '../../../../common/__mocks__/transform_list_row.json'; + +jest.mock('../../../../../shared_imports'); +jest.mock('../../../../../app/app_dependencies'); + +// @ts-expect-error mock data is too loosely typed +const item: TransformListRow = transformListRow; + +describe('Transform: Transform List Actions isDiscoverActionDisabled()', () => { + it('should be disabled when more than one item is passed in', () => { + expect(isDiscoverActionDisabled([item, item], false, true)).toBe(true); + }); + it('should be disabled when forceDisable is true', () => { + expect(isDiscoverActionDisabled([item], true, true)).toBe(true); + }); + it('should be disabled when the index pattern is not available', () => { + expect(isDiscoverActionDisabled([item], false, false)).toBe(true); + }); + it('should be disabled when the transform started but has no index pattern', () => { + const itemCopy = cloneDeep(item); + itemCopy.stats.state = 'started'; + expect(isDiscoverActionDisabled([itemCopy], false, false)).toBe(true); + }); + it('should be enabled when the transform started and has an index pattern', () => { + const itemCopy = cloneDeep(item); + itemCopy.stats.state = 'started'; + expect(isDiscoverActionDisabled([itemCopy], false, true)).toBe(false); + }); + it('should be enabled when the index pattern is available', () => { + expect(isDiscoverActionDisabled([item], false, true)).toBe(false); + }); +}); + +describe('Transform: Transform List Actions ', () => { + it('renders an enabled button', async () => { + // prepare + render( + + + + ); + + // assert + await waitFor(() => { + expect( + screen.queryByTestId('transformDiscoverActionNameText disabled') + ).not.toBeInTheDocument(); + expect(screen.queryByTestId('transformDiscoverActionNameText enabled')).toBeInTheDocument(); + expect(screen.queryByText('View in Discover')).toBeInTheDocument(); + }); + }); + + it('renders a disabled button', async () => { + // prepare + const itemCopy = cloneDeep(item); + itemCopy.stats.checkpointing.last.checkpoint = 0; + render( + + + + ); + + // assert + await waitFor(() => { + expect(screen.queryByTestId('transformDiscoverActionNameText disabled')).toBeInTheDocument(); + expect( + screen.queryByTestId('transformDiscoverActionNameText enabled') + ).not.toBeInTheDocument(); + expect(screen.queryByText('View in Discover')).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.tsx new file mode 100644 index 0000000000000..259bf82371dba --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/discover_action_name.tsx @@ -0,0 +1,97 @@ +/* + * 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, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiToolTip } from '@elastic/eui'; + +import { TRANSFORM_STATE } from '../../../../../../common/constants'; + +import { getTransformProgress, TransformListRow } from '../../../../common'; + +export const discoverActionNameText = i18n.translate( + 'xpack.transform.transformList.discoverActionNameText', + { + defaultMessage: 'View in Discover', + } +); + +export const isDiscoverActionDisabled = ( + items: TransformListRow[], + forceDisable: boolean, + indexPatternExists: boolean +) => { + if (items.length !== 1) { + return true; + } + + const item = items[0]; + + // Disable discover action if it's a batch transform and was never started + const stoppedTransform = item.stats.state === TRANSFORM_STATE.STOPPED; + const transformProgress = getTransformProgress(item); + const isBatchTransform = typeof item.config.sync === 'undefined'; + const transformNeverStarted = + stoppedTransform === true && transformProgress === undefined && isBatchTransform === true; + + return forceDisable === true || indexPatternExists === false || transformNeverStarted === true; +}; + +export interface DiscoverActionNameProps { + indexPatternExists: boolean; + items: TransformListRow[]; +} +export const DiscoverActionName: FC = ({ indexPatternExists, items }) => { + const isBulkAction = items.length > 1; + + const item = items[0]; + + // Disable discover action if it's a batch transform and was never started + const stoppedTransform = item.stats.state === TRANSFORM_STATE.STOPPED; + const transformProgress = getTransformProgress(item); + const isBatchTransform = typeof item.config.sync === 'undefined'; + const transformNeverStarted = + stoppedTransform && transformProgress === undefined && isBatchTransform === true; + + let disabledTransformMessage; + if (isBulkAction === true) { + disabledTransformMessage = i18n.translate( + 'xpack.transform.transformList.discoverTransformBulkToolTip', + { + defaultMessage: 'Links to Discover are not supported as a bulk action.', + } + ); + } else if (!indexPatternExists) { + disabledTransformMessage = i18n.translate( + 'xpack.transform.transformList.discoverTransformNoIndexPatternToolTip', + { + defaultMessage: `A Kibana index pattern is required for the destination index to be viewable in Discover`, + } + ); + } else if (transformNeverStarted) { + disabledTransformMessage = i18n.translate( + 'xpack.transform.transformList.discoverTransformToolTip', + { + defaultMessage: `The transform needs to be started before it's available in Discover.`, + } + ); + } + + if (typeof disabledTransformMessage !== 'undefined') { + return ( + + + {discoverActionNameText} + + + ); + } + + return ( + {discoverActionNameText} + ); +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/index.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/index.ts new file mode 100644 index 0000000000000..b8ba624faf02c --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/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 { useDiscoverAction } from './use_action_discover'; +export { DiscoverActionName } from './discover_action_name'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/use_action_discover.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/use_action_discover.tsx new file mode 100644 index 0000000000000..468ed0e6b892d --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_discover/use_action_discover.tsx @@ -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 React, { useCallback, useEffect, useMemo, useState } from 'react'; + +import { + DiscoverUrlGeneratorState, + DISCOVER_APP_URL_GENERATOR, +} from '../../../../../../../../../src/plugins/discover/public'; + +import { TransformListAction, TransformListRow } from '../../../../common'; + +import { useSearchItems } from '../../../../hooks/use_search_items'; +import { useAppDependencies } from '../../../../app_dependencies'; + +import { + isDiscoverActionDisabled, + discoverActionNameText, + DiscoverActionName, +} from './discover_action_name'; + +const getIndexPatternTitleFromTargetIndex = (item: TransformListRow) => + Array.isArray(item.config.dest.index) ? item.config.dest.index.join(',') : item.config.dest.index; + +export type DiscoverAction = ReturnType; +export const useDiscoverAction = (forceDisable: boolean) => { + const appDeps = useAppDependencies(); + const savedObjectsClient = appDeps.savedObjects.client; + const indexPatterns = appDeps.data.indexPatterns; + const { getUrlGenerator } = appDeps.share.urlGenerators; + const isDiscoverAvailable = !!appDeps.application.capabilities.discover?.show; + + const { getIndexPatternIdByTitle, loadIndexPatterns } = useSearchItems(undefined); + + const [indexPatternsLoaded, setIndexPatternsLoaded] = useState(false); + + useEffect(() => { + async function checkIndexPatternAvailability() { + await loadIndexPatterns(savedObjectsClient, indexPatterns); + setIndexPatternsLoaded(true); + } + + checkIndexPatternAvailability(); + }, [indexPatterns, loadIndexPatterns, savedObjectsClient]); + + const clickHandler = useCallback( + async (item: TransformListRow) => { + let discoverUrlGenerator; + try { + discoverUrlGenerator = getUrlGenerator(DISCOVER_APP_URL_GENERATOR); + } catch (error) { + // ignore error thrown when url generator is not available + return; + } + + const indexPatternTitle = getIndexPatternTitleFromTargetIndex(item); + const indexPatternId = getIndexPatternIdByTitle(indexPatternTitle); + const state: DiscoverUrlGeneratorState = { + indexPatternId, + }; + const path = await discoverUrlGenerator.createUrl(state); + appDeps.application.navigateToApp('discover', { path }); + }, + [appDeps.application, getIndexPatternIdByTitle, getUrlGenerator] + ); + + const indexPatternExists = useCallback( + (item: TransformListRow) => { + const indexPatternTitle = getIndexPatternTitleFromTargetIndex(item); + const indexPatternId = getIndexPatternIdByTitle(indexPatternTitle); + return indexPatternId !== undefined; + }, + [getIndexPatternIdByTitle] + ); + + const action: TransformListAction = useMemo( + () => ({ + name: (item: TransformListRow) => { + return ; + }, + available: () => isDiscoverAvailable, + enabled: (item: TransformListRow) => + indexPatternsLoaded && + !isDiscoverActionDisabled([item], forceDisable, indexPatternExists(item)), + description: discoverActionNameText, + icon: 'visTable', + type: 'icon', + onClick: clickHandler, + 'data-test-subj': 'transformActionDiscover', + }), + [forceDisable, indexPatternExists, indexPatternsLoaded, isDiscoverAvailable, clickHandler] + ); + + return { action }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx index d25f8c62a4e94..77d20dc4d9078 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { render, fireEvent } from '@testing-library/react'; +import { render, fireEvent, waitFor } from '@testing-library/react'; import React from 'react'; import moment from 'moment-timezone'; import { TransformListRow } from '../../../../common'; @@ -41,20 +41,26 @@ describe('Transform: Transform List ', () => { ); - expect(getByText('Details')).toBeInTheDocument(); - expect(getByText('Stats')).toBeInTheDocument(); - expect(getByText('JSON')).toBeInTheDocument(); - expect(getByText('Messages')).toBeInTheDocument(); - expect(getByText('Preview')).toBeInTheDocument(); + await waitFor(() => { + expect(getByText('Details')).toBeInTheDocument(); + expect(getByText('Stats')).toBeInTheDocument(); + expect(getByText('JSON')).toBeInTheDocument(); + expect(getByText('Messages')).toBeInTheDocument(); + expect(getByText('Preview')).toBeInTheDocument(); - const tabContent = getByTestId('transformDetailsTabContent'); - expect(tabContent).toBeInTheDocument(); + const tabContent = getByTestId('transformDetailsTabContent'); + expect(tabContent).toBeInTheDocument(); - expect(getByTestId('transformDetailsTab')).toHaveAttribute('aria-selected', 'true'); - expect(within(tabContent).getByText('General')).toBeInTheDocument(); + expect(getByTestId('transformDetailsTab')).toHaveAttribute('aria-selected', 'true'); + expect(within(tabContent).getByText('General')).toBeInTheDocument(); + }); fireEvent.click(getByTestId('transformStatsTab')); - expect(getByTestId('transformStatsTab')).toHaveAttribute('aria-selected', 'true'); - expect(within(tabContent).getByText('Stats')).toBeInTheDocument(); + + await waitFor(() => { + expect(getByTestId('transformStatsTab')).toHaveAttribute('aria-selected', 'true'); + const tabContent = getByTestId('transformDetailsTabContent'); + expect(within(tabContent).getByText('Stats')).toBeInTheDocument(); + }); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx index 90487d21610ea..b7d5a2b7104ae 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx @@ -7,20 +7,26 @@ import { renderHook } from '@testing-library/react-hooks'; -import { useActions } from './use_actions'; - jest.mock('../../../../../shared_imports'); jest.mock('../../../../../app/app_dependencies'); +import { useActions } from './use_actions'; + describe('Transform: Transform List Actions', () => { - test('useActions()', () => { - const { result } = renderHook(() => useActions({ forceDisable: false, transformNodes: 1 })); + test('useActions()', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useActions({ forceDisable: false, transformNodes: 1 }) + ); + + await waitForNextUpdate(); + const actions = result.current.actions; // Using `any` for the callback. Somehow the EUI types don't pass // on the `data-test-subj` attribute correctly. We're interested // in the runtime result here anyway. expect(actions.map((a: any) => a['data-test-subj'])).toStrictEqual([ + 'transformActionDiscover', 'transformActionStart', 'transformActionStop', 'transformActionEdit', diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx index d9b9008490666..ddf41d356529a 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx @@ -13,6 +13,7 @@ import { TransformListRow } from '../../../../common'; import { useCloneAction } from '../action_clone'; import { useDeleteAction, DeleteActionModal } from '../action_delete'; +import { useDiscoverAction } from '../action_discover'; import { EditTransformFlyout } from '../edit_transform_flyout'; import { useEditAction } from '../action_edit'; import { useStartAction, StartActionModal } from '../action_start'; @@ -30,6 +31,7 @@ export const useActions = ({ } => { const cloneAction = useCloneAction(forceDisable, transformNodes); const deleteAction = useDeleteAction(forceDisable); + const discoverAction = useDiscoverAction(forceDisable); const editAction = useEditAction(forceDisable, transformNodes); const startAction = useStartAction(forceDisable, transformNodes); const stopAction = useStopAction(forceDisable); @@ -45,6 +47,7 @@ export const useActions = ({ ), actions: [ + discoverAction.action, startAction.action, stopAction.action, editAction.action, diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx index 53eed01f1226d..f3974430b662c 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx @@ -13,8 +13,11 @@ jest.mock('../../../../../shared_imports'); jest.mock('../../../../../app/app_dependencies'); describe('Transform: Job List Columns', () => { - test('useColumns()', () => { - const { result } = renderHook(() => useColumns([], () => {}, 1, [])); + test('useColumns()', async () => { + const { result, waitForNextUpdate } = renderHook(() => useColumns([], () => {}, 1, [])); + + await waitForNextUpdate(); + const columns: ReturnType['columns'] = result.current.columns; expect(columns).toHaveLength(7); diff --git a/x-pack/plugins/transform/public/plugin.ts b/x-pack/plugins/transform/public/plugin.ts index 67abd8a7f1a78..b058be46d677b 100644 --- a/x-pack/plugins/transform/public/plugin.ts +++ b/x-pack/plugins/transform/public/plugin.ts @@ -7,11 +7,12 @@ import { i18n as kbnI18n } from '@kbn/i18n'; -import { CoreSetup } from 'src/core/public'; -import { DataPublicPluginStart } from 'src/plugins/data/public'; -import { HomePublicPluginSetup } from 'src/plugins/home/public'; -import { SavedObjectsStart } from 'src/plugins/saved_objects/public'; -import { ManagementSetup } from '../../../../src/plugins/management/public'; +import type { CoreSetup } from 'src/core/public'; +import type { DataPublicPluginStart } from 'src/plugins/data/public'; +import type { HomePublicPluginSetup } from 'src/plugins/home/public'; +import type { SavedObjectsStart } from 'src/plugins/saved_objects/public'; +import type { ManagementSetup } from 'src/plugins/management/public'; +import type { SharePluginStart } from 'src/plugins/share/public'; import { registerFeature } from './register_feature'; export interface PluginsDependencies { @@ -19,6 +20,7 @@ export interface PluginsDependencies { management: ManagementSetup; home: HomePublicPluginSetup; savedObjects: SavedObjectsStart; + share: SharePluginStart; } export class TransformUiPlugin { diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation_index_pattern.ts index a720aec6bb478..61579ac68ae53 100644 --- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation_index_pattern.ts @@ -89,6 +89,7 @@ export default function ({ getService }: FtrProviderContext) { get destinationIndex(): string { return `user-${this.transformId}`; }, + discoverAdjustSuperDatePicker: true, expected: { pivotAdvancedEditorValueArr: ['{', ' "group_by": {', ' "category.keyword": {'], pivotAdvancedEditorValue: { @@ -210,6 +211,7 @@ export default function ({ getService }: FtrProviderContext) { ], }, ], + discoverQueryHits: '7,270', }, } as PivotTransformTestData, { @@ -247,6 +249,7 @@ export default function ({ getService }: FtrProviderContext) { get destinationIndex(): string { return `user-${this.transformId}`; }, + discoverAdjustSuperDatePicker: false, expected: { pivotAdvancedEditorValueArr: ['{', ' "group_by": {', ' "geoip.country_iso_code": {'], pivotAdvancedEditorValue: { @@ -294,6 +297,7 @@ export default function ({ getService }: FtrProviderContext) { rows: 5, }, histogramCharts: [], + discoverQueryHits: '10', }, } as PivotTransformTestData, { @@ -317,6 +321,7 @@ export default function ({ getService }: FtrProviderContext) { get destinationIndex(): string { return `user-${this.transformId}`; }, + discoverAdjustSuperDatePicker: true, expected: { latestPreview: { column: 0, @@ -342,6 +347,7 @@ export default function ({ getService }: FtrProviderContext) { 'July 12th 2019, 23:31:12', ], }, + discoverQueryHits: '10', }, } as LatestTransformTestData, ]; @@ -533,6 +539,26 @@ export default function ({ getService }: FtrProviderContext) { progress: testData.expected.row.progress, }); }); + + it('navigates to discover and displays results of the destination index', async () => { + await transform.testExecution.logTestStep('should show the actions popover'); + await transform.table.assertTransformRowActions(testData.transformId, false); + + await transform.testExecution.logTestStep('should navigate to discover'); + await transform.table.clickTransformRowAction('Discover'); + + if (testData.discoverAdjustSuperDatePicker) { + await transform.discover.assertNoResults(testData.destinationIndex); + await transform.testExecution.logTestStep( + 'should switch quick select lookback to years' + ); + await transform.discover.assertSuperDatePickerToggleQuickMenuButtonExists(); + await transform.discover.openSuperDatePicker(); + await transform.discover.quickSelectYears(); + } + + await transform.discover.assertDiscoverQueryHits(testData.expected.discoverQueryHits); + }); }); } }); diff --git a/x-pack/test/functional/apps/transform/index.ts b/x-pack/test/functional/apps/transform/index.ts index 89ac903b16d01..ca82459c47f2f 100644 --- a/x-pack/test/functional/apps/transform/index.ts +++ b/x-pack/test/functional/apps/transform/index.ts @@ -66,6 +66,7 @@ export interface BaseTransformTestData { transformDescription: string; expected: any; destinationIndex: string; + discoverAdjustSuperDatePicker: boolean; } export interface PivotTransformTestData extends BaseTransformTestData { diff --git a/x-pack/test/functional/services/transform/discover.ts b/x-pack/test/functional/services/transform/discover.ts new file mode 100644 index 0000000000000..a98f7e5ae9890 --- /dev/null +++ b/x-pack/test/functional/services/transform/discover.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function TransformDiscoverProvider({ getService }: FtrProviderContext) { + const find = getService('find'); + const testSubjects = getService('testSubjects'); + + return { + async assertDiscoverQueryHits(expectedDiscoverQueryHits: string) { + await testSubjects.existOrFail('discoverQueryHits'); + + const actualDiscoverQueryHits = await testSubjects.getVisibleText('discoverQueryHits'); + + expect(actualDiscoverQueryHits).to.eql( + expectedDiscoverQueryHits, + `Discover query hits should be ${expectedDiscoverQueryHits}, got ${actualDiscoverQueryHits}` + ); + }, + + async assertNoResults(expectedDestinationIndex: string) { + // Discover should use the destination index pattern + const actualIndexPatternSwitchLinkText = await ( + await testSubjects.find('indexPattern-switch-link') + ).getVisibleText(); + expect(actualIndexPatternSwitchLinkText).to.eql( + expectedDestinationIndex, + `Destination index should be ${expectedDestinationIndex}, got ${actualIndexPatternSwitchLinkText}` + ); + + await testSubjects.existOrFail('discoverNoResults'); + }, + + async assertSuperDatePickerToggleQuickMenuButtonExists() { + await testSubjects.existOrFail('superDatePickerToggleQuickMenuButton'); + }, + + async openSuperDatePicker() { + await testSubjects.click('superDatePickerToggleQuickMenuButton'); + await testSubjects.existOrFail('superDatePickerQuickMenu'); + }, + + async quickSelectYears() { + const quickMenuElement = await testSubjects.find('superDatePickerQuickMenu'); + + // No test subject, select "Years" to look back 15 years instead of 15 minutes. + await find.selectValue(`[aria-label*="Time unit"]`, 'y'); + + // Apply + const applyButton = await quickMenuElement.findByClassName('euiQuickSelect__applyButton'); + const actualApplyButtonText = await applyButton.getVisibleText(); + expect(actualApplyButtonText).to.be('Apply'); + + await applyButton.click(); + await testSubjects.existOrFail('discoverQueryHits'); + }, + }; +} diff --git a/x-pack/test/functional/services/transform/index.ts b/x-pack/test/functional/services/transform/index.ts index 36265fb9369d3..c9179cc307aaf 100644 --- a/x-pack/test/functional/services/transform/index.ts +++ b/x-pack/test/functional/services/transform/index.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { TransformAPIProvider } from './api'; import { TransformEditFlyoutProvider } from './edit_flyout'; +import { TransformDiscoverProvider } from './discover'; import { TransformManagementProvider } from './management'; import { TransformNavigationProvider } from './navigation'; import { TransformSecurityCommonProvider } from './security_common'; @@ -22,6 +23,7 @@ import { MachineLearningTestResourcesProvider } from '../ml/test_resources'; export function TransformProvider(context: FtrProviderContext) { const api = TransformAPIProvider(context); + const discover = TransformDiscoverProvider(context); const editFlyout = TransformEditFlyoutProvider(context); const management = TransformManagementProvider(context); const navigation = TransformNavigationProvider(context); @@ -35,6 +37,7 @@ export function TransformProvider(context: FtrProviderContext) { return { api, + discover, editFlyout, management, navigation, diff --git a/x-pack/test/functional/services/transform/transform_table.ts b/x-pack/test/functional/services/transform/transform_table.ts index 17d4e56e0cdf9..cafaa2606f255 100644 --- a/x-pack/test/functional/services/transform/transform_table.ts +++ b/x-pack/test/functional/services/transform/transform_table.ts @@ -9,6 +9,8 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; +type TransformRowActionName = 'Clone' | 'Delete' | 'Edit' | 'Start' | 'Stop' | 'Discover'; + export function TransformTableProvider({ getService }: FtrProviderContext) { const retry = getService('retry'); const testSubjects = getService('testSubjects'); @@ -238,6 +240,7 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { await testSubjects.existOrFail('transformActionClone'); await testSubjects.existOrFail('transformActionDelete'); + await testSubjects.existOrFail('transformActionDiscover'); await testSubjects.existOrFail('transformActionEdit'); if (isTransformRunning) { @@ -251,7 +254,7 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { public async assertTransformRowActionEnabled( transformId: string, - action: 'Delete' | 'Start' | 'Stop' | 'Clone' | 'Edit', + action: TransformRowActionName, expectedValue: boolean ) { const selector = `transformAction${action}`; @@ -274,7 +277,7 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { public async clickTransformRowActionWithRetry( transformId: string, - action: 'Delete' | 'Start' | 'Stop' | 'Clone' | 'Edit' + action: TransformRowActionName ) { await retry.tryForTime(30 * 1000, async () => { await browser.pressKeys(browser.keys.ESCAPE); @@ -285,7 +288,7 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { }); } - public async clickTransformRowAction(action: string) { + public async clickTransformRowAction(action: TransformRowActionName) { await testSubjects.click(`transformAction${action}`); } From 8de766904d28da27ad707a920ade9510e86a2941 Mon Sep 17 00:00:00 2001 From: Andrea Del Rio Date: Tue, 27 Apr 2021 09:12:17 -0700 Subject: [PATCH 04/70] [K8] Small fixes (#98099) --- .../sidebar/discover_field_search.tsx | 4 +- .../public/lib/panel/_embeddable_panel.scss | 5 +- .../public/top_nav_menu/_index.scss | 10 +++- .../solution_toolbar/items/button.scss | 5 ++ .../solution_toolbar/items/quick_group.scss | 6 +++ .../home/components/filter_list_button.tsx | 46 ++++++++++--------- 6 files changed, 49 insertions(+), 27 deletions(-) diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx index 67dda6dd0e9a8..e11c1716efe6b 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx @@ -233,7 +233,7 @@ export function DiscoverFieldSearch({ onChange, value, types, useNewFieldsApi }: const footer = () => { return ( - + - + {i18n.translate('discover.fieldChooser.filter.filterByTypeLabel', { defaultMessage: 'Filter by type', })} diff --git a/src/plugins/embeddable/public/lib/panel/_embeddable_panel.scss b/src/plugins/embeddable/public/lib/panel/_embeddable_panel.scss index f7ee1f3c741c4..9072c26576097 100644 --- a/src/plugins/embeddable/public/lib/panel/_embeddable_panel.scss +++ b/src/plugins/embeddable/public/lib/panel/_embeddable_panel.scss @@ -120,9 +120,10 @@ // EDITING MODE .embPanel--editing { - border-style: dashed; - border-color: $euiColorMediumShade; + border-style: dashed !important; + border-color: $euiColorMediumShade !important; transition: all $euiAnimSpeedFast $euiAnimSlightResistance; + border-width: $euiBorderWidthThin; &:hover, &:focus { diff --git a/src/plugins/navigation/public/top_nav_menu/_index.scss b/src/plugins/navigation/public/top_nav_menu/_index.scss index bc27cf061eb68..9af1bb5434bb1 100644 --- a/src/plugins/navigation/public/top_nav_menu/_index.scss +++ b/src/plugins/navigation/public/top_nav_menu/_index.scss @@ -1,5 +1,13 @@ .kbnTopNavMenu { - margin-right: $euiSizeXS; + @include kbnThemeStyle('v7') { + margin-right: $euiSizeXS; + } + + @include kbnThemeStyle('v8') { + button:last-child { + margin-right: 0; + } + } } .kbnTopNavMenu__badgeWrapper { diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss b/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss index b8022201acf59..4fc3651ee9f73 100644 --- a/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss +++ b/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss @@ -4,4 +4,9 @@ // Lighten the border color for all states border-color: $euiBorderColor !important; // sass-lint:disable-line no-important + + @include kbnThemeStyle('v8') { + border-width: $euiBorderWidthThin; + border-style: solid; + } } diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.scss b/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.scss index 870a9a945ed5d..876ee659b71d7 100644 --- a/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.scss +++ b/src/plugins/presentation_util/public/components/solution_toolbar/items/quick_group.scss @@ -1,6 +1,12 @@ .quickButtonGroup { .quickButtonGroup__button { background-color: $euiColorEmptyShade; + @include kbnThemeStyle('v8') { + // sass-lint:disable-block no-important + border-width: $euiBorderWidthThin !important; + border-style: solid !important; + border-color: $euiBorderColor !important; + } } // Temporary fix for two tone icons to make them monochrome diff --git a/x-pack/plugins/index_management/public/application/sections/home/components/filter_list_button.tsx b/x-pack/plugins/index_management/public/application/sections/home/components/filter_list_button.tsx index 44f565f98cdb0..4bd9a01380c0e 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/components/filter_list_button.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/components/filter_list_button.tsx @@ -7,7 +7,7 @@ import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiFilterButton, EuiPopover, EuiFilterSelectItem } from '@elastic/eui'; +import { EuiFilterButton, EuiFilterGroup, EuiPopover, EuiFilterSelectItem } from '@elastic/eui'; interface Filter { name: string; @@ -65,26 +65,28 @@ export function FilterListButton({ onChange, filters }: Props< ); return ( - -
- {Object.entries(filters).map(([filter, item], index) => ( - toggleFilter(filter as T)} - data-test-subj="filterItem" - > - {(item as Filter).name} - - ))} -
-
+ + +
+ {Object.entries(filters).map(([filter, item], index) => ( + toggleFilter(filter as T)} + data-test-subj="filterItem" + > + {(item as Filter).name} + + ))} +
+
+
); } From aa281ffad7c0b1808154e00b937251f36e2ff76a Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Tue, 27 Apr 2021 09:36:27 -0700 Subject: [PATCH 05/70] [Metrics UI] Unskip Home Page Functional Test (#98085) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../test/functional/apps/infra/home_page.ts | 48 +------------------ x-pack/test/functional/apps/infra/index.ts | 20 ++++---- x-pack/test/functional/apps/infra/link_to.ts | 2 +- 3 files changed, 14 insertions(+), 56 deletions(-) diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index a5b8e69833fb6..1cc7c87f3a1a8 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -5,24 +5,17 @@ * 2.0. */ -import expect from '@kbn/expect/expect.js'; import { FtrProviderContext } from '../../ftr_provider_context'; import { DATES } from './constants'; const DATE_WITH_DATA = DATES.metricsAndLogs.hosts.withData; const DATE_WITHOUT_DATA = DATES.metricsAndLogs.hosts.withoutData; -const COMMON_REQUEST_HEADERS = { - 'kbn-xsrf': 'some-xsrf-token', -}; - export default ({ getPageObjects, getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const pageObjects = getPageObjects(['common', 'infraHome']); - const supertest = getService('supertest'); - // FLAKY: https://github.com/elastic/kibana/issues/75724 - describe.skip('Home page', function () { + describe('Home page', function () { this.tags('includeFirefox'); before(async () => { await esArchiver.load('empty_kibana'); @@ -54,45 +47,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.infraHome.goToTime(DATE_WITHOUT_DATA); await pageObjects.infraHome.getNoMetricsDataPrompt(); }); - - it('records telemetry for hosts', async () => { - await pageObjects.infraHome.goToTime(DATE_WITH_DATA); - await pageObjects.infraHome.getWaffleMap(); - - const resp = await supertest - .post(`/api/telemetry/v2/clusters/_stats`) - .set(COMMON_REQUEST_HEADERS) - .set('Accept', 'application/json') - .send({ - unencrypted: true, - }) - .expect(200) - .then((res: any) => res.body); - - expect( - resp[0].stack_stats.kibana.plugins.infraops.last_24_hours.hits.infraops_hosts - ).to.be.greaterThan(0); - }); - - it('records telemetry for docker', async () => { - await pageObjects.infraHome.goToTime(DATE_WITH_DATA); - await pageObjects.infraHome.getWaffleMap(); - await pageObjects.infraHome.goToDocker(); - - const resp = await supertest - .post(`/api/telemetry/v2/clusters/_stats`) - .set(COMMON_REQUEST_HEADERS) - .set('Accept', 'application/json') - .send({ - unencrypted: true, - }) - .expect(200) - .then((res: any) => res.body); - - expect( - resp[0].stack_stats.kibana.plugins.infraops.last_24_hours.hits.infraops_docker - ).to.be.greaterThan(0); - }); }); }); }; diff --git a/x-pack/test/functional/apps/infra/index.ts b/x-pack/test/functional/apps/infra/index.ts index 9c828253245d0..8cdcf6b619b26 100644 --- a/x-pack/test/functional/apps/infra/index.ts +++ b/x-pack/test/functional/apps/infra/index.ts @@ -8,15 +8,19 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ loadTestFile }: FtrProviderContext) => { - describe('InfraOps app', function () { + describe('InfraOps App', function () { this.tags('ciGroup7'); - loadTestFile(require.resolve('./metrics_anomalies')); - loadTestFile(require.resolve('./home_page')); loadTestFile(require.resolve('./feature_controls')); - loadTestFile(require.resolve('./log_entry_categories_tab')); - loadTestFile(require.resolve('./log_entry_rate_tab')); - loadTestFile(require.resolve('./logs_source_configuration')); - loadTestFile(require.resolve('./metrics_source_configuration')); - loadTestFile(require.resolve('./link_to')); + describe('Metrics UI', function () { + loadTestFile(require.resolve('./home_page')); + loadTestFile(require.resolve('./metrics_source_configuration')); + loadTestFile(require.resolve('./metrics_anomalies')); + }); + describe('Logs UI', function () { + loadTestFile(require.resolve('./log_entry_categories_tab')); + loadTestFile(require.resolve('./log_entry_rate_tab')); + loadTestFile(require.resolve('./logs_source_configuration')); + loadTestFile(require.resolve('./link_to')); + }); }); }; diff --git a/x-pack/test/functional/apps/infra/link_to.ts b/x-pack/test/functional/apps/infra/link_to.ts index b7a554cea311f..3e070fb9849b1 100644 --- a/x-pack/test/functional/apps/infra/link_to.ts +++ b/x-pack/test/functional/apps/infra/link_to.ts @@ -22,7 +22,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const traceId = '433b4651687e18be2c6c8e3b11f53d09'; - describe('Infra link-to', function () { + describe('link-to Logs', function () { it('redirects to the logs app and parses URL search params correctly', async () => { const location = { hash: '', From 52a90e3dc9a19e7c211d13fd5c672634b3f51526 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 27 Apr 2021 13:04:08 -0400 Subject: [PATCH 06/70] [Fleet] Use default port for fleet server cloud url (#98492) --- .../fleet/server/services/settings.test.ts | 18 +++++++++++++++++- .../plugins/fleet/server/services/settings.ts | 6 +++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/settings.test.ts b/x-pack/plugins/fleet/server/services/settings.test.ts index a9f9600addc39..87b3e163c1bb3 100644 --- a/x-pack/plugins/fleet/server/services/settings.test.ts +++ b/x-pack/plugins/fleet/server/services/settings.test.ts @@ -17,7 +17,7 @@ describe('getCloudFleetServersHosts', () => { expect(getCloudFleetServersHosts()).toBeUndefined(); }); - it('should return fleet server hosts if cloud is correctly setup', () => { + it('should return fleet server hosts if cloud is correctly setup with default port == 443', () => { mockedAppContextService.getCloud.mockReturnValue({ cloudId: 'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==', @@ -32,4 +32,20 @@ describe('getCloudFleetServersHosts', () => { ] `); }); + + it('should return fleet server hosts if cloud is correctly setup with a default port', () => { + mockedAppContextService.getCloud.mockReturnValue({ + cloudId: + 'test:dGVzdC5mcjo5MjQzJGRhM2I2YjNkYWY5ZDRjODE4ZjI4ZmEzNDdjMzgzODViJDgxMmY4NWMxZjNjZTQ2YTliYjgxZjFjMWIxMzRjNmRl', + isCloudEnabled: true, + deploymentId: 'deployment-id-1', + apm: {}, + }); + + expect(getCloudFleetServersHosts()).toMatchInlineSnapshot(` + Array [ + "https://deployment-id-1.fleet.test.fr:9243", + ] + `); + }); }); diff --git a/x-pack/plugins/fleet/server/services/settings.ts b/x-pack/plugins/fleet/server/services/settings.ts index 4ef9a3a95cbd0..2046e2571c926 100644 --- a/x-pack/plugins/fleet/server/services/settings.ts +++ b/x-pack/plugins/fleet/server/services/settings.ts @@ -84,6 +84,10 @@ export function getCloudFleetServersHosts() { } // Fleet Server url are formed like this `https://.fleet. - return [`https://${cloudSetup.deploymentId}.fleet.${res.host}`]; + return [ + `https://${cloudSetup.deploymentId}.fleet.${res.host}${ + res.defaultPort !== '443' ? `:${res.defaultPort}` : '' + }`, + ]; } } From 808959e316082718c1c1081c362fbc6c91f2fdbc Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Tue, 27 Apr 2021 19:09:54 +0200 Subject: [PATCH 07/70] Handle `401 Unauthorized` errors in a more user-friendly way (#94927) --- .eslintrc.js | 4 + test/common/services/security/user.ts | 30 +- test/scripts/jenkins_xpack_build_plugins.sh | 1 + .../plugins/reporting/server/routes/jobs.ts | 2 + x-pack/plugins/security/common/constants.ts | 15 + .../capture_url/capture_url_app.test.ts | 63 ++- .../capture_url/capture_url_app.ts | 45 +- .../authentication/login/components/index.ts | 2 +- .../login/components/login_form/index.ts | 2 +- .../components/login_form/login_form.test.tsx | 6 +- .../components/login_form/login_form.tsx | 14 +- .../authentication/login/login_page.test.tsx | 9 +- .../authentication/login/login_page.tsx | 34 +- .../__snapshots__/prompt_page.test.tsx.snap | 5 + .../unauthenticated_page.test.tsx.snap | 3 + .../authentication_service.test.mocks.ts | 9 + .../authentication_service.test.ts | 458 ++++++++++++++++-- .../authentication/authentication_service.ts | 72 ++- .../authentication/authenticator.test.ts | 72 ++- .../server/authentication/authenticator.ts | 41 +- .../can_redirect_request.test.ts | 30 ++ .../authentication/can_redirect_request.ts | 7 +- .../authentication/providers/base.mock.ts | 1 + .../server/authentication/providers/base.ts | 4 + .../authentication/providers/oidc.test.ts | 76 ++- .../server/authentication/providers/oidc.ts | 54 ++- .../authentication/providers/saml.test.ts | 63 ++- .../server/authentication/providers/saml.ts | 55 ++- .../security/server/authentication/tokens.ts | 10 +- .../unauthenticated_page.test.tsx | 35 ++ .../authentication/unauthenticated_page.tsx | 55 +++ .../reset_session_page.test.tsx.snap | 2 +- .../authorization/authorization_service.tsx | 24 +- .../authorization/reset_session_page.test.tsx | 10 +- .../authorization/reset_session_page.tsx | 120 ++--- x-pack/plugins/security/server/index.ts | 1 + x-pack/plugins/security/server/plugin.ts | 7 +- .../security/server/prompt_page.test.tsx | 57 +++ .../plugins/security/server/prompt_page.tsx | 96 ++++ .../routes/authentication/common.test.ts | 6 +- .../server/routes/authentication/common.ts | 3 +- .../server/routes/authentication/oidc.ts | 17 +- .../server/routes/authentication/saml.test.ts | 7 +- .../server/routes/authentication/saml.ts | 16 +- x-pack/plugins/security/server/routes/tags.ts | 27 ++ .../server/routes/views/capture_url.test.ts | 42 +- .../server/routes/views/capture_url.ts | 6 +- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../apis/security/basic_login.js | 4 +- .../test/functional/apps/security/security.ts | 2 +- .../tests/anonymous/login.ts | 20 +- .../tests/kerberos/kerberos_login.ts | 14 +- .../login_selector/basic_functionality.ts | 61 ++- .../oidc/authorization_code_flow/oidc_auth.ts | 157 +++--- .../tests/oidc/implicit_flow/oidc_auth.ts | 18 +- .../tests/pki/pki_auth.ts | 16 +- .../tests/saml/saml_login.ts | 161 +++--- .../common/test_endpoints/kibana.json | 7 + .../common/test_endpoints/public/index.ts | 9 + .../common/test_endpoints/public/plugin.tsx | 29 ++ .../common/test_endpoints/server/index.ts | 15 + .../test_endpoints/server/init_routes.ts | 38 ++ .../login_selector.config.ts | 3 + .../test/security_functional/oidc.config.ts | 3 + .../test/security_functional/saml.config.ts | 3 + .../login_selector/basic_functionality.ts | 60 +++ .../tests/oidc/url_capture.ts | 21 + .../tests/saml/url_capture.ts | 21 + 69 files changed, 1837 insertions(+), 545 deletions(-) create mode 100644 x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap create mode 100644 x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap create mode 100644 x-pack/plugins/security/server/authentication/authentication_service.test.mocks.ts create mode 100644 x-pack/plugins/security/server/authentication/unauthenticated_page.test.tsx create mode 100644 x-pack/plugins/security/server/authentication/unauthenticated_page.tsx create mode 100644 x-pack/plugins/security/server/prompt_page.test.tsx create mode 100644 x-pack/plugins/security/server/prompt_page.tsx create mode 100644 x-pack/plugins/security/server/routes/tags.ts create mode 100644 x-pack/test/security_functional/fixtures/common/test_endpoints/kibana.json create mode 100644 x-pack/test/security_functional/fixtures/common/test_endpoints/public/index.ts create mode 100644 x-pack/test/security_functional/fixtures/common/test_endpoints/public/plugin.tsx create mode 100644 x-pack/test/security_functional/fixtures/common/test_endpoints/server/index.ts create mode 100644 x-pack/test/security_functional/fixtures/common/test_endpoints/server/init_routes.ts diff --git a/.eslintrc.js b/.eslintrc.js index 19ba7cacc3c44..0f7af42fafbfd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1377,6 +1377,10 @@ module.exports = { ['parent', 'sibling', 'index'], ], pathGroups: [ + { + pattern: '{**,.}/*.test.mocks', + group: 'unknown', + }, { pattern: '{@kbn/**,src/**,kibana{,/**}}', group: 'internal', diff --git a/test/common/services/security/user.ts b/test/common/services/security/user.ts index 3bd31bb5ed186..d6813105ecbf6 100644 --- a/test/common/services/security/user.ts +++ b/test/common/services/security/user.ts @@ -33,7 +33,7 @@ export class User { public async delete(username: string) { this.log.debug(`deleting user ${username}`); - const { data, status, statusText } = await await this.kbnClient.request({ + const { data, status, statusText } = await this.kbnClient.request({ path: `/internal/security/users/${username}`, method: 'DELETE', }); @@ -44,4 +44,32 @@ export class User { } this.log.debug(`deleted user ${username}`); } + + public async disable(username: string) { + this.log.debug(`disabling user ${username}`); + const { data, status, statusText } = await this.kbnClient.request({ + path: `/internal/security/users/${encodeURIComponent(username)}/_disable`, + method: 'POST', + }); + if (status !== 204) { + throw new Error( + `Expected status code of 204, received ${status} ${statusText}: ${util.inspect(data)}` + ); + } + this.log.debug(`disabled user ${username}`); + } + + public async enable(username: string) { + this.log.debug(`enabling user ${username}`); + const { data, status, statusText } = await this.kbnClient.request({ + path: `/internal/security/users/${encodeURIComponent(username)}/_enable`, + method: 'POST', + }); + if (status !== 204) { + throw new Error( + `Expected status code of 204, received ${status} ${statusText}: ${util.inspect(data)}` + ); + } + this.log.debug(`enabled user ${username}`); + } } diff --git a/test/scripts/jenkins_xpack_build_plugins.sh b/test/scripts/jenkins_xpack_build_plugins.sh index 496964983cc6c..cb0b5ec1d56da 100755 --- a/test/scripts/jenkins_xpack_build_plugins.sh +++ b/test/scripts/jenkins_xpack_build_plugins.sh @@ -13,6 +13,7 @@ node scripts/build_kibana_platform_plugins \ --scan-dir "$XPACK_DIR/test/plugin_api_perf/plugins" \ --scan-dir "$XPACK_DIR/test/licensing_plugin/plugins" \ --scan-dir "$XPACK_DIR/test/usage_collection/plugins" \ + --scan-dir "$XPACK_DIR/test/security_functional/fixtures/common" \ --scan-dir "$KIBANA_DIR/examples" \ --scan-dir "$XPACK_DIR/examples" \ --workers 12 \ diff --git a/x-pack/plugins/reporting/server/routes/jobs.ts b/x-pack/plugins/reporting/server/routes/jobs.ts index 7141b1a141185..3f2a95a34224c 100644 --- a/x-pack/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/plugins/reporting/server/routes/jobs.ts @@ -7,6 +7,7 @@ import { schema } from '@kbn/config-schema'; import Boom from '@hapi/boom'; +import { ROUTE_TAG_CAN_REDIRECT } from '../../../security/server'; import { ReportingCore } from '../'; import { API_BASE_URL } from '../../common/constants'; import { authorizedUserPreRoutingFactory } from './lib/authorized_user_pre_routing'; @@ -198,6 +199,7 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { docId: schema.string({ minLength: 3 }), }), }, + options: { tags: [ROUTE_TAG_CAN_REDIRECT] }, }, userHandler(async (user, context, req, res) => { // ensure the async dependencies are loaded diff --git a/x-pack/plugins/security/common/constants.ts b/x-pack/plugins/security/common/constants.ts index a205109f537e7..ef83230fc2aba 100644 --- a/x-pack/plugins/security/common/constants.ts +++ b/x-pack/plugins/security/common/constants.ts @@ -19,7 +19,22 @@ export const GLOBAL_RESOURCE = '*'; export const APPLICATION_PREFIX = 'kibana-'; export const RESERVED_PRIVILEGES_APPLICATION_WILDCARD = 'kibana-*'; +/** + * This is the key of a query parameter that contains the name of the authentication provider that should be used to + * authenticate request. It's also used while the user is being redirected during single-sign-on authentication flows. + * That query parameter is discarded after the authentication flow succeeds. See the `Authenticator`, + * `OIDCAuthenticationProvider`, and `SAMLAuthenticationProvider` classes for more information. + */ export const AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER = 'auth_provider_hint'; + +/** + * This is the key of a query parameter that contains metadata about the (client-side) URL hash while the user is being + * redirected during single-sign-on authentication flows. That query parameter is discarded after the authentication + * flow succeeds. See the `Authenticator`, `OIDCAuthenticationProvider`, and `SAMLAuthenticationProvider` classes for + * more information. + */ +export const AUTH_URL_HASH_QUERY_STRING_PARAMETER = 'auth_url_hash'; + export const LOGOUT_PROVIDER_QUERY_STRING_PARAMETER = 'provider'; export const LOGOUT_REASON_QUERY_STRING_PARAMETER = 'msg'; export const NEXT_URL_QUERY_STRING_PARAMETER = 'next'; diff --git a/x-pack/plugins/security/public/authentication/capture_url/capture_url_app.test.ts b/x-pack/plugins/security/public/authentication/capture_url/capture_url_app.test.ts index bf68d9f7a6e5e..44fd5ab195341 100644 --- a/x-pack/plugins/security/public/authentication/capture_url/capture_url_app.test.ts +++ b/x-pack/plugins/security/public/authentication/capture_url/capture_url_app.test.ts @@ -5,19 +5,29 @@ * 2.0. */ -import type { AppMount, ScopedHistory } from 'src/core/public'; -import { coreMock, scopedHistoryMock } from 'src/core/public/mocks'; +import { coreMock } from 'src/core/public/mocks'; import { captureURLApp } from './capture_url_app'; describe('captureURLApp', () => { + let mockLocationReplace: jest.Mock; beforeAll(() => { + mockLocationReplace = jest.fn(); Object.defineProperty(window, 'location', { - value: { href: 'https://some-host' }, + value: { + href: 'https://some-host', + hash: '#/?_g=()', + origin: 'https://some-host', + replace: mockLocationReplace, + }, writable: true, }); }); + beforeEach(() => { + mockLocationReplace.mockClear(); + }); + it('properly registers application', () => { const coreSetupMock = coreMock.createSetup(); @@ -42,34 +52,37 @@ describe('captureURLApp', () => { it('properly handles captured URL', async () => { window.location.href = `https://host.com/mock-base-path/internal/security/capture-url?next=${encodeURIComponent( - '/mock-base-path/app/home' - )}&providerType=saml&providerName=saml1#/?_g=()`; + '/mock-base-path/app/home?auth_provider_hint=saml1' + )}#/?_g=()`; const coreSetupMock = coreMock.createSetup(); - coreSetupMock.http.post.mockResolvedValue({ location: '/mock-base-path/app/home#/?_g=()' }); - captureURLApp.create(coreSetupMock); const [[{ mount }]] = coreSetupMock.application.register.mock.calls; - await (mount as AppMount)({ - element: document.createElement('div'), - appBasePath: '', - onAppLeave: jest.fn(), - setHeaderActionMenu: jest.fn(), - history: (scopedHistoryMock.create() as unknown) as ScopedHistory, - }); + await mount(coreMock.createAppMountParamters()); - expect(coreSetupMock.http.post).toHaveBeenCalledTimes(1); - expect(coreSetupMock.http.post).toHaveBeenCalledWith('/internal/security/login', { - body: JSON.stringify({ - providerType: 'saml', - providerName: 'saml1', - currentURL: `https://host.com/mock-base-path/internal/security/capture-url?next=${encodeURIComponent( - '/mock-base-path/app/home' - )}&providerType=saml&providerName=saml1#/?_g=()`, - }), - }); + expect(mockLocationReplace).toHaveBeenCalledTimes(1); + expect(mockLocationReplace).toHaveBeenCalledWith( + 'https://some-host/mock-base-path/app/home?auth_provider_hint=saml1&auth_url_hash=%23%2F%3F_g%3D%28%29#/?_g=()' + ); + expect(coreSetupMock.fatalErrors.add).not.toHaveBeenCalled(); + }); - expect(window.location.href).toBe('/mock-base-path/app/home#/?_g=()'); + it('properly handles open redirects', async () => { + window.location.href = `https://host.com/mock-base-path/internal/security/capture-url?next=${encodeURIComponent( + 'https://evil.com/mock-base-path/app/home?auth_provider_hint=saml1' + )}#/?_g=()`; + + const coreSetupMock = coreMock.createSetup(); + captureURLApp.create(coreSetupMock); + + const [[{ mount }]] = coreSetupMock.application.register.mock.calls; + await mount(coreMock.createAppMountParamters()); + + expect(mockLocationReplace).toHaveBeenCalledTimes(1); + expect(mockLocationReplace).toHaveBeenCalledWith( + 'https://some-host/?auth_url_hash=%23%2F%3F_g%3D%28%29' + ); + expect(coreSetupMock.fatalErrors.add).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security/public/authentication/capture_url/capture_url_app.ts b/x-pack/plugins/security/public/authentication/capture_url/capture_url_app.ts index 7797ce4e62102..af45314c5bacb 100644 --- a/x-pack/plugins/security/public/authentication/capture_url/capture_url_app.ts +++ b/x-pack/plugins/security/public/authentication/capture_url/capture_url_app.ts @@ -5,10 +5,11 @@ * 2.0. */ -import { parse } from 'url'; - import type { ApplicationSetup, FatalErrorsSetup, HttpSetup } from 'src/core/public'; +import { AUTH_URL_HASH_QUERY_STRING_PARAMETER } from '../../../common/constants'; +import { parseNext } from '../../../common/parse_next'; + interface CreateDeps { application: ApplicationSetup; http: HttpSetup; @@ -22,20 +23,17 @@ interface CreateDeps { * path segment into the `next` query string parameter (so that it's not lost during redirect). And * since browsers preserve hash fragments during redirects (assuming redirect location doesn't * specify its own hash fragment, which is true in our case) this app can capture both path and - * hash URL segments and send them back to the authentication provider via login endpoint. + * hash URL segments and re-try request sending hash fragment in a dedicated query string parameter. * * The flow can look like this: - * 1. User visits `/app/kibana#/management/elasticsearch` that initiates authentication. - * 2. Provider redirect user to `/internal/security/capture-url?next=%2Fapp%2Fkibana&providerType=saml&providerName=saml1`. - * 3. Browser preserves hash segment and users ends up at `/internal/security/capture-url?next=%2Fapp%2Fkibana&providerType=saml&providerName=saml1#/management/elasticsearch`. - * 4. The app captures full URL and sends it back as is via login endpoint: - * { - * providerType: 'saml', - * providerName: 'saml1', - * currentURL: 'https://kibana.com/internal/security/capture-url?next=%2Fapp%2Fkibana&providerType=saml&providerName=saml1#/management/elasticsearch' - * } - * 5. Login endpoint handler parses and validates `next` parameter, joins it with the hash segment - * and finally passes it to the provider that initiated capturing. + * 1. User visits `https://kibana.com/app/kibana#/management/elasticsearch` that initiates authentication. + * 2. Provider redirect user to `/internal/security/capture-url?next=%2Fapp%2Fkibana&auth_provider_hint=saml1`. + * 3. Browser preserves hash segment and users ends up at `/internal/security/capture-url?next=%2Fapp%2Fkibana&auth_provider_hint=saml1#/management/elasticsearch`. + * 4. The app reconstructs original URL, adds `auth_url_hash` query string parameter with the captured hash fragment and redirects user to: + * https://kibana.com/app/kibana?auth_provider_hint=saml1&auth_url_hash=%23%2Fmanagement%2Felasticsearch#/management/elasticsearch + * 5. Once Kibana receives this request, it immediately picks exactly the same provider to handle authentication (based on `auth_provider_hint=saml1`), + * and, since it has full URL now (original request path, query string and hash extracted from `auth_url_hash=%23%2Fmanagement%2Felasticsearch`), + * it can proceed to a proper authentication handshake. */ export const captureURLApp = Object.freeze({ id: 'security_capture_url', @@ -48,19 +46,14 @@ export const captureURLApp = Object.freeze({ appRoute: '/internal/security/capture-url', async mount() { try { - const { providerName, providerType } = parse(window.location.href, true).query ?? {}; - if (!providerName || !providerType) { - fatalErrors.add(new Error('Provider to capture URL for is not specified.')); - return () => {}; - } - - const { location } = await http.post<{ location: string }>('/internal/security/login', { - body: JSON.stringify({ providerType, providerName, currentURL: window.location.href }), - }); - - window.location.href = location; + const url = new URL( + parseNext(window.location.href, http.basePath.serverBasePath), + window.location.origin + ); + url.searchParams.append(AUTH_URL_HASH_QUERY_STRING_PARAMETER, window.location.hash); + window.location.replace(url.toString()); } catch (err) { - fatalErrors.add(new Error('Cannot login with captured URL.')); + fatalErrors.add(new Error(`Cannot parse current URL: ${err && err.message}.`)); } return () => {}; diff --git a/x-pack/plugins/security/public/authentication/login/components/index.ts b/x-pack/plugins/security/public/authentication/login/components/index.ts index 66e91a390784a..63928e4e82e37 100644 --- a/x-pack/plugins/security/public/authentication/login/components/index.ts +++ b/x-pack/plugins/security/public/authentication/login/components/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export { LoginForm } from './login_form'; +export { LoginForm, LoginFormMessageType } from './login_form'; export { DisabledLoginForm } from './disabled_login_form'; diff --git a/x-pack/plugins/security/public/authentication/login/components/login_form/index.ts b/x-pack/plugins/security/public/authentication/login/components/login_form/index.ts index 6215f4e1e5b7a..d12ea30c784cb 100644 --- a/x-pack/plugins/security/public/authentication/login/components/login_form/index.ts +++ b/x-pack/plugins/security/public/authentication/login/components/login_form/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { LoginForm } from './login_form'; +export { LoginForm, MessageType as LoginFormMessageType } from './login_form'; diff --git a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx index f58150d4580b8..e816fa032a0e5 100644 --- a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx +++ b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx @@ -14,7 +14,7 @@ import ReactMarkdown from 'react-markdown'; import { findTestSubject, mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test/jest'; import { coreMock } from 'src/core/public/mocks'; -import { LoginForm, PageMode } from './login_form'; +import { LoginForm, MessageType, PageMode } from './login_form'; function expectPageMode(wrapper: ReactWrapper, mode: PageMode) { const assertions: Array<[string, boolean]> = @@ -90,7 +90,7 @@ describe('LoginForm', () => { { }); expect(wrapper.find(EuiCallOut).props().title).toEqual( - `Invalid username or password. Please try again.` + `Username or password is incorrect. Please try again.` ); }); diff --git a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx index ca573ada36d22..df131e2eac133 100644 --- a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx +++ b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.tsx @@ -40,7 +40,7 @@ interface Props { http: HttpStart; notifications: NotificationsStart; selector: LoginSelector; - infoMessage?: string; + message?: { type: MessageType.Danger | MessageType.Info; content: string }; loginAssistanceMessage: string; loginHelp?: string; authProviderHint?: string; @@ -66,7 +66,7 @@ enum LoadingStateType { AutoLogin, } -enum MessageType { +export enum MessageType { None, Info, Danger, @@ -106,9 +106,7 @@ export class LoginForm extends Component { loadingState: { type: LoadingStateType.None }, username: '', password: '', - message: this.props.infoMessage - ? { type: MessageType.Info, content: this.props.infoMessage } - : { type: MessageType.None }, + message: this.props.message || { type: MessageType.None }, mode, previousMode: mode, }; @@ -206,7 +204,7 @@ export class LoginForm extends Component { > @@ -480,8 +478,8 @@ export class LoginForm extends Component { const message = (error as IHttpFetchError).response?.status === 401 ? i18n.translate( - 'xpack.security.login.basicLoginForm.invalidUsernameOrPasswordErrorMessage', - { defaultMessage: 'Invalid username or password. Please try again.' } + 'xpack.security.login.basicLoginForm.usernameOrPasswordIsIncorrectErrorMessage', + { defaultMessage: 'Username or password is incorrect. Please try again.' } ) : i18n.translate('xpack.security.login.basicLoginForm.unknownErrorMessage', { defaultMessage: 'Oops! Error. Try again.', diff --git a/x-pack/plugins/security/public/authentication/login/login_page.test.tsx b/x-pack/plugins/security/public/authentication/login/login_page.test.tsx index a9596aff3bf0e..b3e2fac3ea2cc 100644 --- a/x-pack/plugins/security/public/authentication/login/login_page.test.tsx +++ b/x-pack/plugins/security/public/authentication/login/login_page.test.tsx @@ -14,7 +14,7 @@ import { coreMock } from 'src/core/public/mocks'; import { AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER } from '../../../common/constants'; import type { LoginState } from '../../../common/login_state'; -import { DisabledLoginForm, LoginForm } from './components'; +import { DisabledLoginForm, LoginForm, LoginFormMessageType } from './components'; import { LoginPage } from './login_page'; const createLoginState = (options?: Partial) => { @@ -228,9 +228,12 @@ describe('LoginPage', () => { resetHttpMock(); // so the calls don't show in the BasicLoginForm snapshot }); - const { authProviderHint, infoMessage } = wrapper.find(LoginForm).props(); + const { authProviderHint, message } = wrapper.find(LoginForm).props(); expect(authProviderHint).toBe('basic1'); - expect(infoMessage).toBe('Your session has timed out. Please log in again.'); + expect(message).toEqual({ + type: LoginFormMessageType.Info, + content: 'Your session has timed out. Please log in again.', + }); }); it('renders as expected when loginAssistanceMessage is set', async () => { diff --git a/x-pack/plugins/security/public/authentication/login/login_page.tsx b/x-pack/plugins/security/public/authentication/login/login_page.tsx index 562adec7918d3..40438ac1c78f3 100644 --- a/x-pack/plugins/security/public/authentication/login/login_page.tsx +++ b/x-pack/plugins/security/public/authentication/login/login_page.tsx @@ -23,7 +23,7 @@ import { LOGOUT_REASON_QUERY_STRING_PARAMETER, } from '../../../common/constants'; import type { LoginState } from '../../../common/login_state'; -import { DisabledLoginForm, LoginForm } from './components'; +import { DisabledLoginForm, LoginForm, LoginFormMessageType } from './components'; interface Props { http: HttpStart; @@ -36,18 +36,34 @@ interface State { loginState: LoginState | null; } -const infoMessageMap = new Map([ +const messageMap = new Map([ [ 'SESSION_EXPIRED', - i18n.translate('xpack.security.login.sessionExpiredDescription', { - defaultMessage: 'Your session has timed out. Please log in again.', - }), + { + type: LoginFormMessageType.Info, + content: i18n.translate('xpack.security.login.sessionExpiredDescription', { + defaultMessage: 'Your session has timed out. Please log in again.', + }), + }, ], [ 'LOGGED_OUT', - i18n.translate('xpack.security.login.loggedOutDescription', { - defaultMessage: 'You have logged out of Elastic.', - }), + { + type: LoginFormMessageType.Info, + content: i18n.translate('xpack.security.login.loggedOutDescription', { + defaultMessage: 'You have logged out of Elastic.', + }), + }, + ], + [ + 'UNAUTHENTICATED', + { + type: LoginFormMessageType.Danger, + content: i18n.translate('xpack.security.unauthenticated.errorDescription', { + defaultMessage: + "We hit an authentication error. Please check your credentials and try again. If you still can't log in, contact your system administrator.", + }), + }, ], ]); @@ -226,7 +242,7 @@ export class LoginPage extends Component { notifications={this.props.notifications} selector={selector} // @ts-expect-error Map.get is ok with getting `undefined` - infoMessage={infoMessageMap.get(query[LOGOUT_REASON_QUERY_STRING_PARAMETER]?.toString())} + message={messageMap.get(query[LOGOUT_REASON_QUERY_STRING_PARAMETER]?.toString())} loginAssistanceMessage={this.props.loginAssistanceMessage} loginHelp={loginHelp} authProviderHint={query[AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER]?.toString()} diff --git a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap new file mode 100644 index 0000000000000..bcb97538b4f05 --- /dev/null +++ b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PromptPage renders as expected with additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; + +exports[`PromptPage renders as expected without additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; diff --git a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap new file mode 100644 index 0000000000000..55168401992f7 --- /dev/null +++ b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UnauthenticatedPage renders as expected 1`] = `"ElasticMockedFonts

We couldn't log you in

We hit an authentication error. Please check your credentials and try again. If you still can't log in, contact your system administrator.

"`; diff --git a/x-pack/plugins/security/server/authentication/authentication_service.test.mocks.ts b/x-pack/plugins/security/server/authentication/authentication_service.test.mocks.ts new file mode 100644 index 0000000000000..12a63134f4ef2 --- /dev/null +++ b/x-pack/plugins/security/server/authentication/authentication_service.test.mocks.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 mockCanRedirectRequest = jest.fn(); +jest.mock('./can_redirect_request', () => ({ canRedirectRequest: mockCanRedirectRequest })); diff --git a/x-pack/plugins/security/server/authentication/authentication_service.test.ts b/x-pack/plugins/security/server/authentication/authentication_service.test.ts index b0be9445c3fc3..d38f963a60c33 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.test.ts @@ -6,6 +6,9 @@ */ jest.mock('./authenticator'); +jest.mock('./unauthenticated_page'); + +import { mockCanRedirectRequest } from './authentication_service.test.mocks'; import Boom from '@hapi/boom'; @@ -18,6 +21,7 @@ import type { KibanaRequest, Logger, LoggerFactory, + OnPreResponseToolkit, } from 'src/core/server'; import { coreMock, @@ -37,6 +41,7 @@ import type { ConfigType } from '../config'; import { ConfigSchema, createConfig } from '../config'; import type { SecurityFeatureUsageServiceStart } from '../feature_usage'; import { securityFeatureUsageServiceMock } from '../feature_usage/index.mock'; +import { ROUTE_TAG_AUTH_FLOW } from '../routes/tags'; import type { Session } from '../session_management'; import { sessionMock } from '../session_management/session.mock'; import { AuthenticationResult } from './authentication_result'; @@ -47,15 +52,60 @@ describe('AuthenticationService', () => { let logger: jest.Mocked; let mockSetupAuthenticationParams: { http: jest.Mocked; + config: ConfigType; license: jest.Mocked; + buildNumber: number; + }; + let mockStartAuthenticationParams: { + legacyAuditLogger: jest.Mocked; + audit: jest.Mocked; + config: ConfigType; + loggers: LoggerFactory; + http: jest.Mocked; + clusterClient: ReturnType; + featureUsageService: jest.Mocked; + session: jest.Mocked>; }; beforeEach(() => { logger = loggingSystemMock.createLogger(); + const httpMock = coreMock.createSetup().http; + (httpMock.basePath.prepend as jest.Mock).mockImplementation( + (path) => `${httpMock.basePath.serverBasePath}${path}` + ); + (httpMock.basePath.get as jest.Mock).mockImplementation(() => httpMock.basePath.serverBasePath); mockSetupAuthenticationParams = { - http: coreMock.createSetup().http, + http: httpMock, + config: createConfig(ConfigSchema.validate({}), loggingSystemMock.create().get(), { + isTLSEnabled: false, + }), license: licenseMock.create(), + buildNumber: 100500, }; + mockCanRedirectRequest.mockReturnValue(false); + + const coreStart = coreMock.createStart(); + mockStartAuthenticationParams = { + legacyAuditLogger: securityAuditLoggerMock.create(), + audit: auditServiceMock.create(), + config: createConfig( + ConfigSchema.validate({ + encryptionKey: 'ab'.repeat(16), + secureCookies: true, + cookieName: 'my-sid-cookie', + }), + loggingSystemMock.create().get(), + { isTLSEnabled: false } + ), + http: coreStart.http, + clusterClient: elasticsearchServiceMock.createClusterClient(), + loggers: loggingSystemMock.create(), + featureUsageService: securityFeatureUsageServiceMock.createStartContract(), + session: sessionMock.create(), + }; + (mockStartAuthenticationParams.http.basePath.get as jest.Mock).mockImplementation( + () => mockStartAuthenticationParams.http.basePath.serverBasePath + ); service = new AuthenticationService(logger); }); @@ -71,40 +121,19 @@ describe('AuthenticationService', () => { expect.any(Function) ); }); + + it('properly registers onPreResponse handler', () => { + service.setup(mockSetupAuthenticationParams); + + expect(mockSetupAuthenticationParams.http.registerOnPreResponse).toHaveBeenCalledTimes(1); + expect(mockSetupAuthenticationParams.http.registerOnPreResponse).toHaveBeenCalledWith( + expect.any(Function) + ); + }); }); describe('#start()', () => { - let mockStartAuthenticationParams: { - legacyAuditLogger: jest.Mocked; - audit: jest.Mocked; - config: ConfigType; - loggers: LoggerFactory; - http: jest.Mocked; - clusterClient: ReturnType; - featureUsageService: jest.Mocked; - session: jest.Mocked>; - }; beforeEach(() => { - const coreStart = coreMock.createStart(); - mockStartAuthenticationParams = { - legacyAuditLogger: securityAuditLoggerMock.create(), - audit: auditServiceMock.create(), - config: createConfig( - ConfigSchema.validate({ - encryptionKey: 'ab'.repeat(16), - secureCookies: true, - cookieName: 'my-sid-cookie', - }), - loggingSystemMock.create().get(), - { isTLSEnabled: false } - ), - http: coreStart.http, - clusterClient: elasticsearchServiceMock.createClusterClient(), - loggers: loggingSystemMock.create(), - featureUsageService: securityFeatureUsageServiceMock.createStartContract(), - session: sessionMock.create(), - }; - service.setup(mockSetupAuthenticationParams); }); @@ -318,4 +347,371 @@ describe('AuthenticationService', () => { }); }); }); + + describe('onPreResponse handler', () => { + function getService({ runStart = true }: { runStart?: boolean } = {}) { + service.setup(mockSetupAuthenticationParams); + + if (runStart) { + service.start(mockStartAuthenticationParams); + } + + const onPreResponseHandler = + mockSetupAuthenticationParams.http.registerOnPreResponse.mock.calls[0][0]; + const [authenticator] = jest.requireMock('./authenticator').Authenticator.mock.instances; + + return { authenticator, onPreResponseHandler }; + } + + it('ignores responses with non-401 status code', async () => { + const mockReturnedValue = { type: 'next' as any }; + const mockOnPreResponseToolkit = httpServiceMock.createOnPreResponseToolkit(); + mockOnPreResponseToolkit.next.mockReturnValue(mockReturnedValue); + + const { onPreResponseHandler } = getService(); + for (const statusCode of [200, 400, 403, 404]) { + await expect( + onPreResponseHandler( + httpServerMock.createKibanaRequest(), + { statusCode }, + mockOnPreResponseToolkit + ) + ).resolves.toBe(mockReturnedValue); + } + }); + + it('ignores responses to requests that cannot handle redirects', async () => { + const mockReturnedValue = { type: 'next' as any }; + const mockOnPreResponseToolkit = httpServiceMock.createOnPreResponseToolkit(); + mockOnPreResponseToolkit.next.mockReturnValue(mockReturnedValue); + mockCanRedirectRequest.mockReturnValue(false); + + const { onPreResponseHandler } = getService(); + await expect( + onPreResponseHandler( + httpServerMock.createKibanaRequest(), + { statusCode: 401 }, + mockOnPreResponseToolkit + ) + ).resolves.toBe(mockReturnedValue); + }); + + it('ignores responses if authenticator is not initialized', async () => { + // Run `setup`, but not `start` to simulate non-initialized `Authenticator`. + const { onPreResponseHandler } = getService({ runStart: false }); + + const mockReturnedValue = { type: 'next' as any }; + const mockOnPreResponseToolkit = httpServiceMock.createOnPreResponseToolkit(); + mockOnPreResponseToolkit.next.mockReturnValue(mockReturnedValue); + mockCanRedirectRequest.mockReturnValue(true); + + await expect( + onPreResponseHandler( + httpServerMock.createKibanaRequest(), + { statusCode: 401 }, + mockOnPreResponseToolkit + ) + ).resolves.toBe(mockReturnedValue); + }); + + describe('when login form is available', () => { + let mockReturnedValue: { type: any; body: string }; + let mockOnPreResponseToolkit: jest.Mocked; + beforeEach(() => { + mockReturnedValue = { type: 'render' as any, body: 'body' }; + mockOnPreResponseToolkit = httpServiceMock.createOnPreResponseToolkit(); + mockOnPreResponseToolkit.render.mockReturnValue(mockReturnedValue); + }); + + it('redirects to the login page when user does not have an active session', async () => { + mockCanRedirectRequest.mockReturnValue(true); + + const { authenticator, onPreResponseHandler } = getService(); + authenticator.getRequestOriginalURL.mockReturnValue('/mock-server-basepath/app/some'); + + await expect( + onPreResponseHandler( + httpServerMock.createKibanaRequest({ path: '/app/some', query: { param: 'one two' } }), + { statusCode: 401 }, + mockOnPreResponseToolkit + ) + ).resolves.toBe(mockReturnedValue); + + expect(mockOnPreResponseToolkit.render).toHaveBeenCalledWith({ + body: '
', + headers: { + 'Content-Security-Policy': `script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'`, + Refresh: + '0;url=/mock-server-basepath/login?msg=UNAUTHENTICATED&next=%2Fmock-server-basepath%2Fapp%2Fsome', + }, + }); + }); + + it('performs logout if user has an active session', async () => { + mockStartAuthenticationParams.session.getSID.mockResolvedValue('some-sid'); + + const { authenticator, onPreResponseHandler } = getService(); + authenticator.getRequestOriginalURL.mockReturnValue('/mock-server-basepath/app/some'); + mockCanRedirectRequest.mockReturnValue(true); + + await expect( + onPreResponseHandler( + httpServerMock.createKibanaRequest({ path: '/app/some', query: { param: 'one two' } }), + { statusCode: 401 }, + mockOnPreResponseToolkit + ) + ).resolves.toBe(mockReturnedValue); + + expect(mockOnPreResponseToolkit.render).toHaveBeenCalledWith({ + body: '
', + headers: { + 'Content-Security-Policy': `script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'`, + Refresh: + '0;url=/mock-server-basepath/logout?msg=UNAUTHENTICATED&next=%2Fmock-server-basepath%2Fapp%2Fsome', + }, + }); + }); + + it('does not preserve path for the authentication flow paths', async () => { + const { authenticator, onPreResponseHandler } = getService(); + authenticator.getRequestOriginalURL.mockReturnValue('/mock-server-basepath/app/some'); + mockCanRedirectRequest.mockReturnValue(true); + + await expect( + onPreResponseHandler( + httpServerMock.createKibanaRequest({ + path: '/api/security/saml/callback', + query: { param: 'one two' }, + routeTags: [ROUTE_TAG_AUTH_FLOW], + }), + { statusCode: 401 }, + mockOnPreResponseToolkit + ) + ).resolves.toBe(mockReturnedValue); + + expect(mockOnPreResponseToolkit.render).toHaveBeenCalledWith({ + body: '
', + headers: { + 'Content-Security-Policy': `script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'`, + Refresh: + '0;url=/mock-server-basepath/login?msg=UNAUTHENTICATED&next=%2Fmock-server-basepath%2F', + }, + }); + }); + }); + + describe('when login selector is available', () => { + let mockReturnedValue: { type: any; body: string }; + let mockOnPreResponseToolkit: jest.Mocked; + beforeEach(() => { + mockReturnedValue = { type: 'render' as any, body: 'body' }; + mockOnPreResponseToolkit = httpServiceMock.createOnPreResponseToolkit(); + mockOnPreResponseToolkit.render.mockReturnValue(mockReturnedValue); + + mockSetupAuthenticationParams.config = createConfig( + ConfigSchema.validate({ + authc: { + providers: { + saml: { saml1: { order: 0, realm: 'saml1' } }, + basic: { basic1: { order: 1 } }, + }, + }, + }), + loggingSystemMock.create().get(), + { isTLSEnabled: false } + ); + }); + + it('redirects to the login page when user does not have an active session', async () => { + const { authenticator, onPreResponseHandler } = getService(); + authenticator.getRequestOriginalURL.mockReturnValue('/mock-server-basepath/app/some'); + mockCanRedirectRequest.mockReturnValue(true); + + await expect( + onPreResponseHandler( + httpServerMock.createKibanaRequest({ path: '/app/some', query: { param: 'one two' } }), + { statusCode: 401 }, + mockOnPreResponseToolkit + ) + ).resolves.toBe(mockReturnedValue); + + expect(mockOnPreResponseToolkit.render).toHaveBeenCalledWith({ + body: '
', + headers: { + 'Content-Security-Policy': `script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'`, + Refresh: + '0;url=/mock-server-basepath/login?msg=UNAUTHENTICATED&next=%2Fmock-server-basepath%2Fapp%2Fsome', + }, + }); + }); + + it('performs logout if user has an active session', async () => { + mockStartAuthenticationParams.session.getSID.mockResolvedValue('some-sid'); + + const { authenticator, onPreResponseHandler } = getService(); + authenticator.getRequestOriginalURL.mockReturnValue('/mock-server-basepath/app/some'); + mockCanRedirectRequest.mockReturnValue(true); + + await expect( + onPreResponseHandler( + httpServerMock.createKibanaRequest({ path: '/app/some', query: { param: 'one two' } }), + { statusCode: 401 }, + mockOnPreResponseToolkit + ) + ).resolves.toBe(mockReturnedValue); + + expect(mockOnPreResponseToolkit.render).toHaveBeenCalledWith({ + body: '
', + headers: { + 'Content-Security-Policy': `script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'`, + Refresh: + '0;url=/mock-server-basepath/logout?msg=UNAUTHENTICATED&next=%2Fmock-server-basepath%2Fapp%2Fsome', + }, + }); + }); + + it('does not preserve path for the authentication flow paths', async () => { + const { authenticator, onPreResponseHandler } = getService(); + authenticator.getRequestOriginalURL.mockReturnValue('/mock-server-basepath/app/some'); + mockCanRedirectRequest.mockReturnValue(true); + + await expect( + onPreResponseHandler( + httpServerMock.createKibanaRequest({ + path: '/api/security/saml/callback', + query: { param: 'one two' }, + routeTags: [ROUTE_TAG_AUTH_FLOW], + }), + { statusCode: 401 }, + mockOnPreResponseToolkit + ) + ).resolves.toBe(mockReturnedValue); + + expect(mockOnPreResponseToolkit.render).toHaveBeenCalledWith({ + body: '
', + headers: { + 'Content-Security-Policy': `script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'`, + Refresh: + '0;url=/mock-server-basepath/login?msg=UNAUTHENTICATED&next=%2Fmock-server-basepath%2F', + }, + }); + }); + }); + + describe('when neither login selector nor login form is available', () => { + let mockReturnedValue: { type: any; body: string }; + let mockOnPreResponseToolkit: jest.Mocked; + beforeEach(() => { + mockReturnedValue = { type: 'render' as any, body: 'body' }; + mockOnPreResponseToolkit = httpServiceMock.createOnPreResponseToolkit(); + mockOnPreResponseToolkit.render.mockReturnValue(mockReturnedValue); + + mockSetupAuthenticationParams.config = createConfig( + ConfigSchema.validate({ + authc: { providers: { saml: { saml1: { order: 0, realm: 'saml1' } } } }, + }), + loggingSystemMock.create().get(), + { isTLSEnabled: false } + ); + }); + + it('renders unauthenticated page if user does not have an active session', async () => { + const mockRenderUnauthorizedPage = jest + .requireMock('./unauthenticated_page') + .renderUnauthenticatedPage.mockReturnValue('rendered-view'); + + const { authenticator, onPreResponseHandler } = getService(); + authenticator.getRequestOriginalURL.mockReturnValue('/mock-server-basepath/app/some'); + mockCanRedirectRequest.mockReturnValue(true); + + await expect( + onPreResponseHandler( + httpServerMock.createKibanaRequest({ path: '/app/some', query: { param: 'one two' } }), + { statusCode: 401 }, + mockOnPreResponseToolkit + ) + ).resolves.toBe(mockReturnedValue); + + expect(mockOnPreResponseToolkit.render).toHaveBeenCalledWith({ + body: 'rendered-view', + headers: { + 'Content-Security-Policy': `script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'`, + }, + }); + + expect(mockRenderUnauthorizedPage).toHaveBeenCalledWith({ + basePath: mockSetupAuthenticationParams.http.basePath, + buildNumber: 100500, + originalURL: '/mock-server-basepath/app/some', + }); + }); + + it('renders unauthenticated page if user has an active session', async () => { + const mockRenderUnauthorizedPage = jest + .requireMock('./unauthenticated_page') + .renderUnauthenticatedPage.mockReturnValue('rendered-view'); + mockStartAuthenticationParams.session.getSID.mockResolvedValue('some-sid'); + + const { authenticator, onPreResponseHandler } = getService(); + authenticator.getRequestOriginalURL.mockReturnValue('/mock-server-basepath/app/some'); + mockCanRedirectRequest.mockReturnValue(true); + + await expect( + onPreResponseHandler( + httpServerMock.createKibanaRequest({ path: '/app/some', query: { param: 'one two' } }), + { statusCode: 401 }, + mockOnPreResponseToolkit + ) + ).resolves.toBe(mockReturnedValue); + + expect(mockOnPreResponseToolkit.render).toHaveBeenCalledWith({ + body: 'rendered-view', + headers: { + 'Content-Security-Policy': `script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'`, + }, + }); + + expect(mockRenderUnauthorizedPage).toHaveBeenCalledWith({ + basePath: mockSetupAuthenticationParams.http.basePath, + buildNumber: 100500, + originalURL: '/mock-server-basepath/app/some', + }); + }); + + it('does not preserve path for the authentication flow paths', async () => { + const mockRenderUnauthorizedPage = jest + .requireMock('./unauthenticated_page') + .renderUnauthenticatedPage.mockReturnValue('rendered-view'); + + const { authenticator, onPreResponseHandler } = getService(); + authenticator.getRequestOriginalURL.mockReturnValue('/mock-server-basepath/app/some'); + mockCanRedirectRequest.mockReturnValue(true); + + await expect( + onPreResponseHandler( + httpServerMock.createKibanaRequest({ + path: '/api/security/saml/callback', + query: { param: 'one two' }, + routeTags: [ROUTE_TAG_AUTH_FLOW], + }), + { statusCode: 401 }, + mockOnPreResponseToolkit + ) + ).resolves.toBe(mockReturnedValue); + + expect(mockOnPreResponseToolkit.render).toHaveBeenCalledWith({ + body: 'rendered-view', + headers: { + 'Content-Security-Policy': `script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'`, + }, + }); + + expect(mockRenderUnauthorizedPage).toHaveBeenCalledWith({ + basePath: mockSetupAuthenticationParams.http.basePath, + buildNumber: 100500, + originalURL: '/mock-server-basepath/', + }); + }); + }); + }); }); diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index 7feeff7a5d8ed..e5895422e7a74 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -15,22 +15,29 @@ import type { LoggerFactory, } from 'src/core/server'; +import { NEXT_URL_QUERY_STRING_PARAMETER } from '../../common/constants'; import type { SecurityLicense } from '../../common/licensing'; import type { AuthenticatedUser } from '../../common/model'; +import { shouldProviderUseLoginForm } from '../../common/model'; import type { AuditServiceSetup, SecurityAuditLogger } from '../audit'; import type { ConfigType } from '../config'; -import { getErrorStatusCode } from '../errors'; +import { getDetailedErrorMessage, getErrorStatusCode } from '../errors'; import type { SecurityFeatureUsageServiceStart } from '../feature_usage'; +import { ROUTE_TAG_AUTH_FLOW } from '../routes/tags'; import type { Session } from '../session_management'; import { APIKeys } from './api_keys'; import type { AuthenticationResult } from './authentication_result'; import type { ProviderLoginAttempt } from './authenticator'; import { Authenticator } from './authenticator'; +import { canRedirectRequest } from './can_redirect_request'; import type { DeauthenticationResult } from './deauthentication_result'; +import { renderUnauthenticatedPage } from './unauthenticated_page'; interface AuthenticationServiceSetupParams { - http: Pick; + http: Pick; + config: ConfigType; license: SecurityLicense; + buildNumber: number; } interface AuthenticationServiceStartParams { @@ -62,12 +69,23 @@ export interface AuthenticationServiceStart { export class AuthenticationService { private license!: SecurityLicense; private authenticator?: Authenticator; + private session?: PublicMethodsOf; constructor(private readonly logger: Logger) {} - setup({ http, license }: AuthenticationServiceSetupParams) { + setup({ config, http, license, buildNumber }: AuthenticationServiceSetupParams) { this.license = license; + // If we cannot automatically authenticate users we should redirect them straight to the login + // page if possible, so that they can try other methods to log in. If not possible, we should + // render a dedicated `Unauthenticated` page from which users can explicitly trigger a new + // login attempt. There are two cases when we can redirect to the login page: + // 1. Login selector is enabled + // 2. Login selector is disabled, but the provider with the lowest `order` uses login form + const isLoginPageAvailable = + config.authc.selector.enabled || + shouldProviderUseLoginForm(config.authc.sortedProviders[0].type); + http.registerAuth(async (request, response, t) => { if (!license.isLicenseAvailable()) { this.logger.error('License is not available, authentication is not possible.'); @@ -118,8 +136,9 @@ export class AuthenticationService { } if (authenticationResult.failed()) { - this.logger.info(`Authentication attempt failed: ${authenticationResult.error!.message}`); const error = authenticationResult.error!; + this.logger.info(`Authentication attempt failed: ${getDetailedErrorMessage(error)}`); + // proxy Elasticsearch "native" errors const statusCode = getErrorStatusCode(error); if (typeof statusCode === 'number') { @@ -139,7 +158,49 @@ export class AuthenticationService { return t.notHandled(); }); - this.logger.debug('Successfully registered core authentication handler.'); + http.registerOnPreResponse(async (request, preResponse, toolkit) => { + if (preResponse.statusCode !== 401 || !canRedirectRequest(request)) { + return toolkit.next(); + } + + if (!this.authenticator) { + // Core doesn't allow returning error here. + this.logger.error('Authentication sub-system is not fully initialized yet.'); + return toolkit.next(); + } + + // If users can eventually re-login we want to redirect them directly to the page they tried + // to access initially, but we only want to do that for routes that aren't part of the various + // authentication flows that wouldn't make any sense after successful authentication. + const originalURL = !request.route.options.tags.includes(ROUTE_TAG_AUTH_FLOW) + ? this.authenticator.getRequestOriginalURL(request) + : `${http.basePath.get(request)}/`; + if (!isLoginPageAvailable) { + return toolkit.render({ + body: renderUnauthenticatedPage({ buildNumber, basePath: http.basePath, originalURL }), + headers: { 'Content-Security-Policy': http.csp.header }, + }); + } + + const needsToLogout = (await this.session?.getSID(request)) !== undefined; + if (needsToLogout) { + this.logger.warn('Could not authenticate user with the existing session. Forcing logout.'); + } + + return toolkit.render({ + body: '
', + headers: { + 'Content-Security-Policy': http.csp.header, + Refresh: `0;url=${http.basePath.prepend( + `${ + needsToLogout ? '/logout' : '/login' + }?msg=UNAUTHENTICATED&${NEXT_URL_QUERY_STRING_PARAMETER}=${encodeURIComponent( + originalURL + )}` + )}`, + }, + }); + }); } start({ @@ -161,6 +222,7 @@ export class AuthenticationService { const getCurrentUser = (request: KibanaRequest) => http.auth.get(request).state ?? null; + this.session = session; this.authenticator = new Authenticator({ legacyAuditLogger, audit, diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index 1bd430d0c5c98..ca33be92e9e99 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -20,6 +20,10 @@ import { loggingSystemMock, } from 'src/core/server/mocks'; +import { + AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, + AUTH_URL_HASH_QUERY_STRING_PARAMETER, +} from '../../common/constants'; import type { SecurityLicenseFeatures } from '../../common/licensing'; import { licenseMock } from '../../common/licensing/index.mock'; import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; @@ -1780,13 +1784,13 @@ describe('Authenticator', () => { ); }); - it('returns `notHandled` if session does not exist.', async () => { + it('redirects to login form if session does not exist.', async () => { const request = httpServerMock.createKibanaRequest(); mockOptions.session.get.mockResolvedValue(null); mockBasicAuthenticationProvider.logout.mockResolvedValue(DeauthenticationResult.notHandled()); await expect(authenticator.logout(request)).resolves.toEqual( - DeauthenticationResult.notHandled() + DeauthenticationResult.redirectTo('/mock-server-basepath/login?msg=LOGGED_OUT') ); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); @@ -1843,12 +1847,12 @@ describe('Authenticator', () => { expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); }); - it('returns `notHandled` if session does not exist and provider name is invalid', async () => { + it('redirects to login form if session does not exist and provider name is invalid', async () => { const request = httpServerMock.createKibanaRequest({ query: { provider: 'foo' } }); mockOptions.session.get.mockResolvedValue(null); await expect(authenticator.logout(request)).resolves.toEqual( - DeauthenticationResult.notHandled() + DeauthenticationResult.redirectTo('/mock-server-basepath/login?msg=LOGGED_OUT') ); expect(mockBasicAuthenticationProvider.logout).not.toHaveBeenCalled(); @@ -1937,4 +1941,64 @@ describe('Authenticator', () => { ); }); }); + + describe('`getRequestOriginalURL` method', () => { + let authenticator: Authenticator; + let mockOptions: ReturnType; + beforeEach(() => { + mockOptions = getMockOptions({ providers: { basic: { basic1: { order: 0 } } } }); + authenticator = new Authenticator(mockOptions); + }); + + it('filters out auth specific query parameters', () => { + expect(authenticator.getRequestOriginalURL(httpServerMock.createKibanaRequest())).toBe( + '/mock-server-basepath/path' + ); + + expect( + authenticator.getRequestOriginalURL( + httpServerMock.createKibanaRequest({ + query: { + [AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER]: 'saml1', + }, + }) + ) + ).toBe('/mock-server-basepath/path'); + + expect( + authenticator.getRequestOriginalURL( + httpServerMock.createKibanaRequest({ + query: { + [AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER]: 'saml1', + [AUTH_URL_HASH_QUERY_STRING_PARAMETER]: '#some-hash', + }, + }) + ) + ).toBe('/mock-server-basepath/path'); + }); + + it('allows to include additional query parameters', () => { + expect( + authenticator.getRequestOriginalURL(httpServerMock.createKibanaRequest(), [ + ['some-param', 'some-value'], + ['some-param2', 'some-value2'], + ]) + ).toBe('/mock-server-basepath/path?some-param=some-value&some-param2=some-value2'); + + expect( + authenticator.getRequestOriginalURL( + httpServerMock.createKibanaRequest({ + query: { + [AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER]: 'saml1', + [AUTH_URL_HASH_QUERY_STRING_PARAMETER]: '#some-hash', + }, + }), + [ + ['some-param', 'some-value'], + [AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, 'oidc1'], + ] + ) + ).toBe('/mock-server-basepath/path?some-param=some-value&auth_provider_hint=oidc1'); + }); + }); }); diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index f86ff54963da9..4eeadf23c50b2 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -11,6 +11,7 @@ import type { IBasePath, IClusterClient, LoggerFactory } from 'src/core/server'; import { KibanaRequest } from '../../../../../src/core/server'; import { AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, + AUTH_URL_HASH_QUERY_STRING_PARAMETER, LOGOUT_PROVIDER_QUERY_STRING_PARAMETER, LOGOUT_REASON_QUERY_STRING_PARAMETER, NEXT_URL_QUERY_STRING_PARAMETER, @@ -45,6 +46,15 @@ import { } from './providers'; import { Tokens } from './tokens'; +/** + * List of query string parameters used to pass various authentication related metadata that should + * be stripped away from URL as soon as they are no longer needed. + */ +const AUTH_METADATA_QUERY_STRING_PARAMETERS = [ + AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, + AUTH_URL_HASH_QUERY_STRING_PARAMETER, +]; + /** * The shape of the login attempt. */ @@ -201,6 +211,7 @@ export class Authenticator { const providerCommonOptions = { client: this.options.clusterClient, basePath: this.options.basePath, + getRequestOriginalURL: this.getRequestOriginalURL.bind(this), tokens: new Tokens({ client: this.options.clusterClient.asInternalUser, logger: this.options.loggers.get('tokens'), @@ -419,7 +430,9 @@ export class Authenticator { } } - return DeauthenticationResult.notHandled(); + // If none of the configured providers could perform a logout, we should redirect user to the + // default logout location. + return DeauthenticationResult.redirectTo(this.getLoggedOutURL(request)); } /** @@ -452,6 +465,24 @@ export class Authenticator { this.options.featureUsageService.recordPreAccessAgreementUsage(); } + getRequestOriginalURL( + request: KibanaRequest, + additionalQueryStringParameters?: Array<[string, string]> + ) { + const originalURLSearchParams = [ + ...[...request.url.searchParams.entries()].filter( + ([key]) => !AUTH_METADATA_QUERY_STRING_PARAMETERS.includes(key) + ), + ...(additionalQueryStringParameters ?? []), + ]; + + return `${this.options.basePath.get(request)}${request.url.pathname}${ + originalURLSearchParams.length > 0 + ? `?${new URLSearchParams(originalURLSearchParams).toString()}` + : '' + }`; + } + /** * Initializes HTTP Authentication provider and appends it to the end of the list of enabled * authentication providers. @@ -762,9 +793,13 @@ export class Authenticator { /** * Creates a logged out URL for the specified request and provider. * @param request Request that initiated logout. - * @param providerType Type of the provider that handles logout. + * @param providerType Type of the provider that handles logout. If not specified, then the first + * provider in the chain (default) is assumed. */ - private getLoggedOutURL(request: KibanaRequest, providerType: string) { + private getLoggedOutURL( + request: KibanaRequest, + providerType: string = this.options.config.authc.sortedProviders[0].type + ) { // The app that handles logout needs to know the reason of the logout and the URL we may need to // redirect user to once they log in again (e.g. when session expires). const searchParams = new URLSearchParams(); diff --git a/x-pack/plugins/security/server/authentication/can_redirect_request.test.ts b/x-pack/plugins/security/server/authentication/can_redirect_request.test.ts index 1507cd2d3a50a..805d647757ca5 100644 --- a/x-pack/plugins/security/server/authentication/can_redirect_request.test.ts +++ b/x-pack/plugins/security/server/authentication/can_redirect_request.test.ts @@ -7,6 +7,7 @@ import { httpServerMock } from 'src/core/server/mocks'; +import { ROUTE_TAG_API, ROUTE_TAG_CAN_REDIRECT } from '../routes/tags'; import { canRedirectRequest } from './can_redirect_request'; describe('can_redirect_request', () => { @@ -24,4 +25,33 @@ describe('can_redirect_request', () => { expect(canRedirectRequest(request)).toBe(false); }); + + it('returns false for api routes', () => { + expect( + canRedirectRequest(httpServerMock.createKibanaRequest({ path: '/api/security/some' })) + ).toBe(false); + }); + + it('returns false for internal routes', () => { + expect( + canRedirectRequest(httpServerMock.createKibanaRequest({ path: '/internal/security/some' })) + ).toBe(false); + }); + + it('returns true for the routes with the `security:canRedirect` tag', () => { + for (const request of [ + httpServerMock.createKibanaRequest({ routeTags: [ROUTE_TAG_CAN_REDIRECT] }), + httpServerMock.createKibanaRequest({ routeTags: [ROUTE_TAG_API, ROUTE_TAG_CAN_REDIRECT] }), + httpServerMock.createKibanaRequest({ + path: '/api/security/some', + routeTags: [ROUTE_TAG_CAN_REDIRECT], + }), + httpServerMock.createKibanaRequest({ + path: '/internal/security/some', + routeTags: [ROUTE_TAG_CAN_REDIRECT], + }), + ]) { + expect(canRedirectRequest(request)).toBe(true); + } + }); }); diff --git a/x-pack/plugins/security/server/authentication/can_redirect_request.ts b/x-pack/plugins/security/server/authentication/can_redirect_request.ts index 71c6365d9aea4..5a3a09f17eb86 100644 --- a/x-pack/plugins/security/server/authentication/can_redirect_request.ts +++ b/x-pack/plugins/security/server/authentication/can_redirect_request.ts @@ -7,7 +7,8 @@ import type { KibanaRequest } from 'src/core/server'; -const ROUTE_TAG_API = 'api'; +import { ROUTE_TAG_API, ROUTE_TAG_CAN_REDIRECT } from '../routes/tags'; + const KIBANA_XSRF_HEADER = 'kbn-xsrf'; const KIBANA_VERSION_HEADER = 'kbn-version'; @@ -24,9 +25,9 @@ export function canRedirectRequest(request: KibanaRequest) { const isApiRoute = route.options.tags.includes(ROUTE_TAG_API) || - (route.path.startsWith('/api/') && route.path !== '/api/security/logout') || + route.path.startsWith('/api/') || route.path.startsWith('/internal/'); const isAjaxRequest = hasVersionHeader || hasXsrfHeader; - return !isApiRoute && !isAjaxRequest; + return !isAjaxRequest && (!isApiRoute || route.options.tags.includes(ROUTE_TAG_CAN_REDIRECT)); } diff --git a/x-pack/plugins/security/server/authentication/providers/base.mock.ts b/x-pack/plugins/security/server/authentication/providers/base.mock.ts index bb78b6e963763..5d3417ae9db11 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.mock.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.mock.ts @@ -20,6 +20,7 @@ export function mockAuthenticationProviderOptions(options?: { name: string }) { client: elasticsearchServiceMock.createClusterClient(), logger: loggingSystemMock.create().get(), basePath: httpServiceMock.createBasePath(), + getRequestOriginalURL: jest.fn(), tokens: { refresh: jest.fn(), invalidate: jest.fn() }, name: options?.name ?? 'basic1', urls: { diff --git a/x-pack/plugins/security/server/authentication/providers/base.ts b/x-pack/plugins/security/server/authentication/providers/base.ts index 18d567a143fee..c7c0edcf1e9e1 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.ts @@ -27,6 +27,10 @@ import type { Tokens } from '../tokens'; export interface AuthenticationProviderOptions { name: string; basePath: HttpServiceSetup['basePath']; + getRequestOriginalURL: ( + request: KibanaRequest, + additionalQueryStringParameters?: Array<[string, string]> + ) => string; client: IClusterClient; logger: Logger; tokens: PublicMethodsOf; diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts index ebeca42682eb9..444a7f3e50a25 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts @@ -11,6 +11,10 @@ import Boom from '@hapi/boom'; import type { KibanaRequest } from 'src/core/server'; import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { + AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, + AUTH_URL_HASH_QUERY_STRING_PARAMETER, +} from '../../../common/constants'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; import { securityMock } from '../../mocks'; import { AuthenticationResult } from '../authentication_result'; @@ -376,18 +380,78 @@ describe('OIDCAuthenticationProvider', () => { }); it('redirects non-AJAX request that can not be authenticated to the "capture URL" page.', async () => { + mockOptions.getRequestOriginalURL.mockReturnValue( + '/mock-server-basepath/s/foo/some-path?auth_provider_hint=oidc' + ); const request = httpServerMock.createKibanaRequest({ path: '/s/foo/some-path' }); await expect(provider.authenticate(request, null)).resolves.toEqual( AuthenticationResult.redirectTo( - '/mock-server-basepath/internal/security/capture-url?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome-path&providerType=oidc&providerName=oidc', + '/mock-server-basepath/internal/security/capture-url?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome-path%3Fauth_provider_hint%3Doidc', { state: null } ) ); + expect(mockOptions.getRequestOriginalURL).toHaveBeenCalledTimes(1); + expect(mockOptions.getRequestOriginalURL).toHaveBeenCalledWith(request, [ + [AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, 'oidc'], + ]); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); + it('initiates OIDC handshake for non-AJAX request that can not be authenticated, but includes URL hash fragment.', async () => { + mockOptions.getRequestOriginalURL.mockReturnValue('/mock-server-basepath/s/foo/some-path'); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + state: 'statevalue', + nonce: 'noncevalue', + redirect: + 'https://op-host/path/login?response_type=code' + + '&scope=openid%20profile%20email' + + '&client_id=s6BhdRkqt3' + + '&state=statevalue' + + '&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc' + + '&login_hint=loginhint', + }, + }) + ); + + const request = httpServerMock.createKibanaRequest({ + path: '/s/foo/some-path', + query: { [AUTH_URL_HASH_QUERY_STRING_PARAMETER]: '#some-fragment' }, + }); + await expect(provider.authenticate(request)).resolves.toEqual( + AuthenticationResult.redirectTo( + 'https://op-host/path/login?response_type=code' + + '&scope=openid%20profile%20email' + + '&client_id=s6BhdRkqt3' + + '&state=statevalue' + + '&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc' + + '&login_hint=loginhint', + { + state: { + state: 'statevalue', + nonce: 'noncevalue', + redirectURL: '/mock-server-basepath/s/foo/some-path#some-fragment', + realm: 'oidc1', + }, + } + ) + ); + + expect(mockOptions.getRequestOriginalURL).toHaveBeenCalledTimes(1); + expect(mockOptions.getRequestOriginalURL).toHaveBeenCalledWith(request); + + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/oidc/prepare', + body: { realm: 'oidc1' }, + }); + }); + it('succeeds if state contains a valid token.', async () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); const tokenPair = { @@ -520,6 +584,9 @@ describe('OIDCAuthenticationProvider', () => { }); it('redirects non-AJAX requests to the "capture URL" page if refresh token is expired or already refreshed.', async () => { + mockOptions.getRequestOriginalURL.mockReturnValue( + '/mock-server-basepath/s/foo/some-path?auth_provider_hint=oidc' + ); const request = httpServerMock.createKibanaRequest({ path: '/s/foo/some-path', headers: {} }); const tokenPair = { accessToken: 'expired-token', refreshToken: 'expired-refresh-token' }; const authorization = `Bearer ${tokenPair.accessToken}`; @@ -534,11 +601,16 @@ describe('OIDCAuthenticationProvider', () => { provider.authenticate(request, { ...tokenPair, realm: 'oidc1' }) ).resolves.toEqual( AuthenticationResult.redirectTo( - '/mock-server-basepath/internal/security/capture-url?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome-path&providerType=oidc&providerName=oidc', + '/mock-server-basepath/internal/security/capture-url?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome-path%3Fauth_provider_hint%3Doidc', { state: null } ) ); + expect(mockOptions.getRequestOriginalURL).toHaveBeenCalledTimes(1); + expect(mockOptions.getRequestOriginalURL).toHaveBeenCalledWith(request, [ + [AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, 'oidc'], + ]); + expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1); expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken); diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.ts b/x-pack/plugins/security/server/authentication/providers/oidc.ts index 2afa49fe6e082..83f0ec50abb0d 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.ts @@ -10,8 +10,13 @@ import type from 'type-detect'; import type { KibanaRequest } from 'src/core/server'; -import { NEXT_URL_QUERY_STRING_PARAMETER } from '../../../common/constants'; +import { + AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, + AUTH_URL_HASH_QUERY_STRING_PARAMETER, + NEXT_URL_QUERY_STRING_PARAMETER, +} from '../../../common/constants'; import type { AuthenticationInfo } from '../../elasticsearch'; +import { getDetailedErrorMessage } from '../../errors'; import { AuthenticationResult } from '../authentication_result'; import { canRedirectRequest } from '../can_redirect_request'; import { DeauthenticationResult } from '../deauthentication_result'; @@ -201,7 +206,7 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { // We might already have a state and nonce generated by Elasticsearch (from an unfinished authentication in // another tab) return authenticationResult.notHandled() && canStartNewSession(request) - ? await this.captureRedirectURL(request) + ? await this.initiateAuthenticationHandshake(request) : authenticationResult; } @@ -264,7 +269,9 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { }) ).body as any; } catch (err) { - this.logger.debug(`Failed to authenticate request via OpenID Connect: ${err.message}`); + this.logger.debug( + `Failed to authenticate request via OpenID Connect: ${getDetailedErrorMessage(err)}` + ); return AuthenticationResult.failed(err); } @@ -313,7 +320,9 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { { state: { state, nonce, redirectURL, realm: this.realm } } ); } catch (err) { - this.logger.debug(`Failed to initiate OpenID Connect authentication: ${err.message}`); + this.logger.debug( + `Failed to initiate OpenID Connect authentication: ${getDetailedErrorMessage(err)}` + ); return AuthenticationResult.failed(err); } } @@ -341,7 +350,9 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { this.logger.debug('Request has been authenticated via state.'); return AuthenticationResult.succeeded(user, { authHeaders }); } catch (err) { - this.logger.debug(`Failed to authenticate request via state: ${err.message}`); + this.logger.debug( + `Failed to authenticate request via state: ${getDetailedErrorMessage(err)}` + ); return AuthenticationResult.failed(err); } } @@ -379,7 +390,7 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { this.logger.debug( 'Both elasticsearch access and refresh tokens are expired. Re-initiating OpenID Connect authentication.' ); - return this.captureRedirectURL(request); + return this.initiateAuthenticationHandshake(request); } return AuthenticationResult.failed( @@ -440,7 +451,7 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { return DeauthenticationResult.redirectTo(redirect); } } catch (err) { - this.logger.debug(`Failed to deauthenticate user: ${err.message}`); + this.logger.debug(`Failed to deauthenticate user: ${getDetailedErrorMessage(err)}`); return DeauthenticationResult.failed(err); } } @@ -457,22 +468,29 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { } /** - * Tries to capture full redirect URL (both path and fragment) and initiate OIDC handshake. + * Tries to initiate OIDC authentication handshake. If the request already includes user URL hash fragment, we will + * initiate handshake right away, otherwise we'll redirect user to a dedicated page where we capture URL hash fragment + * first and only then initiate SAML handshake. * @param request Request instance. */ - private captureRedirectURL(request: KibanaRequest) { - const searchParams = new URLSearchParams([ - [ - NEXT_URL_QUERY_STRING_PARAMETER, - `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}`, - ], - ['providerType', this.type], - ['providerName', this.options.name], - ]); + private initiateAuthenticationHandshake(request: KibanaRequest) { + const originalURLHash = request.url.searchParams.get(AUTH_URL_HASH_QUERY_STRING_PARAMETER); + if (originalURLHash != null) { + return this.initiateOIDCAuthentication( + request, + { realm: this.realm }, + `${this.options.getRequestOriginalURL(request)}${originalURLHash}` + ); + } + return AuthenticationResult.redirectTo( `${ this.options.basePath.serverBasePath - }/internal/security/capture-url?${searchParams.toString()}`, + }/internal/security/capture-url?${NEXT_URL_QUERY_STRING_PARAMETER}=${encodeURIComponent( + this.options.getRequestOriginalURL(request, [ + [AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, this.options.name], + ]) + )}`, // Here we indicate that current session, if any, should be invalidated. It is a no-op for the // initial handshake, but is essential when both access and refresh tokens are expired. { state: null } diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts index bd51a0f815329..dfcdb66e61c35 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts @@ -10,6 +10,10 @@ import Boom from '@hapi/boom'; import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { + AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, + AUTH_URL_HASH_QUERY_STRING_PARAMETER, +} from '../../../common/constants'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; import { securityMock } from '../../mocks'; import { AuthenticationResult } from '../authentication_result'; @@ -848,18 +852,63 @@ describe('SAMLAuthenticationProvider', () => { }); it('redirects non-AJAX request that can not be authenticated to the "capture URL" page.', async () => { + mockOptions.getRequestOriginalURL.mockReturnValue( + '/mock-server-basepath/s/foo/some-path?auth_provider_hint=saml' + ); const request = httpServerMock.createKibanaRequest({ path: '/s/foo/some-path' }); - await expect(provider.authenticate(request)).resolves.toEqual( AuthenticationResult.redirectTo( - '/mock-server-basepath/internal/security/capture-url?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome-path&providerType=saml&providerName=saml', + '/mock-server-basepath/internal/security/capture-url?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome-path%3Fauth_provider_hint%3Dsaml', { state: null } ) ); + expect(mockOptions.getRequestOriginalURL).toHaveBeenCalledTimes(1); + expect(mockOptions.getRequestOriginalURL).toHaveBeenCalledWith(request, [ + [AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, 'saml'], + ]); + expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled(); }); + it('initiates SAML handshake for non-AJAX request that can not be authenticated, but includes URL hash fragment.', async () => { + mockOptions.getRequestOriginalURL.mockReturnValue('/mock-server-basepath/s/foo/some-path'); + mockOptions.client.asInternalUser.transport.request.mockResolvedValue( + securityMock.createApiResponse({ + body: { + id: 'some-request-id', + redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20', + }, + }) + ); + + const request = httpServerMock.createKibanaRequest({ + path: '/s/foo/some-path', + query: { [AUTH_URL_HASH_QUERY_STRING_PARAMETER]: '#some-fragment' }, + }); + await expect(provider.authenticate(request)).resolves.toEqual( + AuthenticationResult.redirectTo( + 'https://idp-host/path/login?SAMLRequest=some%20request%20', + { + state: { + requestId: 'some-request-id', + redirectURL: '/mock-server-basepath/s/foo/some-path#some-fragment', + realm: 'test-realm', + }, + } + ) + ); + + expect(mockOptions.getRequestOriginalURL).toHaveBeenCalledTimes(1); + expect(mockOptions.getRequestOriginalURL).toHaveBeenCalledWith(request); + + expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/saml/prepare', + body: { realm: 'test-realm' }, + }); + }); + it('succeeds if state contains a valid token.', async () => { const request = httpServerMock.createKibanaRequest({ headers: {} }); const state = { @@ -1024,6 +1073,9 @@ describe('SAMLAuthenticationProvider', () => { }); it('re-capture URL for non-AJAX requests if refresh token is expired.', async () => { + mockOptions.getRequestOriginalURL.mockReturnValue( + '/mock-server-basepath/s/foo/some-path?auth_provider_hint=saml' + ); const request = httpServerMock.createKibanaRequest({ path: '/s/foo/some-path', headers: {} }); const state = { accessToken: 'expired-token', @@ -1040,11 +1092,16 @@ describe('SAMLAuthenticationProvider', () => { await expect(provider.authenticate(request, state)).resolves.toEqual( AuthenticationResult.redirectTo( - '/mock-server-basepath/internal/security/capture-url?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome-path&providerType=saml&providerName=saml', + '/mock-server-basepath/internal/security/capture-url?next=%2Fmock-server-basepath%2Fs%2Ffoo%2Fsome-path%3Fauth_provider_hint%3Dsaml', { state: null } ) ); + expect(mockOptions.getRequestOriginalURL).toHaveBeenCalledTimes(1); + expect(mockOptions.getRequestOriginalURL).toHaveBeenCalledWith(request, [ + [AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, 'saml'], + ]); + expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1); expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(state.refreshToken); diff --git a/x-pack/plugins/security/server/authentication/providers/saml.ts b/x-pack/plugins/security/server/authentication/providers/saml.ts index 7c27e2ebeff10..ea818e5df6e12 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.ts @@ -9,9 +9,14 @@ import Boom from '@hapi/boom'; import type { KibanaRequest } from 'src/core/server'; -import { NEXT_URL_QUERY_STRING_PARAMETER } from '../../../common/constants'; +import { + AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, + AUTH_URL_HASH_QUERY_STRING_PARAMETER, + NEXT_URL_QUERY_STRING_PARAMETER, +} from '../../../common/constants'; import { isInternalURL } from '../../../common/is_internal_url'; import type { AuthenticationInfo } from '../../elasticsearch'; +import { getDetailedErrorMessage } from '../../errors'; import { AuthenticationResult } from '../authentication_result'; import { canRedirectRequest } from '../can_redirect_request'; import { DeauthenticationResult } from '../deauthentication_result'; @@ -185,7 +190,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { } else { this.logger.debug( `Failed to perform a login: ${ - authenticationResult.error && authenticationResult.error.message + authenticationResult.error && getDetailedErrorMessage(authenticationResult.error) }` ); } @@ -230,7 +235,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { // If we couldn't authenticate by means of all methods above, let's try to capture user URL and // initiate SAML handshake, otherwise just return authentication result we have. return authenticationResult.notHandled() && canStartNewSession(request) - ? this.captureRedirectURL(request) + ? this.initiateAuthenticationHandshake(request) : authenticationResult; } @@ -283,7 +288,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { return DeauthenticationResult.redirectTo(redirect); } } catch (err) { - this.logger.debug(`Failed to deauthenticate user: ${err.message}`); + this.logger.debug(`Failed to deauthenticate user: ${getDetailedErrorMessage(err)}`); return DeauthenticationResult.failed(err); } } @@ -362,7 +367,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { }) ).body as any; } catch (err) { - this.logger.debug(`Failed to log in with SAML response: ${err.message}`); + this.logger.debug(`Failed to log in with SAML response: ${getDetailedErrorMessage(err)}`); // Since we don't know upfront what realm is targeted by the Identity Provider initiated login // there is a chance that it failed because of realm mismatch and hence we should return @@ -452,7 +457,9 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { refreshToken: existingState.refreshToken!, }); } catch (err) { - this.logger.debug(`Failed to perform IdP initiated local logout: ${err.message}`); + this.logger.debug( + `Failed to perform IdP initiated local logout: ${getDetailedErrorMessage(err)}` + ); return AuthenticationResult.failed(err); } @@ -483,7 +490,9 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { this.logger.debug('Request has been authenticated via state.'); return AuthenticationResult.succeeded(user, { authHeaders }); } catch (err) { - this.logger.debug(`Failed to authenticate request via state: ${err.message}`); + this.logger.debug( + `Failed to authenticate request via state: ${getDetailedErrorMessage(err)}` + ); return AuthenticationResult.failed(err); } } @@ -520,7 +529,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { this.logger.debug( 'Both access and refresh tokens are expired. Capturing redirect URL and re-initiating SAML handshake.' ); - return this.captureRedirectURL(request); + return this.initiateAuthenticationHandshake(request); } return AuthenticationResult.failed( @@ -569,7 +578,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { state: { requestId, redirectURL, realm: this.realm }, }); } catch (err) { - this.logger.debug(`Failed to initiate SAML handshake: ${err.message}`); + this.logger.debug(`Failed to initiate SAML handshake: ${getDetailedErrorMessage(err)}`); return AuthenticationResult.failed(err); } } @@ -629,22 +638,28 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { } /** - * Tries to capture full redirect URL (both path and fragment) and initiate SAML handshake. + * Tries to initiate SAML authentication handshake. If the request already includes user URL hash fragment, we will + * initiate handshake right away, otherwise we'll redirect user to a dedicated page where we capture URL hash fragment + * first and only then initiate SAML handshake. * @param request Request instance. */ - private captureRedirectURL(request: KibanaRequest) { - const searchParams = new URLSearchParams([ - [ - NEXT_URL_QUERY_STRING_PARAMETER, - `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}`, - ], - ['providerType', this.type], - ['providerName', this.options.name], - ]); + private initiateAuthenticationHandshake(request: KibanaRequest) { + const originalURLHash = request.url.searchParams.get(AUTH_URL_HASH_QUERY_STRING_PARAMETER); + if (originalURLHash != null) { + return this.authenticateViaHandshake( + request, + `${this.options.getRequestOriginalURL(request)}${originalURLHash}` + ); + } + return AuthenticationResult.redirectTo( `${ this.options.basePath.serverBasePath - }/internal/security/capture-url?${searchParams.toString()}`, + }/internal/security/capture-url?${NEXT_URL_QUERY_STRING_PARAMETER}=${encodeURIComponent( + this.options.getRequestOriginalURL(request, [ + [AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, this.options.name], + ]) + )}`, // Here we indicate that current session, if any, should be invalidated. It is a no-op for the // initial handshake, but is essential when both access and refresh tokens are expired. { state: null } diff --git a/x-pack/plugins/security/server/authentication/tokens.ts b/x-pack/plugins/security/server/authentication/tokens.ts index 8f6dd9275e59c..1adbb2dc66533 100644 --- a/x-pack/plugins/security/server/authentication/tokens.ts +++ b/x-pack/plugins/security/server/authentication/tokens.ts @@ -8,7 +8,7 @@ import type { ElasticsearchClient, Logger } from 'src/core/server'; import type { AuthenticationInfo } from '../elasticsearch'; -import { getErrorStatusCode } from '../errors'; +import { getDetailedErrorMessage, getErrorStatusCode } from '../errors'; /** * Represents a pair of access and refresh tokens. @@ -73,11 +73,11 @@ export class Tokens { return { accessToken, refreshToken, - // @ts-expect-error @elastic/elasticsearch decalred GetUserAccessTokenResponse.authentication: string + // @ts-expect-error @elastic/elasticsearch declared GetUserAccessTokenResponse.authentication: string authenticationInfo: authenticationInfo as AuthenticationInfo, }; } catch (err) { - this.logger.debug(`Failed to refresh access token: ${err.message}`); + this.logger.debug(`Failed to refresh access token: ${getDetailedErrorMessage(err)}`); // There are at least two common cases when refresh token request can fail: // 1. Refresh token is valid only for 24 hours and if it hasn't been used it expires. @@ -123,7 +123,7 @@ export class Tokens { }) ).body.invalidated_tokens; } catch (err) { - this.logger.debug(`Failed to invalidate refresh token: ${err.message}`); + this.logger.debug(`Failed to invalidate refresh token: ${getDetailedErrorMessage(err)}`); // When using already deleted refresh token, Elasticsearch responds with 404 and a body that // shows that no tokens were invalidated. @@ -155,7 +155,7 @@ export class Tokens { }) ).body.invalidated_tokens; } catch (err) { - this.logger.debug(`Failed to invalidate access token: ${err.message}`); + this.logger.debug(`Failed to invalidate access token: ${getDetailedErrorMessage(err)}`); // When using already deleted access token, Elasticsearch responds with 404 and a body that // shows that no tokens were invalidated. diff --git a/x-pack/plugins/security/server/authentication/unauthenticated_page.test.tsx b/x-pack/plugins/security/server/authentication/unauthenticated_page.test.tsx new file mode 100644 index 0000000000000..5cb6c899d7560 --- /dev/null +++ b/x-pack/plugins/security/server/authentication/unauthenticated_page.test.tsx @@ -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 React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; + +import { coreMock } from '../../../../../src/core/server/mocks'; +import { UnauthenticatedPage } from './unauthenticated_page'; + +jest.mock('src/core/server/rendering/views/fonts', () => ({ + Fonts: () => <>MockedFonts, +})); + +describe('UnauthenticatedPage', () => { + it('renders as expected', async () => { + const mockCoreSetup = coreMock.createSetup(); + (mockCoreSetup.http.basePath.prepend as jest.Mock).mockImplementation( + (path) => `/mock-basepath${path}` + ); + + const body = renderToStaticMarkup( + + ); + + expect(body).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security/server/authentication/unauthenticated_page.tsx b/x-pack/plugins/security/server/authentication/unauthenticated_page.tsx new file mode 100644 index 0000000000000..48d61a72e085d --- /dev/null +++ b/x-pack/plugins/security/server/authentication/unauthenticated_page.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. + */ + +// @ts-expect-error no definitions in component folder +import { EuiButton } from '@elastic/eui/lib/components/button'; +import React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import type { IBasePath } from 'src/core/server'; + +import { PromptPage } from '../prompt_page'; + +interface Props { + originalURL: string; + buildNumber: number; + basePath: IBasePath; +} + +export function UnauthenticatedPage({ basePath, originalURL, buildNumber }: Props) { + return ( + + +

+ } + actions={[ + + + , + ]} + /> + ); +} + +export function renderUnauthenticatedPage(props: Props) { + return renderToStaticMarkup(); +} diff --git a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap index 785c57490e8ef..1011d82eb1f73 100644 --- a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ResetSessionPage renders as expected 1`] = `"MockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; +exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; diff --git a/x-pack/plugins/security/server/authorization/authorization_service.tsx b/x-pack/plugins/security/server/authorization/authorization_service.tsx index db3c84477ffb1..144a8bc5fd0c4 100644 --- a/x-pack/plugins/security/server/authorization/authorization_service.tsx +++ b/x-pack/plugins/security/server/authorization/authorization_service.tsx @@ -10,7 +10,6 @@ import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import type { Observable, Subscription } from 'rxjs'; -import * as UiSharedDeps from '@kbn/ui-shared-deps'; import type { CapabilitiesSetup, HttpServiceSetup, @@ -163,25 +162,14 @@ export class AuthorizationService { http.registerOnPreResponse((request, preResponse, toolkit) => { if (preResponse.statusCode === 403 && canRedirectRequest(request)) { - const basePath = http.basePath.get(request); - const next = `${basePath}${request.url.pathname}${request.url.search}`; - const regularBundlePath = `${basePath}/${buildNumber}/bundles`; - - const logoutUrl = http.basePath.prepend( - `/api/security/logout?${querystring.stringify({ next })}` - ); - const styleSheetPaths = [ - `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`, - `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`, - `${basePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`, - `${basePath}/ui/legacy_light_theme.css`, - ]; - + const next = `${http.basePath.get(request)}${request.url.pathname}${request.url.search}`; const body = renderToStaticMarkup( ); diff --git a/x-pack/plugins/security/server/authorization/reset_session_page.test.tsx b/x-pack/plugins/security/server/authorization/reset_session_page.test.tsx index e76c8ff138fcb..d5e27c9d39ffd 100644 --- a/x-pack/plugins/security/server/authorization/reset_session_page.test.tsx +++ b/x-pack/plugins/security/server/authorization/reset_session_page.test.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; +import { coreMock } from '../../../../../src/core/server/mocks'; import { ResetSessionPage } from './reset_session_page'; jest.mock('src/core/server/rendering/views/fonts', () => ({ @@ -16,11 +17,16 @@ jest.mock('src/core/server/rendering/views/fonts', () => ({ describe('ResetSessionPage', () => { it('renders as expected', async () => { + const mockCoreSetup = coreMock.createSetup(); + (mockCoreSetup.http.basePath.prepend as jest.Mock).mockImplementation( + (path) => `/mock-basepath${path}` + ); + const body = renderToStaticMarkup( ); diff --git a/x-pack/plugins/security/server/authorization/reset_session_page.tsx b/x-pack/plugins/security/server/authorization/reset_session_page.tsx index c2d43cd3dd030..4e2e6f4631287 100644 --- a/x-pack/plugins/security/server/authorization/reset_session_page.tsx +++ b/x-pack/plugins/security/server/authorization/reset_session_page.tsx @@ -7,101 +7,53 @@ // @ts-expect-error no definitions in component folder import { EuiButton, EuiButtonEmpty } from '@elastic/eui/lib/components/button'; -// @ts-expect-error no definitions in component folder -import { EuiEmptyPrompt } from '@elastic/eui/lib/components/empty_prompt'; -// @ts-expect-error no definitions in component folder -import { icon as EuiIconAlert } from '@elastic/eui/lib/components/icon/assets/alert'; -// @ts-expect-error no definitions in component folder -import { appendIconComponentCache } from '@elastic/eui/lib/components/icon/icon'; -// @ts-expect-error no definitions in component folder -import { EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui/lib/components/page'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { Fonts } from '../../../../../src/core/server/rendering/views/fonts'; +import { FormattedMessage } from '@kbn/i18n/react'; +import type { IBasePath } from 'src/core/server'; -// Preload the alert icon used by `EuiEmptyPrompt` to ensure that it's loaded -// in advance the first time this page is rendered server-side. If not, the -// icon svg wouldn't contain any paths the first time the page was rendered. -appendIconComponentCache({ - alert: EuiIconAlert, -}); +import { PromptPage } from '../prompt_page'; export function ResetSessionPage({ logoutUrl, - styleSheetPaths, + buildNumber, basePath, }: { logoutUrl: string; - styleSheetPaths: string[]; - basePath: string; + buildNumber: number; + basePath: IBasePath; }) { - const uiPublicUrl = `${basePath}/ui`; return ( - - - {styleSheetPaths.map((path) => ( - - ))} - - {/* The alternate icon is a fallback for Safari which does not yet support SVG favicons */} - - - - - - - - - - - - - - } - body={ -

- -

- } - actions={[ - - - , - - - , - ]} - /> -
-
-
-
- - + + +

+ } + actions={[ + + + , + + + , + ]} + /> ); } diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts index b66ed6e9eb7ca..087cf8f4f8ee8 100644 --- a/x-pack/plugins/security/server/index.ts +++ b/x-pack/plugins/security/server/index.ts @@ -30,6 +30,7 @@ export type { CheckPrivilegesPayload } from './authorization'; export { LegacyAuditLogger, AuditLogger, AuditEvent } from './audit'; export type { SecurityPluginSetup, SecurityPluginStart }; export type { AuthenticatedUser } from '../common/model'; +export { ROUTE_TAG_CAN_REDIRECT } from './routes/tags'; export const config: PluginConfigDescriptor> = { schema: ConfigSchema, diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 586707dd8c9aa..57be308525fdd 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -246,7 +246,12 @@ export class SecurityPlugin this.elasticsearchService.setup({ license, status: core.status }); this.featureUsageService.setup({ featureUsage: licensing.featureUsage }); this.sessionManagementService.setup({ config, http: core.http, taskManager }); - this.authenticationService.setup({ http: core.http, license }); + this.authenticationService.setup({ + http: core.http, + config, + license, + buildNumber: this.initializerContext.env.packageInfo.buildNum, + }); registerSecurityUsageCollector({ usageCollection, config, license }); diff --git a/x-pack/plugins/security/server/prompt_page.test.tsx b/x-pack/plugins/security/server/prompt_page.test.tsx new file mode 100644 index 0000000000000..01c4488576f57 --- /dev/null +++ b/x-pack/plugins/security/server/prompt_page.test.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 from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; + +import { coreMock } from '../../../../src/core/server/mocks'; +import { PromptPage } from './prompt_page'; + +jest.mock('src/core/server/rendering/views/fonts', () => ({ + Fonts: () => <>MockedFonts, +})); + +describe('PromptPage', () => { + it('renders as expected without additional scripts', async () => { + const mockCoreSetup = coreMock.createSetup(); + (mockCoreSetup.http.basePath.prepend as jest.Mock).mockImplementation( + (path) => `/mock-basepath${path}` + ); + + const body = renderToStaticMarkup( + Some Body
} + actions={[Action#1, Action#2]} + /> + ); + + expect(body).toMatchSnapshot(); + }); + + it('renders as expected with additional scripts', async () => { + const mockCoreSetup = coreMock.createSetup(); + (mockCoreSetup.http.basePath.prepend as jest.Mock).mockImplementation( + (path) => `/mock-basepath${path}` + ); + + const body = renderToStaticMarkup( + Some Body
} + actions={[Action#1, Action#2]} + /> + ); + + expect(body).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security/server/prompt_page.tsx b/x-pack/plugins/security/server/prompt_page.tsx new file mode 100644 index 0000000000000..338d39b29e534 --- /dev/null +++ b/x-pack/plugins/security/server/prompt_page.tsx @@ -0,0 +1,96 @@ +/* + * 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. + */ + +// @ts-expect-error no definitions in component folder +import { EuiEmptyPrompt } from '@elastic/eui/lib/components/empty_prompt'; +// @ts-expect-error no definitions in component folder +import { icon as EuiIconAlert } from '@elastic/eui/lib/components/icon/assets/alert'; +// @ts-expect-error no definitions in component folder +import { appendIconComponentCache } from '@elastic/eui/lib/components/icon/icon'; +// @ts-expect-error no definitions in component folder +import { EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui/lib/components/page'; +import type { ReactNode } from 'react'; +import React from 'react'; + +import { i18n } from '@kbn/i18n'; +import { I18nProvider } from '@kbn/i18n/react'; +import * as UiSharedDeps from '@kbn/ui-shared-deps'; +import type { IBasePath } from 'src/core/server'; + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { Fonts } from '../../../../src/core/server/rendering/views/fonts'; + +// Preload the alert icon used by `EuiEmptyPrompt` to ensure that it's loaded +// in advance the first time this page is rendered server-side. If not, the +// icon svg wouldn't contain any paths the first time the page was rendered. +appendIconComponentCache({ + alert: EuiIconAlert, +}); + +interface Props { + buildNumber: number; + basePath: IBasePath; + scriptPaths?: string[]; + title: ReactNode; + body: ReactNode; + actions: ReactNode; +} + +export function PromptPage({ + basePath, + buildNumber, + scriptPaths = [], + title, + body, + actions, +}: Props) { + const uiPublicURL = `${basePath.serverBasePath}/ui`; + const regularBundlePath = `${basePath.serverBasePath}/${buildNumber}/bundles`; + const styleSheetPaths = [ + `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`, + `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`, + `${basePath.serverBasePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`, + `${basePath.serverBasePath}/ui/legacy_light_theme.css`, + ]; + + return ( + + + Elastic + {styleSheetPaths.map((path) => ( + + ))} + + {/* The alternate icon is a fallback for Safari which does not yet support SVG favicons */} + + + {scriptPaths.map((path) => ( + +++++ \ No newline at end of file From 78ae33164ef449e5696e65299cb193a055a869db Mon Sep 17 00:00:00 2001 From: Andrew Goldstein Date: Wed, 28 Apr 2021 12:26:52 -0600 Subject: [PATCH 45/70] [RAC][Alert Triage][TGrid] Update the Alerts Table (TGrid) API to implement a subset of the `EuiDataGridColumn` API (#98241) ## [RAC][Alert Triage][TGrid] Update the Alerts Table (TGrid) API to implement a subset of the `EuiDataGridColumn` API This PR implements the following subset of the `EuiDataGridColumn` API from [EuiDataGrid](https://elastic.github.io/eui/#/tabular-content/data-grid) in the `TGrid` (Timeline grid): ```ts Pick ``` The above properties are [documented in EuiDataGrid's data_grid_types.ts](https://github.com/elastic/eui/blob/master/src/components/datagrid/data_grid_types.ts), and summarized in the table below: | Property | Description | |----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `display?: ReactNode` | A `ReactNode` used when rendering the column header | | `displayAsText?: string` | Displays the column name as text (in lieu of using `display`). If not used, `id` will be shown as the column name. | | `id: string` | The unique identifier for this column, e.g. `user.name` | | `initialWidth?: number` | Initial width (in pixels) of the column | The following screenshot shows the `TGrid` rendering (from left-to-right): - An (example) RAC-flavored Observability alerts table - An (example) RAC-flavored Security Solution alerts table - The production alerts table in the Security Solutions `Detections` page, which remains the default ![three_table_configurations](https://user-images.githubusercontent.com/4459398/115944491-5a69a780-a473-11eb-85b6-36120c3092d6.png) _Above, three table configurations, rendered via the updated API_ The `public/detections/configurations` directory contains the configurations for the three tables shown in the screenshot above This change works in concert with another recent change to the `TGrid` that [added support for the `renderCellValue` API](https://github.com/elastic/kibana/pull/96098). ### Example configurations #### (example) RAC-flavored Observability alerts table ![observability_alerts_example](https://user-images.githubusercontent.com/4459398/115944556-b3d1d680-a473-11eb-8338-6097731f2d48.png) The column specification for the (example) RAC-flavored Observability alerts table, shown in the screenshot above is defined in `x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/columns.ts`: ```ts export const columns: Array< Pick & ColumnHeaderOptions > = [ { columnHeaderType: defaultColumnHeaderType, displayAsText: i18n.STATUS, id: 'kibana.rac.alert.status', initialWidth: 74, }, { columnHeaderType: defaultColumnHeaderType, displayAsText: i18n.TRIGGERED, id: '@timestamp', initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH + 5, }, { columnHeaderType: defaultColumnHeaderType, displayAsText: i18n.ALERT_DURATION, id: 'kibana.rac.alert.duration.us', initialWidth: 116, }, { columnHeaderType: defaultColumnHeaderType, displayAsText: i18n.ALERTS_HEADERS_SEVERITY, id: 'signal.rule.severity', initialWidth: 102, }, { columnHeaderType: defaultColumnHeaderType, displayAsText: i18n.ALERTS_HEADERS_REASON, id: 'signal.reason', initialWidth: 644, }, ]; ``` The example implementation of `EuiDataGrid`'s [`renderCellValue` API](https://github.com/elastic/kibana/pull/96098) used to render the RAC-flavored Observability alerts table shown in the screenshot above is located in `x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.tsx`: ```ts /** * This implementation of `EuiDataGrid`'s `renderCellValue` * accepts `EuiDataGridCellValueElementProps`, plus `data` * from the TGrid */ export const renderCellValue: React.FC< EuiDataGridCellValueElementProps & CellValueElementProps > = ({ columnId, data, eventId, header, isDetails, isExpandable, isExpanded, linkValues, rowIndex, setCellProps, timelineId, }) => { const value = getMappedNonEcsValue({ data, fieldName: columnId, })?.reduce((x) => x[0]) ?? ''; switch (columnId) { case 'kibana.rac.alert.status': return ; case 'kibana.rac.alert.duration.us': return {moment(value).fromNow(true)}; case 'signal.rule.severity': return ; case 'signal.reason': return ( {reason} ); default: // NOTE: we're using `DefaultCellRenderer` in this example configuration as a fallback, but // using `DefaultCellRenderer` here is entirely optional return ( ); } }; ``` #### (example) RAC-flavored Security Solution alerts table ![secuirty_solution_rac_example](https://user-images.githubusercontent.com/4459398/115944592-e8459280-a473-11eb-9e0f-cef8519102d4.png) The column specification for the (example) RAC-flavored Security Solution alerts table, shown in the screenshot above is defined in `x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/columns.ts`: ```ts /** * columns implements a subset of `EuiDataGrid`'s `EuiDataGridColumn` interface, * plus additional TGrid column properties */ export const columns: Array< Pick & ColumnHeaderOptions > = [ { columnHeaderType: defaultColumnHeaderType, id: '@timestamp', initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH + 5, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.name', displayAsText: i18n.ALERTS_HEADERS_RULE_NAME, linkField: 'signal.rule.id', initialWidth: 212, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.severity', displayAsText: i18n.ALERTS_HEADERS_SEVERITY, initialWidth: 104, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.reason', displayAsText: i18n.ALERTS_HEADERS_REASON, initialWidth: 644, }, ]; ``` ### Testing the example configurations locally For now, the alerts table in the Security Solution's `Detections` page is configured to use the existing (`7.13`) column configuration. To test the Alerts table in the Security Solution `Detections` page with the example configurations provided in this PR: 1. Edit `x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx` and change the following line: ```ts import { columns, RenderCellValue } from '../../configurations/security_solution_detections'; ``` from the above to ```ts import { columns, RenderCellValue } from '../../configurations/examples/observablity_alerts'; ``` for the (example) RAC-flavored Observability alerts table, or change it to ```ts import { columns, RenderCellValue } from '../../configurations/examples/security_solution_rac'; ``` for the (example) RAC-flavored Security solution alerts table. 2. Navigate to your local instance of the Security Solution [Detections page](http://localhost:5601/xyx/app/security/detections) (Note: you may need to enable detection rules to populate the alerts table.) 3. Click the `customize_columns` button shown in the screenshot below: ![customize_columns](https://user-images.githubusercontent.com/4459398/115796322-e3f37980-a38e-11eb-930b-5b21dfcb5e65.png) 4. In the `Customize Columns` popover, click the `Reset Fields` button, shown in the screenshot below: ![reset-fields](https://user-images.githubusercontent.com/4459398/115797081-49943580-a390-11eb-9485-7e6cae2f2a6f.png) After clicking `Reset Fields`, the new default columns will be displayed. ### Backwards compatibility The `width` property of Timeline's model was changed to `initialWidth` as part of this PR. - This change has no effect on Timelines persisted as saved objects - This change has no effect on Timeline's [Export and Import Timelines](https://www.elastic.co/guide/en/security/current/timelines-ui.html#import-export-timelines) feature - When a TGrid's column configuration containing the legacy `width` and `label` `ColumnHeaderOptions` is read from `localstorage`, these properties are migrated to `initialWidth` and `displayAsText` respectively. - Backwards compatibility was desk tested by persisting a custom column configuration while running off `master`, and then re-visiting the page after running this PR branch. As expected, the previously persisted column configuration was rendered correctly after running the PR branch. - Unit tests were added to `x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts` to test the migration of the `width` and `label` properties ### Other changes - The minium width of a resized column is now `70px`. The new minium is no longer data-type specific. --- .../alerts_viewer/default_headers.ts | 20 +- .../components/drag_and_drop/helpers.ts | 6 +- .../components/event_details/columns.tsx | 2 +- .../components/event_details/helpers.tsx | 2 +- .../events_viewer/default_headers.tsx | 18 +- .../public/common/mock/header.ts | 22 +- .../public/common/mock/timeline_results.ts | 30 +- .../components/alerts_table/actions.test.tsx | 14 +- .../alerts_table/default_config.tsx | 90 +----- .../components/alerts_table/index.tsx | 9 +- .../components/alerts_table/translations.ts | 35 +++ .../components/severity/index.test.tsx | 80 +++++ .../detections/components/severity/index.tsx | 56 ++++ .../components/status/index.test.tsx | 53 ++++ .../detections/components/status/index.tsx | 53 ++++ .../examples/observablity_alerts/columns.ts | 53 ++++ .../examples/observablity_alerts/index.ts | 11 + .../render_cell_value.test.tsx | 114 +++++++ .../observablity_alerts/render_cell_value.tsx | 83 ++++++ .../examples/security_solution_rac/columns.ts | 47 +++ .../examples/security_solution_rac/index.ts | 11 + .../render_cell_value.test.tsx | 90 ++++++ .../render_cell_value.tsx | 79 +++++ .../security_solution_detections/columns.ts | 101 +++++++ .../security_solution_detections/index.ts | 11 + .../render_cell_value.test.tsx | 62 ++++ .../render_cell_value.tsx | 47 +++ .../fields_browser/field_items.test.tsx | 6 +- .../components/fields_browser/field_items.tsx | 2 +- .../components/fields_browser/helpers.tsx | 2 +- .../components/open_timeline/helpers.test.ts | 124 ++++---- .../components/open_timeline/helpers.ts | 3 +- .../__snapshots__/index.test.tsx.snap | 16 +- .../body/column_headers/column_header.tsx | 9 +- .../body/column_headers/default_headers.ts | 16 +- .../body/column_headers/filter/index.tsx | 3 +- .../header/__snapshots__/index.test.tsx.snap | 6 +- .../column_headers/header/header_content.tsx | 12 +- .../body/column_headers/header/index.test.tsx | 53 +++- .../body/column_headers/helpers.test.ts | 6 +- .../timeline/body/column_headers/index.tsx | 8 +- .../components/timeline/body/constants.ts | 4 + .../__snapshots__/index.test.tsx.snap | 14 +- .../body/data_driven_columns/index.tsx | 2 +- .../components/timeline/body/index.tsx | 6 +- .../components/timeline/body/translations.ts | 14 + .../__snapshots__/index.test.tsx.snap | 22 +- .../__snapshots__/index.test.tsx.snap | 22 +- .../__snapshots__/index.test.tsx.snap | 22 +- .../containers/local_storage/index.test.ts | 279 ++++++++++++++++++ .../containers/local_storage/index.tsx | 36 ++- .../timelines/store/timeline/epic.test.ts | 16 +- .../public/timelines/store/timeline/epic.ts | 4 +- .../timelines/store/timeline/helpers.ts | 14 +- .../public/timelines/store/timeline/model.ts | 12 +- .../timelines/store/timeline/reducer.test.ts | 51 ++-- 56 files changed, 1635 insertions(+), 348 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detections/components/severity/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/detections/components/severity/index.tsx create mode 100644 x-pack/plugins/security_solution/public/detections/components/status/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/detections/components/status/index.tsx create mode 100644 x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/columns.ts create mode 100644 x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/index.ts create mode 100644 x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.test.tsx create mode 100644 x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.tsx create mode 100644 x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/columns.ts create mode 100644 x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/index.ts create mode 100644 x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.test.tsx create mode 100644 x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.tsx create mode 100644 x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/columns.ts create mode 100644 x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/index.ts create mode 100644 x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.test.tsx create mode 100644 x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/default_headers.ts b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/default_headers.ts index b0a497123f218..74ba4ec4a3be3 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/default_headers.ts +++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/default_headers.ts @@ -18,53 +18,53 @@ export const alertsHeaders: ColumnHeaderOptions[] = [ { columnHeaderType: defaultColumnHeaderType, id: '@timestamp', - width: DEFAULT_DATE_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'event.module', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, linkField: 'rule.reference', }, { columnHeaderType: defaultColumnHeaderType, id: 'event.dataset', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'event.category', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'event.severity', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'observer.name', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'host.name', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'message', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'agent.id', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'agent.type', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, ]; diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts index 3466298d5ede3..e2e506e6e1a3f 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts @@ -11,7 +11,7 @@ import { Dispatch } from 'redux'; import { ActionCreator } from 'typescript-fsa'; import { stopPropagationAndPreventDefault } from '../accessibility/helpers'; -import { alertsHeaders } from '../../../detections/components/alerts_table/default_config'; +import { alertsHeaders } from '../alerts_viewer/default_headers'; import { BrowserField, BrowserFields, getAllFieldsByName } from '../../containers/source'; import { dragAndDropActions } from '../../store/actions'; import { IdToDataProvider } from '../../store/drag_and_drop/model'; @@ -218,7 +218,7 @@ export const addFieldToTimelineColumns = ({ linkField: linkFields[fieldId] ?? undefined, type: column.type, aggregatable: column.aggregatable, - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, ...initColumnHeader, }, id: timelineId, @@ -232,7 +232,7 @@ export const addFieldToTimelineColumns = ({ column: { columnHeaderType: 'not-filtered', id: fieldId, - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, id: timelineId, index: result.destination != null ? result.destination.index : 0, diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx index 836a67441ef8a..22c2b40ed62ce 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx @@ -98,7 +98,7 @@ export const getColumns = ({ toggleColumn({ columnHeaderType: defaultColumnHeaderType, id: field, - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }) } disabled={data.isObjectArray && data.type !== 'geo_point'} diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx index dfbaadbeed7b1..1f12c2de5e24f 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx @@ -98,7 +98,7 @@ export const getColumnHeaderFromBrowserField = ({ id: browserField.name || '', type: browserField.type, aggregatable: browserField.aggregatable, - width, + initialWidth: width, }); /** diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/default_headers.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/default_headers.tsx index 59d475b0b8d81..7c84a325cb667 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/default_headers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/default_headers.tsx @@ -16,46 +16,46 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ { columnHeaderType: defaultColumnHeaderType, id: '@timestamp', - width: DEFAULT_DATE_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'message', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'host.name', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'event.module', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'event.dataset', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'event.action', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'user.name', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'source.ip', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'destination.ip', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, ]; diff --git a/x-pack/plugins/security_solution/public/common/mock/header.ts b/x-pack/plugins/security_solution/public/common/mock/header.ts index 45339bd0d3df6..ae7d3c9e576a8 100644 --- a/x-pack/plugins/security_solution/public/common/mock/header.ts +++ b/x-pack/plugins/security_solution/public/common/mock/header.ts @@ -22,7 +22,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ id: '@timestamp', type: 'date', aggregatable: true, - width: DEFAULT_DATE_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH, }, { category: 'event', @@ -33,7 +33,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ id: 'event.severity', type: 'long', aggregatable: true, - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { category: 'event', @@ -44,7 +44,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ id: 'event.category', type: 'keyword', aggregatable: true, - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { category: 'event', @@ -55,7 +55,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ id: 'event.action', type: 'keyword', aggregatable: true, - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { category: 'host', @@ -66,7 +66,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ id: 'host.name', type: 'keyword', aggregatable: true, - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { category: 'source', @@ -76,7 +76,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ id: 'source.ip', type: 'ip', aggregatable: true, - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { category: 'destination', @@ -86,7 +86,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ id: 'destination.ip', type: 'ip', aggregatable: true, - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { aggregatable: true, @@ -97,7 +97,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ format: 'bytes', id: 'destination.bytes', type: 'number', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { category: 'user', @@ -107,7 +107,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ id: 'user.name', type: 'keyword', aggregatable: true, - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { category: 'base', @@ -117,7 +117,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ id: '_id', type: 'keyword', aggregatable: true, - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { category: 'base', @@ -128,6 +128,6 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ id: 'message', type: 'text', aggregatable: false, - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, ]; 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 c02c47d45f732..806031b07e0c9 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 @@ -1937,37 +1937,37 @@ export const mockTimelineModel: TimelineModel = { { columnHeaderType: 'not-filtered', id: '@timestamp', - width: 190, + initialWidth: 190, }, { columnHeaderType: 'not-filtered', id: 'message', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.category', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'host.name', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'source.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'destination.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'user.name', - width: 180, + initialWidth: 180, }, ], dataProviders: [], @@ -2082,14 +2082,14 @@ export const defaultTimelineProps: CreateTimelineProps = { activeTab: TimelineTabs.query, prevActiveTab: TimelineTabs.query, columns: [ - { columnHeaderType: 'not-filtered', id: '@timestamp', type: 'number', width: 190 }, - { columnHeaderType: 'not-filtered', id: 'message', width: 180 }, - { columnHeaderType: 'not-filtered', id: 'event.category', width: 180 }, - { columnHeaderType: 'not-filtered', id: 'event.action', width: 180 }, - { columnHeaderType: 'not-filtered', id: 'host.name', width: 180 }, - { columnHeaderType: 'not-filtered', id: 'source.ip', width: 180 }, - { columnHeaderType: 'not-filtered', id: 'destination.ip', width: 180 }, - { columnHeaderType: 'not-filtered', id: 'user.name', width: 180 }, + { columnHeaderType: 'not-filtered', id: '@timestamp', type: 'number', initialWidth: 190 }, + { columnHeaderType: 'not-filtered', id: 'message', initialWidth: 180 }, + { columnHeaderType: 'not-filtered', id: 'event.category', initialWidth: 180 }, + { columnHeaderType: 'not-filtered', id: 'event.action', initialWidth: 180 }, + { columnHeaderType: 'not-filtered', id: 'host.name', initialWidth: 180 }, + { columnHeaderType: 'not-filtered', id: 'source.ip', initialWidth: 180 }, + { columnHeaderType: 'not-filtered', id: 'destination.ip', initialWidth: 180 }, + { columnHeaderType: 'not-filtered', id: 'user.name', initialWidth: 180 }, ], dataProviders: [ { 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 d5b64a8fe27fc..08e88567b0fd0 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 @@ -106,37 +106,37 @@ describe('alert actions', () => { columnHeaderType: 'not-filtered', id: '@timestamp', type: 'number', - width: 190, + initialWidth: 190, }, { columnHeaderType: 'not-filtered', id: 'message', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.category', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'host.name', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'source.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'destination.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'user.name', - width: 180, + initialWidth: 180, }, ], dataProviders: [], diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx index 6a83039bf1ec8..478c8930b8dd3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx @@ -9,15 +9,9 @@ import { RowRendererId } from '../../../../common/types/timeline'; import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; import { Filter } from '../../../../../../../src/plugins/data/common/es_query'; -import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers'; -import { - DEFAULT_COLUMN_MIN_WIDTH, - DEFAULT_DATE_COLUMN_MIN_WIDTH, -} from '../../../timelines/components/timeline/body/constants'; -import { ColumnHeaderOptions, SubsetTimelineModel } from '../../../timelines/store/timeline/model'; +import { SubsetTimelineModel } from '../../../timelines/store/timeline/model'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; - -import * as i18n from './translations'; +import { columns } from '../../configurations/security_solution_detections/columns'; export const buildAlertStatusFilter = (status: Status): Filter[] => [ { @@ -98,87 +92,9 @@ export const buildThreatMatchFilter = (showOnlyThreatIndicatorAlerts: boolean): ] : []; -export const alertsHeaders: ColumnHeaderOptions[] = [ - { - columnHeaderType: defaultColumnHeaderType, - id: '@timestamp', - width: DEFAULT_DATE_COLUMN_MIN_WIDTH + 5, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'signal.rule.name', - label: i18n.ALERTS_HEADERS_RULE, - linkField: 'signal.rule.id', - width: DEFAULT_COLUMN_MIN_WIDTH, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'signal.rule.version', - label: i18n.ALERTS_HEADERS_VERSION, - width: 95, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'signal.rule.type', - label: i18n.ALERTS_HEADERS_METHOD, - width: 100, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'signal.rule.severity', - label: i18n.ALERTS_HEADERS_SEVERITY, - width: 105, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'signal.rule.risk_score', - label: i18n.ALERTS_HEADERS_RISK_SCORE, - width: 115, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'event.module', - linkField: 'rule.reference', - width: DEFAULT_COLUMN_MIN_WIDTH, - }, - { - category: 'event', - columnHeaderType: defaultColumnHeaderType, - id: 'event.action', - type: 'string', - aggregatable: true, - width: 140, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'event.category', - width: 150, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'host.name', - width: 120, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'user.name', - width: 120, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'source.ip', - width: 120, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'destination.ip', - width: 140, - }, -]; - export const alertsDefaultModel: SubsetTimelineModel = { ...timelineDefaults, - columns: alertsHeaders, + columns, showCheckboxes: true, excludedRowRendererIds: Object.values(RowRendererId), }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 2890eb912b84c..9dc83d7898963 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -48,8 +48,8 @@ import { import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import { useSourcererScope } from '../../../common/containers/sourcerer'; import { buildTimeRangeFilter } from './helpers'; -import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers'; +import { columns, RenderCellValue } from '../../configurations/security_solution_detections'; interface OwnProps { defaultFilters?: Filter[]; @@ -311,7 +311,10 @@ export const AlertsTableComponent: React.FC = ({ useEffect(() => { initializeTimeline({ - defaultModel: alertsDefaultModel, + defaultModel: { + ...alertsDefaultModel, + columns, + }, documentType: i18n.ALERTS_DOCUMENT_TYPE, filterManager, footerText: i18n.TOTAL_COUNT_OF_ALERTS, @@ -346,7 +349,7 @@ export const AlertsTableComponent: React.FC = ({ headerFilterGroup={headerFilterGroup} id={timelineId} onRuleChange={onRuleChange} - renderCellValue={DefaultCellRenderer} + renderCellValue={RenderCellValue} rowRenderers={defaultRowRenderers} scopeId={SourcererScopeName.detections} start={from} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts index 56f6337d5a55c..2d9f947dcea67 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts @@ -60,6 +60,13 @@ export const ALERTS_HEADERS_RULE = i18n.translate( } ); +export const ALERTS_HEADERS_RULE_NAME = i18n.translate( + 'xpack.securitySolution.eventsViewer.alerts.defaultHeaders.ruleNameTitle', + { + defaultMessage: 'Rule name', + } +); + export const ALERTS_HEADERS_VERSION = i18n.translate( 'xpack.securitySolution.eventsViewer.alerts.defaultHeaders.versionTitle', { @@ -81,6 +88,13 @@ export const ALERTS_HEADERS_SEVERITY = i18n.translate( } ); +export const ALERTS_HEADERS_REASON = i18n.translate( + 'xpack.securitySolution.eventsViewer.alerts.defaultHeaders.reasonTitle', + { + defaultMessage: 'Reason', + } +); + export const ALERTS_HEADERS_RISK_SCORE = i18n.translate( 'xpack.securitySolution.eventsViewer.alerts.defaultHeaders.riskScoreTitle', { @@ -172,6 +186,13 @@ export const CLOSED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => 'Successfully closed {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', }); +export const ALERT_DURATION = i18n.translate( + 'xpack.securitySolution.eventsViewer.alerts.defaultHeaders.alertDurationTitle', + { + defaultMessage: 'Alert duration', + } +); + export const OPENED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => i18n.translate('xpack.securitySolution.detectionEngine.alerts.openedAlertSuccessToastMessage', { values: { totalAlerts }, @@ -216,3 +237,17 @@ export const MORE_ACTIONS = i18n.translate( defaultMessage: 'More actions', } ); + +export const STATUS = i18n.translate( + 'xpack.securitySolution.eventsViewer.alerts.defaultHeaders.statusTitle', + { + defaultMessage: 'Status', + } +); + +export const TRIGGERED = i18n.translate( + 'xpack.securitySolution.eventsViewer.alerts.defaultHeaders.triggeredTitle', + { + defaultMessage: 'Triggered', + } +); diff --git a/x-pack/plugins/security_solution/public/detections/components/severity/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/severity/index.test.tsx new file mode 100644 index 0000000000000..946a59b5bdf0a --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/severity/index.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 { mount } from 'enzyme'; +import React from 'react'; + +import { Severity } from '.'; + +interface Expected { + color: string; + severity: 'low' | 'medium' | 'high' | 'critical' | 'any-other-severity'; + textColor: string; +} + +describe('Severity', () => { + const expected: Expected[] = [ + { + color: '#C5CFD8', + severity: 'low', + textColor: 'default', + }, + { + color: '#EFC44C', + severity: 'medium', + textColor: 'default', + }, + { + color: '#FF7E62', + severity: 'high', + textColor: 'ghost', + }, + { + color: '#C3505E', + severity: 'critical', + textColor: 'ghost', + }, + { + color: 'hollow', + severity: 'any-other-severity', + textColor: 'default', + }, + ]; + + test('it capitalizes the provided `severity`', () => { + const wrapper = mount(); + + expect(wrapper.find('[data-test-subj="severity-badge"]').first()).toHaveStyleRule( + 'text-transform', + 'capitalize' + ); + }); + + test('it renders the provided `severity`', () => { + const wrapper = mount(); + + expect(wrapper.text()).toBe('critical'); + }); + + expected.forEach(({ severity, color, textColor }) => { + test(`it renders the expected badge color when severity is ${severity}`, () => { + const wrapper = mount(); + + expect(wrapper.find('[data-test-subj="severity-badge"]').first().props().color).toEqual( + color + ); + }); + + test(`it renders the expected text color when severity is ${severity}`, () => { + const wrapper = mount(); + + expect(wrapper.find('[data-test-subj="severity-text"]').first().props().color).toEqual( + textColor + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/severity/index.tsx b/x-pack/plugins/security_solution/public/detections/components/severity/index.tsx new file mode 100644 index 0000000000000..23361e3ceed59 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/severity/index.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 { EuiBadge, EuiText } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; + +const SeverityBadge = styled(EuiBadge)` + align-items: center; + display: inline-flex; + height: 40px; + text-transform: capitalize; +`; + +const getBadgeColorFromSeverity = (severity: string) => { + switch (`${severity}`.toLowerCase()) { + case 'low': + return '#C5CFD8'; + case 'medium': + return '#EFC44C'; + case 'high': + return '#FF7E62'; + case 'critical': + return '#C3505E'; + default: + return 'hollow'; + } +}; + +const getTextColorFromSeverity = (severity: string) => { + switch (`${severity}`.toLowerCase()) { + case 'critical': // fall through + case 'high': + return 'ghost'; + default: + return 'default'; + } +}; + +interface Props { + severity: string; +} + +const SeverityComponent: React.FC = ({ severity }) => ( + + + {severity} + + +); + +export const Severity = React.memo(SeverityComponent); diff --git a/x-pack/plugins/security_solution/public/detections/components/status/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/status/index.test.tsx new file mode 100644 index 0000000000000..1df652a9b09af --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/status/index.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 { mount } from 'enzyme'; +import React from 'react'; + +import { Status } from '.'; + +interface Expected { + badgeColor: string; + iconType: 'check' | 'alert'; + status: 'active' | 'recovered' | 'any-other-status'; +} + +describe('Status', () => { + const expected: Expected[] = [ + { + badgeColor: 'danger', + iconType: 'alert', + status: 'active', + }, + { + badgeColor: 'hollow', + iconType: 'check', + status: 'recovered', + }, + { + badgeColor: 'danger', + iconType: 'alert', + status: 'any-other-status', + }, + ]; + + expected.forEach(({ status, badgeColor, iconType }) => { + test(`it renders the expected badge color when status is ${status}`, () => { + const wrapper = mount(); + + expect(wrapper.find('[data-test-subj="status-icon"]').first().props().color).toEqual( + badgeColor + ); + }); + + test(`it renders the expected icon type when status is ${status}`, () => { + const wrapper = mount(); + + expect(wrapper.find('[data-test-subj="status-icon"]').first().props().type).toEqual(iconType); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/status/index.tsx b/x-pack/plugins/security_solution/public/detections/components/status/index.tsx new file mode 100644 index 0000000000000..c9ece19d1b1e2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/status/index.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 { EuiIcon } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; + +export const STATUS_CLASS_NAME = 'alert-status-icon'; + +const StatusContainer = styled.span` + display: inline-flex; + justify-content: center; + width: 100%; +`; + +export const getBadgeColorFromStatus = (status: string) => { + switch (`${status}`.toLowerCase()) { + case 'recovered': + return 'hollow'; + default: + return 'danger'; + } +}; + +export const getIconTypeFromStatus = (status: string) => { + switch (`${status}`.toLowerCase()) { + case 'recovered': + return 'check'; + default: + return 'alert'; + } +}; + +interface Props { + status: string; +} + +const StatusComponent: React.FC = ({ status }) => ( + + + +); + +export const Status = React.memo(StatusComponent); diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/columns.ts b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/columns.ts new file mode 100644 index 0000000000000..8cbb532501a2c --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/columns.ts @@ -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 { EuiDataGridColumn } from '@elastic/eui'; + +import { defaultColumnHeaderType } from '../../../../timelines/components/timeline/body/column_headers/default_headers'; +import { DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../../../../timelines/components/timeline/body/constants'; +import { ColumnHeaderOptions } from '../../../../timelines/store/timeline/model'; + +import * as i18n from '../../../components/alerts_table/translations'; + +/** + * columns implements a subset of `EuiDataGrid`'s `EuiDataGridColumn` interface, + * plus additional TGrid column properties + */ +export const columns: Array< + Pick & ColumnHeaderOptions +> = [ + { + columnHeaderType: defaultColumnHeaderType, + displayAsText: i18n.STATUS, + id: 'kibana.rac.alert.status', + initialWidth: 74, + }, + { + columnHeaderType: defaultColumnHeaderType, + displayAsText: i18n.TRIGGERED, + id: '@timestamp', + initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH + 5, + }, + { + columnHeaderType: defaultColumnHeaderType, + displayAsText: i18n.ALERT_DURATION, + id: 'kibana.rac.alert.duration.us', + initialWidth: 116, + }, + { + columnHeaderType: defaultColumnHeaderType, + displayAsText: i18n.ALERTS_HEADERS_SEVERITY, + id: 'signal.rule.severity', + initialWidth: 102, + }, + { + columnHeaderType: defaultColumnHeaderType, + displayAsText: i18n.ALERTS_HEADERS_REASON, + id: 'signal.reason', + initialWidth: 644, + }, +]; diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/index.ts b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/index.ts new file mode 100644 index 0000000000000..dfd4d9499f6e5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/index.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 { columns } from './columns'; +import { RenderCellValue } from './render_cell_value'; + +export { columns, RenderCellValue }; diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.test.tsx b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.test.tsx new file mode 100644 index 0000000000000..9c2114a4ef085 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.test.tsx @@ -0,0 +1,114 @@ +/* + * 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 { mount } from 'enzyme'; +import { cloneDeep } from 'lodash/fp'; +import React from 'react'; + +import { mockBrowserFields } from '../../../../common/containers/source/mock'; +import { DragDropContextWrapper } from '../../../../common/components/drag_and_drop/drag_drop_context_wrapper'; +import { defaultHeaders, mockTimelineData, TestProviders } from '../../../../common/mock'; +import { TimelineNonEcsData } from '../../../../../common/search_strategy/timeline'; +import { CellValueElementProps } from '../../../../timelines/components/timeline/cell_rendering'; +import { DefaultCellRenderer } from '../../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; +import { ColumnHeaderOptions } from '../../../../timelines/store/timeline/model'; + +import { RenderCellValue } from '.'; + +describe('RenderCellValue', () => { + const columnId = '@timestamp'; + const eventId = '_id-123'; + const linkValues = ['foo', 'bar', '@baz']; + const rowIndex = 5; + const timelineId = 'test'; + + let data: TimelineNonEcsData[]; + let header: ColumnHeaderOptions; + let props: CellValueElementProps; + + beforeEach(() => { + data = cloneDeep(mockTimelineData[0].data); + header = cloneDeep(defaultHeaders[0]); + props = { + columnId, + data, + eventId, + header, + isDetails: false, + isExpandable: false, + isExpanded: false, + linkValues, + rowIndex, + setCellProps: jest.fn(), + timelineId, + }; + }); + + test('it renders a custom alert status', () => { + const wrapper = mount( + + + + + + ); + + expect(wrapper.find('[data-test-subj="alert-status"]').exists()).toBe(true); + }); + + test('it renders a custom alert duration', () => { + const wrapper = mount( + + + + + + ); + + expect(wrapper.find('[data-test-subj="alert-duration"]').exists()).toBe(true); + }); + + test('it renders a custom rule severity', () => { + const wrapper = mount( + + + + + + ); + + expect(wrapper.find('[data-test-subj="rule-severity"]').exists()).toBe(true); + }); + + test('it renders a custom reason', () => { + const wrapper = mount( + + + + + + ); + + expect(wrapper.find('[data-test-subj="reason"]').exists()).toBe(true); + }); + + test('it forwards the `CellValueElementProps` to the `DefaultCellRenderer` for any other field', () => { + const aRandomFieldName = 'a.random.field.name'; + const wrapper = mount( + + + + + + ); + + expect(wrapper.find(DefaultCellRenderer).first().props()).toEqual({ + ...props, + columnId: aRandomFieldName, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.tsx b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.tsx new file mode 100644 index 0000000000000..bc8c4bd6bfe69 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiDataGridCellValueElementProps, EuiLink } from '@elastic/eui'; +import { random } from 'lodash/fp'; +import moment from 'moment'; +import React from 'react'; + +import { TruncatableText } from '../../../../common/components/truncatable_text'; +import { Severity } from '../../../components/severity'; +import { getMappedNonEcsValue } from '../../../../timelines/components/timeline/body/data_driven_columns'; +import { CellValueElementProps } from '../../../../timelines/components/timeline/cell_rendering'; +import { DefaultCellRenderer } from '../../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; +import { Status } from '../../../components/status'; + +const reason = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; + +/** + * This implementation of `EuiDataGrid`'s `renderCellValue` + * accepts `EuiDataGridCellValueElementProps`, plus `data` + * from the TGrid + */ +export const RenderCellValue: React.FC< + EuiDataGridCellValueElementProps & CellValueElementProps +> = ({ + columnId, + data, + eventId, + header, + isDetails, + isExpandable, + isExpanded, + linkValues, + rowIndex, + setCellProps, + timelineId, +}) => { + const value = + getMappedNonEcsValue({ + data, + fieldName: columnId, + })?.reduce((x) => x[0]) ?? ''; + + switch (columnId) { + case 'kibana.rac.alert.status': + return ( + + ); + case 'kibana.rac.alert.duration.us': + return {moment().fromNow(true)}; + case 'signal.rule.severity': + return ; + case 'signal.reason': + return ( + + {reason} + + ); + default: + // NOTE: we're using `DefaultCellRenderer` in this example configuration as a fallback, but + // using `DefaultCellRenderer` here is entirely optional + return ( + + ); + } +}; diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/columns.ts b/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/columns.ts new file mode 100644 index 0000000000000..96d2d870b1270 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/columns.ts @@ -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 { EuiDataGridColumn } from '@elastic/eui'; + +import { defaultColumnHeaderType } from '../../../../timelines/components/timeline/body/column_headers/default_headers'; +import { DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../../../../timelines/components/timeline/body/constants'; +import { ColumnHeaderOptions } from '../../../../timelines/store/timeline/model'; + +import * as i18n from '../../../components/alerts_table/translations'; + +/** + * columns implements a subset of `EuiDataGrid`'s `EuiDataGridColumn` interface, + * plus additional TGrid column properties + */ +export const columns: Array< + Pick & ColumnHeaderOptions +> = [ + { + columnHeaderType: defaultColumnHeaderType, + id: '@timestamp', + initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH + 5, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'signal.rule.name', + displayAsText: i18n.ALERTS_HEADERS_RULE_NAME, + linkField: 'signal.rule.id', + initialWidth: 212, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'signal.rule.severity', + displayAsText: i18n.ALERTS_HEADERS_SEVERITY, + initialWidth: 104, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'signal.reason', + displayAsText: i18n.ALERTS_HEADERS_REASON, + initialWidth: 644, + }, +]; diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/index.ts b/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/index.ts new file mode 100644 index 0000000000000..dfd4d9499f6e5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/index.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 { columns } from './columns'; +import { RenderCellValue } from './render_cell_value'; + +export { columns, RenderCellValue }; diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.test.tsx b/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.test.tsx new file mode 100644 index 0000000000000..aa4eb543a3d9b --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.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 { mount } from 'enzyme'; +import { cloneDeep } from 'lodash/fp'; +import React from 'react'; + +import { mockBrowserFields } from '../../../../common/containers/source/mock'; +import { DragDropContextWrapper } from '../../../../common/components/drag_and_drop/drag_drop_context_wrapper'; +import { defaultHeaders, mockTimelineData, TestProviders } from '../../../../common/mock'; +import { TimelineNonEcsData } from '../../../../../common/search_strategy/timeline'; +import { CellValueElementProps } from '../../../../timelines/components/timeline/cell_rendering'; +import { DefaultCellRenderer } from '../../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; +import { ColumnHeaderOptions } from '../../../../timelines/store/timeline/model'; + +import { RenderCellValue } from '.'; + +describe('RenderCellValue', () => { + const columnId = '@timestamp'; + const eventId = '_id-123'; + const linkValues = ['foo', 'bar', '@baz']; + const rowIndex = 5; + const timelineId = 'test'; + + let data: TimelineNonEcsData[]; + let header: ColumnHeaderOptions; + let props: CellValueElementProps; + + beforeEach(() => { + data = cloneDeep(mockTimelineData[0].data); + header = cloneDeep(defaultHeaders[0]); + props = { + columnId, + data, + eventId, + header, + isDetails: false, + isExpandable: false, + isExpanded: false, + linkValues, + rowIndex, + setCellProps: jest.fn(), + timelineId, + }; + }); + + test('it renders a custom severity', () => { + const wrapper = mount( + + + + + + ); + + expect(wrapper.find('[data-test-subj="custom-severity"]').exists()).toBe(true); + }); + + test('it renders a custom reason', () => { + const wrapper = mount( + + + + + + ); + + expect(wrapper.find('[data-test-subj="custom-reason"]').exists()).toBe(true); + }); + + test('it forwards the `CellValueElementProps` to the `DefaultCellRenderer` for any other field', () => { + const aRandomFieldName = 'a.random.field.name'; + const wrapper = mount( + + + + + + ); + + expect(wrapper.find(DefaultCellRenderer).first().props()).toEqual({ + ...props, + columnId: aRandomFieldName, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.tsx b/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.tsx new file mode 100644 index 0000000000000..097cb54a7b0ef --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.tsx @@ -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 { EuiDataGridCellValueElementProps } from '@elastic/eui'; +import React from 'react'; + +import { DefaultDraggable } from '../../../../common/components/draggables'; +import { TruncatableText } from '../../../../common/components/truncatable_text'; +import { Severity } from '../../../components/severity'; +import { getMappedNonEcsValue } from '../../../../timelines/components/timeline/body/data_driven_columns'; +import { CellValueElementProps } from '../../../../timelines/components/timeline/cell_rendering'; +import { DefaultCellRenderer } from '../../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; + +const reason = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; + +/** + * This implementation of `EuiDataGrid`'s `renderCellValue` + * accepts `EuiDataGridCellValueElementProps`, plus `data` + * from the TGrid + */ +export const RenderCellValue: React.FC< + EuiDataGridCellValueElementProps & CellValueElementProps +> = ({ + columnId, + data, + eventId, + header, + isDetails, + isExpandable, + isExpanded, + linkValues, + rowIndex, + setCellProps, + timelineId, +}) => { + const value = + getMappedNonEcsValue({ + data, + fieldName: columnId, + })?.reduce((x) => x[0]) ?? ''; + const draggableId = `${timelineId}-${eventId}-${columnId}-${value}`; + + switch (columnId) { + case 'signal.rule.severity': + return ( + + + + ); + case 'signal.reason': + return {reason}; + default: + return ( + + ); + } +}; diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/columns.ts b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/columns.ts new file mode 100644 index 0000000000000..23a0740294e84 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/columns.ts @@ -0,0 +1,101 @@ +/* + * 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 { EuiDataGridColumn } from '@elastic/eui'; + +import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers'; +import { + DEFAULT_COLUMN_MIN_WIDTH, + DEFAULT_DATE_COLUMN_MIN_WIDTH, +} from '../../../timelines/components/timeline/body/constants'; +import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; + +import * as i18n from '../../components/alerts_table/translations'; + +/** + * columns implements a subset of `EuiDataGrid`'s `EuiDataGridColumn` interface, + * plus additional TGrid column properties + */ +export const columns: Array< + Pick & ColumnHeaderOptions +> = [ + { + columnHeaderType: defaultColumnHeaderType, + id: '@timestamp', + initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH + 5, + }, + { + columnHeaderType: defaultColumnHeaderType, + displayAsText: i18n.ALERTS_HEADERS_RULE, + id: 'signal.rule.name', + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, + linkField: 'signal.rule.id', + }, + { + columnHeaderType: defaultColumnHeaderType, + displayAsText: i18n.ALERTS_HEADERS_VERSION, + id: 'signal.rule.version', + initialWidth: 95, + }, + { + columnHeaderType: defaultColumnHeaderType, + displayAsText: i18n.ALERTS_HEADERS_METHOD, + id: 'signal.rule.type', + initialWidth: 100, + }, + { + columnHeaderType: defaultColumnHeaderType, + displayAsText: i18n.ALERTS_HEADERS_SEVERITY, + id: 'signal.rule.severity', + initialWidth: 105, + }, + { + columnHeaderType: defaultColumnHeaderType, + displayAsText: i18n.ALERTS_HEADERS_RISK_SCORE, + id: 'signal.rule.risk_score', + initialWidth: 115, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'event.module', + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, + linkField: 'rule.reference', + }, + { + aggregatable: true, + category: 'event', + columnHeaderType: defaultColumnHeaderType, + id: 'event.action', + initialWidth: 140, + type: 'string', + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'event.category', + initialWidth: 150, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'host.name', + initialWidth: 120, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'user.name', + initialWidth: 120, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'source.ip', + initialWidth: 120, + }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'destination.ip', + initialWidth: 140, + }, +]; diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/index.ts b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/index.ts new file mode 100644 index 0000000000000..dfd4d9499f6e5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/index.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 { columns } from './columns'; +import { RenderCellValue } from './render_cell_value'; + +export { columns, RenderCellValue }; diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.test.tsx b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.test.tsx new file mode 100644 index 0000000000000..18350c102c049 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.test.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 { mount } from 'enzyme'; +import { cloneDeep } from 'lodash/fp'; +import React from 'react'; + +import { mockBrowserFields } from '../../../common/containers/source/mock'; +import { DragDropContextWrapper } from '../../../common/components/drag_and_drop/drag_drop_context_wrapper'; +import { defaultHeaders, mockTimelineData, TestProviders } from '../../../common/mock'; +import { TimelineNonEcsData } from '../../../../common/search_strategy/timeline'; +import { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering'; +import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; +import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; + +import { RenderCellValue } from '.'; + +describe('RenderCellValue', () => { + const columnId = '@timestamp'; + const eventId = '_id-123'; + const linkValues = ['foo', 'bar', '@baz']; + const rowIndex = 5; + const timelineId = 'test'; + + let data: TimelineNonEcsData[]; + let header: ColumnHeaderOptions; + let props: CellValueElementProps; + + beforeEach(() => { + data = cloneDeep(mockTimelineData[0].data); + header = cloneDeep(defaultHeaders[0]); + props = { + columnId, + data, + eventId, + header, + isDetails: false, + isExpandable: false, + isExpanded: false, + linkValues, + rowIndex, + setCellProps: jest.fn(), + timelineId, + }; + }); + + test('it forwards the `CellValueElementProps` to the `DefaultCellRenderer`', () => { + const wrapper = mount( + + + + + + ); + + expect(wrapper.find(DefaultCellRenderer).first().props()).toEqual(props); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx new file mode 100644 index 0000000000000..e9bfdefa433c2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.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 { EuiDataGridCellValueElementProps } from '@elastic/eui'; +import React from 'react'; + +import { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering'; +import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; + +/** + * This implementation of `EuiDataGrid`'s `renderCellValue` + * accepts `EuiDataGridCellValueElementProps`, plus `data` + * from the TGrid + */ +export const RenderCellValue: React.FC< + EuiDataGridCellValueElementProps & CellValueElementProps +> = ({ + columnId, + data, + eventId, + header, + isDetails, + isExpandable, + isExpanded, + linkValues, + rowIndex, + setCellProps, + timelineId, +}) => ( + +); diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx index f55af79a5dbee..07911541bb2fe 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx @@ -34,7 +34,7 @@ const columnHeaders: ColumnHeaderOptions[] = [ id: '@timestamp', type: 'date', aggregatable: true, - width: DEFAULT_DATE_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH, }, ]; @@ -207,7 +207,7 @@ describe('field_items', () => { expect(toggleColumn).toBeCalledWith({ columnHeaderType: 'not-filtered', id: '@timestamp', - width: 180, + initialWidth: 180, }); }); @@ -266,7 +266,7 @@ describe('field_items', () => { expect(toggleColumn).toBeCalledWith({ columnHeaderType: 'not-filtered', id: 'signal.rule.name', - width: 180, + initialWidth: 180, }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx index e50f5a6e39ee3..a2db284e51790 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx @@ -228,7 +228,7 @@ export const getFieldItems = ({ toggleColumn({ columnHeaderType: defaultColumnHeaderType, id: field.name ?? '', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, ...getAlertColumnHeader(timelineId, field.name ?? ''), }) } diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx index 576ce6239645f..4d06632d6441d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx @@ -16,7 +16,7 @@ import { } from '../../../common/components/accessibility/helpers'; import { TimelineId } from '../../../../common/types/timeline'; import { BrowserField, BrowserFields } from '../../../common/containers/source'; -import { alertsHeaders } from '../../../detections/components/alerts_table/default_config'; +import { alertsHeaders } from '../../../common/components/alerts_viewer/default_headers'; import { DEFAULT_CATEGORY_NAME, defaultHeaders, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index 1222f168b2ae9..c06c3f076e097 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -252,42 +252,42 @@ describe('helpers', () => { columnHeaderType: 'not-filtered', id: '@timestamp', type: 'number', - width: 190, + initialWidth: 190, }, { columnHeaderType: 'not-filtered', id: 'message', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.category', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.action', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'host.name', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'source.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'destination.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'user.name', - width: 180, + initialWidth: 180, }, ], dataProviders: [], @@ -363,42 +363,42 @@ describe('helpers', () => { columnHeaderType: 'not-filtered', id: '@timestamp', type: 'number', - width: 190, + initialWidth: 190, }, { columnHeaderType: 'not-filtered', id: 'message', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.category', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.action', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'host.name', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'source.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'destination.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'user.name', - width: 180, + initialWidth: 180, }, ], dataProviders: [], @@ -474,42 +474,42 @@ describe('helpers', () => { columnHeaderType: 'not-filtered', id: '@timestamp', type: 'number', - width: 190, + initialWidth: 190, }, { columnHeaderType: 'not-filtered', id: 'message', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.category', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.action', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'host.name', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'source.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'destination.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'user.name', - width: 180, + initialWidth: 180, }, ], dataProviders: [], @@ -583,42 +583,42 @@ describe('helpers', () => { columnHeaderType: 'not-filtered', id: '@timestamp', type: 'number', - width: 190, + initialWidth: 190, }, { columnHeaderType: 'not-filtered', id: 'message', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.category', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.action', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'host.name', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'source.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'destination.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'user.name', - width: 180, + initialWidth: 180, }, ], dataProviders: [], @@ -698,7 +698,7 @@ describe('helpers', () => { id: '@timestamp', placeholder: undefined, type: 'number', - width: 190, + initialWidth: 190, }, { aggregatable: undefined, @@ -709,7 +709,7 @@ describe('helpers', () => { id: 'message', placeholder: undefined, type: undefined, - width: 180, + initialWidth: 180, }, { aggregatable: undefined, @@ -720,7 +720,7 @@ describe('helpers', () => { id: 'event.category', placeholder: undefined, type: undefined, - width: 180, + initialWidth: 180, }, { aggregatable: undefined, @@ -731,7 +731,7 @@ describe('helpers', () => { id: 'host.name', placeholder: undefined, type: undefined, - width: 180, + initialWidth: 180, }, { aggregatable: undefined, @@ -742,7 +742,7 @@ describe('helpers', () => { id: 'source.ip', placeholder: undefined, type: undefined, - width: 180, + initialWidth: 180, }, { aggregatable: undefined, @@ -753,7 +753,7 @@ describe('helpers', () => { id: 'destination.ip', placeholder: undefined, type: undefined, - width: 180, + initialWidth: 180, }, { aggregatable: undefined, @@ -764,7 +764,7 @@ describe('helpers', () => { id: 'user.name', placeholder: undefined, type: undefined, - width: 180, + initialWidth: 180, }, ], version: '1', @@ -870,37 +870,37 @@ describe('helpers', () => { columnHeaderType: 'not-filtered', id: '@timestamp', type: 'number', - width: 190, + initialWidth: 190, }, { columnHeaderType: 'not-filtered', id: 'message', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.category', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'host.name', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'source.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'destination.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'user.name', - width: 180, + initialWidth: 180, }, ], version: '1', @@ -1018,42 +1018,42 @@ describe('helpers', () => { columnHeaderType: 'not-filtered', id: '@timestamp', type: 'number', - width: 190, + initialWidth: 190, }, { columnHeaderType: 'not-filtered', id: 'message', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.category', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.action', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'host.name', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'source.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'destination.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'user.name', - width: 180, + initialWidth: 180, }, ], dataProviders: [], @@ -1129,42 +1129,42 @@ describe('helpers', () => { columnHeaderType: 'not-filtered', id: '@timestamp', type: 'number', - width: 190, + initialWidth: 190, }, { columnHeaderType: 'not-filtered', id: 'message', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.category', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.action', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'host.name', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'source.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'destination.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'user.name', - width: 180, + initialWidth: 180, }, ], dataProviders: [], 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 8c4eb2112640f..e45a1a117769b 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 @@ -113,7 +113,8 @@ const setTimelineColumn = (col: ColumnHeaderResult) => { columnHeaderType: defaultColumnHeaderType, id: col.id != null ? col.id : 'unknown', - width: col.id === '@timestamp' ? DEFAULT_DATE_COLUMN_MIN_WIDTH : DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: + col.id === '@timestamp' ? DEFAULT_DATE_COLUMN_MIN_WIDTH : DEFAULT_COLUMN_MIN_WIDTH, } ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap index 9fe8b954657a3..6a56d1b16238a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap @@ -465,43 +465,43 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` Object { "columnHeaderType": "not-filtered", "id": "@timestamp", + "initialWidth": 190, "type": "number", - "width": 190, }, Object { "columnHeaderType": "not-filtered", "id": "message", - "width": 180, + "initialWidth": 180, }, Object { "columnHeaderType": "not-filtered", "id": "event.category", - "width": 180, + "initialWidth": 180, }, Object { "columnHeaderType": "not-filtered", "id": "event.action", - "width": 180, + "initialWidth": 180, }, Object { "columnHeaderType": "not-filtered", "id": "host.name", - "width": 180, + "initialWidth": 180, }, Object { "columnHeaderType": "not-filtered", "id": "source.ip", - "width": 180, + "initialWidth": 180, }, Object { "columnHeaderType": "not-filtered", "id": "destination.ip", - "width": 180, + "initialWidth": 180, }, Object { "columnHeaderType": "not-filtered", "id": "user.name", - "width": 180, + "initialWidth": 180, }, ] } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx index 7d203fab9e88f..3ab4d564391f3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx @@ -14,6 +14,7 @@ import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import { useDraggableKeyboardWrapper } from '../../../../../common/components/drag_and_drop/draggable_keyboard_wrapper_hook'; +import { DEFAULT_COLUMN_MIN_WIDTH } from '../constants'; import { DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME, getDraggableFieldId, @@ -76,10 +77,10 @@ const ColumnHeaderComponent: React.FC = ({ const dispatch = useDispatch(); const resizableSize = useMemo( () => ({ - width: header.width, + width: header.initialWidth ?? DEFAULT_COLUMN_MIN_WIDTH, height: 'auto', }), - [header.width] + [header.initialWidth] ); const resizableStyle: { position: 'absolute' | 'relative'; @@ -220,7 +221,7 @@ const ColumnHeaderComponent: React.FC = ({ ref={dragProvided.innerRef} > - + = ({ ), - [handleClosePopOverTrigger, headerButton, header.width, hoverActionsOwnFocus, panels] + [handleClosePopOverTrigger, headerButton, header.initialWidth, hoverActionsOwnFocus, panels] ); const onFocus = useCallback(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/default_headers.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/default_headers.ts index c4f49b240b6e6..fea65d0499a13 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/default_headers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/default_headers.ts @@ -15,42 +15,42 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ columnHeaderType: defaultColumnHeaderType, id: '@timestamp', type: 'number', - width: DEFAULT_DATE_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'message', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'event.category', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'event.action', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'host.name', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'source.ip', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'destination.ip', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { columnHeaderType: defaultColumnHeaderType, id: 'user.name', - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, ]; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/filter/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/filter/index.tsx index 64b3598fa5d89..bdf4cc42fa794 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/filter/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/filter/index.tsx @@ -8,6 +8,7 @@ import { noop } from 'lodash/fp'; import React from 'react'; +import { DEFAULT_COLUMN_MIN_WIDTH } from '../../constants'; import { OnFilterChange } from '../../../events'; import { ColumnHeaderOptions } from '../../../../../../timelines/store/timeline/model'; import { TextFilter } from '../text_filter'; @@ -24,7 +25,7 @@ export const Filter = React.memo(({ header, onFilterChange = noop }) => { return ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap index 4cd2193f148a3..50da19c3d48f3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap @@ -7,8 +7,8 @@ exports[`Header renders correctly against snapshot 1`] = ` Object { "columnHeaderType": "not-filtered", "id": "@timestamp", + "initialWidth": 190, "type": "number", - "width": 190, } } isLoading={false} @@ -30,8 +30,8 @@ exports[`Header renders correctly against snapshot 1`] = ` Object { "columnHeaderType": "not-filtered", "id": "@timestamp", + "initialWidth": 190, "type": "number", - "width": 190, } } isLoading={false} @@ -52,8 +52,8 @@ exports[`Header renders correctly against snapshot 1`] = ` Object { "columnHeaderType": "not-filtered", "id": "@timestamp", + "initialWidth": 190, "type": "number", - "width": 190, } } onFilterChange={[Function]} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/header_content.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/header_content.tsx index 393594c69bb81..484cb78417c2f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/header_content.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/header_content.tsx @@ -46,7 +46,11 @@ const HeaderContentComponent: React.FC = ({ data-test-subj="header-tooltip" content={} > - <>{header.label ?? header.id} + <> + {React.isValidElement(header.display) + ? header.display + : header.displayAsText ?? header.id} + @@ -63,7 +67,11 @@ const HeaderContentComponent: React.FC = ({ data-test-subj="header-tooltip" content={} > - <>{header.label ?? header.id} + <> + {React.isValidElement(header.display) + ? header.display + : header.displayAsText ?? header.id} + diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx index b0198e60f3b9a..f2496484c25ea 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx @@ -65,9 +65,9 @@ describe('Header', () => { ).toEqual(columnHeader.id); }); - test('it renders the header text alias when label is provided', () => { - const label = 'Timestamp'; - const headerWithLabel = { ...columnHeader, label }; + test('it renders the header text alias when displayAsText is provided', () => { + const displayAsText = 'Timestamp'; + const headerWithLabel = { ...columnHeader, displayAsText }; const wrapper = mount( @@ -76,7 +76,52 @@ describe('Header', () => { expect( wrapper.find(`[data-test-subj="header-text-${columnHeader.id}"]`).first().text() - ).toEqual(label); + ).toEqual(displayAsText); + }); + + test('it renders the header as a `ReactNode` when `display` is provided', () => { + const display: React.ReactNode = ( +
+ {'The display property renders the column heading as a ReactNode'} +
+ ); + const headerWithLabel = { ...columnHeader, display }; + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="rendered-via-display"]`).exists()).toBe(true); + }); + + test('it prefers to render `display` instead of `displayAsText` when both are provided', () => { + const displayAsText = 'this text should NOT be rendered'; + const display: React.ReactNode = ( +
{'this text is rendered via display'}
+ ); + const headerWithLabel = { ...columnHeader, display, displayAsText }; + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toBe('this text is rendered via display'); + }); + + test('it falls back to rendering header.id when `display` is not a valid React node', () => { + const display = {}; // a plain object is NOT a `ReactNode` + const headerWithLabel = { ...columnHeader, display }; + const wrapper = mount( + + + + ); + + expect( + wrapper.find(`[data-test-subj="header-text-${columnHeader.id}"]`).first().text() + ).toEqual(columnHeader.id); }); test('it renders a sort indicator', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts index 1260f59be3621..2fcfed6489eb2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts @@ -67,7 +67,7 @@ describe('helpers', () => { name: '@timestamp', searchable: true, type: 'date', - width: 190, + initialWidth: 190, }, { aggregatable: true, @@ -81,7 +81,7 @@ describe('helpers', () => { name: 'source.ip', searchable: true, type: 'ip', - width: 180, + initialWidth: 180, }, { aggregatable: true, @@ -96,7 +96,7 @@ describe('helpers', () => { name: 'destination.ip', searchable: true, type: 'ip', - width: 180, + initialWidth: 180, }, ]; const mockHeader = defaultHeaders.filter((h) => diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx index 99fb6c3dd8907..efb076337864b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx @@ -170,7 +170,7 @@ export const ColumnHeadersComponent = ({ > - + @@ -218,10 +218,10 @@ export const ColumnHeadersComponent = ({ const myColumns = useMemo( () => - columnHeaders.map(({ aggregatable, label, id, type }) => ({ + columnHeaders.map(({ aggregatable, displayAsText, id, type }) => ({ id, isSortable: aggregatable, - displayAsText: label, + displayAsText, schema: type, })), [columnHeaders] @@ -254,7 +254,7 @@ export const ColumnHeadersComponent = ({ [onSortColumns, sort] ); const displayValues = useMemo( - () => columnHeaders.reduce((acc, ch) => ({ ...acc, [ch.id]: ch.label ?? ch.id }), {}), + () => columnHeaders.reduce((acc, ch) => ({ ...acc, [ch.id]: ch.displayAsText ?? ch.id }), {}), [columnHeaders] ); const ColumnSorting = useDataGridColumnSorting(myColumns, sortedColumns, {}, [], displayValues); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/constants.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/constants.ts index e33efe75e6895..445211229574b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/constants.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/constants.ts @@ -21,5 +21,9 @@ export const EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH = SHOW_CHECK_BOXES_COLUMN_WIDTH /** The default minimum width of a column (when a width for the column type is not specified) */ export const DEFAULT_COLUMN_MIN_WIDTH = 180; // px + +/** The minimum width of a resized column */ +export const RESIZED_COLUMN_MIN_WITH = 70; // px + /** The default minimum width of a column of type `date` */ export const DEFAULT_DATE_COLUMN_MIN_WIDTH = 190; // px diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap index 91d039a19495c..9cba2f98428a1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap @@ -87,7 +87,7 @@ exports[`Columns it renders the expected columns 1`] = ` Object { "columnHeaderType": "not-filtered", "id": "message", - "width": 180, + "initialWidth": 180, } } linkValues={Array []} @@ -179,7 +179,7 @@ exports[`Columns it renders the expected columns 1`] = ` Object { "columnHeaderType": "not-filtered", "id": "event.category", - "width": 180, + "initialWidth": 180, } } linkValues={Array []} @@ -271,7 +271,7 @@ exports[`Columns it renders the expected columns 1`] = ` Object { "columnHeaderType": "not-filtered", "id": "event.action", - "width": 180, + "initialWidth": 180, } } linkValues={Array []} @@ -363,7 +363,7 @@ exports[`Columns it renders the expected columns 1`] = ` Object { "columnHeaderType": "not-filtered", "id": "host.name", - "width": 180, + "initialWidth": 180, } } linkValues={Array []} @@ -455,7 +455,7 @@ exports[`Columns it renders the expected columns 1`] = ` Object { "columnHeaderType": "not-filtered", "id": "source.ip", - "width": 180, + "initialWidth": 180, } } linkValues={Array []} @@ -547,7 +547,7 @@ exports[`Columns it renders the expected columns 1`] = ` Object { "columnHeaderType": "not-filtered", "id": "destination.ip", - "width": 180, + "initialWidth": 180, } } linkValues={Array []} @@ -639,7 +639,7 @@ exports[`Columns it renders the expected columns 1`] = ` Object { "columnHeaderType": "not-filtered", "id": "user.name", - "width": 180, + "initialWidth": 180, } } linkValues={Array []} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx index aeb9af46ea2ec..e5012ec3522b0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx @@ -98,7 +98,7 @@ export const DataDrivenColumns = React.memo( onKeyDown={onKeyDown} role="button" tabIndex={0} - width={header.width} + width={header.initialWidth} > <> 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 59c0610c544e9..4d5f773b73e1d 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 @@ -12,6 +12,7 @@ import { connect, ConnectedProps } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import { CellValueElementProps } from '../cell_rendering'; +import { DEFAULT_COLUMN_MIN_WIDTH } from './constants'; import { RowRendererId, TimelineId, TimelineTabs } from '../../../../../common/types/timeline'; import { FIRST_ARIA_INDEX, @@ -162,7 +163,10 @@ export const BodyComponent = React.memo( const columnWidths = useMemo( () => - columnHeaders.reduce((totalWidth, header) => totalWidth + header.width, actionsColumnWidth), + columnHeaders.reduce( + (totalWidth, header) => totalWidth + (header.initialWidth ?? DEFAULT_COLUMN_MIN_WIDTH), + actionsColumnWidth + ), [actionsColumnWidth, columnHeaders] ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts index bc9c774b40413..d104dc3a85f72 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts @@ -28,6 +28,13 @@ export const COPY_TO_CLIPBOARD = i18n.translate( } ); +export const INVESTIGATE = i18n.translate( + 'xpack.securitySolution.timeline.body.actions.investigateLabel', + { + defaultMessage: 'Investigate', + } +); + export const UNPINNED = i18n.translate( 'xpack.securitySolution.timeline.body.pinning.unpinnedTooltip', { @@ -74,6 +81,13 @@ export const VIEW_DETAILS = i18n.translate( } ); +export const VIEW_SUMMARY = i18n.translate( + 'xpack.securitySolution.timeline.body.actions.viewSummaryLabel', + { + defaultMessage: 'View summary', + } +); + export const VIEW_DETAILS_FOR_ROW = ({ ariaRowindex, columnValues, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/__snapshots__/index.test.tsx.snap index 9ec1fa7071277..78f19e390ae28 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/__snapshots__/index.test.tsx.snap @@ -14,8 +14,8 @@ For log events this is the date/time when the event was generated, and not when Required field for all events.", "example": "2016-05-23T08:05:34.853Z", "id": "@timestamp", + "initialWidth": 190, "type": "date", - "width": 190, }, Object { "aggregatable": true, @@ -24,8 +24,8 @@ Required field for all events.", "description": "Severity describes the severity of the event. What the different severity values mean can very different between use cases. It's up to the implementer to make sure severities are consistent across events.", "example": "7", "id": "event.severity", + "initialWidth": 180, "type": "long", - "width": 180, }, Object { "aggregatable": true, @@ -35,8 +35,8 @@ Required field for all events.", This contains high-level information about the contents of the event. It is more generic than \`event.action\`, in the sense that typically a category contains multiple actions. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.", "example": "user-management", "id": "event.category", + "initialWidth": 180, "type": "keyword", - "width": 180, }, Object { "aggregatable": true, @@ -46,8 +46,8 @@ This contains high-level information about the contents of the event. It is more This describes the information in the event. It is more specific than \`event.category\`. Examples are \`group-add\`, \`process-started\`, \`file-created\`. The value is normally defined by the implementer.", "example": "user-password-change", "id": "event.action", + "initialWidth": 180, "type": "keyword", - "width": 180, }, Object { "aggregatable": true, @@ -57,8 +57,8 @@ This describes the information in the event. It is more specific than \`event.ca It can contain what \`hostname\` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.", "example": "", "id": "host.name", + "initialWidth": 180, "type": "keyword", - "width": 180, }, Object { "aggregatable": true, @@ -68,8 +68,8 @@ It can contain what \`hostname\` returns on Unix systems, the fully qualified do Can be one or multiple IPv4 or IPv6 addresses.", "example": "", "id": "source.ip", + "initialWidth": 180, "type": "ip", - "width": 180, }, Object { "aggregatable": true, @@ -79,8 +79,8 @@ Can be one or multiple IPv4 or IPv6 addresses.", Can be one or multiple IPv4 or IPv6 addresses.", "example": "", "id": "destination.ip", + "initialWidth": 180, "type": "ip", - "width": 180, }, Object { "aggregatable": true, @@ -90,8 +90,8 @@ Can be one or multiple IPv4 or IPv6 addresses.", "example": "123", "format": "bytes", "id": "destination.bytes", + "initialWidth": 180, "type": "number", - "width": 180, }, Object { "aggregatable": true, @@ -100,8 +100,8 @@ Can be one or multiple IPv4 or IPv6 addresses.", "description": "Short name or login of the user.", "example": "albert", "id": "user.name", + "initialWidth": 180, "type": "keyword", - "width": 180, }, Object { "aggregatable": true, @@ -110,8 +110,8 @@ Can be one or multiple IPv4 or IPv6 addresses.", "description": "Each document has an _id that uniquely identifies it", "example": "Y-6TfmcB0WOhS6qyMv3s", "id": "_id", + "initialWidth": 180, "type": "keyword", - "width": 180, }, Object { "aggregatable": false, @@ -121,8 +121,8 @@ Can be one or multiple IPv4 or IPv6 addresses.", In other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.", "example": "Hello World", "id": "message", + "initialWidth": 180, "type": "text", - "width": 180, }, ] } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/__snapshots__/index.test.tsx.snap index ce59d191a472d..2ccf562c9ca6f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/__snapshots__/index.test.tsx.snap @@ -13,8 +13,8 @@ For log events this is the date/time when the event was generated, and not when Required field for all events.", "example": "2016-05-23T08:05:34.853Z", "id": "@timestamp", + "initialWidth": 190, "type": "date", - "width": 190, }, Object { "aggregatable": true, @@ -23,8 +23,8 @@ Required field for all events.", "description": "Severity describes the severity of the event. What the different severity values mean can very different between use cases. It's up to the implementer to make sure severities are consistent across events.", "example": "7", "id": "event.severity", + "initialWidth": 180, "type": "long", - "width": 180, }, Object { "aggregatable": true, @@ -34,8 +34,8 @@ Required field for all events.", This contains high-level information about the contents of the event. It is more generic than \`event.action\`, in the sense that typically a category contains multiple actions. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.", "example": "user-management", "id": "event.category", + "initialWidth": 180, "type": "keyword", - "width": 180, }, Object { "aggregatable": true, @@ -45,8 +45,8 @@ This contains high-level information about the contents of the event. It is more This describes the information in the event. It is more specific than \`event.category\`. Examples are \`group-add\`, \`process-started\`, \`file-created\`. The value is normally defined by the implementer.", "example": "user-password-change", "id": "event.action", + "initialWidth": 180, "type": "keyword", - "width": 180, }, Object { "aggregatable": true, @@ -56,8 +56,8 @@ This describes the information in the event. It is more specific than \`event.ca It can contain what \`hostname\` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.", "example": "", "id": "host.name", + "initialWidth": 180, "type": "keyword", - "width": 180, }, Object { "aggregatable": true, @@ -67,8 +67,8 @@ It can contain what \`hostname\` returns on Unix systems, the fully qualified do Can be one or multiple IPv4 or IPv6 addresses.", "example": "", "id": "source.ip", + "initialWidth": 180, "type": "ip", - "width": 180, }, Object { "aggregatable": true, @@ -78,8 +78,8 @@ Can be one or multiple IPv4 or IPv6 addresses.", Can be one or multiple IPv4 or IPv6 addresses.", "example": "", "id": "destination.ip", + "initialWidth": 180, "type": "ip", - "width": 180, }, Object { "aggregatable": true, @@ -89,8 +89,8 @@ Can be one or multiple IPv4 or IPv6 addresses.", "example": "123", "format": "bytes", "id": "destination.bytes", + "initialWidth": 180, "type": "number", - "width": 180, }, Object { "aggregatable": true, @@ -99,8 +99,8 @@ Can be one or multiple IPv4 or IPv6 addresses.", "description": "Short name or login of the user.", "example": "albert", "id": "user.name", + "initialWidth": 180, "type": "keyword", - "width": 180, }, Object { "aggregatable": true, @@ -109,8 +109,8 @@ Can be one or multiple IPv4 or IPv6 addresses.", "description": "Each document has an _id that uniquely identifies it", "example": "Y-6TfmcB0WOhS6qyMv3s", "id": "_id", + "initialWidth": 180, "type": "keyword", - "width": 180, }, Object { "aggregatable": false, @@ -120,8 +120,8 @@ Can be one or multiple IPv4 or IPv6 addresses.", In other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.", "example": "Hello World", "id": "message", + "initialWidth": 180, "type": "text", - "width": 180, }, ] } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap index f6ff6b50221b7..5f529ba827c45 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap @@ -14,8 +14,8 @@ For log events this is the date/time when the event was generated, and not when Required field for all events.", "example": "2016-05-23T08:05:34.853Z", "id": "@timestamp", + "initialWidth": 190, "type": "date", - "width": 190, }, Object { "aggregatable": true, @@ -24,8 +24,8 @@ Required field for all events.", "description": "Severity describes the severity of the event. What the different severity values mean can very different between use cases. It's up to the implementer to make sure severities are consistent across events.", "example": "7", "id": "event.severity", + "initialWidth": 180, "type": "long", - "width": 180, }, Object { "aggregatable": true, @@ -35,8 +35,8 @@ Required field for all events.", This contains high-level information about the contents of the event. It is more generic than \`event.action\`, in the sense that typically a category contains multiple actions. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.", "example": "user-management", "id": "event.category", + "initialWidth": 180, "type": "keyword", - "width": 180, }, Object { "aggregatable": true, @@ -46,8 +46,8 @@ This contains high-level information about the contents of the event. It is more This describes the information in the event. It is more specific than \`event.category\`. Examples are \`group-add\`, \`process-started\`, \`file-created\`. The value is normally defined by the implementer.", "example": "user-password-change", "id": "event.action", + "initialWidth": 180, "type": "keyword", - "width": 180, }, Object { "aggregatable": true, @@ -57,8 +57,8 @@ This describes the information in the event. It is more specific than \`event.ca It can contain what \`hostname\` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.", "example": "", "id": "host.name", + "initialWidth": 180, "type": "keyword", - "width": 180, }, Object { "aggregatable": true, @@ -68,8 +68,8 @@ It can contain what \`hostname\` returns on Unix systems, the fully qualified do Can be one or multiple IPv4 or IPv6 addresses.", "example": "", "id": "source.ip", + "initialWidth": 180, "type": "ip", - "width": 180, }, Object { "aggregatable": true, @@ -79,8 +79,8 @@ Can be one or multiple IPv4 or IPv6 addresses.", Can be one or multiple IPv4 or IPv6 addresses.", "example": "", "id": "destination.ip", + "initialWidth": 180, "type": "ip", - "width": 180, }, Object { "aggregatable": true, @@ -90,8 +90,8 @@ Can be one or multiple IPv4 or IPv6 addresses.", "example": "123", "format": "bytes", "id": "destination.bytes", + "initialWidth": 180, "type": "number", - "width": 180, }, Object { "aggregatable": true, @@ -100,8 +100,8 @@ Can be one or multiple IPv4 or IPv6 addresses.", "description": "Short name or login of the user.", "example": "albert", "id": "user.name", + "initialWidth": 180, "type": "keyword", - "width": 180, }, Object { "aggregatable": true, @@ -110,8 +110,8 @@ Can be one or multiple IPv4 or IPv6 addresses.", "description": "Each document has an _id that uniquely identifies it", "example": "Y-6TfmcB0WOhS6qyMv3s", "id": "_id", + "initialWidth": 180, "type": "keyword", - "width": 180, }, Object { "aggregatable": false, @@ -121,8 +121,8 @@ Can be one or multiple IPv4 or IPv6 addresses.", In other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.", "example": "Hello World", "id": "message", + "initialWidth": 180, "type": "text", - "width": 180, }, ] } diff --git a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts index 2846d95051b17..f1b5f6a944678 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts @@ -5,8 +5,11 @@ * 2.0. */ +import { cloneDeep } from 'lodash/fp'; import { LOCAL_STORAGE_TIMELINE_KEY, + migrateColumnWidthToInitialWidth, + migrateColumnLabelToDisplayAsText, useTimelinesStorage, getTimelinesInStorageByIds, getAllTimelinesInStorage, @@ -16,11 +19,15 @@ import { import { TimelineId } from '../../../../common/types/timeline'; import { mockTimelineModel, createSecuritySolutionStorageMock } from '../../../common/mock'; import { useKibana } from '../../../common/lib/kibana'; +import { TimelineModel } from '../../store/timeline/model'; jest.mock('../../../common/lib/kibana'); const useKibanaMock = useKibana as jest.Mocked; +const getExpectedColumns = (model: TimelineModel) => + model.columns.map(migrateColumnWidthToInitialWidth).map(migrateColumnLabelToDisplayAsText); + describe('SiemLocalStorage', () => { const { localStorage, storage } = createSecuritySolutionStorageMock(); @@ -122,6 +129,179 @@ describe('SiemLocalStorage', () => { [TimelineId.hostsPageEvents]: mockTimelineModel, }); }); + + it('migrates columns saved to localstorage with a `width` to `initialWidth`', () => { + const timelineStorage = useTimelinesStorage(); + + // create a mock that mimics a column saved to localstoarge in the "old" format, with `width` instead of `initialWidth` + const unmigratedMockTimelineModel = { + ...cloneDeep(mockTimelineModel), + columns: mockTimelineModel.columns.map((c) => ({ + ...c, + width: 98765, // create a legacy `width` column + initialWidth: undefined, // `initialWidth` must be undefined, otherwise the migration will not occur + })), + }; + timelineStorage.addTimeline(TimelineId.hostsPageEvents, unmigratedMockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); + const timelines = getTimelinesInStorageByIds(storage, [ + TimelineId.hostsPageEvents, + TimelineId.hostsPageExternalAlerts, + ]); + + // all legacy `width` values are migrated to `initialWidth`: + expect(timelines).toStrictEqual({ + [TimelineId.hostsPageEvents]: { + ...mockTimelineModel, + columns: mockTimelineModel.columns.map((c) => ({ + ...c, + displayAsText: undefined, + initialWidth: 98765, + width: 98765, + })), + }, + [TimelineId.hostsPageExternalAlerts]: { + ...mockTimelineModel, + columns: getExpectedColumns(mockTimelineModel), + }, + }); + }); + + it('does NOT migrate columns saved to localstorage with a `width` to `initialWidth` when `initialWidth` is valid', () => { + const timelineStorage = useTimelinesStorage(); + + // create a mock that mimics a column saved to localstoarge in the "old" format, with `width` instead of `initialWidth` + const unmigratedMockTimelineModel = { + ...cloneDeep(mockTimelineModel), + columns: mockTimelineModel.columns.map((c) => ({ + ...c, + width: 98765, // create a legacy `width` column + })), + }; + timelineStorage.addTimeline(TimelineId.hostsPageEvents, unmigratedMockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); + const timelines = getTimelinesInStorageByIds(storage, [ + TimelineId.hostsPageEvents, + TimelineId.hostsPageExternalAlerts, + ]); + + expect(timelines).toStrictEqual({ + [TimelineId.hostsPageEvents]: { + ...mockTimelineModel, + columns: mockTimelineModel.columns.map((c) => ({ + ...c, + displayAsText: undefined, + initialWidth: c.initialWidth, // initialWidth is unchanged + width: 98765, + })), + }, + [TimelineId.hostsPageExternalAlerts]: { + ...mockTimelineModel, + columns: getExpectedColumns(mockTimelineModel), + }, + }); + }); + + it('migrates columns saved to localstorage with a `label` to `displayAsText`', () => { + const timelineStorage = useTimelinesStorage(); + + // create a mock that mimics a column saved to localstoarge in the "old" format, with `label` instead of `displayAsText` + const unmigratedMockTimelineModel = { + ...cloneDeep(mockTimelineModel), + columns: mockTimelineModel.columns.map((c, i) => ({ + ...c, + label: `A legacy label ${i}`, // create a legacy `label` column + })), + }; + timelineStorage.addTimeline(TimelineId.hostsPageEvents, unmigratedMockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); + const timelines = getTimelinesInStorageByIds(storage, [ + TimelineId.hostsPageEvents, + TimelineId.hostsPageExternalAlerts, + ]); + + // all legacy `label` values are migrated to `displayAsText`: + expect(timelines).toStrictEqual({ + [TimelineId.hostsPageEvents]: { + ...mockTimelineModel, + columns: mockTimelineModel.columns.map((c, i) => ({ + ...c, + displayAsText: `A legacy label ${i}`, + label: `A legacy label ${i}`, + })), + }, + [TimelineId.hostsPageExternalAlerts]: { + ...mockTimelineModel, + columns: getExpectedColumns(mockTimelineModel), + }, + }); + }); + + it('does NOT migrate columns saved to localstorage with a `label` to `displayAsText` when `displayAsText` is valid', () => { + const timelineStorage = useTimelinesStorage(); + + // create a mock that mimics a column saved to localstoarge in the "old" format, with `label` instead of `displayAsText` + const unmigratedMockTimelineModel = { + ...cloneDeep(mockTimelineModel), + columns: mockTimelineModel.columns.map((c, i) => ({ + ...c, + displayAsText: + 'Label will NOT be migrated to displayAsText, because displayAsText already has a value', + label: `A legacy label ${i}`, // create a legacy `label` column + })), + }; + timelineStorage.addTimeline(TimelineId.hostsPageEvents, unmigratedMockTimelineModel); + timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); + const timelines = getTimelinesInStorageByIds(storage, [ + TimelineId.hostsPageEvents, + TimelineId.hostsPageExternalAlerts, + ]); + + expect(timelines).toStrictEqual({ + [TimelineId.hostsPageEvents]: { + ...mockTimelineModel, + columns: mockTimelineModel.columns.map((c, i) => ({ + ...c, + displayAsText: + 'Label will NOT be migrated to displayAsText, because displayAsText already has a value', + label: `A legacy label ${i}`, + })), + }, + [TimelineId.hostsPageExternalAlerts]: { + ...mockTimelineModel, + columns: getExpectedColumns(mockTimelineModel), + }, + }); + }); + + it('does NOT migrate `columns` when `columns` is not an array', () => { + const timelineStorage = useTimelinesStorage(); + + const invalidColumnsMockTimelineModel = { + ...cloneDeep(mockTimelineModel), + columns: 'this is NOT an array', + }; + timelineStorage.addTimeline( + TimelineId.hostsPageEvents, + (invalidColumnsMockTimelineModel as unknown) as TimelineModel + ); + timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); + const timelines = getTimelinesInStorageByIds(storage, [ + TimelineId.hostsPageEvents, + TimelineId.hostsPageExternalAlerts, + ]); + + expect(timelines).toStrictEqual({ + [TimelineId.hostsPageEvents]: { + ...mockTimelineModel, + columns: 'this is NOT an array', + }, + [TimelineId.hostsPageExternalAlerts]: { + ...mockTimelineModel, + columns: getExpectedColumns(mockTimelineModel), + }, + }); + }); }); describe('getAllTimelinesInStorage', () => { @@ -159,4 +339,103 @@ describe('SiemLocalStorage', () => { }); }); }); + + describe('migrateColumnWidthToInitialWidth', () => { + it('migrates the `width` property to `initialWidth` for older columns saved to localstorage', () => { + const column = { + ...cloneDeep(mockTimelineModel.columns[0]), + width: 1234, // the column `width` was saved to localstorage before the `initialWidth` property existed + initialWidth: undefined, // `initialWidth` did not exist when this column was saved to localstorage + }; + + expect(migrateColumnWidthToInitialWidth(column)).toStrictEqual({ + columnHeaderType: 'not-filtered', + id: '@timestamp', + initialWidth: 1234, // migrated from `width` + width: 1234, + }); + }); + + it("leaves `initialWidth` unchanged when the column read from localstorage doesn't have a `width`", () => { + const column = cloneDeep(mockTimelineModel.columns[0]); // `column.width` does not exist + + expect(migrateColumnWidthToInitialWidth(column)).toStrictEqual({ + columnHeaderType: 'not-filtered', + id: '@timestamp', + initialWidth: 190, // unchanged, because there is no `width` to migrate + }); + }); + + it('does NOT migrate the `width` property to `initialWidth` when the column read from localstorage already has a valid `initialWidth`', () => { + const column = { + ...cloneDeep(mockTimelineModel.columns[0]), // `column.initialWidth` already exists + width: 1234, + }; + + expect(migrateColumnWidthToInitialWidth(column)).toStrictEqual({ + columnHeaderType: 'not-filtered', + id: '@timestamp', + initialWidth: 190, // unchanged, because the column read from localstorge already has a valid `initialWidth` + width: 1234, + }); + }); + }); + + describe('migrateColumnLabelToDisplayAsText', () => { + it('migrates the `label` property to `displayAsText` for older columns saved to localstorage', () => { + const column = { + ...cloneDeep(mockTimelineModel.columns[0]), + label: 'A legacy label', // the column `label` was saved to localstorage before the `displayAsText` property existed + }; + + expect(migrateColumnLabelToDisplayAsText(column)).toStrictEqual({ + columnHeaderType: 'not-filtered', + displayAsText: 'A legacy label', // migrated from `label` + id: '@timestamp', + initialWidth: 190, + label: 'A legacy label', + }); + }); + + it("leaves `displayAsText` undefined when the column read from localstorage doesn't have a `label`", () => { + const column = cloneDeep(mockTimelineModel.columns[0]); // `column.label` does not exist + + expect(migrateColumnLabelToDisplayAsText(column)).toStrictEqual({ + columnHeaderType: 'not-filtered', + displayAsText: undefined, // undefined, because there is no `label` to migrate + id: '@timestamp', + initialWidth: 190, + }); + }); + + it("leaves `displayAsText` unchanged when the column read from localstorage doesn't have a `label`", () => { + const column = { + ...cloneDeep(mockTimelineModel.columns[0]), + displayAsText: 'Do NOT update this', + }; + + expect(migrateColumnLabelToDisplayAsText(column)).toStrictEqual({ + columnHeaderType: 'not-filtered', + displayAsText: 'Do NOT update this', // unchanged, because there is no `label` to migrate + id: '@timestamp', + initialWidth: 190, + }); + }); + + it('does NOT migrate the `label` property to `displayAsText` when the column read from localstorage already has a valid `displayAsText`', () => { + const column = { + ...cloneDeep(mockTimelineModel.columns[0]), + displayAsText: 'Already valid', + label: 'A legacy label', + }; + + expect(migrateColumnLabelToDisplayAsText(column)).toStrictEqual({ + columnHeaderType: 'not-filtered', + displayAsText: 'Already valid', // unchanged, because the column read from localstorge already has a valid `displayAsText` + label: 'A legacy label', + id: '@timestamp', + initialWidth: 190, + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx index 19ccc0bc6ef85..38eb6d3d222f8 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.tsx @@ -5,10 +5,11 @@ * 2.0. */ +import { isEmpty } from 'lodash/fp'; import { Storage } from '../../../../../../../src/plugins/kibana_utils/public'; import { TimelinesStorage } from './types'; import { useKibana } from '../../../common/lib/kibana'; -import { TimelineModel } from '../../store/timeline/model'; +import { ColumnHeaderOptions, TimelineModel } from '../../store/timeline/model'; import { TimelineIdLiteral } from '../../../../common/types/timeline'; export const LOCAL_STORAGE_TIMELINE_KEY = 'timelines'; @@ -16,6 +17,32 @@ const EMPTY_TIMELINE = {} as { [K in TimelineIdLiteral]: TimelineModel; }; +/** + * Migrates the value of the column's `width` property to `initialWidth` + * when `width` is valid, and `initialWidth` is invalid + */ +export const migrateColumnWidthToInitialWidth = ( + column: ColumnHeaderOptions & { width?: number } +) => ({ + ...column, + initialWidth: + Number.isInteger(column.width) && !Number.isInteger(column.initialWidth) + ? column.width + : column.initialWidth, +}); + +/** + * Migrates the value of the column's `label` property to `displayAsText` + * when `label` is valid, and `displayAsText` is `undefined` + */ +export const migrateColumnLabelToDisplayAsText = ( + column: ColumnHeaderOptions & { label?: string } +) => ({ + ...column, + displayAsText: + !isEmpty(column.label) && column.displayAsText == null ? column.label : column.displayAsText, +}); + export const getTimelinesInStorageByIds = (storage: Storage, timelineIds: TimelineIdLiteral[]) => { const allTimelines = storage.get(LOCAL_STORAGE_TIMELINE_KEY); @@ -38,6 +65,13 @@ export const getTimelinesInStorageByIds = (storage: Storage, timelineIds: Timeli ...(timelineModel.sort != null && !Array.isArray(timelineModel.sort) ? { sort: [timelineModel.sort] } : {}), + ...(Array.isArray(timelineModel.columns) + ? { + columns: timelineModel.columns + .map(migrateColumnWidthToInitialWidth) + .map(migrateColumnLabelToDisplayAsText), + } + : {}), }, }; }, {} as { [K in TimelineIdLiteral]: TimelineModel }); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts index c8e8e00caf530..eabcdd53fb994 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts @@ -21,42 +21,42 @@ describe('Epic Timeline', () => { { columnHeaderType: 'not-filtered', id: '@timestamp', - width: 190, + initialWidth: 190, }, { columnHeaderType: 'not-filtered', id: 'message', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.category', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'event.action', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'host.name', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'source.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'destination.ip', - width: 180, + initialWidth: 180, }, { columnHeaderType: 'not-filtered', id: 'user.name', - width: 180, + initialWidth: 180, }, ], dataProviders: [ diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts index 30d09da2f736d..5f5d76990b5ff 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts @@ -360,7 +360,9 @@ export const convertTimelineAsInput = ( } else if (key === 'columns' && get(key, timeline) != null) { return set( key, - get(key, timeline).map((col: ColumnHeaderOptions) => omit(['width', '__typename'], col)), + get(key, timeline).map((col: ColumnHeaderOptions) => + omit(['initialWidth', 'width', '__typename'], col) + ), acc ); } else if (key === 'filters' && get(key, timeline) != null) { diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts index 135cbb3f73281..2172cf8562c97 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts @@ -11,7 +11,6 @@ import uuid from 'uuid'; import { ToggleDetailPanel } from './actions'; import { Filter } from '../../../../../../../src/plugins/data/public'; -import { getColumnWidthFromType } from '../../../timelines/components/timeline/body/column_headers/helpers'; import { Sort } from '../../../timelines/components/timeline/body/sort'; import { DataProvider, @@ -42,6 +41,10 @@ import { DEFAULT_FROM_MOMENT, DEFAULT_TO_MOMENT, } from '../../../common/utils/default_date_settings'; +import { + DEFAULT_COLUMN_MIN_WIDTH, + RESIZED_COLUMN_MIN_WITH, +} from '../../components/timeline/body/constants'; import { activeTimeline } from '../../containers/active_timeline_context'; export const isNotNull = (value: T | null): value is T => value !== null; @@ -495,13 +498,14 @@ export const applyDeltaToTimelineColumnWidth = ({ }, }; } - const minWidthPixels = getColumnWidthFromType(timeline.columns[columnIndex].type!); - const requestedWidth = timeline.columns[columnIndex].width + delta; // raw change in width - const width = Math.max(minWidthPixels, requestedWidth); // if the requested width is smaller than the min, use the min + + const requestedWidth = + (timeline.columns[columnIndex].initialWidth ?? DEFAULT_COLUMN_MIN_WIDTH) + delta; // raw change in width + const initialWidth = Math.max(RESIZED_COLUMN_MIN_WITH, requestedWidth); // if the requested width is smaller than the min, use the min const columnWithNewWidth = { ...timeline.columns[columnIndex], - width, + initialWidth, }; const columns = [ diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts index faece61cf9b7e..559cec57dd55c 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { EuiDataGridColumn } from '@elastic/eui'; + import { Filter, IFieldSubType } from '../../../../../../../src/plugins/data/public'; import { DataProvider } from '../../components/timeline/data_providers/data_provider'; @@ -32,21 +34,21 @@ export type ColumnHeaderType = 'not-filtered' | 'text-filter'; export type ColumnId = string; /** The specification of a column header */ -export interface ColumnHeaderOptions { +export type ColumnHeaderOptions = Pick< + EuiDataGridColumn, + 'display' | 'displayAsText' | 'id' | 'initialWidth' +> & { aggregatable?: boolean; category?: string; columnHeaderType: ColumnHeaderType; description?: string; example?: string; format?: string; - id: ColumnId; - label?: string; linkField?: string; placeholder?: string; subType?: IFieldSubType; type?: string; - width: number; -} +}; export interface TimelineModel { /** The selected tab to displayed in the timeline */ diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts index d467747346b8b..1c65c01a0bdfc 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts @@ -20,8 +20,10 @@ import { DataProvidersAnd, } from '../../../timelines/components/timeline/data_providers/data_provider'; import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers'; -import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants'; -import { getColumnWidthFromType } from '../../../timelines/components/timeline/body/column_headers/helpers'; +import { + DEFAULT_COLUMN_MIN_WIDTH, + RESIZED_COLUMN_MIN_WITH, +} from '../../../timelines/components/timeline/body/constants'; import { defaultHeaders } from '../../../common/mock'; import { @@ -278,7 +280,7 @@ describe('Timeline', () => { id: 'event.action', type: 'keyword', aggregatable: true, - width: DEFAULT_COLUMN_MIN_WIDTH, + initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }; mockWithExistingColumns = { ...timelineById, @@ -600,12 +602,12 @@ describe('Timeline', () => { expect(update).not.toBe(timelineByIdMock); }); - test('should update (just) the specified column of type `date` when the id matches, and the result of applying the delta is greater than the min width for a date column', () => { + test('should update initialWidth with the specified delta when the delta is positive', () => { const aDateColumn = columnsMock[0]; const delta = 50; const expectedToHaveNewWidth = { ...aDateColumn, - width: getColumnWidthFromType(aDateColumn.type!) + delta, + initialWidth: Number(aDateColumn.initialWidth) + 50, }; const expectedColumns = [expectedToHaveNewWidth, columnsMock[1], columnsMock[2]]; @@ -619,12 +621,12 @@ describe('Timeline', () => { expect(update.foo.columns).toEqual(expectedColumns); }); - test('should NOT update (just) the specified column of type `date` when the id matches, because the result of applying the delta is less than the min width for a date column', () => { + test('should update initialWidth with the specified delta when the delta is negative, and the resulting width is greater than the min column width', () => { const aDateColumn = columnsMock[0]; - const delta = -50; // this will be less than the min + const delta = 50 * -1; // the result will still be above the min column size const expectedToHaveNewWidth = { ...aDateColumn, - width: getColumnWidthFromType(aDateColumn.type!), // we expect the minimum + initialWidth: Number(aDateColumn.initialWidth) - 50, }; const expectedColumns = [expectedToHaveNewWidth, columnsMock[1], columnsMock[2]]; @@ -638,37 +640,18 @@ describe('Timeline', () => { expect(update.foo.columns).toEqual(expectedColumns); }); - test('should update (just) the specified non-date column when the id matches, and the result of applying the delta is greater than the min width for the column', () => { - const aNonDateColumn = columnsMock[1]; - const delta = 50; - const expectedToHaveNewWidth = { - ...aNonDateColumn, - width: getColumnWidthFromType(aNonDateColumn.type!) + delta, - }; - const expectedColumns = [columnsMock[0], expectedToHaveNewWidth, columnsMock[2]]; - - const update = applyDeltaToTimelineColumnWidth({ - id: 'foo', - columnId: aNonDateColumn.id, - delta, - timelineById: mockWithExistingColumns, - }); - - expect(update.foo.columns).toEqual(expectedColumns); - }); - - test('should NOT update the specified non-date column when the id matches, because the result of applying the delta is less than the min width for the column', () => { - const aNonDateColumn = columnsMock[1]; - const delta = -50; + test('should set initialWidth to `RESIZED_COLUMN_MIN_WITH` when the requested delta results in a column that is too small ', () => { + const aDateColumn = columnsMock[0]; + const delta = (Number(aDateColumn.initialWidth) - 5) * -1; // the requested delta would result in a width of just 5 pixels, which is too small const expectedToHaveNewWidth = { - ...aNonDateColumn, - width: getColumnWidthFromType(aNonDateColumn.type!), + ...aDateColumn, + initialWidth: RESIZED_COLUMN_MIN_WITH, // we expect the minimum }; - const expectedColumns = [columnsMock[0], expectedToHaveNewWidth, columnsMock[2]]; + const expectedColumns = [expectedToHaveNewWidth, columnsMock[1], columnsMock[2]]; const update = applyDeltaToTimelineColumnWidth({ id: 'foo', - columnId: aNonDateColumn.id, + columnId: aDateColumn.id, delta, timelineById: mockWithExistingColumns, }); From 9703b74efcc341d5c55b5d2dfb4babe91c509952 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 28 Apr 2021 14:36:50 -0400 Subject: [PATCH 46/70] [Fleet] Fix opening the settings flyout from the add agent flyout (#98536) --- .../components/settings_flyout/index.tsx | 2 +- .../applications/fleet/layouts/default.tsx | 21 +++++++++++++------ .../sections/agents/agent_list_page/index.tsx | 10 +++++---- .../agent_enrollment_flyout/index.tsx | 21 ++++++++++++------- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout/index.tsx index f3c353fd75dba..56f28ada004e2 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/settings_flyout/index.tsx @@ -340,7 +340,7 @@ export const SettingFlyout: React.FunctionComponent = ({ onClose }) => { onClose={onConfirmModalClose} /> )} - +

diff --git a/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx index 543819aca87a5..4ff5243483a3a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/layouts/default.tsx @@ -7,7 +7,14 @@ import React from 'react'; import styled from 'styled-components'; -import { EuiTabs, EuiTab, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; +import { + EuiTabs, + EuiTab, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiPortal, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import type { Section } from '../sections'; @@ -58,11 +65,13 @@ export const DefaultLayout: React.FunctionComponent = ({ return ( <> {modal === 'settings' && ( - { - setModal(null); - }} - /> + + { + setModal(null); + }} + /> + )} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx index 88249f7f5d5ce..70cb6cddad5fa 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx @@ -481,10 +481,12 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { return ( <> {isEnrollmentFlyoutOpen ? ( - setIsEnrollmentFlyoutOpen(false)} - /> + + setIsEnrollmentFlyoutOpen(false)} + /> + ) : null} {agentToReassign && ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/index.tsx index 0ad1706e5273f..1aa88dcef4adc 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_enrollment_flyout/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { EuiFlyout, EuiFlyoutBody, @@ -37,9 +37,7 @@ interface Props { agentPolicies?: AgentPolicy[]; } -const MissingFleetServerHostCallout: React.FunctionComponent<{ onClose: () => void }> = ({ - onClose, -}) => { +const MissingFleetServerHostCallout: React.FunctionComponent = () => { const { setModal } = useUrlModal(); return ( vo fill iconType="gear" onClick={() => { - onClose(); setModal('settings'); }} > @@ -89,11 +86,21 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({ }) => { const [mode, setMode] = useState<'managed' | 'standalone'>('managed'); + const { modal } = useUrlModal(); + const [lastModal, setLastModal] = useState(modal); const settings = useGetSettings(); const fleetServerHosts = settings.data?.item?.fleet_server_hosts || []; + // Refresh settings when there is a modal/flyout change + useEffect(() => { + if (modal !== lastModal) { + settings.resendRequest(); + setLastModal(modal); + } + }, [modal, lastModal, settings]); + return ( - +

@@ -130,7 +137,7 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({ + ) : undefined } > From 3f46d6f8a76e7a2a88489de6947760194ee55f0c Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Wed, 28 Apr 2021 20:37:58 +0200 Subject: [PATCH 47/70] [ML] Refresh Anomaly Detection jobs list on alerting rules updates (#98603) --- x-pack/plugins/ml/public/alerting/job_selector.tsx | 5 +---- .../plugins/ml/public/alerting/ml_alerting_flyout.tsx | 11 +++++++++-- .../components/job_details/extract_job_details.js | 4 ++-- .../jobs_list/components/job_details/job_details.js | 5 +++-- .../components/jobs_list_view/jobs_list_view.js | 1 + .../components/multi_job_actions/actions_menu.js | 2 +- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/ml/public/alerting/job_selector.tsx b/x-pack/plugins/ml/public/alerting/job_selector.tsx index da353b52ef1c0..d00d4efc25b8d 100644 --- a/x-pack/plugins/ml/public/alerting/job_selector.tsx +++ b/x-pack/plugins/ml/public/alerting/job_selector.tsx @@ -99,10 +99,7 @@ export const JobSelectorControl: FC = ({ + } isInvalid={!!errors?.length} error={errors} diff --git a/x-pack/plugins/ml/public/alerting/ml_alerting_flyout.tsx b/x-pack/plugins/ml/public/alerting/ml_alerting_flyout.tsx index dac1fad72255c..b87a447bd4b15 100644 --- a/x-pack/plugins/ml/public/alerting/ml_alerting_flyout.tsx +++ b/x-pack/plugins/ml/public/alerting/ml_alerting_flyout.tsx @@ -82,6 +82,7 @@ export const MlAnomalyAlertFlyout: FC = ({ interface JobListMlAnomalyAlertFlyoutProps { setShowFunction: (callback: Function) => void; unsetShowFunction: () => void; + onSave: () => void; } /** @@ -93,6 +94,7 @@ interface JobListMlAnomalyAlertFlyoutProps { export const JobListMlAnomalyAlertFlyout: FC = ({ setShowFunction, unsetShowFunction, + onSave, }) => { const [isVisible, setIsVisible] = useState(false); const [jobIds, setJobIds] = useState(); @@ -115,6 +117,7 @@ export const JobListMlAnomalyAlertFlyout: FC = onCloseFlyout={() => setIsVisible(false)} onSave={() => { setIsVisible(false); + onSave(); }} /> ) : null; @@ -122,9 +125,10 @@ export const JobListMlAnomalyAlertFlyout: FC = interface EditRuleFlyoutProps { initialAlert: MlAnomalyDetectionAlertRule; + onSave: () => void; } -export const EditAlertRule: FC = ({ initialAlert }) => { +export const EditAlertRule: FC = ({ initialAlert, onSave }) => { const [isVisible, setIsVisible] = useState(false); return ( <> @@ -136,7 +140,10 @@ export const EditAlertRule: FC = ({ initialAlert }) => { { + setIsVisible(false); + onSave(); + }} /> ) : null} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js index 5b7a41e572dab..673484f08e196 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js @@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n'; import { EuiLink } from '@elastic/eui'; import { EditAlertRule } from '../../../../../alerting/ml_alerting_flyout'; -export function extractJobDetails(job, basePath) { +export function extractJobDetails(job, basePath, refreshJobList) { if (Object.keys(job).length === 0) { return {}; } @@ -82,7 +82,7 @@ export function extractJobDetails(job, basePath) { }), position: 'right', items: (job.alerting_rules ?? []).map((v) => { - return ['', ]; + return ['', ]; }), }; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js index c8412a2a83d8a..812d156421c16 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js @@ -55,6 +55,8 @@ export class JobDetailsUI extends Component {

); } else { + const { showFullDetails, refreshJobList } = this.props; + const { general, customUrl, @@ -71,9 +73,8 @@ export class JobDetailsUI extends Component { jobTimingStats, datafeedTimingStats, alertRules, - } = extractJobDetails(job, basePath); + } = extractJobDetails(job, basePath, refreshJobList); - const { showFullDetails, refreshJobList } = this.props; const tabs = [ { id: 'job-settings', diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js index ac7224b3f3164..214b7616cf927 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js @@ -521,6 +521,7 @@ export class JobsListView extends Component {
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/actions_menu.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/actions_menu.js index 6b3d6bc8971f5..2c73a73b77abe 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/actions_menu.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/actions_menu.js @@ -145,7 +145,7 @@ class MultiJobActionsMenuUI extends Component { ); } - if (this.canCreateMlAlerts) { + if (this.canCreateMlAlerts && this.props.jobs.length === 1) { items.push( Date: Wed, 28 Apr 2021 21:49:47 +0300 Subject: [PATCH 48/70] [Search] return full IKibanaSearchResponse from fetch$ (#98268) * return full object from fetch$ * jest * fun-ctional tests * jest * functional * test * test * tesssst * Update src/plugins/data/common/search/search_source/fetch/request_error.ts Co-authored-by: Michael Dokolin * Update src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts Co-authored-by: Michael Dokolin * ts * ts * ts * Revert "Update src/plugins/data/common/search/search_source/fetch/request_error.ts" This reverts commit ab182e84edea2cb693b59faefeb9bae626703a3e. Co-authored-by: Michael Dokolin Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- dev_docs/tutorials/data/search.mdx | 2 +- ...plugins-data-public.searchsource.fetch_.md | 4 +-- .../search_examples/public/search/app.tsx | 2 +- .../data/common/search/aggs/buckets/terms.ts | 2 +- .../esaggs/request_handler.test.ts | 10 +++++- .../expressions/esaggs/request_handler.ts | 2 +- .../data/common/search/poll_search.test.ts | 2 ++ .../search_source/fetch/request_error.ts | 8 ++--- .../search/search_source/fetch/types.ts | 7 ++-- .../search_source/search_source.test.ts | 32 +++++++++++-------- .../search/search_source/search_source.ts | 18 +++++++---- src/plugins/data/common/search/utils.ts | 2 +- src/plugins/data/public/public.api.md | 2 +- .../search/fetch/handle_response.test.ts | 26 +++++++++------ .../public/search/fetch/handle_response.tsx | 15 +++++---- .../search_interceptor.test.ts | 26 +++++++++++++-- .../data/server/search/search_service.ts | 2 +- .../public/application/angular/discover.js | 2 +- .../embeddable/search_embeddable.ts | 2 +- .../classes/sources/es_source/es_source.ts | 6 ++-- .../apps/maps/blended_vector_layer.js | 6 ++-- .../maps/documents_source/docvalue_fields.js | 6 ++-- .../apps/maps/embeddable/dashboard.js | 6 ++-- .../apps/maps/es_geo_grid_source.js | 10 +++--- .../functional/apps/maps/es_pew_pew_source.js | 2 +- x-pack/test/functional/apps/maps/joins.js | 4 +-- 26 files changed, 124 insertions(+), 82 deletions(-) diff --git a/dev_docs/tutorials/data/search.mdx b/dev_docs/tutorials/data/search.mdx index 69b4d5dab58b5..9cf46bb96c72a 100644 --- a/dev_docs/tutorials/data/search.mdx +++ b/dev_docs/tutorials/data/search.mdx @@ -355,7 +355,7 @@ export class SearchEmbeddable this.updateOutput({ loading: true, error: undefined }); // Make the request, wait for the final result - const resp = await searchSource.fetch$({ + const {rawResponse: resp} = await searchSource.fetch$({ sessionId: searchSessionId, }).toPromise(); diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.fetch_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.fetch_.md index 4369cf7c087da..8bc4b7606ab51 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.fetch_.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.fetch_.md @@ -9,7 +9,7 @@ Fetch this source from Elasticsearch, returning an observable over the response( Signature: ```typescript -fetch$(options?: ISearchOptions): Observable>; +fetch$(options?: ISearchOptions): Observable>>; ``` ## Parameters @@ -20,5 +20,5 @@ fetch$(options?: ISearchOptions): Observable>; Returns: -`Observable>` +`Observable>>` diff --git a/examples/search_examples/public/search/app.tsx b/examples/search_examples/public/search/app.tsx index 65d939088515a..c9ede2ff2b45f 100644 --- a/examples/search_examples/public/search/app.tsx +++ b/examples/search_examples/public/search/app.tsx @@ -233,7 +233,7 @@ export const SearchExamplesApp = ({ } setRequest(searchSource.getSearchRequestBody()); - const res = await searchSource.fetch$().toPromise(); + const { rawResponse: res } = await searchSource.fetch$().toPromise(); setResponse(res); const message = Searched {res.hits.total} documents.; diff --git a/src/plugins/data/common/search/aggs/buckets/terms.ts b/src/plugins/data/common/search/aggs/buckets/terms.ts index 03cf14a577a50..1b876051d009b 100644 --- a/src/plugins/data/common/search/aggs/buckets/terms.ts +++ b/src/plugins/data/common/search/aggs/buckets/terms.ts @@ -101,7 +101,7 @@ export const getTermsBucketAgg = () => nestedSearchSource.setField('aggs', filterAgg); - const response = await nestedSearchSource + const { rawResponse: response } = await nestedSearchSource .fetch$({ abortSignal, sessionId: searchSessionId, diff --git a/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts index b30e5740fa3fb..32775464d055f 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts @@ -11,7 +11,7 @@ import type { Filter } from '../../../es_query'; import type { IndexPattern } from '../../../index_patterns'; import type { IAggConfigs } from '../../aggs'; import type { ISearchSource } from '../../search_source'; -import { searchSourceCommonMock } from '../../search_source/mocks'; +import { searchSourceCommonMock, searchSourceInstanceMock } from '../../search_source/mocks'; import { handleRequest, RequestHandlerParams } from './request_handler'; @@ -20,12 +20,20 @@ jest.mock('../../tabify', () => ({ })); import { tabifyAggResponse } from '../../tabify'; +import { of } from 'rxjs'; describe('esaggs expression function - public', () => { let mockParams: MockedKeys; beforeEach(() => { jest.clearAllMocks(); + + searchSourceInstanceMock.fetch$ = jest.fn().mockReturnValue( + of({ + rawResponse: {}, + }) + ); + mockParams = { abortSignal: (jest.fn() as unknown) as jest.Mocked, aggs: ({ diff --git a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts index 173b2067cad6b..d152ebf159a8e 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts @@ -111,7 +111,7 @@ export const handleRequest = async ({ inspectorAdapters.requests?.reset(); - const response = await requestSearchSource + const { rawResponse: response } = await requestSearchSource .fetch$({ abortSignal, sessionId: searchSessionId, diff --git a/src/plugins/data/common/search/poll_search.test.ts b/src/plugins/data/common/search/poll_search.test.ts index 037fd0fc059d1..38c52f5d5bec4 100644 --- a/src/plugins/data/common/search/poll_search.test.ts +++ b/src/plugins/data/common/search/poll_search.test.ts @@ -20,11 +20,13 @@ describe('pollSearch', () => { resolve({ isRunning: false, isPartial: finishWithError, + rawResponse: {}, }); } else { resolve({ isRunning: true, isPartial: true, + rawResponse: {}, }); } }); diff --git a/src/plugins/data/common/search/search_source/fetch/request_error.ts b/src/plugins/data/common/search/search_source/fetch/request_error.ts index d8c750d011b03..48e216fa05541 100644 --- a/src/plugins/data/common/search/search_source/fetch/request_error.ts +++ b/src/plugins/data/common/search/search_source/fetch/request_error.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { estypes } from '@elastic/elasticsearch'; import { KbnError } from '../../../../../kibana_utils/common'; +import { IKibanaSearchResponse } from '../../types'; import { SearchError } from './types'; /** @@ -16,9 +16,9 @@ import { SearchError } from './types'; * @param {Object} resp - optional HTTP response */ export class RequestFailure extends KbnError { - public resp?: estypes.SearchResponse; - constructor(err: SearchError | null = null, resp?: estypes.SearchResponse) { - super(`Request to Elasticsearch failed: ${JSON.stringify(resp || err?.message)}`); + public resp?: IKibanaSearchResponse; + constructor(err: SearchError | null = null, resp?: IKibanaSearchResponse) { + super(`Request to Elasticsearch failed: ${JSON.stringify(resp?.rawResponse || err?.message)}`); this.resp = resp; } diff --git a/src/plugins/data/common/search/search_source/fetch/types.ts b/src/plugins/data/common/search/search_source/fetch/types.ts index 79aa45163b913..069b2a3117a0a 100644 --- a/src/plugins/data/common/search/search_source/fetch/types.ts +++ b/src/plugins/data/common/search/search_source/fetch/types.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { estypes } from '@elastic/elasticsearch'; import { GetConfigFn } from '../../../types'; +import { IKibanaSearchResponse } from '../../types'; /** * @internal @@ -24,10 +24,7 @@ export interface FetchHandlers { * Callback which can be used to hook into responses, modify them, or perform * side effects like displaying UI errors on the client. */ - onResponse: ( - request: SearchRequest, - response: estypes.SearchResponse - ) => estypes.SearchResponse; + onResponse: (request: SearchRequest, response: IKibanaSearchResponse) => IKibanaSearchResponse; } export interface SearchError { diff --git a/src/plugins/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index 68e386acfd48c..a3f043a5e2657 100644 --- a/src/plugins/data/common/search/search_source/search_source.test.ts +++ b/src/plugins/data/common/search/search_source/search_source.test.ts @@ -903,18 +903,26 @@ describe('SearchSource', () => { expect(next).toBeCalledTimes(2); expect(complete).toBeCalledTimes(1); expect(next.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { + Array [ + Object { + "isPartial": true, + "isRunning": true, + "rawResponse": Object { "test": 1, }, - ] + }, + ] `); expect(next.mock.calls[1]).toMatchInlineSnapshot(` - Array [ - Object { + Array [ + Object { + "isPartial": false, + "isRunning": false, + "rawResponse": Object { "test": 2, }, - ] + }, + ] `); }); @@ -958,13 +966,9 @@ describe('SearchSource', () => { expect(next).toBeCalledTimes(1); expect(error).toBeCalledTimes(1); expect(complete).toBeCalledTimes(0); - expect(next.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "test": 1, - }, - ] - `); + expect(next.mock.calls[0][0].rawResponse).toStrictEqual({ + test: 1, + }); expect(error.mock.calls[0][0]).toBe(undefined); }); }); @@ -1174,7 +1178,7 @@ describe('SearchSource', () => { expect(fetchSub.next).toHaveBeenCalledTimes(3); expect(fetchSub.complete).toHaveBeenCalledTimes(1); expect(fetchSub.error).toHaveBeenCalledTimes(0); - expect(resp).toStrictEqual({ other: 5 }); + expect(resp.rawResponse).toStrictEqual({ other: 5 }); expect(typesRegistry.get('avg').postFlightRequest).toHaveBeenCalledTimes(3); }); diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index 585126e1184d2..5130224329ba2 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -271,7 +271,9 @@ export class SearchSource { * Fetch this source from Elasticsearch, returning an observable over the response(s) * @param options */ - fetch$(options: ISearchOptions = {}) { + fetch$( + options: ISearchOptions = {} + ): Observable>> { const { getConfig } = this.dependencies; const syncSearchByDefault = getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES); @@ -308,7 +310,11 @@ export class SearchSource { * @deprecated Use fetch$ instead */ fetch(options: ISearchOptions = {}) { - return this.fetch$(options).toPromise(); + return this.fetch$(options) + .toPromise() + .then((r) => { + return r.rawResponse as estypes.SearchResponse; + }); } /** @@ -341,7 +347,7 @@ export class SearchSource { * PRIVATE APIS ******/ - private inspectSearch(s$: Observable>, options: ISearchOptions) { + private inspectSearch(s$: Observable>, options: ISearchOptions) { const { id, title, description, adapter } = options.inspector || { title: '' }; const requestResponder = adapter?.start(title, { @@ -384,7 +390,7 @@ export class SearchSource { last(undefined, null), tap((finalResponse) => { if (finalResponse) { - requestResponder?.stats(getResponseInspectorStats(finalResponse, this)); + requestResponder?.stats(getResponseInspectorStats(finalResponse.rawResponse, this)); requestResponder?.ok({ json: finalResponse }); } }), @@ -424,8 +430,8 @@ export class SearchSource { ); } } - return response; } + return response; } /** @@ -477,7 +483,7 @@ export class SearchSource { } }); }), - map(({ rawResponse }) => onResponse(searchRequest, rawResponse)) + map((response) => onResponse(searchRequest, response)) ); } diff --git a/src/plugins/data/common/search/utils.ts b/src/plugins/data/common/search/utils.ts index e87434cd6ca83..e11957c6fa9fc 100644 --- a/src/plugins/data/common/search/utils.ts +++ b/src/plugins/data/common/search/utils.ts @@ -12,7 +12,7 @@ import type { IKibanaSearchResponse } from './types'; * @returns true if response had an error while executing in ES */ export const isErrorResponse = (response?: IKibanaSearchResponse) => { - return !response || (!response.isRunning && response.isPartial); + return !response || !response.rawResponse || (!response.isRunning && response.isPartial); }; /** diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index cb3dfb839a023..868330ce078c7 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2446,7 +2446,7 @@ export class SearchSource { createChild(options?: {}): SearchSource; createCopy(): SearchSource; destroy(): void; - fetch$(options?: ISearchOptions): Observable>; + fetch$(options?: ISearchOptions): Observable>>; // @deprecated fetch(options?: ISearchOptions): Promise>; getField(field: K, recurse?: boolean): SearchSourceFields[K]; diff --git a/src/plugins/data/public/search/fetch/handle_response.test.ts b/src/plugins/data/public/search/fetch/handle_response.test.ts index 8854bee5c7657..1a430f860f438 100644 --- a/src/plugins/data/public/search/fetch/handle_response.test.ts +++ b/src/plugins/data/public/search/fetch/handle_response.test.ts @@ -12,7 +12,7 @@ import { handleResponse } from './handle_response'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { notificationServiceMock } from '../../../../../core/public/notifications/notifications_service.mock'; import { setNotifications } from '../../services'; -import { SearchResponse } from 'elasticsearch'; +import { IKibanaSearchResponse } from 'src/plugins/data/common'; jest.mock('@kbn/i18n', () => { return { @@ -33,8 +33,10 @@ describe('handleResponse', () => { test('should notify if timed out', () => { const request = { body: {} }; const response = { - timed_out: true, - } as SearchResponse; + rawResponse: { + timed_out: true, + }, + } as IKibanaSearchResponse; const result = handleResponse(request, response); expect(result).toBe(response); expect(notifications.toasts.addWarning).toBeCalled(); @@ -46,13 +48,15 @@ describe('handleResponse', () => { test('should notify if shards failed', () => { const request = { body: {} }; const response = { - _shards: { - failed: 1, - total: 2, - successful: 1, - skipped: 1, + rawResponse: { + _shards: { + failed: 1, + total: 2, + successful: 1, + skipped: 1, + }, }, - } as SearchResponse; + } as IKibanaSearchResponse; const result = handleResponse(request, response); expect(result).toBe(response); expect(notifications.toasts.addWarning).toBeCalled(); @@ -63,7 +67,9 @@ describe('handleResponse', () => { test('returns the response', () => { const request = {}; - const response = {} as SearchResponse; + const response = { + rawResponse: {}, + } as IKibanaSearchResponse; const result = handleResponse(request, response); expect(result).toBe(response); }); diff --git a/src/plugins/data/public/search/fetch/handle_response.tsx b/src/plugins/data/public/search/fetch/handle_response.tsx index 57ee5737e50a2..58e4da6b95fae 100644 --- a/src/plugins/data/public/search/fetch/handle_response.tsx +++ b/src/plugins/data/public/search/fetch/handle_response.tsx @@ -9,14 +9,15 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer } from '@elastic/eui'; -import type { estypes } from '@elastic/elasticsearch'; +import { IKibanaSearchResponse } from 'src/plugins/data/common'; import { ShardFailureOpenModalButton } from '../../ui/shard_failure_modal'; import { toMountPoint } from '../../../../kibana_react/public'; import { getNotifications } from '../../services'; import { SearchRequest } from '..'; -export function handleResponse(request: SearchRequest, response: estypes.SearchResponse) { - if (response.timed_out) { +export function handleResponse(request: SearchRequest, response: IKibanaSearchResponse) { + const { rawResponse } = response; + if (rawResponse.timed_out) { getNotifications().toasts.addWarning({ title: i18n.translate('data.search.searchSource.fetch.requestTimedOutNotificationMessage', { defaultMessage: 'Data might be incomplete because your request timed out', @@ -24,12 +25,12 @@ export function handleResponse(request: SearchRequest, response: estypes.SearchR }); } - if (response._shards && response._shards.failed) { + if (rawResponse._shards && rawResponse._shards.failed) { const title = i18n.translate('data.search.searchSource.fetch.shardsFailedNotificationMessage', { defaultMessage: '{shardsFailed} of {shardsTotal} shards failed', values: { - shardsFailed: response._shards.failed, - shardsTotal: response._shards.total, + shardsFailed: rawResponse._shards.failed, + shardsTotal: rawResponse._shards.total, }, }); const description = i18n.translate( @@ -43,7 +44,7 @@ export function handleResponse(request: SearchRequest, response: estypes.SearchR <> {description} - + ); diff --git a/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts b/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts index 89b696a80d57d..0e81f362a030d 100644 --- a/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts +++ b/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts @@ -144,7 +144,7 @@ describe('SearchInterceptor', () => { describe('search', () => { test('Observable should resolve if fetch is successful', async () => { - const mockResponse: any = { result: 200 }; + const mockResponse: any = { rawResponse: {} }; fetchMock.mockResolvedValueOnce(mockResponse); const mockRequest: IEsSearchRequest = { params: {}, @@ -233,6 +233,7 @@ describe('SearchInterceptor', () => { value: { isPartial: true, isRunning: false, + rawResponse: {}, id: 1, }, }, @@ -255,6 +256,7 @@ describe('SearchInterceptor', () => { value: { isPartial: false, isRunning: false, + rawResponse: {}, id: 1, }, }, @@ -281,6 +283,7 @@ describe('SearchInterceptor', () => { value: { isPartial: true, isRunning: true, + rawResponse: {}, id: 1, }, }, @@ -289,6 +292,7 @@ describe('SearchInterceptor', () => { value: { isPartial: false, isRunning: false, + rawResponse: {}, id: 1, }, }, @@ -325,6 +329,7 @@ describe('SearchInterceptor', () => { value: { isPartial: false, isRunning: false, + rawResponse: {}, id: 1, }, }, @@ -349,6 +354,7 @@ describe('SearchInterceptor', () => { value: { isPartial: true, isRunning: true, + rawResponse: {}, id: 1, }, }, @@ -357,6 +363,7 @@ describe('SearchInterceptor', () => { value: { isPartial: false, isRunning: false, + rawResponse: {}, id: 1, }, }, @@ -389,6 +396,7 @@ describe('SearchInterceptor', () => { value: { isPartial: true, isRunning: true, + rawResponse: {}, id: 1, }, }, @@ -433,6 +441,7 @@ describe('SearchInterceptor', () => { value: { isPartial: true, isRunning: true, + rawResponse: {}, id: 1, }, }, @@ -441,6 +450,7 @@ describe('SearchInterceptor', () => { value: { isPartial: false, isRunning: false, + rawResponse: {}, id: 1, }, }, @@ -511,7 +521,10 @@ describe('SearchInterceptor', () => { sessionId, }); - await searchInterceptor.search(mockRequest, { sessionId }).toPromise(); + await searchInterceptor + .search(mockRequest, { sessionId }) + .toPromise() + .catch(() => {}); expect(fetchMock.mock.calls[0][0]).toEqual( expect.objectContaining({ options: { sessionId, isStored: true, isRestore: true, strategy: 'ese' }, @@ -527,7 +540,10 @@ describe('SearchInterceptor', () => { const sessionId = 'sid'; setup(null); - await searchInterceptor.search(mockRequest, { sessionId }).toPromise(); + await searchInterceptor + .search(mockRequest, { sessionId }) + .toPromise() + .catch(() => {}); expect(fetchMock.mock.calls[0][0]).toEqual( expect.not.objectContaining({ options: { sessionId }, @@ -548,6 +564,7 @@ describe('SearchInterceptor', () => { value: { isPartial: true, isRunning: true, + rawResponse: {}, id: 1, }, }, @@ -556,6 +573,7 @@ describe('SearchInterceptor', () => { value: { isPartial: false, isRunning: false, + rawResponse: {}, id: 1, }, }, @@ -792,6 +810,7 @@ describe('SearchInterceptor', () => { value: { isPartial: true, isRunning: true, + rawResponse: {}, id: 1, }, }, @@ -838,6 +857,7 @@ describe('SearchInterceptor', () => { value: { isPartial: true, isRunning: false, + rawResponse: {}, id: 1, }, }, diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 383e09b4a6ebe..f52c622c48ed0 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -251,7 +251,7 @@ export class SearchService implements Plugin { private registerSearchStrategy = < SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, - SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse + SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse >( name: string, strategy: ISearchStrategy diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 4099d5e8ef7e2..c66ca19c96743 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -431,7 +431,7 @@ function discoverController($route, $scope) { }, }) .toPromise() - .then(onResults) + .then(({ rawResponse }) => onResults(rawResponse)) .catch((error) => { // If the request was aborted then no need to surface this error in the UI if (error instanceof Error && error.name === 'AbortError') return; diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts index dbaf07fed18c2..99ecb4c11eef2 100644 --- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts +++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts @@ -325,7 +325,7 @@ export class SearchEmbeddable try { // Make the request - const resp = await searchSource + const { rawResponse: resp } = await searchSource .fetch$({ abortSignal: this.abortController.signal, sessionId: searchSessionId, diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts index 50043772af95b..8e31ad7855197 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts @@ -167,9 +167,8 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource const abortController = new AbortController(); registerCancelCallback(() => abortController.abort()); - let resp; try { - resp = await searchSource + const { rawResponse: resp } = await searchSource .fetch$({ abortSignal: abortController.signal, sessionId: searchSessionId, @@ -182,6 +181,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource }, }) .toPromise(); + return resp; } catch (error) { if (isSearchSourceAbortError(error)) { throw new DataRequestAbortError(); @@ -194,8 +194,6 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource }) ); } - - return resp; } async makeSearchSource( diff --git a/x-pack/test/functional/apps/maps/blended_vector_layer.js b/x-pack/test/functional/apps/maps/blended_vector_layer.js index 6d4fca1b0b7c0..e207410eb2281 100644 --- a/x-pack/test/functional/apps/maps/blended_vector_layer.js +++ b/x-pack/test/functional/apps/maps/blended_vector_layer.js @@ -27,20 +27,20 @@ export default function ({ getPageObjects, getService }) { }); it('should request documents when zoomed to smaller regions showing less data', async () => { - const response = await PageObjects.maps.getResponse(); + const { rawResponse: response } = await PageObjects.maps.getResponse(); // Allow a range of hits to account for variances in browser window size. expect(response.hits.hits.length).to.be.within(30, 40); }); it('should request clusters when zoomed to larger regions showing lots of data', async () => { await PageObjects.maps.setView(20, -90, 2); - const response = await PageObjects.maps.getResponse(); + const { rawResponse: response } = await PageObjects.maps.getResponse(); expect(response.aggregations.gridSplit.buckets.length).to.equal(17); }); it('should request documents when query narrows data', async () => { await PageObjects.maps.setAndSubmitQuery('bytes > 19000'); - const response = await PageObjects.maps.getResponse(); + const { rawResponse: response } = await PageObjects.maps.getResponse(); expect(response.hits.hits.length).to.equal(75); }); }); diff --git a/x-pack/test/functional/apps/maps/documents_source/docvalue_fields.js b/x-pack/test/functional/apps/maps/documents_source/docvalue_fields.js index 1d6477b243cdf..fb0fdcf333cf2 100644 --- a/x-pack/test/functional/apps/maps/documents_source/docvalue_fields.js +++ b/x-pack/test/functional/apps/maps/documents_source/docvalue_fields.js @@ -23,7 +23,7 @@ export default function ({ getPageObjects, getService }) { it('should only fetch geo_point field and nothing else when source does not have data driven styling', async () => { await PageObjects.maps.loadSavedMap('document example'); - const response = await PageObjects.maps.getResponse(); + const { rawResponse: response } = await PageObjects.maps.getResponse(); const firstHit = response.hits.hits[0]; expect(firstHit).to.only.have.keys(['_id', '_index', '_score', 'fields']); expect(firstHit.fields).to.only.have.keys(['geo.coordinates']); @@ -31,7 +31,7 @@ export default function ({ getPageObjects, getService }) { it('should only fetch geo_point field and data driven styling fields', async () => { await PageObjects.maps.loadSavedMap('document example with data driven styles'); - const response = await PageObjects.maps.getResponse(); + const { rawResponse: response } = await PageObjects.maps.getResponse(); const firstHit = response.hits.hits[0]; expect(firstHit).to.only.have.keys(['_id', '_index', '_score', 'fields']); expect(firstHit.fields).to.only.have.keys(['bytes', 'geo.coordinates', 'hour_of_day']); @@ -39,7 +39,7 @@ export default function ({ getPageObjects, getService }) { it('should format date fields as epoch_millis when data driven styling is applied to a date field', async () => { await PageObjects.maps.loadSavedMap('document example with data driven styles on date field'); - const response = await PageObjects.maps.getResponse(); + const { rawResponse: response } = await PageObjects.maps.getResponse(); const firstHit = response.hits.hits[0]; expect(firstHit).to.only.have.keys(['_id', '_index', '_score', 'fields']); expect(firstHit.fields).to.only.have.keys(['@timestamp', 'bytes', 'geo.coordinates']); diff --git a/x-pack/test/functional/apps/maps/embeddable/dashboard.js b/x-pack/test/functional/apps/maps/embeddable/dashboard.js index 860273bc23cc1..6c962c98c6a98 100644 --- a/x-pack/test/functional/apps/maps/embeddable/dashboard.js +++ b/x-pack/test/functional/apps/maps/embeddable/dashboard.js @@ -83,7 +83,7 @@ export default function ({ getPageObjects, getService }) { }); it('should apply container state (time, query, filters) to embeddable when loaded', async () => { - const response = await PageObjects.maps.getResponseFromDashboardPanel( + const { rawResponse: response } = await PageObjects.maps.getResponseFromDashboardPanel( 'geo grid vector grid example' ); expect(response.aggregations.gridSplit.buckets.length).to.equal(6); @@ -95,12 +95,12 @@ export default function ({ getPageObjects, getService }) { await filterBar.selectIndexPattern('meta_for_geo_shapes*'); await filterBar.addFilter('shape_name', 'is', 'alpha'); // runtime fields do not have autocomplete - const gridResponse = await PageObjects.maps.getResponseFromDashboardPanel( + const { rawResponse: gridResponse } = await PageObjects.maps.getResponseFromDashboardPanel( 'geo grid vector grid example' ); expect(gridResponse.aggregations.gridSplit.buckets.length).to.equal(1); - const joinResponse = await PageObjects.maps.getResponseFromDashboardPanel( + const { rawResponse: joinResponse } = await PageObjects.maps.getResponseFromDashboardPanel( 'join example', 'meta_for_geo_shapes*.runtime_shape_name' ); diff --git a/x-pack/test/functional/apps/maps/es_geo_grid_source.js b/x-pack/test/functional/apps/maps/es_geo_grid_source.js index 6dee4b87bceea..27949ca720e34 100644 --- a/x-pack/test/functional/apps/maps/es_geo_grid_source.js +++ b/x-pack/test/functional/apps/maps/es_geo_grid_source.js @@ -141,7 +141,7 @@ export default function ({ getPageObjects, getService }) { }); it('should apply query to geotile_grid aggregation request', async () => { - const response = await PageObjects.maps.getResponse(); + const { rawResponse: response } = await PageObjects.maps.getResponse(); expect(response.aggregations.gridSplit.buckets.length).to.equal(1); }); }); @@ -152,7 +152,7 @@ export default function ({ getPageObjects, getService }) { }); it('should contain geotile_grid aggregation elasticsearch request', async () => { - const response = await PageObjects.maps.getResponse(); + const { rawResponse: response } = await PageObjects.maps.getResponse(); expect(response.aggregations.gridSplit.buckets.length).to.equal(4); }); @@ -204,7 +204,7 @@ export default function ({ getPageObjects, getService }) { }); it('should apply query to geotile_grid aggregation request', async () => { - const response = await PageObjects.maps.getResponse(); + const { rawResponse: response } = await PageObjects.maps.getResponse(); expect(response.aggregations.gridSplit.buckets.length).to.equal(1); }); }); @@ -215,7 +215,7 @@ export default function ({ getPageObjects, getService }) { }); it('should contain geotile_grid aggregation elasticsearch request', async () => { - const response = await PageObjects.maps.getResponse(); + const { rawResponse: response } = await PageObjects.maps.getResponse(); expect(response.aggregations.gridSplit.buckets.length).to.equal(4); }); @@ -244,7 +244,7 @@ export default function ({ getPageObjects, getService }) { }); it('should contain geotile_grid aggregation elasticsearch request', async () => { - const response = await PageObjects.maps.getResponse(); + const { rawResponse: response } = await PageObjects.maps.getResponse(); expect(response.aggregations.gridSplit.buckets.length).to.equal(13); }); }); diff --git a/x-pack/test/functional/apps/maps/es_pew_pew_source.js b/x-pack/test/functional/apps/maps/es_pew_pew_source.js index 66406cd6d8f91..ea94ee3bc67d8 100644 --- a/x-pack/test/functional/apps/maps/es_pew_pew_source.js +++ b/x-pack/test/functional/apps/maps/es_pew_pew_source.js @@ -24,7 +24,7 @@ export default function ({ getPageObjects, getService }) { }); it('should request source clusters for destination locations', async () => { - const response = await PageObjects.maps.getResponse(); + const { rawResponse: response } = await PageObjects.maps.getResponse(); expect(response.aggregations.destSplit.buckets.length).to.equal(2); }); diff --git a/x-pack/test/functional/apps/maps/joins.js b/x-pack/test/functional/apps/maps/joins.js index 181b6928e0ec0..a3210e61f86a9 100644 --- a/x-pack/test/functional/apps/maps/joins.js +++ b/x-pack/test/functional/apps/maps/joins.js @@ -121,7 +121,7 @@ export default function ({ getPageObjects, getService }) { }); it('should not apply query to source and apply query to join', async () => { - const joinResponse = await PageObjects.maps.getResponse( + const { rawResponse: joinResponse } = await PageObjects.maps.getResponse( 'meta_for_geo_shapes*.runtime_shape_name' ); expect(joinResponse.aggregations.join.buckets.length).to.equal(2); @@ -138,7 +138,7 @@ export default function ({ getPageObjects, getService }) { }); it('should apply query to join request', async () => { - const joinResponse = await PageObjects.maps.getResponse( + const { rawResponse: joinResponse } = await PageObjects.maps.getResponse( 'meta_for_geo_shapes*.runtime_shape_name' ); expect(joinResponse.aggregations.join.buckets.length).to.equal(1); From b31f4a1a97736043a18e2dc2741ddd0667ec0128 Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Wed, 28 Apr 2021 15:26:47 -0400 Subject: [PATCH 49/70] [actions] adds config allowing per-host networking options (#96630) resolves: https://github.com/elastic/kibana/issues/80120 Adds a new Kibana configuration key xpack.actions.customHostSettings which allows per-host configuration of connection settings for https and smtp for alerting actions. Initially this is just for TLS settings, expandable to other settings in the future. The purpose of these is to allow customers to provide server certificates for servers accessed by actions, whose certificate authority is not available publicly. Alternatively, a per-server rejectUnauthorized: false configuration may be used to bypass the verification step for specific servers, but require it for other servers that do not have per-host customization. Support was also added to allow per-host customization of ignoreTLS and requireTLS flags for use with the email action. --- docs/settings/alert-action-settings.asciidoc | 91 +++- .../alerting-troubleshooting.asciidoc | 16 + .../resources/base/bin/kibana-docker | 1 + .../actions/server/actions_config.mock.ts | 1 + .../actions/server/actions_config.test.ts | 81 +++ .../plugins/actions/server/actions_config.ts | 27 +- .../server/builtin_action_types/email.test.ts | 2 + .../lib/axios_utils_connection.test.ts | 277 ++++++++++ .../lib/get_custom_agents.test.ts | 118 ++++ .../lib/get_custom_agents.ts | 45 +- .../lib/send_email.test.ts | 144 ++++- .../builtin_action_types/lib/send_email.ts | 35 +- .../server/builtin_action_types/teams.test.ts | 2 + .../builtin_action_types/webhook.test.ts | 2 + x-pack/plugins/actions/server/config.test.ts | 13 + x-pack/plugins/actions/server/config.ts | 25 + .../server/lib/custom_host_settings.test.ts | 504 ++++++++++++++++++ .../server/lib/custom_host_settings.ts | 173 ++++++ x-pack/plugins/actions/server/plugin.ts | 6 +- .../alerting_api_integration/common/config.ts | 65 ++- .../common/lib/get_tls_webhook_servers.ts | 78 +++ .../tests/actions/get_all.ts | 24 +- .../spaces_only/config.ts | 1 + .../actions/builtin_action_types/webhook.ts | 89 ++++ .../spaces_only/tests/actions/get_all.ts | 37 +- 25 files changed, 1840 insertions(+), 17 deletions(-) create mode 100644 x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils_connection.test.ts create mode 100644 x-pack/plugins/actions/server/lib/custom_host_settings.test.ts create mode 100644 x-pack/plugins/actions/server/lib/custom_host_settings.ts create mode 100644 x-pack/test/alerting_api_integration/common/lib/get_tls_webhook_servers.ts diff --git a/docs/settings/alert-action-settings.asciidoc b/docs/settings/alert-action-settings.asciidoc index c748d63484e28..50ed0d2652c6f 100644 --- a/docs/settings/alert-action-settings.asciidoc +++ b/docs/settings/alert-action-settings.asciidoc @@ -47,6 +47,88 @@ You can configure the following settings in the `kibana.yml` file. | A list of hostnames that {kib} is allowed to connect to when built-in actions are triggered. It defaults to `[*]`, allowing any host, but keep in mind the potential for SSRF attacks when hosts are not explicitly added to the allowed hosts. An empty list `[]` can be used to block built-in actions from making any external connections. + + Note that hosts associated with built-in actions, such as Slack and PagerDuty, are not automatically added to allowed hosts. If you are not using the default `[*]` setting, you must ensure that the corresponding endpoints are added to the allowed hosts as well. + +| `xpack.actions.customHostSettings` {ess-icon} + | A list of custom host settings to override existing global settings. + Defaults to an empty list. + + + + Each entry in the list must have a `url` property, to associate a connection + type (mail or https), hostname and port with the remaining options in the + entry. + + + In the following example, two custom host settings + are defined. The first provides a custom host setting for mail server + `mail.example.com` using port 465 that supplies server certificate authorization + data from both a file and inline, and requires TLS for the + connection. The second provides a custom host setting for https server + `webhook.example.com` which turns off server certificate authorization. + +|=== + +[source,yaml] +-- +xpack.actions.customHostSettings: + - url: smtp://mail.example.com:465 + tls: + certificateAuthoritiesFiles: [ 'one.crt' ] + certificateAuthoritiesData: | + -----BEGIN CERTIFICATE----- + ... multiple lines of certificate data here ... + -----END CERTIFICATE----- + smtp: + requireTLS: true + - url: https://webhook.example.com + tls: + rejectUnauthorized: false +-- + +[cols="2*<"] +|=== + +| `xpack.actions.customHostSettings[n]` +`.url` {ess-icon} + | A URL associated with this custom host setting. Should be in the form of + `protocol://hostname:port`, where `protocol` is `https` or `smtp`. If the + port is not provided, 443 is used for `https` and 25 is used for + `smtp`. The `smtp` URLs are used for the Email actions that use this + server, and the `https` URLs are used for actions which use `https` to + connect to services. + + + + Entries with `https` URLs can use the `tls` options, and entries with `smtp` + URLs can use both the `tls` and `smtp` options. + + + + No other URL values should be part of this URL, including paths, + query strings, and authentication information. When an http or smtp request + is made as part of executing an action, only the protocol, hostname, and + port of the URL for that request are used to look up these configuration + values. + +| `xpack.actions.customHostSettings[n]` +`.smtp.ignoreTLS` {ess-icon} + | A boolean value indicating that TLS must not be used for this connection. + The options `smtp.ignoreTLS` and `smtp.requireTLS` can not both be set to true. + +| `xpack.actions.customHostSettings[n]` +`.smtp.requireTLS` {ess-icon} + | A boolean value indicating that TLS must be used for this connection. + The options `smtp.ignoreTLS` and `smtp.requireTLS` can not both be set to true. + +| `xpack.actions.customHostSettings[n]` +`.tls.rejectUnauthorized` {ess-icon} + | A boolean value indicating whether to bypass server certificate validation. + Overrides the general `xpack.actions.rejectUnauthorized` configuration + for requests made for this hostname/port. + +| `xpack.actions.customHostSettings[n]` +`.tls.certificateAuthoritiesFiles` + | A file name or list of file names of PEM-encoded certificate files to use + to validate the server. + +| `xpack.actions.customHostSettings[n]` +`.tls.certificateAuthoritiesData` {ess-icon} + | The contents of a PEM-encoded certificate file, or multiple files appended + into a single string. This configuration can be used for environments where + the files cannot be made available. | `xpack.actions.enabledActionTypes` {ess-icon} | A list of action types that are enabled. It defaults to `[*]`, enabling all types. The names for built-in {kib} action types are prefixed with a `.` and include: `.server-log`, `.slack`, `.email`, `.index`, `.pagerduty`, and `.webhook`. An empty list `[]` will disable all action types. + @@ -79,13 +161,18 @@ a|`xpack.actions.` | `xpack.actions.rejectUnauthorized` {ess-icon} | Set to `false` to bypass certificate validation for actions. Defaults to `true`. + + - As an alternative to setting both `xpack.actions.proxyRejectUnauthorizedCertificates` and `xpack.actions.rejectUnauthorized`, you can point the OS level environment variable `NODE_EXTRA_CA_CERTS` to a file that contains the root CAs needed to trust certificates. + As an alternative to setting `xpack.actions.rejectUnauthorized`, you can use the setting + `xpack.actions.customHostSettings` to set TLS options for specific servers. | `xpack.actions.maxResponseContentLength` {ess-icon} | Specifies the max number of bytes of the http response for requests to external resources. Defaults to 1000000 (1MB). | `xpack.actions.responseTimeout` {ess-icon} - | Specifies the time allowed for requests to external resources. Requests that take longer are aborted. The time is formatted as [ms|s|m|h|d|w|M|Y], for example, '20m', '24h', '7d', '1w'. Defaults to 60s. + | Specifies the time allowed for requests to external resources. Requests that take longer are aborted. The time is formatted as: + + + + `[ms,s,m,h,d,w,M,Y]` + + + + For example, `20m`, `24h`, `7d`, `1w`. Defaults to `60s`. |=== diff --git a/docs/user/alerting/alerting-troubleshooting.asciidoc b/docs/user/alerting/alerting-troubleshooting.asciidoc index f4673d10bc248..6d4a0e9375678 100644 --- a/docs/user/alerting/alerting-troubleshooting.asciidoc +++ b/docs/user/alerting/alerting-troubleshooting.asciidoc @@ -53,3 +53,19 @@ Alerting and action tasks are identified by their type. When diagnosing issues related to Alerting, focus on the tasks that begin with `alerting:` and `actions:`. For more details on monitoring and diagnosing task execution in Task Manager, see <>. + +[float] +[[connector-tls-settings]] +=== Connectors have TLS errors when executing actions + +*Problem*: + +When executing actions, a connector gets a TLS socket error when connecting to +the server. + +*Resolution*: + +Configuration options are available to specialize connections to TLS servers, +including ignoring server certificate validation, and providing certificate +authority data to verify servers using custom certificates. For more details, +see <>. 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 220bd2c91057d..3b2feeecabb7c 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 @@ -162,6 +162,7 @@ kibana_vars=( timelion.enabled vega.enableExternalUrls xpack.actions.allowedHosts + xpack.actions.customHostSettings xpack.actions.enabled xpack.actions.enabledActionTypes xpack.actions.preconfiguredAlertHistoryEsIndex diff --git a/x-pack/plugins/actions/server/actions_config.mock.ts b/x-pack/plugins/actions/server/actions_config.mock.ts index 76f6a62ce6597..fbd9a8cddbdcb 100644 --- a/x-pack/plugins/actions/server/actions_config.mock.ts +++ b/x-pack/plugins/actions/server/actions_config.mock.ts @@ -21,6 +21,7 @@ const createActionsConfigMock = () => { maxContentLength: 1000000, timeout: 360000, }), + getCustomHostSettings: jest.fn().mockReturnValue(undefined), }; return mocked; }; diff --git a/x-pack/plugins/actions/server/actions_config.test.ts b/x-pack/plugins/actions/server/actions_config.test.ts index 70c8b0e8185d5..925e77ca85fb2 100644 --- a/x-pack/plugins/actions/server/actions_config.test.ts +++ b/x-pack/plugins/actions/server/actions_config.test.ts @@ -13,8 +13,14 @@ import { AllowedHosts, EnabledActionTypes, } from './actions_config'; +import { resolveCustomHosts } from './lib/custom_host_settings'; +import { Logger } from '../../../../src/core/server'; +import { loggingSystemMock } from '../../../../src/core/server/mocks'; + import moment from 'moment'; +const mockLogger = loggingSystemMock.create().get() as jest.Mocked; + const defaultActionsConfig: ActionsConfig = { enabled: false, allowedHosts: [], @@ -355,4 +361,79 @@ describe('getProxySettings', () => { const proxySettings = getActionsConfigurationUtilities(config).getProxySettings(); expect(proxySettings?.proxyOnlyHosts).toEqual(new Set(proxyOnlyHosts)); }); + + test('getCustomHostSettings() returns undefined when no matching config', () => { + const httpsUrl = 'https://elastic.co/foo/bar'; + const smtpUrl = 'smtp://elastic.co'; + let config: ActionsConfig = resolveCustomHosts(mockLogger, { + ...defaultActionsConfig, + }); + + let chs = getActionsConfigurationUtilities(config).getCustomHostSettings(httpsUrl); + expect(chs).toEqual(undefined); + chs = getActionsConfigurationUtilities(config).getCustomHostSettings(smtpUrl); + expect(chs).toEqual(undefined); + + config = resolveCustomHosts(mockLogger, { + ...defaultActionsConfig, + customHostSettings: [], + }); + chs = getActionsConfigurationUtilities(config).getCustomHostSettings(httpsUrl); + expect(chs).toEqual(undefined); + chs = getActionsConfigurationUtilities(config).getCustomHostSettings(smtpUrl); + expect(chs).toEqual(undefined); + + config = resolveCustomHosts(mockLogger, { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'https://www.elastic.co:443', + }, + ], + }); + chs = getActionsConfigurationUtilities(config).getCustomHostSettings(httpsUrl); + expect(chs).toEqual(undefined); + chs = getActionsConfigurationUtilities(config).getCustomHostSettings(smtpUrl); + expect(chs).toEqual(undefined); + }); + + test('getCustomHostSettings() returns matching config', () => { + const httpsUrl = 'https://elastic.co/ignoring/paths/here'; + const smtpUrl = 'smtp://elastic.co:123'; + const config: ActionsConfig = resolveCustomHosts(mockLogger, { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'https://elastic.co', + tls: { + rejectUnauthorized: true, + }, + }, + { + url: 'smtp://elastic.co:123', + tls: { + rejectUnauthorized: false, + }, + smtp: { + ignoreTLS: true, + }, + }, + ], + }); + + let chs = getActionsConfigurationUtilities(config).getCustomHostSettings(httpsUrl); + expect(chs).toEqual(config.customHostSettings![0]); + chs = getActionsConfigurationUtilities(config).getCustomHostSettings(smtpUrl); + expect(chs).toEqual(config.customHostSettings![1]); + }); + + test('getCustomHostSettings() returns undefined when bad url is passed in', () => { + const badUrl = 'https://elastic.co/foo/bar'; + const config: ActionsConfig = resolveCustomHosts(mockLogger, { + ...defaultActionsConfig, + }); + + const chs = getActionsConfigurationUtilities(config).getCustomHostSettings(badUrl); + expect(chs).toEqual(undefined); + }); }); diff --git a/x-pack/plugins/actions/server/actions_config.ts b/x-pack/plugins/actions/server/actions_config.ts index 4c73cab76f9e8..b8cd5878a8972 100644 --- a/x-pack/plugins/actions/server/actions_config.ts +++ b/x-pack/plugins/actions/server/actions_config.ts @@ -11,7 +11,8 @@ import url from 'url'; import { curry } from 'lodash'; import { pipe } from 'fp-ts/lib/pipeable'; -import { ActionsConfig, AllowedHosts, EnabledActionTypes } from './config'; +import { ActionsConfig, AllowedHosts, EnabledActionTypes, CustomHostSettings } from './config'; +import { getCanonicalCustomHostUrl } from './lib/custom_host_settings'; import { ActionTypeDisabledError } from './lib'; import { ProxySettings, ResponseSettings } from './types'; @@ -32,6 +33,7 @@ export interface ActionsConfigurationUtilities { isRejectUnauthorizedCertificatesEnabled: () => boolean; getProxySettings: () => undefined | ProxySettings; getResponseSettings: () => ResponseSettings; + getCustomHostSettings: (targetUrl: string) => CustomHostSettings | undefined; } function allowListErrorMessage(field: AllowListingField, value: string) { @@ -107,6 +109,27 @@ function getResponseSettingsFromConfig(config: ActionsConfig): ResponseSettings }; } +function getCustomHostSettings( + config: ActionsConfig, + targetUrl: string +): CustomHostSettings | undefined { + const customHostSettings = config.customHostSettings; + if (!customHostSettings) { + return; + } + + let parsedUrl: URL | undefined; + try { + parsedUrl = new URL(targetUrl); + } catch (err) { + // presumably this bad URL is reported elsewhere + return; + } + + const canonicalUrl = getCanonicalCustomHostUrl(parsedUrl); + return customHostSettings.find((settings) => settings.url === canonicalUrl); +} + export function getActionsConfigurationUtilities( config: ActionsConfig ): ActionsConfigurationUtilities { @@ -119,6 +142,7 @@ export function getActionsConfigurationUtilities( isActionTypeEnabled, getProxySettings: () => getProxySettingsFromConfig(config), getResponseSettings: () => getResponseSettingsFromConfig(config), + // returns the global rejectUnauthorized setting isRejectUnauthorizedCertificatesEnabled: () => config.rejectUnauthorized, ensureUriAllowed(uri: string) { if (!isUriAllowed(uri)) { @@ -135,5 +159,6 @@ export function getActionsConfigurationUtilities( throw new ActionTypeDisabledError(disabledActionTypeErrorMessage(actionType), 'config'); } }, + getCustomHostSettings: (targetUrl: string) => getCustomHostSettings(config, targetUrl), }; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts index 4596619c50940..5747b4bbb28f4 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts @@ -282,6 +282,7 @@ describe('execute()', () => { "ensureActionTypeEnabled": [MockFunction], "ensureHostnameAllowed": [MockFunction], "ensureUriAllowed": [MockFunction], + "getCustomHostSettings": [MockFunction], "getProxySettings": [MockFunction], "getResponseSettings": [MockFunction], "isActionTypeEnabled": [MockFunction], @@ -342,6 +343,7 @@ describe('execute()', () => { "ensureActionTypeEnabled": [MockFunction], "ensureHostnameAllowed": [MockFunction], "ensureUriAllowed": [MockFunction], + "getCustomHostSettings": [MockFunction], "getProxySettings": [MockFunction], "getResponseSettings": [MockFunction], "isActionTypeEnabled": [MockFunction], diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils_connection.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils_connection.test.ts new file mode 100644 index 0000000000000..80bf51e19c379 --- /dev/null +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils_connection.test.ts @@ -0,0 +1,277 @@ +/* + * 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 { readFileSync as fsReadFileSync } from 'fs'; +import { resolve as pathResolve, join as pathJoin } from 'path'; +import http from 'http'; +import https from 'https'; +import axios from 'axios'; +import { duration as momentDuration } from 'moment'; +import { schema } from '@kbn/config-schema'; + +import { request } from './axios_utils'; +import { ByteSizeValue } from '@kbn/config-schema'; +import { Logger } from '../../../../../../src/core/server'; +import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; +import { createReadySignal } from '../../../../event_log/server/lib/ready_signal'; +import { ActionsConfig } from '../../config'; +import { + ActionsConfigurationUtilities, + getActionsConfigurationUtilities, +} from '../../actions_config'; + +const logger = loggingSystemMock.create().get() as jest.Mocked; + +const CERT_DIR = '../../../../../../../packages/kbn-dev-utils/certs'; + +const KIBANA_CRT_FILE = pathResolve(__filename, pathJoin(CERT_DIR, 'kibana.crt')); +const KIBANA_KEY_FILE = pathResolve(__filename, pathJoin(CERT_DIR, 'kibana.key')); +const CA_FILE = pathResolve(__filename, pathJoin(CERT_DIR, 'ca.crt')); + +const KIBANA_KEY = fsReadFileSync(KIBANA_KEY_FILE, 'utf8'); +const KIBANA_CRT = fsReadFileSync(KIBANA_CRT_FILE, 'utf8'); +const CA = fsReadFileSync(CA_FILE, 'utf8'); + +describe('axios connections', () => { + let testServer: http.Server | https.Server; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let savedAxiosDefaultsAdapter: any; + + beforeAll(() => { + // needed to prevent the dreaded Error: Cross origin http://localhost forbidden + // see: https://github.com/axios/axios/issues/1754#issuecomment-572778305 + savedAxiosDefaultsAdapter = axios.defaults.adapter; + axios.defaults.adapter = require('axios/lib/adapters/http'); + }); + + afterAll(() => { + axios.defaults.adapter = savedAxiosDefaultsAdapter; + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + afterEach(() => { + testServer.close(); + }); + + describe('http', () => { + test('it works', async () => { + const { url, server } = await createServer(); + testServer = server; + + const configurationUtilities = getACUfromConfig(); + const res = await request({ axios, url, logger, configurationUtilities }); + expect(res.status).toBe(200); + }); + }); + + describe('https', () => { + test('it fails with self-signed cert and no overrides', async () => { + const { url, server } = await createServer(true); + testServer = server; + + const configurationUtilities = getACUfromConfig(); + const fn = async () => await request({ axios, url, logger, configurationUtilities }); + await expect(fn()).rejects.toThrow('certificate'); + }); + + test('it works with rejectUnauthorized false config', async () => { + const { url, server } = await createServer(true); + testServer = server; + + const configurationUtilities = getACUfromConfig({ + rejectUnauthorized: false, + }); + const res = await request({ axios, url, logger, configurationUtilities }); + expect(res.status).toBe(200); + }); + + test('it works with rejectUnauthorized custom host config', async () => { + const { url, server } = await createServer(true); + testServer = server; + + const configurationUtilities = getACUfromConfig({ + customHostSettings: [{ url, tls: { rejectUnauthorized: false } }], + }); + const res = await request({ axios, url, logger, configurationUtilities }); + expect(res.status).toBe(200); + }); + + test('it works with ca in custom host config', async () => { + const { url, server } = await createServer(true); + testServer = server; + + const configurationUtilities = getACUfromConfig({ + customHostSettings: [{ url, tls: { certificateAuthoritiesData: CA } }], + }); + const res = await request({ axios, url, logger, configurationUtilities }); + expect(res.status).toBe(200); + }); + + test('it fails with incorrect ca in custom host config', async () => { + const { url, server } = await createServer(true); + testServer = server; + + const configurationUtilities = getACUfromConfig({ + customHostSettings: [{ url, tls: { certificateAuthoritiesData: KIBANA_CRT } }], + }); + const fn = async () => await request({ axios, url, logger, configurationUtilities }); + await expect(fn()).rejects.toThrow('certificate'); + }); + + test('it works with incorrect ca in custom host config but rejectUnauthorized false', async () => { + const { url, server } = await createServer(true); + testServer = server; + + const configurationUtilities = getACUfromConfig({ + customHostSettings: [ + { + url, + tls: { + certificateAuthoritiesData: CA, + rejectUnauthorized: false, + }, + }, + ], + }); + const res = await request({ axios, url, logger, configurationUtilities }); + expect(res.status).toBe(200); + }); + + test('it works with incorrect ca in custom host config but rejectUnauthorized config true', async () => { + const { url, server } = await createServer(true); + testServer = server; + + const configurationUtilities = getACUfromConfig({ + rejectUnauthorized: false, + customHostSettings: [ + { + url, + tls: { + certificateAuthoritiesData: CA, + }, + }, + ], + }); + const res = await request({ axios, url, logger, configurationUtilities }); + expect(res.status).toBe(200); + }); + + test('it fails with no matching custom host settings', async () => { + const { url, server } = await createServer(true); + const otherUrl = 'https://example.com'; + testServer = server; + + const configurationUtilities = getACUfromConfig({ + customHostSettings: [{ url: otherUrl, tls: { rejectUnauthorized: false } }], + }); + const fn = async () => await request({ axios, url, logger, configurationUtilities }); + await expect(fn()).rejects.toThrow('certificate'); + }); + + test('it fails cleanly with a garbage CA 1', async () => { + const { url, server } = await createServer(true); + testServer = server; + + const configurationUtilities = getACUfromConfig({ + customHostSettings: [{ url, tls: { certificateAuthoritiesData: 'garbage' } }], + }); + const fn = async () => await request({ axios, url, logger, configurationUtilities }); + await expect(fn()).rejects.toThrow('certificate'); + }); + + test('it fails cleanly with a garbage CA 2', async () => { + const { url, server } = await createServer(true); + testServer = server; + + const ca = '-----BEGIN CERTIFICATE-----\ngarbage\n-----END CERTIFICATE-----\n'; + const configurationUtilities = getACUfromConfig({ + customHostSettings: [{ url, tls: { certificateAuthoritiesData: ca } }], + }); + const fn = async () => await request({ axios, url, logger, configurationUtilities }); + await expect(fn()).rejects.toThrow('certificate'); + }); + }); +}); + +interface CreateServerResult { + url: string; + server: http.Server | https.Server; +} + +async function createServer(useHttps: boolean = false): Promise { + let server: http.Server | https.Server; + const readySignal = createReadySignal(); + + if (!useHttps) { + server = http.createServer((req, res) => { + res.writeHead(200); + res.end('http: just testing that a connection could be made'); + }); + } else { + const httpsOptions = { + cert: KIBANA_CRT, + key: KIBANA_KEY, + }; + server = https.createServer(httpsOptions, (req, res) => { + res.writeHead(200); + res.end('https: just testing that a connection could be made'); + }); + } + + server.listen(() => { + const addressInfo = server.address(); + if (addressInfo == null || typeof addressInfo === 'string') { + server.close(); + throw new Error('error getting address of server, closing'); + } + + const url = localUrlFromPort(useHttps, addressInfo.port, 'localhost'); + readySignal.signal({ server, url }); + }); + + // let the node process stop if for some reason this server isn't closed + server.unref(); + + return readySignal.wait(); +} + +const BaseActionsConfig: ActionsConfig = { + enabled: true, + allowedHosts: ['*'], + enabledActionTypes: ['*'], + preconfiguredAlertHistoryEsIndex: false, + preconfigured: {}, + proxyUrl: undefined, + proxyHeaders: undefined, + proxyRejectUnauthorizedCertificates: true, + proxyBypassHosts: undefined, + proxyOnlyHosts: undefined, + rejectUnauthorized: true, + maxResponseContentLength: ByteSizeValue.parse('1mb'), + responseTimeout: momentDuration(1000 * 30), + customHostSettings: undefined, + cleanupFailedExecutionsTask: { + enabled: true, + cleanupInterval: schema.duration().validate('5m'), + idleInterval: schema.duration().validate('1h'), + pageSize: 100, + }, +}; + +function getACUfromConfig(config: Partial = {}): ActionsConfigurationUtilities { + return getActionsConfigurationUtilities({ + ...BaseActionsConfig, + ...config, + }); +} + +function localUrlFromPort(useHttps: boolean, port: number, host: string): string { + return `${useHttps ? 'https' : 'http'}://${host}:${port}`; +} diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.test.ts index f6d1be9bffc6b..805c22806ce4c 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.test.ts @@ -16,11 +16,16 @@ const logger = loggingSystemMock.create().get() as jest.Mocked; const targetHost = 'elastic.co'; const targetUrl = `https://${targetHost}/foo/bar/baz`; +const targetUrlCanonical = `https://${targetHost}:443`; const nonMatchingUrl = `https://${targetHost}m/foo/bar/baz`; describe('getCustomAgents', () => { const configurationUtilities = actionsConfigMock.create(); + beforeEach(() => { + jest.resetAllMocks(); + }); + test('get agents for valid proxy URL', () => { configurationUtilities.getProxySettings.mockReturnValue({ proxyUrl: 'https://someproxyhost', @@ -106,4 +111,117 @@ describe('getCustomAgents', () => { expect(httpAgent instanceof HttpProxyAgent).toBeFalsy(); expect(httpsAgent instanceof HttpsProxyAgent).toBeFalsy(); }); + + test('handles custom host settings', () => { + configurationUtilities.getCustomHostSettings.mockReturnValue({ + url: targetUrlCanonical, + tls: { + rejectUnauthorized: false, + certificateAuthoritiesData: 'ca data here', + }, + }); + const { httpsAgent } = getCustomAgents(configurationUtilities, logger, targetUrl); + expect(httpsAgent?.options.ca).toBe('ca data here'); + expect(httpsAgent?.options.rejectUnauthorized).toBe(false); + }); + + test('handles custom host settings with proxy', () => { + configurationUtilities.getProxySettings.mockReturnValue({ + proxyUrl: 'https://someproxyhost', + proxyRejectUnauthorizedCertificates: false, + proxyBypassHosts: undefined, + proxyOnlyHosts: undefined, + }); + configurationUtilities.getCustomHostSettings.mockReturnValue({ + url: targetUrlCanonical, + tls: { + rejectUnauthorized: false, + certificateAuthoritiesData: 'ca data here', + }, + }); + const { httpAgent, httpsAgent } = getCustomAgents(configurationUtilities, logger, targetUrl); + expect(httpAgent instanceof HttpProxyAgent).toBeTruthy(); + expect(httpsAgent instanceof HttpsProxyAgent).toBeTruthy(); + + expect(httpsAgent?.options.ca).toBe('ca data here'); + expect(httpsAgent?.options.rejectUnauthorized).toBe(false); + }); + + test('handles overriding global rejectUnauthorized false', () => { + configurationUtilities.isRejectUnauthorizedCertificatesEnabled.mockReturnValue(false); + configurationUtilities.getCustomHostSettings.mockReturnValue({ + url: targetUrlCanonical, + tls: { + rejectUnauthorized: true, + }, + }); + + const { httpAgent, httpsAgent } = getCustomAgents(configurationUtilities, logger, targetUrl); + expect(httpAgent instanceof HttpProxyAgent).toBeFalsy(); + expect(httpsAgent instanceof HttpsAgent).toBeTruthy(); + expect(httpsAgent instanceof HttpsProxyAgent).toBeFalsy(); + expect(httpsAgent?.options.rejectUnauthorized).toBeTruthy(); + }); + + test('handles overriding global rejectUnauthorized true', () => { + configurationUtilities.isRejectUnauthorizedCertificatesEnabled.mockReturnValue(true); + configurationUtilities.getCustomHostSettings.mockReturnValue({ + url: targetUrlCanonical, + tls: { + rejectUnauthorized: false, + }, + }); + + const { httpAgent, httpsAgent } = getCustomAgents(configurationUtilities, logger, targetUrl); + expect(httpAgent instanceof HttpProxyAgent).toBeFalsy(); + expect(httpsAgent instanceof HttpsAgent).toBeTruthy(); + expect(httpsAgent instanceof HttpsProxyAgent).toBeFalsy(); + expect(httpsAgent?.options.rejectUnauthorized).toBeFalsy(); + }); + + test('handles overriding global rejectUnauthorized false with a proxy', () => { + configurationUtilities.isRejectUnauthorizedCertificatesEnabled.mockReturnValue(false); + configurationUtilities.getCustomHostSettings.mockReturnValue({ + url: targetUrlCanonical, + tls: { + rejectUnauthorized: true, + }, + }); + configurationUtilities.getProxySettings.mockReturnValue({ + proxyUrl: 'https://someproxyhost', + // note: this setting doesn't come into play, it's for the connection to + // the proxy, not the target url + proxyRejectUnauthorizedCertificates: false, + proxyBypassHosts: undefined, + proxyOnlyHosts: undefined, + }); + + const { httpAgent, httpsAgent } = getCustomAgents(configurationUtilities, logger, targetUrl); + expect(httpAgent instanceof HttpProxyAgent).toBeTruthy(); + expect(httpsAgent instanceof HttpsProxyAgent).toBeTruthy(); + expect(httpsAgent?.options.rejectUnauthorized).toBeTruthy(); + }); + + test('handles overriding global rejectUnauthorized true with a proxy', () => { + configurationUtilities.isRejectUnauthorizedCertificatesEnabled.mockReturnValue(true); + configurationUtilities.getCustomHostSettings.mockReturnValue({ + url: targetUrlCanonical, + tls: { + rejectUnauthorized: false, + }, + }); + configurationUtilities.getProxySettings.mockReturnValue({ + proxyUrl: 'https://someproxyhost', + // note: this setting doesn't come into play, it's for the connection to + // the proxy, not the target url + proxyRejectUnauthorizedCertificates: false, + proxyBypassHosts: undefined, + proxyOnlyHosts: undefined, + }); + + const { httpAgent, httpsAgent } = getCustomAgents(configurationUtilities, logger, targetUrl); + expect(httpAgent instanceof HttpProxyAgent).toBeTruthy(); + expect(httpsAgent instanceof HttpsProxyAgent).toBeTruthy(); + expect(httpsAgent?.options.rejectUnauthorized).toBeFalsy(); + }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.ts index ff2d005f4d841..6ec926004e73e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.ts @@ -6,7 +6,7 @@ */ import { Agent as HttpAgent } from 'http'; -import { Agent as HttpsAgent } from 'https'; +import { Agent as HttpsAgent, AgentOptions } from 'https'; import HttpProxyAgent from 'http-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { Logger } from '../../../../../../src/core/server'; @@ -22,7 +22,8 @@ export function getCustomAgents( logger: Logger, url: string ): GetCustomAgentsResponse { - const proxySettings = configurationUtilities.getProxySettings(); + // the default for rejectUnauthorized is the global setting, which can + // be overridden (below) with a custom host setting const defaultAgents = { httpAgent: undefined, httpsAgent: new HttpsAgent({ @@ -30,10 +31,39 @@ export function getCustomAgents( }), }; + // Get the current proxy settings, and custom host settings for this URL. + // If there are neither of these, return the default agents + const proxySettings = configurationUtilities.getProxySettings(); + const customHostSettings = configurationUtilities.getCustomHostSettings(url); + if (!proxySettings && !customHostSettings) { + return defaultAgents; + } + + // update the defaultAgents.httpsAgent if configured + const tlsSettings = customHostSettings?.tls; + let agentOptions: AgentOptions | undefined; + if (tlsSettings) { + logger.debug(`Creating customized connection settings for: ${url}`); + agentOptions = defaultAgents.httpsAgent.options; + + if (tlsSettings.certificateAuthoritiesData) { + agentOptions.ca = tlsSettings.certificateAuthoritiesData; + } + + // see: src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts + // This is where the global rejectUnauthorized is overridden by a custom host + if (tlsSettings.rejectUnauthorized !== undefined) { + agentOptions.rejectUnauthorized = tlsSettings.rejectUnauthorized; + } + } + + // if there weren't any proxy settings, return the currently calculated agents if (!proxySettings) { return defaultAgents; } + // there is a proxy in use, but it's possible we won't use it via custom host + // proxyOnlyHosts and proxyBypassHosts let targetUrl: URL; try { targetUrl = new URL(url); @@ -56,6 +86,7 @@ export function getCustomAgents( return defaultAgents; } } + logger.debug(`Creating proxy agents for proxy: ${proxySettings.proxyUrl}`); let proxyUrl: URL; try { @@ -65,6 +96,9 @@ export function getCustomAgents( return defaultAgents; } + // At this point, we are going to use a proxy, so we need new agents. + // We will though, copy over the calculated tls options from above, into + // the https agent. const httpAgent = new HttpProxyAgent(proxySettings.proxyUrl); const httpsAgent = (new HttpsProxyAgent({ host: proxyUrl.hostname, @@ -76,5 +110,12 @@ export function getCustomAgents( }) as unknown) as HttpsAgent; // vsCode wasn't convinced HttpsProxyAgent is an https.Agent, so we convinced it + if (agentOptions) { + httpsAgent.options = { + ...httpsAgent.options, + ...agentOptions, + }; + } + return { httpAgent, httpsAgent }; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts index 4b45c6d787cd6..cceeefde71dc2 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts @@ -15,6 +15,7 @@ import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; import nodemailer from 'nodemailer'; import { ProxySettings } from '../../types'; import { actionsConfigMock } from '../../actions_config.mock'; +import { CustomHostSettings } from '../../config'; const createTransportMock = nodemailer.createTransport as jest.Mock; const sendMailMockResult = { result: 'does not matter' }; @@ -356,16 +357,151 @@ describe('send_email module', () => { ] `); }); + + test('it handles custom host settings from config', async () => { + const sendEmailOptions = getSendEmailOptionsNoAuth( + { + transport: { + host: 'example.com', + port: 1025, + }, + }, + undefined, + { + url: 'smtp://example.com:1025', + tls: { + certificateAuthoritiesData: 'ca cert data goes here', + }, + smtp: { + ignoreTLS: false, + requireTLS: true, + }, + } + ); + + const result = await sendEmail(mockLogger, sendEmailOptions); + expect(result).toBe(sendMailMockResult); + + // note in the object below, the rejectUnauthenticated got set to false, + // given the implementation allowing that for no auth and !secure. + expect(createTransportMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "host": "example.com", + "port": 1025, + "requireTLS": true, + "secure": false, + "tls": Object { + "ca": "ca cert data goes here", + "rejectUnauthorized": false, + }, + }, + ] + `); + }); + + test('it allows custom host settings to override calculated values', async () => { + const sendEmailOptions = getSendEmailOptionsNoAuth( + { + transport: { + host: 'example.com', + port: 1025, + }, + }, + undefined, + { + url: 'smtp://example.com:1025', + tls: { + certificateAuthoritiesData: 'ca cert data goes here', + rejectUnauthorized: true, + }, + smtp: { + ignoreTLS: true, + requireTLS: false, + }, + } + ); + + const result = await sendEmail(mockLogger, sendEmailOptions); + expect(result).toBe(sendMailMockResult); + + // in this case, rejectUnauthorized is true, as the custom host settings + // overrode the calculated value of false + expect(createTransportMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "host": "example.com", + "ignoreTLS": true, + "port": 1025, + "secure": false, + "tls": Object { + "ca": "ca cert data goes here", + "rejectUnauthorized": true, + }, + }, + ] + `); + }); + + test('it handles custom host settings with a proxy', async () => { + const sendEmailOptions = getSendEmailOptionsNoAuth( + { + transport: { + host: 'example.com', + port: 1025, + }, + }, + { + proxyUrl: 'https://proxy.com', + proxyRejectUnauthorizedCertificates: false, + proxyBypassHosts: undefined, + proxyOnlyHosts: undefined, + }, + { + url: 'smtp://example.com:1025', + tls: { + certificateAuthoritiesData: 'ca cert data goes here', + rejectUnauthorized: true, + }, + smtp: { + requireTLS: true, + }, + } + ); + + const result = await sendEmail(mockLogger, sendEmailOptions); + expect(result).toBe(sendMailMockResult); + expect(createTransportMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "headers": undefined, + "host": "example.com", + "port": 1025, + "proxy": "https://proxy.com", + "requireTLS": true, + "secure": false, + "tls": Object { + "ca": "ca cert data goes here", + "rejectUnauthorized": true, + }, + }, + ] + `); + }); }); function getSendEmailOptions( { content = {}, routing = {}, transport = {} } = {}, - proxySettings?: ProxySettings + proxySettings?: ProxySettings, + customHostSettings?: CustomHostSettings ) { const configurationUtilities = actionsConfigMock.create(); if (proxySettings) { configurationUtilities.getProxySettings.mockReturnValue(proxySettings); } + if (customHostSettings) { + configurationUtilities.getCustomHostSettings.mockReturnValue(customHostSettings); + } return { content: { ...content, @@ -392,12 +528,16 @@ function getSendEmailOptions( function getSendEmailOptionsNoAuth( { content = {}, routing = {}, transport = {} } = {}, - proxySettings?: ProxySettings + proxySettings?: ProxySettings, + customHostSettings?: CustomHostSettings ) { const configurationUtilities = actionsConfigMock.create(); if (proxySettings) { configurationUtilities.getProxySettings.mockReturnValue(proxySettings); } + if (customHostSettings) { + configurationUtilities.getCustomHostSettings.mockReturnValue(customHostSettings); + } return { content: { ...content, diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts index c0a254967b4fe..005e73b1fc2f7 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts @@ -11,6 +11,7 @@ import { default as MarkdownIt } from 'markdown-it'; import { Logger } from '../../../../../../src/core/server'; import { ActionsConfigurationUtilities } from '../../actions_config'; +import { CustomHostSettings } from '../../config'; // an email "service" which doesn't actually send, just returns what it would send export const JSON_TRANSPORT_SERVICE = '__json'; @@ -52,7 +53,10 @@ export async function sendEmail(logger: Logger, options: SendEmailOptions): Prom const { from, to, cc, bcc } = routing; const { subject, message } = content; - const transportConfig: Record = {}; + // The transport options do not seem to be exposed as a type, and we reference + // some deep properties, so need to use any here. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const transportConfig: Record = {}; const proxySettings = configurationUtilities.getProxySettings(); const rejectUnauthorized = configurationUtilities.isRejectUnauthorizedCertificatesEnabled(); @@ -73,6 +77,7 @@ export async function sendEmail(logger: Logger, options: SendEmailOptions): Prom useProxy = false; } } + let customHostSettings: CustomHostSettings | undefined; if (service === JSON_TRANSPORT_SERVICE) { transportConfig.jsonTransport = true; @@ -83,6 +88,7 @@ export async function sendEmail(logger: Logger, options: SendEmailOptions): Prom transportConfig.host = host; transportConfig.port = port; transportConfig.secure = !!secure; + customHostSettings = configurationUtilities.getCustomHostSettings(`smtp://${host}:${port}`); if (proxySettings && useProxy) { transportConfig.tls = { @@ -99,6 +105,33 @@ export async function sendEmail(logger: Logger, options: SendEmailOptions): Prom } else { transportConfig.tls = { rejectUnauthorized }; } + + // finally, allow customHostSettings to override some of the settings + // see: https://nodemailer.com/smtp/ + if (customHostSettings) { + const tlsConfig: Record = {}; + const tlsSettings = customHostSettings.tls; + const smtpSettings = customHostSettings.smtp; + + if (tlsSettings?.certificateAuthoritiesData) { + tlsConfig.ca = tlsSettings?.certificateAuthoritiesData; + } + if (tlsSettings?.rejectUnauthorized !== undefined) { + tlsConfig.rejectUnauthorized = tlsSettings?.rejectUnauthorized; + } + + if (!transportConfig.tls) { + transportConfig.tls = tlsConfig; + } else { + transportConfig.tls = { ...transportConfig.tls, ...tlsConfig }; + } + + if (smtpSettings?.ignoreTLS) { + transportConfig.ignoreTLS = true; + } else if (smtpSettings?.requireTLS) { + transportConfig.requireTLS = true; + } + } } const nodemailerTransport = nodemailer.createTransport(transportConfig); diff --git a/x-pack/plugins/actions/server/builtin_action_types/teams.test.ts b/x-pack/plugins/actions/server/builtin_action_types/teams.test.ts index 8a185d353de02..95088fa5f7965 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/teams.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/teams.test.ts @@ -167,6 +167,7 @@ describe('execute()', () => { "ensureActionTypeEnabled": [MockFunction], "ensureHostnameAllowed": [MockFunction], "ensureUriAllowed": [MockFunction], + "getCustomHostSettings": [MockFunction], "getProxySettings": [MockFunction], "getResponseSettings": [MockFunction], "isActionTypeEnabled": [MockFunction], @@ -230,6 +231,7 @@ describe('execute()', () => { "ensureActionTypeEnabled": [MockFunction], "ensureHostnameAllowed": [MockFunction], "ensureUriAllowed": [MockFunction], + "getCustomHostSettings": [MockFunction], "getProxySettings": [MockFunction], "getResponseSettings": [MockFunction], "isActionTypeEnabled": [MockFunction], diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts index d3f059eede615..00e56303dbe22 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts @@ -290,6 +290,7 @@ describe('execute()', () => { "ensureActionTypeEnabled": [MockFunction], "ensureHostnameAllowed": [MockFunction], "ensureUriAllowed": [MockFunction], + "getCustomHostSettings": [MockFunction], "getProxySettings": [MockFunction], "getResponseSettings": [MockFunction], "isActionTypeEnabled": [MockFunction], @@ -382,6 +383,7 @@ describe('execute()', () => { "ensureActionTypeEnabled": [MockFunction], "ensureHostnameAllowed": [MockFunction], "ensureUriAllowed": [MockFunction], + "getCustomHostSettings": [MockFunction], "getProxySettings": [MockFunction], "getResponseSettings": [MockFunction], "isActionTypeEnabled": [MockFunction], diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts index 092b5d2cce587..4c4fd143369e1 100644 --- a/x-pack/plugins/actions/server/config.test.ts +++ b/x-pack/plugins/actions/server/config.test.ts @@ -164,6 +164,19 @@ describe('config validation', () => { ] `); }); + + // Most of the customHostSettings tests are in ./lib/custom_host_settings.test.ts + // but this one seemed more relevant for this test suite, since url is the one + // required property. + test('validates customHostSettings contains a URL', () => { + const config: Record = { + customHostSettings: [{}], + }; + + expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot( + `"[customHostSettings.0.url]: expected value of type [string] but got [undefined]"` + ); + }); }); // object creator that ensures we can create a property named __proto__ on an diff --git a/x-pack/plugins/actions/server/config.ts b/x-pack/plugins/actions/server/config.ts index 7225c54d57596..0dc1aed68f4d0 100644 --- a/x-pack/plugins/actions/server/config.ts +++ b/x-pack/plugins/actions/server/config.ts @@ -23,6 +23,30 @@ const preconfiguredActionSchema = schema.object({ secrets: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), }); +const customHostSettingsSchema = schema.object({ + url: schema.string({ minLength: 1 }), + smtp: schema.maybe( + schema.object({ + ignoreTLS: schema.maybe(schema.boolean()), + requireTLS: schema.maybe(schema.boolean()), + }) + ), + tls: schema.maybe( + schema.object({ + rejectUnauthorized: schema.maybe(schema.boolean()), + certificateAuthoritiesFiles: schema.maybe( + schema.oneOf([ + schema.string({ minLength: 1 }), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + ]) + ), + certificateAuthoritiesData: schema.maybe(schema.string({ minLength: 1 })), + }) + ), +}); + +export type CustomHostSettings = TypeOf; + export const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), allowedHosts: schema.arrayOf( @@ -50,6 +74,7 @@ export const configSchema = schema.object({ rejectUnauthorized: schema.boolean({ defaultValue: true }), maxResponseContentLength: schema.byteSize({ defaultValue: '1mb' }), responseTimeout: schema.duration({ defaultValue: '60s' }), + customHostSettings: schema.maybe(schema.arrayOf(customHostSettingsSchema)), cleanupFailedExecutionsTask: schema.object({ enabled: schema.boolean({ defaultValue: true }), cleanupInterval: schema.duration({ defaultValue: '5m' }), diff --git a/x-pack/plugins/actions/server/lib/custom_host_settings.test.ts b/x-pack/plugins/actions/server/lib/custom_host_settings.test.ts new file mode 100644 index 0000000000000..ad07ea21d7917 --- /dev/null +++ b/x-pack/plugins/actions/server/lib/custom_host_settings.test.ts @@ -0,0 +1,504 @@ +/* + * 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 { readFileSync as fsReadFileSync } from 'fs'; +import { resolve as pathResolve, join as pathJoin } from 'path'; +import { schema, ByteSizeValue } from '@kbn/config-schema'; +import moment from 'moment'; + +import { ActionsConfig } from '../config'; +import { Logger } from '../../../../../src/core/server'; +import { loggingSystemMock } from '../../../../../src/core/server/mocks'; + +import { resolveCustomHosts, getCanonicalCustomHostUrl } from './custom_host_settings'; + +const CA_DIR = '../../../../../../packages/kbn-dev-utils/certs'; +const CA_FILE1 = pathResolve(__filename, pathJoin(CA_DIR, 'ca.crt')); +const CA_CONTENTS1 = fsReadFileSync(CA_FILE1, 'utf8'); +const CA_FILE2 = pathResolve(__filename, pathJoin(CA_DIR, 'kibana.crt')); +const CA_CONTENTS2 = fsReadFileSync(CA_FILE2, 'utf8'); + +let mockLogger: Logger = loggingSystemMock.create().get(); + +function warningLogs() { + const calls = loggingSystemMock.collect(mockLogger).warn; + return calls.map((call) => `${call[0]}`); +} + +describe('custom_host_settings', () => { + beforeEach(() => { + jest.resetAllMocks(); + mockLogger = loggingSystemMock.create().get(); + }); + + describe('getCanonicalCustomHostUrl()', () => { + test('minimal urls', () => { + expect(getCanonicalCustomHostUrl(new URL('http://elastic.com'))).toBe( + 'http://elastic.com:80' + ); + expect(getCanonicalCustomHostUrl(new URL('https://elastic.co'))).toBe( + 'https://elastic.co:443' + ); + expect(getCanonicalCustomHostUrl(new URL('smtp://mail.elastic.co'))).toBe( + 'smtp://mail.elastic.co:25' + ); + expect(warningLogs()).toEqual([]); + }); + + test('maximal urls', () => { + expect( + getCanonicalCustomHostUrl(new URL('http://user1:pass1@elastic.co:81/foo?bar#car')) + ).toBe('http://elastic.co:81'); + expect( + getCanonicalCustomHostUrl(new URL('https://user1:pass1@elastic.co:82/foo?bar#car')) + ).toBe('https://elastic.co:82'); + expect( + getCanonicalCustomHostUrl(new URL('smtp://user1:pass1@mail.elastic.co:83/foo?bar#car')) + ).toBe('smtp://mail.elastic.co:83'); + expect(warningLogs()).toEqual([]); + }); + }); + + describe('resolveCustomHosts()', () => { + const defaultActionsConfig: ActionsConfig = { + enabled: true, + allowedHosts: [], + enabledActionTypes: [], + preconfiguredAlertHistoryEsIndex: false, + preconfigured: {}, + proxyRejectUnauthorizedCertificates: true, + rejectUnauthorized: true, + maxResponseContentLength: new ByteSizeValue(1000000), + responseTimeout: moment.duration(60000), + cleanupFailedExecutionsTask: { + enabled: true, + cleanupInterval: schema.duration().validate('5m'), + idleInterval: schema.duration().validate('1h'), + pageSize: 100, + }, + }; + + test('ensure it copies over the config parts that it does not touch', () => { + const config: ActionsConfig = { ...defaultActionsConfig }; + const resConfig = resolveCustomHosts(mockLogger, config); + expect(resConfig).toMatchObject(config); + expect(config).toMatchObject(resConfig); + expect(warningLogs()).toEqual([]); + }); + + test('handles undefined customHostSettings', () => { + const config: ActionsConfig = { ...defaultActionsConfig, customHostSettings: undefined }; + const resConfig = resolveCustomHosts(mockLogger, config); + expect(resConfig).toMatchObject(config); + expect(config).toMatchObject(resConfig); + expect(warningLogs()).toEqual([]); + }); + + test('handles empty object customHostSettings', () => { + const config: ActionsConfig = { ...defaultActionsConfig, customHostSettings: [] }; + const resConfig = resolveCustomHosts(mockLogger, config); + expect(resConfig).toMatchObject(config); + expect(config).toMatchObject(resConfig); + expect(warningLogs()).toEqual([]); + }); + + test('handles multiple valid settings', () => { + const config: ActionsConfig = { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'https://elastic.co:443', + tls: { + certificateAuthoritiesData: 'xyz', + rejectUnauthorized: false, + }, + }, + { + url: 'smtp://mail.elastic.com:25', + tls: { + certificateAuthoritiesData: 'abc', + rejectUnauthorized: true, + }, + smtp: { + ignoreTLS: true, + }, + }, + ], + }; + const resConfig = resolveCustomHosts(mockLogger, config); + expect(resConfig).toMatchObject(config); + expect(config).toMatchObject(resConfig); + expect(warningLogs()).toEqual([]); + }); + + test('handles bad url', () => { + const config: ActionsConfig = { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'this! is! not! a! url!', + }, + ], + }; + const resConfig = resolveCustomHosts(mockLogger, config); + const expConfig = { ...config, customHostSettings: [] }; + expect(resConfig).toMatchObject(expConfig); + expect(expConfig).toMatchObject(resConfig); + expect(warningLogs()).toMatchInlineSnapshot(` + Array [ + "In configuration xpack.actions.customHosts, invalid URL \\"this! is! not! a! url!\\", ignoring; error: Invalid URL: this! is! not! a! url!", + ] + `); + }); + + test('handles bad port', () => { + const config: ActionsConfig = { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'https://almost.purrfect.com:0', + }, + ], + }; + const resConfig = resolveCustomHosts(mockLogger, config); + const expConfig = { ...config, customHostSettings: [] }; + expect(resConfig).toMatchObject(expConfig); + expect(expConfig).toMatchObject(resConfig); + expect(warningLogs()).toMatchInlineSnapshot(` + Array [ + "In configuration xpack.actions.customHosts, unable to determine port for URL \\"https://almost.purrfect.com:0\\", ignoring", + ] + `); + }); + + test('handles auth info', () => { + const config: ActionsConfig = { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'https://kitty:cat@almost.purrfect.com', + }, + ], + }; + const resConfig = resolveCustomHosts(mockLogger, config); + const expConfig = { + ...config, + customHostSettings: [ + { + url: 'https://almost.purrfect.com:443', + }, + ], + }; + expect(resConfig).toMatchObject(expConfig); + expect(expConfig).toMatchObject(resConfig); + expect(warningLogs()).toMatchInlineSnapshot(` + Array [ + "In configuration xpack.actions.customHosts, URL \\"https://kitty:cat@almost.purrfect.com\\" contains authentication information which will be ignored, but should be removed from the configuration", + ] + `); + }); + + test('handles hash', () => { + const config: ActionsConfig = { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'https://almost.purrfect.com#important', + }, + ], + }; + const resConfig = resolveCustomHosts(mockLogger, config); + const expConfig = { + ...config, + customHostSettings: [ + { + url: 'https://almost.purrfect.com:443', + }, + ], + }; + expect(resConfig).toMatchObject(expConfig); + expect(expConfig).toMatchObject(resConfig); + expect(warningLogs()).toMatchInlineSnapshot(` + Array [ + "In configuration xpack.actions.customHosts, URL \\"https://almost.purrfect.com#important\\" contains hash information which will be ignored", + ] + `); + }); + + test('handles path', () => { + const config: ActionsConfig = { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'https://almost.purrfect.com/about', + }, + ], + }; + const resConfig = resolveCustomHosts(mockLogger, config); + const expConfig = { + ...config, + customHostSettings: [ + { + url: 'https://almost.purrfect.com:443', + }, + ], + }; + expect(resConfig).toMatchObject(expConfig); + expect(expConfig).toMatchObject(resConfig); + expect(warningLogs()).toMatchInlineSnapshot(` + Array [ + "In configuration xpack.actions.customHosts, URL \\"https://almost.purrfect.com/about\\" contains path information which will be ignored", + ] + `); + }); + + test('handles / path same as no path, since we have no choice', () => { + const config: ActionsConfig = { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'https://almost.purrfect.com/', + }, + ], + }; + const resConfig = resolveCustomHosts(mockLogger, config); + const expConfig = { + ...config, + customHostSettings: [ + { + url: 'https://almost.purrfect.com:443', + }, + ], + }; + expect(resConfig).toMatchObject(expConfig); + expect(expConfig).toMatchObject(resConfig); + expect(warningLogs()).toEqual([]); + }); + + test('handles unsupported URL protocols', () => { + const config: ActionsConfig = { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'http://almost.purrfect.com/', + }, + ], + }; + const resConfig = resolveCustomHosts(mockLogger, config); + const expConfig = { + ...config, + customHostSettings: [], + }; + expect(resConfig).toMatchObject(expConfig); + expect(expConfig).toMatchObject(resConfig); + expect(warningLogs()).toMatchInlineSnapshot(` + Array [ + "In configuration xpack.actions.customHosts, unsupported protocol used in URL \\"http://almost.purrfect.com/\\", ignoring", + ] + `); + }); + + test('handles smtp options for non-smtp urls', () => { + const config: ActionsConfig = { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'https://almost.purrfect.com/', + smtp: { + ignoreTLS: true, + }, + }, + ], + }; + const resConfig = resolveCustomHosts(mockLogger, config); + const expConfig = { + ...config, + customHostSettings: [ + { + url: 'https://almost.purrfect.com:443', + }, + ], + }; + expect(resConfig).toMatchObject(expConfig); + expect(expConfig).toMatchObject(resConfig); + expect(warningLogs()).toMatchInlineSnapshot(` + Array [ + "In configuration xpack.actions.customHosts, URL \\"https://almost.purrfect.com/\\" contains smtp properties but does not use smtp; ignoring smtp properties", + ] + `); + }); + + test('handles ca files not found', () => { + const config: ActionsConfig = { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'https://almost.purrfect.com/', + tls: { + certificateAuthoritiesFiles: 'this-file-does-not-exist', + }, + }, + ], + }; + const resConfig = resolveCustomHosts(mockLogger, config); + const expConfig = { + ...config, + customHostSettings: [ + { + url: 'https://almost.purrfect.com:443', + tls: { + certificateAuthoritiesFiles: 'this-file-does-not-exist', + }, + }, + ], + }; + expect(resConfig).toMatchObject(expConfig); + expect(expConfig).toMatchObject(resConfig); + expect(warningLogs()).toMatchInlineSnapshot(` + Array [ + "error reading file \\"this-file-does-not-exist\\" specified in xpack.actions.customHosts, ignoring: ENOENT: no such file or directory, open 'this-file-does-not-exist'", + ] + `); + }); + + test('handles a single ca file', () => { + const config: ActionsConfig = { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'https://almost.purrfect.com/', + tls: { + certificateAuthoritiesFiles: CA_FILE1, + }, + }, + ], + }; + const resConfig = resolveCustomHosts(mockLogger, config); + + // not checking the full structure anymore, just ca bits + expect(resConfig?.customHostSettings?.[0].tls?.certificateAuthoritiesData).toBe(CA_CONTENTS1); + expect(warningLogs()).toEqual([]); + }); + + test('handles multiple ca files', () => { + const config: ActionsConfig = { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'https://almost.purrfect.com/', + tls: { + certificateAuthoritiesFiles: [CA_FILE1, CA_FILE2], + }, + }, + ], + }; + const resConfig = resolveCustomHosts(mockLogger, config); + + // not checking the full structure anymore, just ca bits + expect(resConfig?.customHostSettings?.[0].tls?.certificateAuthoritiesData).toBe( + `${CA_CONTENTS1}\n${CA_CONTENTS2}` + ); + expect(warningLogs()).toEqual([]); + }); + + test('handles ca files and ca data', () => { + const config: ActionsConfig = { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'https://almost.purrfect.com/', + tls: { + certificateAuthoritiesFiles: [CA_FILE2], + certificateAuthoritiesData: CA_CONTENTS1, + }, + }, + ], + }; + const resConfig = resolveCustomHosts(mockLogger, config); + + // not checking the full structure anymore, just ca bits + expect(resConfig?.customHostSettings?.[0].tls?.certificateAuthoritiesData).toBe( + `${CA_CONTENTS1}\n${CA_CONTENTS2}` + ); + expect(warningLogs()).toEqual([]); + }); + + test('handles smtp ignoreTLS and requireTLS both used', () => { + const config: ActionsConfig = { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'smtp://almost.purrfect.com/', + smtp: { + ignoreTLS: true, + requireTLS: true, + }, + }, + ], + }; + const resConfig = resolveCustomHosts(mockLogger, config); + const expConfig = { + ...config, + customHostSettings: [ + { + url: 'smtp://almost.purrfect.com:25', + smtp: { + ignoreTLS: false, + requireTLS: true, + }, + }, + ], + }; + expect(resConfig).toMatchObject(expConfig); + expect(expConfig).toMatchObject(resConfig); + expect(warningLogs()).toMatchInlineSnapshot(` + Array [ + "In configuration xpack.actions.customHosts, URL \\"smtp://almost.purrfect.com/\\" cannot have both requireTLS and ignoreTLS set to true; using requireTLS: true and ignoreTLS: false", + ] + `); + }); + + test('handles duplicate URLs', () => { + const config: ActionsConfig = { + ...defaultActionsConfig, + customHostSettings: [ + { + url: 'https://almost.purrfect.com/', + tls: { + rejectUnauthorized: true, + }, + }, + { + url: 'https://almost.purrfect.com:443', + tls: { + rejectUnauthorized: false, + }, + }, + ], + }; + const resConfig = resolveCustomHosts(mockLogger, config); + const expConfig = { + ...config, + customHostSettings: [ + { + url: 'https://almost.purrfect.com:443', + tls: { + rejectUnauthorized: true, + }, + }, + ], + }; + expect(resConfig).toMatchObject(expConfig); + expect(expConfig).toMatchObject(resConfig); + expect(warningLogs()).toMatchInlineSnapshot(` + Array [ + "In configuration xpack.actions.customHosts, multiple URLs match the canonical url \\"https://almost.purrfect.com:443\\"; only the first will be used", + ] + `); + }); + }); +}); diff --git a/x-pack/plugins/actions/server/lib/custom_host_settings.ts b/x-pack/plugins/actions/server/lib/custom_host_settings.ts new file mode 100644 index 0000000000000..bfc8dad48aab6 --- /dev/null +++ b/x-pack/plugins/actions/server/lib/custom_host_settings.ts @@ -0,0 +1,173 @@ +/* + * 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 { readFileSync } from 'fs'; +import { cloneDeep } from 'lodash'; +import { Logger } from '../../../../../src/core/server'; +import { ActionsConfig, CustomHostSettings } from '../config'; + +type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; + +type ActionsConfigWriteable = DeepWriteable; +type CustomHostSettingsWriteable = DeepWriteable; + +export function getCanonicalCustomHostUrl(url: URL): string { + const port = getActualPort(url.protocol, url.port); + + return `${url.protocol}//${url.hostname}:${port}`; +} + +const ErrorPrefix = 'In configuration xpack.actions.customHosts,'; +const ValidProtocols = new Set(['https:', 'smtp:']); +const ProtocolsForSmtp = new Set(['smtp:']); + +// converts the custom host data in config, for ease of use, and to perform +// validation we can't do in config-schema, since the cloud validation can't +// do these sorts of validations +export function resolveCustomHosts(logger: Logger, config: ActionsConfig): ActionsConfig { + const result: ActionsConfigWriteable = cloneDeep(config); + + if (!result.customHostSettings) { + return result as ActionsConfig; + } + + const savedSettings: CustomHostSettingsWriteable[] = []; + + for (const customHostSetting of result.customHostSettings) { + const originalUrl = customHostSetting.url; + let parsedUrl: URL | undefined; + try { + parsedUrl = new URL(originalUrl); + } catch (err) { + logger.warn(`${ErrorPrefix} invalid URL "${originalUrl}", ignoring; error: ${err.message}`); + continue; + } + + customHostSetting.url = getCanonicalCustomHostUrl(parsedUrl); + + if (!ValidProtocols.has(parsedUrl.protocol)) { + logger.warn(`${ErrorPrefix} unsupported protocol used in URL "${originalUrl}", ignoring`); + continue; + } + + const port = getActualPort(parsedUrl.protocol, parsedUrl.port); + if (!port) { + logger.warn(`${ErrorPrefix} unable to determine port for URL "${originalUrl}", ignoring`); + continue; + } + + if (parsedUrl.username || parsedUrl.password) { + logger.warn( + `${ErrorPrefix} URL "${originalUrl}" contains authentication information which will be ignored, but should be removed from the configuration` + ); + } + + if (parsedUrl.hash) { + logger.warn( + `${ErrorPrefix} URL "${originalUrl}" contains hash information which will be ignored` + ); + } + + if (parsedUrl.pathname && parsedUrl.pathname !== '/') { + logger.warn( + `${ErrorPrefix} URL "${originalUrl}" contains path information which will be ignored` + ); + } + + if (!ProtocolsForSmtp.has(parsedUrl.protocol) && customHostSetting.smtp) { + logger.warn( + `${ErrorPrefix} URL "${originalUrl}" contains smtp properties but does not use smtp; ignoring smtp properties` + ); + delete customHostSetting.smtp; + } + + // read the specified ca files, add their content to certificateAuthoritiesData + if (customHostSetting.tls) { + let files = customHostSetting.tls?.certificateAuthoritiesFiles || []; + if (typeof files === 'string') { + files = [files]; + } + for (const file of files) { + const contents = getFileContents(logger, file); + if (contents) { + appendToCertificateAuthoritiesData(customHostSetting, contents); + } + } + } + + const customSmtpSettings = customHostSetting.smtp; + if (customSmtpSettings) { + if (customSmtpSettings.requireTLS && customSmtpSettings.ignoreTLS) { + logger.warn( + `${ErrorPrefix} URL "${originalUrl}" cannot have both requireTLS and ignoreTLS set to true; using requireTLS: true and ignoreTLS: false` + ); + customSmtpSettings.requireTLS = true; + customSmtpSettings.ignoreTLS = false; + } + } + + savedSettings.push(customHostSetting); + } + + // check to see if there are any dups on the url + const existingUrls = new Set(); + for (const customHostSetting of savedSettings) { + const url = customHostSetting.url; + if (existingUrls.has(url)) { + logger.warn( + `${ErrorPrefix} multiple URLs match the canonical url "${url}"; only the first will be used` + ); + // mark this entry to be able to delete it after processing them all + customHostSetting.url = ''; + } + existingUrls.add(url); + } + + // remove the settings we want to skip + result.customHostSettings = savedSettings.filter((setting) => setting.url !== ''); + + return result as ActionsConfig; +} + +function appendToCertificateAuthoritiesData(customHost: CustomHostSettingsWriteable, cert: string) { + const tls = customHost.tls; + if (tls) { + if (!tls.certificateAuthoritiesData) { + tls.certificateAuthoritiesData = cert; + } else { + tls.certificateAuthoritiesData += '\n' + cert; + } + } +} + +function getFileContents(logger: Logger, fileName: string): string | undefined { + try { + return readFileSync(fileName, 'utf8'); + } catch (err) { + logger.warn( + `error reading file "${fileName}" specified in xpack.actions.customHosts, ignoring: ${err.message}` + ); + return; + } +} + +// 0 isn't a valid port, so result can be checked as falsy +function getActualPort(protocol: string, port: string): number { + if (port !== '') { + const portNumber = parseInt(port, 10); + if (isNaN(portNumber)) { + return 0; + } + return portNumber; + } + + // from https://nodejs.org/dist/latest-v14.x/docs/api/url.html#url_url_port + if (protocol === 'http:') return 80; + if (protocol === 'https:') return 443; + if (protocol === 'smtp:') return 25; + return 0; +} diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 106e41259e692..2036ed6c7d343 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -35,6 +35,7 @@ import { } from './cleanup_failed_executions'; import { ActionsConfig, getValidatedConfig } from './config'; +import { resolveCustomHosts } from './lib/custom_host_settings'; import { ActionsClient } from './actions_client'; import { ActionTypeRegistry } from './action_type_registry'; import { createExecutionEnqueuerFunction } from './create_execute_function'; @@ -157,7 +158,10 @@ export class ActionsPlugin implements Plugin()); + this.actionsConfig = getValidatedConfig( + this.logger, + resolveCustomHosts(this.logger, initContext.config.get()) + ); this.telemetryLogger = initContext.logger.get('usage'); this.preconfiguredActions = []; this.kibanaIndexConfig = initContext.config.legacy.get(); diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 6a0ab54087844..7844eaf3920c6 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -12,6 +12,7 @@ import { CA_CERT_PATH } from '@kbn/dev-utils'; import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; import { services } from './services'; import { getAllExternalServiceSimulatorPaths } from './fixtures/plugins/actions_simulators/server/plugin'; +import { getTlsWebhookServerUrls } from './lib/get_tls_webhook_servers'; interface CreateTestConfigOptions { license: string; @@ -21,6 +22,7 @@ interface CreateTestConfigOptions { rejectUnauthorized?: boolean; publicBaseUrl?: boolean; preconfiguredAlertHistoryEsIndex?: boolean; + customizeLocalHostTls?: boolean; } // test.not-enabled is specifically not enabled @@ -49,6 +51,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) ssl = false, rejectUnauthorized = true, preconfiguredAlertHistoryEsIndex = false, + customizeLocalHostTls = false, } = options; return async ({ readConfigFile }: FtrConfigProviderContext) => { @@ -69,7 +72,11 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) ); const proxyPort = - process.env.ALERTING_PROXY_PORT ?? (await getPort({ port: getPort.makeRange(6200, 6300) })); + process.env.ALERTING_PROXY_PORT ?? (await getPort({ port: getPort.makeRange(6200, 6299) })); + + // Create URLs of identical simple webhook servers using TLS, but we'll + // create custom host settings for them below. + const tlsWebhookServers = await getTlsWebhookServerUrls(6300, 6399); // If testing with proxy, also test proxyOnlyHosts for this proxy; // all the actions are assumed to be acccessing localhost anyway. @@ -89,6 +96,32 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) `--xpack.actions.proxyBypassHosts=${JSON.stringify(proxyHosts)}`, ]; + // set up custom host settings for webhook ports; don't set one for noCustom + const customHostSettingsValue = [ + { + url: tlsWebhookServers.rejectUnauthorizedFalse, + tls: { + rejectUnauthorized: false, + }, + }, + { + url: tlsWebhookServers.rejectUnauthorizedTrue, + tls: { + rejectUnauthorized: true, + }, + }, + { + url: tlsWebhookServers.caFile, + tls: { + rejectUnauthorized: true, + certificateAuthoritiesFiles: [CA_CERT_PATH], + }, + }, + ]; + const customHostSettings = customizeLocalHostTls + ? [`--xpack.actions.customHostSettings=${JSON.stringify(customHostSettingsValue)}`] + : []; + return { testFiles: [require.resolve(`../${name}/tests/`)], servers, @@ -119,7 +152,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, `--xpack.actions.rejectUnauthorized=${rejectUnauthorized}`, ...actionsProxyUrl, - + ...customHostSettings, '--xpack.eventLog.logEntries=true', `--xpack.actions.preconfiguredAlertHistoryEsIndex=${preconfiguredAlertHistoryEsIndex}`, `--xpack.actions.preconfigured=${JSON.stringify({ @@ -162,6 +195,34 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) encrypted: 'this-is-also-ignored-and-also-required', }, }, + 'custom.tls.noCustom': { + actionTypeId: '.webhook', + name: `${tlsWebhookServers.noCustom}`, + config: { + url: tlsWebhookServers.noCustom, + }, + }, + 'custom.tls.rejectUnauthorizedFalse': { + actionTypeId: '.webhook', + name: `${tlsWebhookServers.rejectUnauthorizedFalse}`, + config: { + url: tlsWebhookServers.rejectUnauthorizedFalse, + }, + }, + 'custom.tls.rejectUnauthorizedTrue': { + actionTypeId: '.webhook', + name: `${tlsWebhookServers.rejectUnauthorizedTrue}`, + config: { + url: tlsWebhookServers.rejectUnauthorizedTrue, + }, + }, + 'custom.tls.caFile': { + actionTypeId: '.webhook', + name: `${tlsWebhookServers.caFile}`, + config: { + url: tlsWebhookServers.caFile, + }, + }, })}`, ...disabledPlugins.map((key) => `--xpack.${key}.enabled=false`), ...plugins.map( diff --git a/x-pack/test/alerting_api_integration/common/lib/get_tls_webhook_servers.ts b/x-pack/test/alerting_api_integration/common/lib/get_tls_webhook_servers.ts new file mode 100644 index 0000000000000..026cf21cb5920 --- /dev/null +++ b/x-pack/test/alerting_api_integration/common/lib/get_tls_webhook_servers.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 fs from 'fs'; +import https from 'https'; +import getPort from 'get-port'; +import { KBN_KEY_PATH, KBN_CERT_PATH } from '@kbn/dev-utils'; + +interface TlsWebhookURLs { + noCustom: string; + rejectUnauthorizedFalse: string; + rejectUnauthorizedTrue: string; + caFile: string; +} + +const ServerCert = fs.readFileSync(KBN_CERT_PATH, 'utf8'); +const ServerKey = fs.readFileSync(KBN_KEY_PATH, 'utf8'); + +export async function getTlsWebhookServerUrls( + portRangeStart: number, + portRangeEnd: number +): Promise { + let port: number; + + port = await getPort({ port: getPort.makeRange(portRangeStart, portRangeEnd) }); + const noCustom = `https://localhost:${port}`; + + port = await getPort({ port: getPort.makeRange(portRangeStart, portRangeEnd) }); + const rejectUnauthorizedFalse = `https://localhost:${port}`; + + port = await getPort({ port: getPort.makeRange(portRangeStart, portRangeEnd) }); + const rejectUnauthorizedTrue = `https://localhost:${port}`; + + port = await getPort({ port: getPort.makeRange(portRangeStart, portRangeEnd) }); + const caFile = `https://localhost:${port}`; + + return { + noCustom, + rejectUnauthorizedFalse, + rejectUnauthorizedTrue, + caFile, + }; +} + +export async function createTlsWebhookServer(port: string): Promise { + const httpsOptions = { + cert: ServerCert, + key: ServerKey, + }; + + const server = https.createServer(httpsOptions, async (req, res) => { + if (req.method === 'POST' || req.method === 'PUT') { + const allRead = new Promise((resolve) => { + req.on('data', (chunk) => {}); + req.on('end', () => resolve(null)); + }); + await allRead; + } + + res.writeHead(200); + res.end('https: just testing that a connection could be made'); + }); + const listening = new Promise((resolve) => { + server.listen(port, () => { + resolve(null); + }); + }); + await listening; + + // let node exit even if we don't close this server + server.unref(); + + return server; +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts index 059ef59fc614a..9a3a78342c5aa 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/get_all.ts @@ -60,7 +60,13 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { case 'space_1_all at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); - expect(response.body).to.eql([ + + // the custom tls connectors have dynamic ports, so remove them before + // comparing to what we expect + const nonCustomTlsConnectors = response.body.filter( + (conn: { id: string }) => !conn.id.startsWith('custom.tls.') + ); + expect(nonCustomTlsConnectors).to.eql([ { id: createdAction.id, is_preconfigured: false, @@ -168,7 +174,13 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { case 'space_1_all at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); - expect(response.body).to.eql([ + + // the custom tls connectors have dynamic ports, so remove them before + // comparing to what we expect + const nonCustomTlsConnectors = response.body.filter( + (conn: { id: string }) => !conn.id.startsWith('custom.tls.') + ); + expect(nonCustomTlsConnectors).to.eql([ { id: createdAction.id, is_preconfigured: false, @@ -252,7 +264,13 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { case 'global_read at space1': case 'superuser at space1': expect(response.statusCode).to.eql(200); - expect(response.body).to.eql([ + + // the custom tls connectors have dynamic ports, so remove them before + // comparing to what we expect + const nonCustomTlsConnectors = response.body.filter( + (conn: { id: string }) => !conn.id.startsWith('custom.tls.') + ); + expect(nonCustomTlsConnectors).to.eql([ { id: 'preconfigured-es-index-action', is_preconfigured: true, diff --git a/x-pack/test/alerting_api_integration/spaces_only/config.ts b/x-pack/test/alerting_api_integration/spaces_only/config.ts index 49d5f52869b89..3b3a15b6d62e4 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/config.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/config.ts @@ -13,5 +13,6 @@ export default createTestConfig('spaces_only', { license: 'trial', enableActionsProxy: false, rejectUnauthorized: false, + customizeLocalHostTls: true, preconfiguredAlertHistoryEsIndex: true, }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/webhook.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/webhook.ts index 8ef573a3ae2c3..4af33136cd42c 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/webhook.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/webhook.ts @@ -15,6 +15,7 @@ import { getWebhookServer, getHttpsWebhookServer, } from '../../../../common/fixtures/plugins/actions_simulators/server/plugin'; +import { createTlsWebhookServer } from '../../../../common/lib/get_tls_webhook_servers'; // eslint-disable-next-line import/no-default-export export default function webhookTest({ getService }: FtrProviderContext) { @@ -47,6 +48,19 @@ export default function webhookTest({ getService }: FtrProviderContext) { return createdAction.id; } + async function getPortOfConnector(connectorId: string): Promise { + const response = await supertest.get(`/api/actions/connectors`).expect(200); + const connector = response.body.find((conn: { id: string }) => conn.id === connectorId); + if (connector === undefined) { + throw new Error(`unable to find connector with id ${connectorId}`); + } + + // server URL is the connector name + const url = connector.name; + const parsedUrl = new URL(url); + return parsedUrl.port; + } + describe('webhook action', () => { describe('with http endpoint', () => { let webhookSimulatorURL: string = ''; @@ -108,5 +122,80 @@ export default function webhookTest({ getService }: FtrProviderContext) { webhookServer.close(); }); }); + + describe('tls customization', () => { + it('should handle the xpack.actions.rejectUnauthorized: false', async () => { + const connectorId = 'custom.tls.noCustom'; + const port = await getPortOfConnector(connectorId); + const server = await createTlsWebhookServer(port); + const { status, body } = await supertest + .post(`/api/actions/connector/${connectorId}/_execute`) + .set('kbn-xsrf', 'test') + .send({ + params: { + body: 'foo', + }, + }); + expect(status).to.eql(200); + server.close(); + + expect(body.status).to.eql('ok'); + }); + + it('should handle the customized rejectUnauthorized: false', async () => { + const connectorId = 'custom.tls.rejectUnauthorizedFalse'; + const port = await getPortOfConnector(connectorId); + const server = await createTlsWebhookServer(port); + const { status, body } = await supertest + .post(`/api/actions/connector/custom.tls.rejectUnauthorizedFalse/_execute`) + .set('kbn-xsrf', 'test') + .send({ + params: { + body: 'foo', + }, + }); + expect(status).to.eql(200); + server.close(); + + expect(body.status).to.eql('ok'); + }); + + it('should handle the customized rejectUnauthorized: true', async () => { + const connectorId = 'custom.tls.rejectUnauthorizedTrue'; + const port = await getPortOfConnector(connectorId); + const server = await createTlsWebhookServer(port); + const { status, body } = await supertest + .post(`/api/actions/connector/custom.tls.rejectUnauthorizedTrue/_execute`) + .set('kbn-xsrf', 'test') + .send({ + params: { + body: 'foo', + }, + }); + expect(status).to.eql(200); + server.close(); + + expect(body.status).to.eql('error'); + expect(body.service_message.indexOf('certificate')).to.be.greaterThan(0); + }); + + it('should handle the customized ca file', async () => { + const connectorId = 'custom.tls.caFile'; + const port = await getPortOfConnector(connectorId); + const server = await createTlsWebhookServer(port); + const { status, body } = await supertest + .post(`/api/actions/connector/custom.tls.caFile/_execute`) + .set('kbn-xsrf', 'test') + .send({ + params: { + body: 'foo', + }, + }); + expect(status).to.eql(200); + server.close(); + + expect(body.status).to.eql('ok'); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts index 28abd0b79c57c..e7f500f2771e3 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts @@ -5,6 +5,7 @@ * 2.0. */ +import expect from '@kbn/expect'; import { Spaces } from '../../scenarios'; import { getUrlPrefix, ObjectRemover } from '../../../common/lib'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; @@ -35,7 +36,17 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { .expect(200); objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); - await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connectors`).expect(200, [ + const { body: connectors } = await supertest + .get(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connectors`) + .expect(200); + + // the custom tls connectors have dynamic ports, so remove them before + // comparing to what we expect + const nonCustomTlsConnectors = connectors.filter( + (conn: { id: string }) => !conn.id.startsWith('custom.tls.') + ); + + expect(nonCustomTlsConnectors).to.eql([ { id: 'preconfigured-alert-history-es-index', name: 'Alert history Elasticsearch index', @@ -102,7 +113,17 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { .expect(200); objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); - await supertest.get(`${getUrlPrefix(Spaces.other.id)}/api/actions/connectors`).expect(200, [ + const { body: connectors } = await supertest + .get(`${getUrlPrefix(Spaces.other.id)}/api/actions/connectors`) + .expect(200); + + // the custom tls connectors have dynamic ports, so remove them before + // comparing to what we expect + const nonCustomTlsConnectors = connectors.filter( + (conn: { id: string }) => !conn.id.startsWith('custom.tls.') + ); + + expect(nonCustomTlsConnectors).to.eql([ { id: 'preconfigured-alert-history-es-index', name: 'Alert history Elasticsearch index', @@ -159,7 +180,17 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { .expect(200); objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); - await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/actions`).expect(200, [ + const { body: connectors } = await supertest + .get(`${getUrlPrefix(Spaces.space1.id)}/api/actions`) + .expect(200); + + // the custom tls connectors have dynamic ports, so remove them before + // comparing to what we expect + const nonCustomTlsConnectors = connectors.filter( + (conn: { id: string }) => !conn.id.startsWith('custom.tls.') + ); + + expect(nonCustomTlsConnectors).to.eql([ { id: 'preconfigured-alert-history-es-index', name: 'Alert history Elasticsearch index', From 9a15accb7ff7d117e82d0c881e8a4ad6f600726d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 28 Apr 2021 16:00:05 -0400 Subject: [PATCH 50/70] [APM] Service overview page fetches data with wrong transaction type (#98657) --- .../public/components/shared/transaction_type_select.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/transaction_type_select.tsx b/x-pack/plugins/apm/public/components/shared/transaction_type_select.tsx index dc071fe93bbbd..9353c37b90728 100644 --- a/x-pack/plugins/apm/public/components/shared/transaction_type_select.tsx +++ b/x-pack/plugins/apm/public/components/shared/transaction_type_select.tsx @@ -10,7 +10,6 @@ import React, { FormEvent, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import styled from 'styled-components'; import { useApmServiceContext } from '../../context/apm_service/use_apm_service_context'; -import { useUrlParams } from '../../context/url_params_context/use_url_params'; import * as urlHelpers from './Links/url_helpers'; // The default transaction type (for non-RUM services) is "request". Set the @@ -21,11 +20,8 @@ const EuiSelectWithWidth = styled(EuiSelect)` `; export function TransactionTypeSelect() { - const { transactionTypes } = useApmServiceContext(); + const { transactionTypes, transactionType } = useApmServiceContext(); const history = useHistory(); - const { - urlParams: { transactionType }, - } = useUrlParams(); const handleChange = useCallback( (event: FormEvent) => { From 9c469feb3befcabc9b3199ca10d9682a5c432986 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Wed, 28 Apr 2021 16:07:37 -0400 Subject: [PATCH 51/70] Allow reserved privileges to coexist with other privileges (#98530) --- x-pack/plugins/security/common/constants.ts | 6 ++++ .../privilege_space_table.test.tsx | 15 +++++++++ .../routes/authorization/roles/get.test.ts | 2 +- .../authorization/roles/get_all.test.ts | 29 ++++++++++++++--- .../roles/model/elasticsearch_role.ts | 31 ++++++++++--------- 5 files changed, 62 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/security/common/constants.ts b/x-pack/plugins/security/common/constants.ts index ef83230fc2aba..0ff04e4f731d0 100644 --- a/x-pack/plugins/security/common/constants.ts +++ b/x-pack/plugins/security/common/constants.ts @@ -17,6 +17,12 @@ export const UNKNOWN_SPACE = '?'; export const GLOBAL_RESOURCE = '*'; export const APPLICATION_PREFIX = 'kibana-'; + +/** + * Reserved application privileges are always assigned to this "wildcard" application. + * This allows them to be applied to any Kibana "tenant" (`kibana.index`). Since reserved privileges are always assigned to reserved (built-in) roles, + * it's not possible to know the tenant ahead of time. + */ export const RESERVED_PRIVILEGES_APPLICATION_WILDCARD = 'kibana-*'; /** diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx index 4c657294c965c..6f00df3a4ee7b 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx @@ -742,6 +742,21 @@ describe('global base read', () => { }); }); +describe('global and reserved', () => { + it('base all, reserved_foo', () => { + const props = buildProps([ + { spaces: ['*'], base: ['all'], feature: {} }, + { spaces: ['*'], base: [], feature: {}, _reserved: ['foo'] }, + ]); + const component = mountWithIntl(); + const actualTable = getTableFromComponent(component); + expect(actualTable).toEqual([ + { spaces: ['*'], privileges: { summary: 'Foo', overridden: false } }, + { spaces: ['*'], privileges: { summary: 'All', overridden: false } }, + ]); + }); +}); + describe('global normal feature privilege all', () => { describe('default and marketing space', () => { it('base all', () => { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts index 24366a250cf11..d2385adc99162 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts @@ -285,7 +285,7 @@ describe('GET role', () => { indices: [], applications: [ { - application, + application: reservedPrivilegesApplicationWildcard, privileges: ['reserved_customApplication1', 'reserved_customApplication2'], resources: ['*'], }, diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts index d490153b30394..09262d7cbbadd 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts @@ -283,7 +283,7 @@ describe('GET all roles', () => { indices: [], applications: [ { - application, + application: reservedPrivilegesApplicationWildcard, privileges: ['reserved_customApplication1', 'reserved_customApplication2'], resources: ['*'], }, @@ -1030,7 +1030,7 @@ describe('GET all roles', () => { ); getRolesTest( - `reserved privilege assigned with a feature privilege returns empty kibana section with _transform_error set to ['kibana']`, + `reserved privilege assigned with a feature privilege returns populated kibana section`, { apiResponse: async () => ({ first_role: { @@ -1039,7 +1039,12 @@ describe('GET all roles', () => { applications: [ { application, - privileges: ['reserved_foo', 'feature_foo.foo-privilege-1'], + privileges: ['feature_foo.foo-privilege-1'], + resources: ['*'], + }, + { + application: reservedPrivilegesApplicationWildcard, + privileges: ['reserved_foo'], resources: ['*'], }, ], @@ -1068,8 +1073,22 @@ describe('GET all roles', () => { indices: [], run_as: [], }, - kibana: [], - _transform_error: ['kibana'], + kibana: [ + { + base: [], + feature: { + foo: ['foo-privilege-1'], + }, + spaces: ['*'], + }, + { + base: [], + feature: {}, + _reserved: ['foo'], + spaces: ['*'], + }, + ], + _transform_error: [], _unrecognized_applications: [], }, ], diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts index 74a035cdd0cb6..fa119ca704753 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/elasticsearch_role.ts @@ -83,13 +83,13 @@ function transformRoleApplicationsToKibanaPrivileges( }; } - // if space privilege assigned globally, we can't transform these + // if there is a reserved privilege assigned to an application other than the reserved privileges application wildcard, we won't transform these. if ( roleKibanaApplications.some( (entry) => - entry.resources.includes(GLOBAL_RESOURCE) && + entry.application !== RESERVED_PRIVILEGES_APPLICATION_WILDCARD && entry.privileges.some((privilege) => - PrivilegeSerializer.isSerializedSpaceBasePrivilege(privilege) + PrivilegeSerializer.isSerializedReservedPrivilege(privilege) ) ) ) { @@ -98,15 +98,13 @@ function transformRoleApplicationsToKibanaPrivileges( }; } - // if global base or reserved privilege assigned at a space, we can't transform these + // if space privilege assigned globally, we can't transform these if ( roleKibanaApplications.some( (entry) => - !entry.resources.includes(GLOBAL_RESOURCE) && - entry.privileges.some( - (privilege) => - PrivilegeSerializer.isSerializedGlobalBasePrivilege(privilege) || - PrivilegeSerializer.isSerializedReservedPrivilege(privilege) + entry.resources.includes(GLOBAL_RESOURCE) && + entry.privileges.some((privilege) => + PrivilegeSerializer.isSerializedSpaceBasePrivilege(privilege) ) ) ) { @@ -115,15 +113,15 @@ function transformRoleApplicationsToKibanaPrivileges( }; } - // if reserved privilege assigned with feature or base privileges, we won't transform these + // if global base or reserved privilege assigned at a space, we can't transform these if ( roleKibanaApplications.some( (entry) => - entry.privileges.some((privilege) => - PrivilegeSerializer.isSerializedReservedPrivilege(privilege) - ) && + !entry.resources.includes(GLOBAL_RESOURCE) && entry.privileges.some( - (privilege) => !PrivilegeSerializer.isSerializedReservedPrivilege(privilege) + (privilege) => + PrivilegeSerializer.isSerializedGlobalBasePrivilege(privilege) || + PrivilegeSerializer.isSerializedReservedPrivilege(privilege) ) ) ) { @@ -163,7 +161,10 @@ function transformRoleApplicationsToKibanaPrivileges( }; } - const allResources = roleKibanaApplications.map((entry) => entry.resources).flat(); + const allResources = roleKibanaApplications + .filter((entry) => entry.application !== RESERVED_PRIVILEGES_APPLICATION_WILDCARD) + .flatMap((entry) => entry.resources); + // if we have improperly formatted resource entries, we can't transform these if ( allResources.some( From 40fddce405fcbe62ed9aed115b1f5e3d5b0bbfeb Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 28 Apr 2021 16:05:52 -0600 Subject: [PATCH 52/70] [Maps] use index_exists route instead of /api/index_management/indices (#98479) * [Maps] use index_exists route instead of /api/index_management/indices * fix functional test * add retry and correct permissions to fix functional tests * fix upload functional test --- .../plugins/file_upload/public/api/index.ts | 18 +++-- .../geojson_upload_form.tsx | 20 ++++- .../geojson_upload_form/index_name_form.tsx | 29 +++++++- .../components/json_upload_and_parse.tsx | 8 +- .../public/lazy_load_bundle/index.ts | 3 +- .../public/util/indexing_service.ts | 73 ------------------- ...ce.test.ts => validate_index_name.test.ts} | 8 +- .../file_upload/public/validate_index_name.ts | 45 ++++++++++++ x-pack/plugins/file_upload/server/routes.ts | 2 +- .../layers/file_upload_wizard/wizard.tsx | 24 ++---- .../import_geojson/add_layer_import_panel.js | 31 ++++---- .../import_geojson/file_indexing_panel.js | 5 ++ 12 files changed, 143 insertions(+), 123 deletions(-) delete mode 100644 x-pack/plugins/file_upload/public/util/indexing_service.ts rename x-pack/plugins/file_upload/public/{util/indexing_service.test.ts => validate_index_name.test.ts} (88%) create mode 100644 x-pack/plugins/file_upload/public/validate_index_name.ts diff --git a/x-pack/plugins/file_upload/public/api/index.ts b/x-pack/plugins/file_upload/public/api/index.ts index 86b2d37967daa..c2520547ddad9 100644 --- a/x-pack/plugins/file_upload/public/api/index.ts +++ b/x-pack/plugins/file_upload/public/api/index.ts @@ -92,13 +92,17 @@ export async function checkIndexExists( ): Promise { const body = JSON.stringify({ index }); const fileUploadModules = await lazyLoadModules(); - const { exists } = await fileUploadModules.getHttp().fetch<{ exists: boolean }>({ - path: `/internal/file_upload/index_exists`, - method: 'POST', - body, - query: params, - }); - return exists; + try { + const { exists } = await fileUploadModules.getHttp().fetch<{ exists: boolean }>({ + path: `/internal/file_upload/index_exists`, + method: 'POST', + body, + query: params, + }); + return exists; + } catch (error) { + return false; + } } export async function getTimeFieldRange(index: string, query: unknown, timeFieldName?: string) { diff --git a/x-pack/plugins/file_upload/public/components/geojson_upload_form/geojson_upload_form.tsx b/x-pack/plugins/file_upload/public/components/geojson_upload_form/geojson_upload_form.tsx index 65866243a3e47..ddb0e7d9b2b22 100644 --- a/x-pack/plugins/file_upload/public/components/geojson_upload_form/geojson_upload_form.tsx +++ b/x-pack/plugins/file_upload/public/components/geojson_upload_form/geojson_upload_form.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { GeoJsonFilePicker, OnFileSelectParameters } from './geojson_file_picker'; import { ES_FIELD_TYPES } from '../../../../../../src/plugins/data/public'; import { IndexNameForm } from './index_name_form'; -import { validateIndexName } from '../../util/indexing_service'; +import { validateIndexName } from '../../validate_index_name'; const GEO_FIELD_TYPE_OPTIONS = [ { @@ -32,6 +32,8 @@ interface Props { onFileSelect: (onFileSelectParameters: OnFileSelectParameters) => void; onGeoFieldTypeSelect: (geoFieldType: ES_FIELD_TYPES.GEO_POINT | ES_FIELD_TYPES.GEO_SHAPE) => void; onIndexNameChange: (name: string, error?: string) => void; + onIndexNameValidationStart: () => void; + onIndexNameValidationEnd: () => void; } interface State { @@ -40,11 +42,20 @@ interface State { } export class GeoJsonUploadForm extends Component { + private _isMounted = false; state: State = { hasFile: false, isPointsOnly: false, }; + componentDidMount() { + this._isMounted = true; + } + + componentWillUnmount() { + this._isMounted = false; + } + _onFileSelect = async (onFileSelectParameters: OnFileSelectParameters) => { this.setState({ hasFile: true, @@ -53,7 +64,12 @@ export class GeoJsonUploadForm extends Component { this.props.onFileSelect(onFileSelectParameters); + this.props.onIndexNameValidationStart(); const indexNameError = await validateIndexName(onFileSelectParameters.indexName); + if (!this._isMounted) { + return; + } + this.props.onIndexNameValidationEnd(); this.props.onIndexNameChange(onFileSelectParameters.indexName, indexNameError); const geoFieldType = @@ -107,6 +123,8 @@ export class GeoJsonUploadForm extends Component { indexName={this.props.indexName} indexNameError={this.props.indexNameError} onIndexNameChange={this.props.onIndexNameChange} + onIndexNameValidationStart={this.props.onIndexNameValidationStart} + onIndexNameValidationEnd={this.props.onIndexNameValidationEnd} /> ) : null} diff --git a/x-pack/plugins/file_upload/public/components/geojson_upload_form/index_name_form.tsx b/x-pack/plugins/file_upload/public/components/geojson_upload_form/index_name_form.tsx index a6e83cfa6f3ab..0a70111e76b23 100644 --- a/x-pack/plugins/file_upload/public/components/geojson_upload_form/index_name_form.tsx +++ b/x-pack/plugins/file_upload/public/components/geojson_upload_form/index_name_form.tsx @@ -5,23 +5,46 @@ * 2.0. */ +import _ from 'lodash'; import React, { ChangeEvent, Component } from 'react'; import { EuiFormRow, EuiFieldText, EuiCallOut, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { validateIndexName } from '../../util/indexing_service'; +import { validateIndexName } from '../../validate_index_name'; export interface Props { indexName: string; indexNameError?: string; onIndexNameChange: (name: string, error?: string) => void; + onIndexNameValidationStart: () => void; + onIndexNameValidationEnd: () => void; } export class IndexNameForm extends Component { - _onIndexNameChange = async (event: ChangeEvent) => { + private _isMounted = false; + + componentDidMount() { + this._isMounted = true; + } + + componentWillUnmount() { + this._isMounted = false; + } + + _onIndexNameChange = (event: ChangeEvent) => { const indexName = event.target.value; + this.props.onIndexNameChange(indexName); + this._validateIndexName(indexName); + this.props.onIndexNameValidationStart(); + }; + + _validateIndexName = _.debounce(async (indexName: string) => { const indexNameError = await validateIndexName(indexName); + if (!this._isMounted || indexName !== this.props.indexName) { + return; + } + this.props.onIndexNameValidationEnd(); this.props.onIndexNameChange(indexName, indexNameError); - }; + }, 500); render() { const errors = [...(this.props.indexNameError ? [this.props.indexNameError] : [])]; diff --git a/x-pack/plugins/file_upload/public/components/json_upload_and_parse.tsx b/x-pack/plugins/file_upload/public/components/json_upload_and_parse.tsx index 5863b18d0cea0..28e99e7ffb18b 100644 --- a/x-pack/plugins/file_upload/public/components/json_upload_and_parse.tsx +++ b/x-pack/plugins/file_upload/public/components/json_upload_and_parse.tsx @@ -274,7 +274,11 @@ export class JsonUploadAndParse extends Component ); } diff --git a/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts b/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts index c2bc36e3cc450..b0f1b98a9ae72 100644 --- a/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts +++ b/x-pack/plugins/file_upload/public/lazy_load_bundle/index.ts @@ -24,7 +24,8 @@ export interface FileUploadComponentProps { isIndexingTriggered: boolean; onFileSelect: (geojsonFile: FeatureCollection, name: string, previewCoverage: number) => void; onFileClear: () => void; - onIndexReady: (indexReady: boolean) => void; + enableImportBtn: () => void; + disableImportBtn: () => void; onUploadComplete: (results: FileUploadGeoResults) => void; onUploadError: () => void; } diff --git a/x-pack/plugins/file_upload/public/util/indexing_service.ts b/x-pack/plugins/file_upload/public/util/indexing_service.ts deleted file mode 100644 index 4dcff3dbe7f0e..0000000000000 --- a/x-pack/plugins/file_upload/public/util/indexing_service.ts +++ /dev/null @@ -1,73 +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 _ from 'lodash'; -import { i18n } from '@kbn/i18n'; -import { getIndexPatternService, getHttp } from '../kibana_services'; - -export const getExistingIndexNames = _.debounce( - async () => { - let indexes; - try { - indexes = await getHttp().fetch({ - path: `/api/index_management/indices`, - method: 'GET', - }); - } catch (e) { - // Log to console. Further diagnostics can be made in network request - // eslint-disable-next-line no-console - console.error(e); - } - return indexes ? indexes.map(({ name }: { name: string }) => name) : []; - }, - 10000, - { leading: true } -); - -export function checkIndexPatternValid(name: string) { - const byteLength = encodeURI(name).split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./).length - 1; - const reg = new RegExp('[\\\\/*?"<>|\\s,#]+'); - const indexPatternInvalid = - byteLength > 255 || // name can't be greater than 255 bytes - name !== name.toLowerCase() || // name should be lowercase - name === '.' || - name === '..' || // name can't be . or .. - name.match(/^[-_+]/) !== null || // name can't start with these chars - name.match(reg) !== null; // name can't contain these chars - return !indexPatternInvalid; -} - -export const validateIndexName = async (indexName: string) => { - if (!checkIndexPatternValid(indexName)) { - return i18n.translate( - 'xpack.fileUpload.util.indexingService.indexNameContainsIllegalCharactersErrorMessage', - { - defaultMessage: 'Index name contains illegal characters.', - } - ); - } - - const indexNames = await getExistingIndexNames(); - const indexPatternNames = await getIndexPatternService().getTitles(); - let indexNameError; - if (indexNames.includes(indexName)) { - indexNameError = i18n.translate( - 'xpack.fileUpload.util.indexingService.indexNameAlreadyExistsErrorMessage', - { - defaultMessage: 'Index name already exists.', - } - ); - } else if (indexPatternNames.includes(indexName)) { - indexNameError = i18n.translate( - 'xpack.fileUpload.util.indexingService.indexPatternAlreadyExistsErrorMessage', - { - defaultMessage: 'Index pattern already exists.', - } - ); - } - return indexNameError; -}; diff --git a/x-pack/plugins/file_upload/public/util/indexing_service.test.ts b/x-pack/plugins/file_upload/public/validate_index_name.test.ts similarity index 88% rename from x-pack/plugins/file_upload/public/util/indexing_service.test.ts rename to x-pack/plugins/file_upload/public/validate_index_name.test.ts index b8dfde9ccdc48..7422ced974e37 100644 --- a/x-pack/plugins/file_upload/public/util/indexing_service.test.ts +++ b/x-pack/plugins/file_upload/public/validate_index_name.test.ts @@ -5,12 +5,10 @@ * 2.0. */ -// Not all index pattern dependencies are avab. in jest context, -// prevent unrelated import errors by mocking kibana services -jest.mock('../kibana_services', () => {}); -import { checkIndexPatternValid } from './indexing_service'; +jest.mock('./kibana_services', () => {}); +import { checkIndexPatternValid } from './validate_index_name'; -describe('indexing_service', () => { +describe('checkIndexPatternValid', () => { const validNames = [ 'lowercaseletters', // Lowercase only '123', // Cannot include \, /, *, ?, ", <, >, |, " " (space character), , (comma), # diff --git a/x-pack/plugins/file_upload/public/validate_index_name.ts b/x-pack/plugins/file_upload/public/validate_index_name.ts new file mode 100644 index 0000000000000..cd190188b6a63 --- /dev/null +++ b/x-pack/plugins/file_upload/public/validate_index_name.ts @@ -0,0 +1,45 @@ +/* + * 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 { getIndexPatternService } from './kibana_services'; +import { checkIndexExists } from './api'; + +export function checkIndexPatternValid(name: string) { + const byteLength = encodeURI(name).split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./).length - 1; + const reg = new RegExp('[\\\\/*?"<>|\\s,#]+'); + const indexPatternInvalid = + byteLength > 255 || // name can't be greater than 255 bytes + name !== name.toLowerCase() || // name should be lowercase + name === '.' || + name === '..' || // name can't be . or .. + name.match(/^[-_+]/) !== null || // name can't start with these chars + name.match(reg) !== null; // name can't contain these chars + return !indexPatternInvalid; +} + +export const validateIndexName = async (indexName: string) => { + if (!checkIndexPatternValid(indexName)) { + return i18n.translate('xpack.fileUpload.indexNameContainsIllegalCharactersErrorMessage', { + defaultMessage: 'Index name contains illegal characters.', + }); + } + + const indexPatternNames = await getIndexPatternService().getTitles(); + if (indexPatternNames.includes(indexName)) { + return i18n.translate('xpack.fileUpload.indexPatternAlreadyExistsErrorMessage', { + defaultMessage: 'Index pattern already exists.', + }); + } + + const indexExists = await checkIndexExists(indexName); + if (indexExists) { + return i18n.translate('xpack.fileUpload.indexNameAlreadyExistsErrorMessage', { + defaultMessage: 'Index name already exists.', + }); + } +}; diff --git a/x-pack/plugins/file_upload/server/routes.ts b/x-pack/plugins/file_upload/server/routes.ts index f2e796ec53ce0..8e6651ed891c6 100644 --- a/x-pack/plugins/file_upload/server/routes.ts +++ b/x-pack/plugins/file_upload/server/routes.ts @@ -195,7 +195,7 @@ export function fileUploadRoutes(coreSetup: CoreSetup, logge body: schema.object({ index: schema.string() }), }, options: { - tags: ['access:fileUpload:analyzeFile'], + tags: ['access:fileUpload:import'], }, }, async (context, request, response) => { diff --git a/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx b/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx index 79902cf620511..7d6f6757bef18 100644 --- a/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx +++ b/x-pack/plugins/maps/public/classes/layers/file_upload_wizard/wizard.tsx @@ -26,14 +26,14 @@ export enum UPLOAD_STEPS { } enum INDEXING_STAGE { - READY = 'READY', + CONFIGURE = 'CONFIGURE', TRIGGERED = 'TRIGGERED', SUCCESS = 'SUCCESS', ERROR = 'ERROR', } interface State { - indexingStage: INDEXING_STAGE | null; + indexingStage: INDEXING_STAGE; fileUploadComponent: React.ComponentType | null; results?: FileUploadGeoResults; } @@ -42,7 +42,7 @@ export class ClientFileCreateSourceEditor extends Component { - if (!this._isMounted) { - return; - } - this.setState({ indexingStage: indexReady ? INDEXING_STAGE.READY : null }); - if (indexReady) { - this.props.enableNextBtn(); - } else { - this.props.disableNextBtn(); - } - }; - render() { if (!this.state.fileUploadComponent) { return null; @@ -181,7 +168,8 @@ export class ClientFileCreateSourceEditor extends Component diff --git a/x-pack/test/functional/apps/maps/import_geojson/add_layer_import_panel.js b/x-pack/test/functional/apps/maps/import_geojson/add_layer_import_panel.js index 7bdaa3898aa47..4b973b9f66edd 100644 --- a/x-pack/test/functional/apps/maps/import_geojson/add_layer_import_panel.js +++ b/x-pack/test/functional/apps/maps/import_geojson/add_layer_import_panel.js @@ -14,10 +14,15 @@ export default function ({ getPageObjects, getService }) { const FILE_LOAD_DIR = 'test_upload_files'; const DEFAULT_LOAD_FILE_NAME = 'point.json'; const security = getService('security'); + const retry = getService('retry'); describe('GeoJSON import layer panel', () => { before(async () => { - await security.testUser.setRoles(['global_maps_all', 'global_index_pattern_management_all']); + await security.testUser.setRoles([ + 'global_maps_all', + 'geoall_data_writer', + 'global_index_pattern_management_all', + ]); await PageObjects.maps.openNewMap(); }); @@ -87,23 +92,23 @@ export default function ({ getPageObjects, getService }) { }); it('should prevent import button from activating unless valid index name provided', async () => { - // Set index to invalid name await PageObjects.maps.setIndexName('NoCapitalLetters'); - // Check button - let importButtonActive = await PageObjects.maps.importFileButtonEnabled(); - expect(importButtonActive).to.be(false); + await retry.try(async () => { + const importButtonActive = await PageObjects.maps.importFileButtonEnabled(); + expect(importButtonActive).to.be(false); + }); - // Set index to valid name await PageObjects.maps.setIndexName('validindexname'); - // Check button - importButtonActive = await PageObjects.maps.importFileButtonEnabled(); - expect(importButtonActive).to.be(true); + await retry.try(async () => { + const importButtonActive = await PageObjects.maps.importFileButtonEnabled(); + expect(importButtonActive).to.be(true); + }); - // Set index back to invalid name await PageObjects.maps.setIndexName('?noquestionmarks?'); - // Check button - importButtonActive = await PageObjects.maps.importFileButtonEnabled(); - expect(importButtonActive).to.be(false); + await retry.try(async () => { + const importButtonActive = await PageObjects.maps.importFileButtonEnabled(); + expect(importButtonActive).to.be(false); + }); }); }); } diff --git a/x-pack/test/functional/apps/maps/import_geojson/file_indexing_panel.js b/x-pack/test/functional/apps/maps/import_geojson/file_indexing_panel.js index a5b376cbb33a5..1ce4ccdcec97f 100644 --- a/x-pack/test/functional/apps/maps/import_geojson/file_indexing_panel.js +++ b/x-pack/test/functional/apps/maps/import_geojson/file_indexing_panel.js @@ -14,6 +14,7 @@ export default function ({ getService, getPageObjects }) { const log = getService('log'); const security = getService('security'); const browser = getService('browser'); + const retry = getService('retry'); const IMPORT_FILE_PREVIEW_NAME = 'Import File'; const FILE_LOAD_DIR = 'test_upload_files'; @@ -32,6 +33,10 @@ export default function ({ getService, getPageObjects }) { const indexName = uuid(); await PageObjects.maps.setIndexName(indexName); + await retry.try(async () => { + const importButtonActive = await PageObjects.maps.importFileButtonEnabled(); + expect(importButtonActive).to.be(true); + }); await PageObjects.maps.clickImportFileButton(); return indexName; } From 22b32c23f2eec0b24c11a054f63c6f6b93b06629 Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Wed, 28 Apr 2021 15:24:07 -0700 Subject: [PATCH 53/70] Bump elastic-apm-node from v3.10.0 to v3.14.0 (#97509) Changelog: https://www.elastic.co/guide/en/apm/agent/nodejs/current/release-notes-3.x.html Notably: - Adds apm.addMetadataFilter(fn) that can be used for PII filtering - Improves communication with APM server to not be pathological if APM server is down for extended period of time and load is high. - Fixes bugs in data for the Dependencies and Service Map in the APM app. - The APM agent now collects cloud metadata. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 131 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 93 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index 52e4aaae665bc..e1966459c97f2 100644 --- a/package.json +++ b/package.json @@ -208,7 +208,7 @@ "deep-freeze-strict": "^1.1.1", "deepmerge": "^4.2.2", "del": "^5.1.0", - "elastic-apm-node": "^3.10.0", + "elastic-apm-node": "^3.14.0", "elasticsearch": "^16.7.0", "execa": "^4.0.2", "exit-hook": "^2.2.0", diff --git a/yarn.lock b/yarn.lock index 9219199f1e753..9998790690ad9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1389,6 +1389,20 @@ version "0.0.0" uid "" +"@elastic/ecs-helpers@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@elastic/ecs-helpers/-/ecs-helpers-1.1.0.tgz#ee7e6f870f75a2222c5d7179b36a628f1db4779e" + integrity sha512-MDLb2aFeGjg46O5mLpdCzT5yOUDnXToJSrco2ShqGIXxNJaM8uJjX+4nd+hRYV4Vex8YJyDtOFEVBldQct6ndg== + dependencies: + fast-json-stringify "^2.4.1" + +"@elastic/ecs-pino-format@^1.1.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@elastic/ecs-pino-format/-/ecs-pino-format-1.1.1.tgz#f996a7a0074155cb6d63499332092bc9c74ac5e4" + integrity sha512-I7SzS0JYA8tdfsw4aTR+33HWWCaU7QY759kzt4sXm+O1waILaUWMzW3C2RL0ihQ66M99t+XMhRrA4cKStkHNXg== + dependencies: + "@elastic/ecs-helpers" "^1.1.0" + "@elastic/elasticsearch@npm:@elastic/elasticsearch-canary@^8.0.0-canary.4": version "8.0.0-canary.4" resolved "https://registry.yarnpkg.com/@elastic/elasticsearch-canary/-/elasticsearch-canary-8.0.0-canary.4.tgz#6f1a592974941baae347eb8c66a2006848349717" @@ -6545,7 +6559,7 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.5.5, ajv@ json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^6.12.5: +ajv@^6.11.0, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -7331,6 +7345,11 @@ atob@^2.1.1, atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +atomic-sleep@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== + attr-accept@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-1.1.3.tgz#48230c79f93790ef2775fcec4f0db0f5db41ca52" @@ -7381,11 +7400,6 @@ available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: dependencies: array-filter "^1.0.0" -await-event@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/await-event/-/await-event-2.1.0.tgz#78e9f92684bae4022f9fa0b5f314a11550f9aa76" - integrity sha1-eOn5JoS65AIvn6C18xShFVD5qnY= - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -11969,48 +11983,47 @@ ejs@^3.1.2, ejs@^3.1.5, ejs@^3.1.6: dependencies: jake "^10.6.1" -elastic-apm-http-client@^9.4.2: - version "9.4.2" - resolved "https://registry.yarnpkg.com/elastic-apm-http-client/-/elastic-apm-http-client-9.4.2.tgz#b479817b13ef38020991ccf1c9af9e335f92314a" - integrity sha512-zhOf0+cIO45tJgvQw3fWjXRWqO2MizCC9cvnQpMH2NNsQItXnZfJilhmiYJr8XYi50FxnlOvaav8koZ6tcObmw== +elastic-apm-http-client@^9.8.0: + version "9.8.0" + resolved "https://registry.yarnpkg.com/elastic-apm-http-client/-/elastic-apm-http-client-9.8.0.tgz#caa738c2663b3ec8521ebede86cc841e4c77863c" + integrity sha512-JrlQbijs4dY8539zH+QNKLqLDCNyNymyy720tDaj+/i5pcwWYz5ipPARAdrKkor56AmKBxib8Fd6KsSWtIYjcA== dependencies: breadth-filter "^2.0.0" container-info "^1.0.1" end-of-stream "^1.4.4" fast-safe-stringify "^2.0.7" fast-stream-to-buffer "^1.0.0" - pump "^3.0.0" + object-filter-sequence "^1.0.0" readable-stream "^3.4.0" stream-chopper "^3.0.1" unicode-byte-truncate "^1.0.0" -elastic-apm-node@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.10.0.tgz#2b061613a2fbeb3bba4e3b87040dab55df1d8583" - integrity sha512-H1DOrpr0CwX88awQqSM4UbHGdfsk7xJ4GM6R1uYuFk1zILX/eozylcm6dYSKirpXwwMLxGSRFTOCaMa8fqiLjQ== +elastic-apm-node@^3.14.0: + version "3.14.0" + resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.14.0.tgz#942d6e86bd9d3710f51f0e43f04965d63c3fefd3" + integrity sha512-B7Xkz6UL44mm+2URdZy2yxpEB2C5CvZLOP3sGpf2h/hepXr4NgrVoRxGqO1F2b2wCB48smPv4a3v35b396VSwA== dependencies: + "@elastic/ecs-pino-format" "^1.1.0" after-all-results "^2.0.0" async-value-promise "^1.1.1" basic-auth "^2.0.1" - console-log-level "^1.4.1" cookie "^0.4.0" core-util-is "^1.0.2" - elastic-apm-http-client "^9.4.2" + elastic-apm-http-client "^9.8.0" end-of-stream "^1.4.4" error-stack-parser "^2.0.6" escape-string-regexp "^4.0.0" fast-safe-stringify "^2.0.7" http-headers "^3.0.2" - http-request-to-url "^1.0.0" is-native "^1.0.1" measured-reporting "^1.51.1" monitor-event-loop-delay "^1.0.0" object-filter-sequence "^1.0.0" object-identity-map "^1.0.2" original-url "^1.2.3" + pino "^6.11.2" read-pkg-up "^7.0.1" relative-microtime "^2.0.0" - require-ancestors "^1.0.0" require-in-the-middle "^5.0.3" semver "^6.3.0" set-cookie-serde "^1.0.0" @@ -13246,6 +13259,16 @@ fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@~2.1.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-json-stringify@^2.4.1: + version "2.6.0" + resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-2.6.0.tgz#3dcb4835b63d4e17dbd17411594aa63df8c0f95b" + integrity sha512-xTZtZRopWp2Aun7sGX2EB2mFw4bMQ+xnR8BmD5Rn4K0hKXGkbcZAzTtxEX0P4KNaNx1RAwvf+FESfuM0+F4WZg== + dependencies: + ajv "^6.11.0" + deepmerge "^4.2.2" + rfdc "^1.2.0" + string-similarity "^4.0.1" + fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -13256,6 +13279,11 @@ fast-memoize@^2.5.1: resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.5.1.tgz#c3519241e80552ce395e1a32dcdde8d1fd680f5d" integrity sha512-xdmw296PCL01tMOXx9mdJSmWY29jQgxyuZdq0rEHMu+Tpe1eOEtCycoG6chzlcrWsNgpZP7oL8RiQr7+G6Bl6g== +fast-redact@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.0.0.tgz#ac2f9e36c9f4976f5db9fb18c6ffbaf308cf316d" + integrity sha512-a/S/Hp6aoIjx7EmugtzLqXmcNsyFszqbt6qQ99BdG61QjBZF6shNis0BYR6TsZOQ1twYc0FN2Xdhwwbv6+KD0w== + fast-safe-stringify@2.x.x, fast-safe-stringify@^2.0.4, fast-safe-stringify@^2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" @@ -13628,6 +13656,11 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== +flatstr@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/flatstr/-/flatstr-1.0.12.tgz#c2ba6a08173edbb6c9640e3055b95e287ceb5931" + integrity sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw== + flatted@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" @@ -15462,14 +15495,6 @@ http-proxy@^1.17.0, http-proxy@^1.18.1: follow-redirects "^1.0.0" requires-port "^1.0.0" -http-request-to-url@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/http-request-to-url/-/http-request-to-url-1.0.0.tgz#e56b9418f79f29d344fed05cfe2c56ccb8cc79ac" - integrity sha512-YYx0lKXG9+T1fT2q3ZgXLczMI3jW09g9BvIA6L3BG0tFqGm83Ka/+RUZGANRG7Ut/yueD7LPcZQ/+pA5ndNajw== - dependencies: - await-event "^2.1.0" - socket-location "^1.0.0" - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -21329,6 +21354,23 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= +pino-std-serializers@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz#b56487c402d882eb96cd67c257868016b61ad671" + integrity sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg== + +pino@^6.11.2: + version "6.11.3" + resolved "https://registry.yarnpkg.com/pino/-/pino-6.11.3.tgz#0c02eec6029d25e6794fdb6bbea367247d74bc29" + integrity sha512-drPtqkkSf0ufx2gaea3TryFiBHdNIdXKf5LN0hTM82SXI4xVIve2wLwNg92e1MT6m3jASLu6VO7eGY6+mmGeyw== + dependencies: + fast-redact "^3.0.0" + fast-safe-stringify "^2.0.7" + flatstr "^1.0.12" + pino-std-serializers "^3.1.0" + quick-format-unescaped "^4.0.3" + sonic-boom "^1.0.2" + pirates@^4.0.0, pirates@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" @@ -22369,6 +22411,11 @@ queue@6.0.1: dependencies: inherits "~2.0.3" +quick-format-unescaped@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.3.tgz#6d6b66b8207aa2b35eef12be1421bb24c428f652" + integrity sha512-MaL/oqh02mhEo5m5J2rwsVL23Iw2PEaGVHgT2vFt8AAsr0lfvQA5dpXo9TPu0rz7tSBdUPgkbam0j/fj5ZM8yg== + quick-lru@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" @@ -24048,11 +24095,6 @@ request@2.81.0, request@^2.44.0, request@^2.87.0, request@^2.88.0, request@^2.88 tunnel-agent "^0.6.0" uuid "^3.3.2" -require-ancestors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/require-ancestors/-/require-ancestors-1.0.0.tgz#807831f8f8081fb12863da81ddb15c8f2a73a004" - integrity sha512-Nqeo9Gfp0KvnxTixnxLGEbThMAi+YYgnwRoigtOs1Oo3eGBYfqCd3dagq1vBCVVuc1EnIt3Eu1eGemwOOEZozw== - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -24286,6 +24328,11 @@ reusify@^1.0.0: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rfdc@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -25090,13 +25137,6 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^2.0.0" -socket-location@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/socket-location/-/socket-location-1.0.0.tgz#6f0c6f891c9a61c9a750265c14921d12196d266f" - integrity sha512-TwxpRM0pPE/3b24XQGLx8zq2J8kOwTy40FtiNC1KrWvl/Tsf7RYXruE9icecMhQwicXMo/HUJlGap8DNt2cgYw== - dependencies: - await-event "^2.1.0" - sockjs-client@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" @@ -25118,6 +25158,14 @@ sockjs@0.3.20: uuid "^3.4.0" websocket-driver "0.6.5" +sonic-boom@^1.0.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-1.3.0.tgz#5c77c846ce6c395dddf2eb8e8e65f9cc576f2e76" + integrity sha512-4nX6OYvOYr6R76xfQKi6cZpTO3YSWe/vd+QdIfoH0lBy0MnPkeAbb2rRWgmgADkXUeCKPwO1FZAKlAVWAadELw== + dependencies: + atomic-sleep "^1.0.0" + flatstr "^1.0.12" + sort-keys@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" @@ -25702,6 +25750,11 @@ string-replace-loader@^2.2.0: loader-utils "^1.2.3" schema-utils "^1.0.0" +string-similarity@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.3.tgz#ef52d6fc59c8a0fc93b6307fbbc08cc6e18cde21" + integrity sha512-QEwJzNFCqq+5AGImk5z4vbsEPTN/+gtyKfXBVLBcbPBRPNganZGfQnIuf9yJ+GiwSnD65sT8xrw/uwU1Q1WmfQ== + string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" From f690c60517ee023fe076e4a1d253d908f5b7a5ed Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 28 Apr 2021 23:46:33 +0100 Subject: [PATCH 54/70] chore(NA): moving @kbn/dev-utils into bazel (#98496) * chore(NA): moving @kbn/dev-utils into bazel * docs(NA): updated generated plugin list --- .../monorepo-packages.asciidoc | 1 + docs/developer/plugin-list.asciidoc | 2 +- package.json | 2 +- packages/BUILD.bazel | 1 + .../elastic-eslint-config-kibana/package.json | 5 +- packages/kbn-ace/package.json | 3 - packages/kbn-analytics/package.json | 3 - packages/kbn-cli-dev-mode/package.json | 3 +- packages/kbn-config/package.json | 4 - packages/kbn-crypto/package.json | 4 - packages/kbn-dev-utils/BUILD.bazel | 128 ++++++++++++++++++ packages/kbn-dev-utils/package.json | 8 -- .../src/plugin_list/generate_plugin_list.ts | 3 +- packages/kbn-dev-utils/tsconfig.json | 3 +- packages/kbn-docs-utils/package.json | 3 +- packages/kbn-es-archiver/package.json | 1 - packages/kbn-es/package.json | 3 - packages/kbn-i18n/package.json | 3 - packages/kbn-interpreter/package.json | 3 - packages/kbn-monaco/package.json | 3 - packages/kbn-optimizer/package.json | 1 - packages/kbn-plugin-generator/package.json | 3 - packages/kbn-plugin-helpers/package.json | 1 - packages/kbn-pm/package.json | 3 - packages/kbn-storybook/package.json | 3 - packages/kbn-telemetry-tools/package.json | 4 - packages/kbn-test/package.json | 4 - packages/kbn-ui-shared-deps/package.json | 3 - x-pack/package.json | 2 - yarn.lock | 2 +- 30 files changed, 139 insertions(+), 73 deletions(-) create mode 100644 packages/kbn-dev-utils/BUILD.bazel diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc index dcfe317e5c826..fafbca550ae5d 100644 --- a/docs/developer/getting-started/monorepo-packages.asciidoc +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -68,6 +68,7 @@ yarn kbn watch-bazel - @kbn/babel-code-parser - @kbn/babel-preset - @kbn/config-schema +- @kbn/dev-utils - @kbn/expect - @kbn/logging - @kbn/std diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 7d7d2c1246872..6f54e924769b8 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -6,7 +6,7 @@ NOTE: node scripts/build_plugin_list_docs - You can update the template within packages/kbn-dev-utils/target/plugin_list/generate_plugin_list.js + You can update the template within node_modules/@kbn/dev-utils/target/plugin_list/generate_plugin_list.js //// diff --git a/package.json b/package.json index e1966459c97f2..773fd3ac6ad11 100644 --- a/package.json +++ b/package.json @@ -440,7 +440,7 @@ "@kbn/babel-code-parser": "link:bazel-bin/packages/kbn-babel-code-parser/npm_module", "@kbn/babel-preset": "link:bazel-bin/packages/kbn-babel-preset/npm_module", "@kbn/cli-dev-mode": "link:packages/kbn-cli-dev-mode", - "@kbn/dev-utils": "link:packages/kbn-dev-utils", + "@kbn/dev-utils": "link:bazel-bin/packages/kbn-dev-utils/npm_module", "@kbn/docs-utils": "link:packages/kbn-docs-utils", "@kbn/es": "link:packages/kbn-es", "@kbn/es-archiver": "link:packages/kbn-es-archiver", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 2850a377aaf03..d5c9560179c61 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -10,6 +10,7 @@ filegroup( "//packages/kbn-babel-code-parser:build", "//packages/kbn-babel-preset:build", "//packages/kbn-config-schema:build", + "//packages/kbn-dev-utils:build", "//packages/kbn-expect:build", "//packages/kbn-logging:build", "//packages/kbn-std:build", diff --git a/packages/elastic-eslint-config-kibana/package.json b/packages/elastic-eslint-config-kibana/package.json index 71283df00a8dd..5fb485b86fd38 100644 --- a/packages/elastic-eslint-config-kibana/package.json +++ b/packages/elastic-eslint-config-kibana/package.json @@ -16,8 +16,5 @@ "bugs": { "url": "https://github.com/elastic/kibana/tree/master/packages/elastic-eslint-config-kibana" }, - "homepage": "https://github.com/elastic/kibana/tree/master/packages/elastic-eslint-config-kibana", - "dependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils" - } + "homepage": "https://github.com/elastic/kibana/tree/master/packages/elastic-eslint-config-kibana" } \ No newline at end of file diff --git a/packages/kbn-ace/package.json b/packages/kbn-ace/package.json index 5b4b0312aa1ae..30a87dbd1e21b 100644 --- a/packages/kbn-ace/package.json +++ b/packages/kbn-ace/package.json @@ -8,8 +8,5 @@ "scripts": { "build": "node ./scripts/build.js", "kbn:bootstrap": "yarn build --dev" - }, - "devDependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils" } } \ No newline at end of file diff --git a/packages/kbn-analytics/package.json b/packages/kbn-analytics/package.json index 5b9db79febd77..2195de578081e 100644 --- a/packages/kbn-analytics/package.json +++ b/packages/kbn-analytics/package.json @@ -12,8 +12,5 @@ "build": "node scripts/build", "kbn:bootstrap": "node scripts/build --source-maps", "kbn:watch": "node scripts/build --source-maps --watch" - }, - "devDependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils" } } \ No newline at end of file diff --git a/packages/kbn-cli-dev-mode/package.json b/packages/kbn-cli-dev-mode/package.json index 9ffc7f690fd6a..9def59623c938 100644 --- a/packages/kbn-cli-dev-mode/package.json +++ b/packages/kbn-cli-dev-mode/package.json @@ -16,7 +16,6 @@ "dependencies": { "@kbn/config": "link:../kbn-config", "@kbn/server-http-tools": "link:../kbn-server-http-tools", - "@kbn/optimizer": "link:../kbn-optimizer", - "@kbn/dev-utils": "link:../kbn-dev-utils" + "@kbn/optimizer": "link:../kbn-optimizer" } } \ No newline at end of file diff --git a/packages/kbn-config/package.json b/packages/kbn-config/package.json index 90f2a661b91dc..b114cb13933d1 100644 --- a/packages/kbn-config/package.json +++ b/packages/kbn-config/package.json @@ -8,9 +8,5 @@ "scripts": { "build": "../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build" - }, - "devDependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils", - "@kbn/utility-types": "link:../kbn-utility-types" } } \ No newline at end of file diff --git a/packages/kbn-crypto/package.json b/packages/kbn-crypto/package.json index 7e26b96218319..0787427c60b10 100644 --- a/packages/kbn-crypto/package.json +++ b/packages/kbn-crypto/package.json @@ -9,9 +9,5 @@ "build": "../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build", "kbn:watch": "yarn build --watch" - }, - "dependencies": {}, - "devDependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils" } } \ No newline at end of file diff --git a/packages/kbn-dev-utils/BUILD.bazel b/packages/kbn-dev-utils/BUILD.bazel new file mode 100644 index 0000000000000..e3935040240dc --- /dev/null +++ b/packages/kbn-dev-utils/BUILD.bazel @@ -0,0 +1,128 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-dev-utils" +PKG_REQUIRE_NAME = "@kbn/dev-utils" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = [ + "**/*.test.*" + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +filegroup( + name = "certs", + srcs = glob( + [ + "certs/**/*", + ], + exclude = [ + "**/README.md" + ], + ), +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md", + ":certs", + "ci_stats_reporter/package.json", + "stdio/package.json", + "tooling_log/package.json" +] + +SRC_DEPS = [ + "//packages/kbn-expect", + "//packages/kbn-utils", + "@npm//@babel/core", + "@npm//axios", + "@npm//chalk", + "@npm//chance", + "@npm//cheerio", + "@npm//dedent", + "@npm//execa", + "@npm//exit-hook", + "@npm//getopts", + "@npm//globby", + "@npm//jest-styled-components", + "@npm//load-json-file", + "@npm//markdown-it", + "@npm//moment", + "@npm//normalize-path", + "@npm//rxjs", + "@npm//tree-kill", + "@npm//tslib", + "@npm//typescript", + "@npm//vinyl" +] + +TYPES_DEPS = [ + "@npm//@types/babel__core", + "@npm//@types/cheerio", + "@npm//@types/dedent", + "@npm//@types/flot", + "@npm//@types/jest", + "@npm//@types/markdown-it", + "@npm//@types/node", + "@npm//@types/normalize-path", + "@npm//@types/react", + "@npm//@types/testing-library__jest-dom", + "@npm//@types/vinyl" +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = [":tsc"] + DEPS, + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-dev-utils/package.json b/packages/kbn-dev-utils/package.json index 4ce2880afbbda..90c5ef17d1859 100644 --- a/packages/kbn-dev-utils/package.json +++ b/packages/kbn-dev-utils/package.json @@ -5,15 +5,7 @@ "license": "SSPL-1.0 OR Elastic License 2.0", "main": "./target/index.js", "types": "./target/index.d.ts", - "scripts": { - "build": "../../node_modules/.bin/tsc", - "kbn:bootstrap": "yarn build", - "kbn:watch": "yarn build --watch" - }, "kibana": { "devOnly": true - }, - "devDependencies": { - "@kbn/expect": "link:../kbn-expect" } } \ No newline at end of file diff --git a/packages/kbn-dev-utils/src/plugin_list/generate_plugin_list.ts b/packages/kbn-dev-utils/src/plugin_list/generate_plugin_list.ts index b88382c3b0da4..127e2a9904a4f 100644 --- a/packages/kbn-dev-utils/src/plugin_list/generate_plugin_list.ts +++ b/packages/kbn-dev-utils/src/plugin_list/generate_plugin_list.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import Fs from 'fs'; import Path from 'path'; import normalizePath from 'normalize-path'; @@ -49,7 +48,7 @@ NOTE: node scripts/build_plugin_list_docs You can update the template within ${normalizePath( - Path.relative(REPO_ROOT, Fs.realpathSync(Path.resolve(__dirname, __filename))) + Path.relative(REPO_ROOT, Path.resolve(__dirname, __filename)) )} //// diff --git a/packages/kbn-dev-utils/tsconfig.json b/packages/kbn-dev-utils/tsconfig.json index 65536c576b679..5bb7bd0424daf 100644 --- a/packages/kbn-dev-utils/tsconfig.json +++ b/packages/kbn-dev-utils/tsconfig.json @@ -1,12 +1,13 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "incremental": false, + "incremental": true, "outDir": "target", "stripInternal": false, "target": "ES2019", "declaration": true, "declarationMap": true, + "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../packages/kbn-dev-utils/src", "types": [ diff --git a/packages/kbn-docs-utils/package.json b/packages/kbn-docs-utils/package.json index e2db07001b543..6aca554f0f945 100644 --- a/packages/kbn-docs-utils/package.json +++ b/packages/kbn-docs-utils/package.json @@ -13,7 +13,6 @@ "kbn:watch": "../../node_modules/.bin/tsc --watch" }, "dependencies": { - "@kbn/config": "link:../kbn-config", - "@kbn/dev-utils": "link:../kbn-dev-utils" + "@kbn/config": "link:../kbn-config" } } \ No newline at end of file diff --git a/packages/kbn-es-archiver/package.json b/packages/kbn-es-archiver/package.json index 0e4c9884d2c39..c86d94c70d739 100644 --- a/packages/kbn-es-archiver/package.json +++ b/packages/kbn-es-archiver/package.json @@ -13,7 +13,6 @@ "kbn:watch": "rm -rf target && ../../node_modules/.bin/tsc --watch" }, "dependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils", "@kbn/test": "link:../kbn-test" } } \ No newline at end of file diff --git a/packages/kbn-es/package.json b/packages/kbn-es/package.json index f47f042505cad..e7356794b6113 100644 --- a/packages/kbn-es/package.json +++ b/packages/kbn-es/package.json @@ -11,8 +11,5 @@ "build": "node scripts/build", "kbn:bootstrap": "node scripts/build", "kbn:watch": "node scripts/build --watch" - }, - "dependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils" } } \ No newline at end of file diff --git a/packages/kbn-i18n/package.json b/packages/kbn-i18n/package.json index 570110589490b..1f9d21f724ea8 100644 --- a/packages/kbn-i18n/package.json +++ b/packages/kbn-i18n/package.json @@ -10,8 +10,5 @@ "build": "node scripts/build", "kbn:bootstrap": "node scripts/build --source-maps", "kbn:watch": "node scripts/build --watch --source-maps" - }, - "devDependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils" } } \ No newline at end of file diff --git a/packages/kbn-interpreter/package.json b/packages/kbn-interpreter/package.json index 491a7205be210..997fbb0eb8a4f 100644 --- a/packages/kbn-interpreter/package.json +++ b/packages/kbn-interpreter/package.json @@ -11,8 +11,5 @@ }, "dependencies": { "@kbn/i18n": "link:../kbn-i18n" - }, - "devDependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils" } } \ No newline at end of file diff --git a/packages/kbn-monaco/package.json b/packages/kbn-monaco/package.json index f4309e08f5bdb..75f1d74f1c9c9 100644 --- a/packages/kbn-monaco/package.json +++ b/packages/kbn-monaco/package.json @@ -10,9 +10,6 @@ "kbn:bootstrap": "yarn build --dev", "build:antlr4ts": "../../node_modules/antlr4ts-cli/antlr4ts ./src/painless/antlr/painless_lexer.g4 ./src/painless/antlr/painless_parser.g4 && node ./scripts/fix_generated_antlr.js" }, - "devDependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils" - }, "dependencies": { "@kbn/i18n": "link:../kbn-i18n" } diff --git a/packages/kbn-optimizer/package.json b/packages/kbn-optimizer/package.json index 423bba0fd8c7a..f193fcf898a3d 100644 --- a/packages/kbn-optimizer/package.json +++ b/packages/kbn-optimizer/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@kbn/config": "link:../kbn-config", - "@kbn/dev-utils": "link:../kbn-dev-utils", "@kbn/ui-shared-deps": "link:../kbn-ui-shared-deps" } } \ No newline at end of file diff --git a/packages/kbn-plugin-generator/package.json b/packages/kbn-plugin-generator/package.json index ae4dfbc670f19..583085430d915 100644 --- a/packages/kbn-plugin-generator/package.json +++ b/packages/kbn-plugin-generator/package.json @@ -8,8 +8,5 @@ "scripts": { "kbn:bootstrap": "node scripts/build", "kbn:watch": "node scripts/build --watch" - }, - "dependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils" } } \ No newline at end of file diff --git a/packages/kbn-plugin-helpers/package.json b/packages/kbn-plugin-helpers/package.json index 6b9dd4d51baf9..2d642d9ede13b 100644 --- a/packages/kbn-plugin-helpers/package.json +++ b/packages/kbn-plugin-helpers/package.json @@ -17,7 +17,6 @@ "kbn:watch": "../../node_modules/.bin/tsc --watch" }, "dependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils", "@kbn/optimizer": "link:../kbn-optimizer" } } \ No newline at end of file diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json index c46906112b2e2..72061c9625b09 100644 --- a/packages/kbn-pm/package.json +++ b/packages/kbn-pm/package.json @@ -11,8 +11,5 @@ "build": "../../node_modules/.bin/webpack", "kbn:watch": "../../node_modules/.bin/webpack --watch", "prettier": "../../node_modules/.bin/prettier --write './src/**/*.ts'" - }, - "devDependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils" } } \ No newline at end of file diff --git a/packages/kbn-storybook/package.json b/packages/kbn-storybook/package.json index fdc7359aab58d..0e70f7c340a90 100644 --- a/packages/kbn-storybook/package.json +++ b/packages/kbn-storybook/package.json @@ -12,8 +12,5 @@ "build": "../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build", "watch": "yarn build --watch" - }, - "devDependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils" } } \ No newline at end of file diff --git a/packages/kbn-telemetry-tools/package.json b/packages/kbn-telemetry-tools/package.json index 2ae1f596a1c68..31fac5c043832 100644 --- a/packages/kbn-telemetry-tools/package.json +++ b/packages/kbn-telemetry-tools/package.json @@ -12,9 +12,5 @@ "build": "../../node_modules/.bin/babel src --out-dir target --delete-dir-on-start --extensions .ts --source-maps=inline", "kbn:bootstrap": "yarn build", "kbn:watch": "yarn build --watch" - }, - "devDependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils", - "@kbn/utility-types": "link:../kbn-utility-types" } } \ No newline at end of file diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index 9bf8a01e031cc..15d6ac90b2ebe 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -17,9 +17,5 @@ "@kbn/es": "link:../kbn-es", "@kbn/i18n": "link:../kbn-i18n", "@kbn/optimizer": "link:../kbn-optimizer" - }, - "devDependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils", - "@kbn/expect": "link:../kbn-expect" } } \ No newline at end of file diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 00c6f677cd223..8b08f64ba0f62 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -12,8 +12,5 @@ "@kbn/analytics": "link:../kbn-analytics", "@kbn/i18n": "link:../kbn-i18n", "@kbn/monaco": "link:../kbn-monaco" - }, - "devDependencies": { - "@kbn/dev-utils": "link:../kbn-dev-utils" } } \ No newline at end of file diff --git a/x-pack/package.json b/x-pack/package.json index c09db67483121..129c8d86adecc 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -27,9 +27,7 @@ "yarn": "^1.21.1" }, "devDependencies": { - "@kbn/dev-utils": "link:../packages/kbn-dev-utils", "@kbn/es": "link:../packages/kbn-es", - "@kbn/expect": "link:../packages/kbn-expect", "@kbn/plugin-helpers": "link:../packages/kbn-plugin-helpers", "@kbn/storybook": "link:../packages/kbn-storybook", "@kbn/test": "link:../packages/kbn-test" diff --git a/yarn.lock b/yarn.lock index 9998790690ad9..45b7a0eaada46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2623,7 +2623,7 @@ version "0.0.0" uid "" -"@kbn/dev-utils@link:packages/kbn-dev-utils": +"@kbn/dev-utils@link:bazel-bin/packages/kbn-dev-utils/npm_module": version "0.0.0" uid "" From b94f712f8c60dbdaa5cea2a1aa33ecb3af0b5e48 Mon Sep 17 00:00:00 2001 From: Bryan Clement Date: Wed, 28 Apr 2021 19:54:09 -0700 Subject: [PATCH 55/70] [Asset management] Text updates (#98192) * updated scheduled query activation toggle text and interval header in query group * added id validation for schedule queries * fixed up agent resolution to ignore inactive agents, and properly pull all agents * nixed unused file * more validation for query fields * added status table to the results data tab, added more validation * updated wording * added error notifications for failed queries * pr feedback and cleanup * fix up last hook * use the pluralize macro, removed rbac tags Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../osquery/common/schemas/common/schemas.ts | 10 ++ .../create_action_request_body_schema.ts | 17 ++ .../common/schemas/routes/action/index.ts | 8 + .../action_results/use_action_results.ts | 14 +- .../public/actions/use_action_details.ts | 12 +- .../osquery/public/actions/use_all_actions.ts | 12 +- .../agent_policies/use_agent_policies.ts | 12 +- .../public/agent_policies/use_agent_policy.ts | 12 +- .../osquery/public/agents/use_agent_groups.ts | 12 +- .../public/agents/use_agent_policies.ts | 12 +- .../osquery/public/agents/use_agent_status.ts | 12 +- .../osquery/public/agents/use_all_agents.ts | 12 +- .../public/agents/use_osquery_policies.ts | 27 ++- .../common/hooks/use_osquery_integration.tsx | 12 +- .../osquery/public/common/validations.ts | 17 ++ .../live_queries/agent_results/index.tsx | 2 +- .../public/live_queries/form/index.tsx | 37 +++- .../form/live_query_query_field.tsx | 70 +------- .../osquery/public/queries/edit/tabs.tsx | 2 +- .../public/queries/form/code_editor_field.tsx | 7 +- .../osquery/public/results/results_table.tsx | 164 ++++++++++++++---- .../osquery/public/results/translations.ts | 8 + .../osquery/public/results/use_all_results.ts | 12 +- .../form/add_query_flyout.tsx | 4 + .../form/confirmation_modal.tsx | 2 +- .../form/edit_query_flyout.tsx | 4 + .../form/translations.ts | 12 ++ .../form/validations.ts | 48 +++++ .../scheduled_query_group_queries_table.tsx | 2 +- .../plugins/osquery/public/shared_imports.ts | 1 + .../osquery/server/lib/parse_agent_groups.ts | 86 ++++++--- .../routes/action/create_action_route.ts | 33 ++-- 32 files changed, 532 insertions(+), 163 deletions(-) create mode 100644 x-pack/plugins/osquery/common/schemas/routes/action/create_action_request_body_schema.ts create mode 100644 x-pack/plugins/osquery/common/schemas/routes/action/index.ts create mode 100644 x-pack/plugins/osquery/public/common/validations.ts create mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/form/translations.ts create mode 100644 x-pack/plugins/osquery/public/scheduled_query_groups/form/validations.ts diff --git a/x-pack/plugins/osquery/common/schemas/common/schemas.ts b/x-pack/plugins/osquery/common/schemas/common/schemas.ts index ffcadc7cfea8f..f5d0a357b85b8 100644 --- a/x-pack/plugins/osquery/common/schemas/common/schemas.ts +++ b/x-pack/plugins/osquery/common/schemas/common/schemas.ts @@ -12,6 +12,16 @@ export type Name = t.TypeOf; export const nameOrUndefined = t.union([name, t.undefined]); export type NameOrUndefined = t.TypeOf; +export const agentSelection = t.type({ + agents: t.array(t.string), + allAgentsSelected: t.boolean, + platformsSelected: t.array(t.string), + policiesSelected: t.array(t.string), +}); +export type AgentSelection = t.TypeOf; +export const agentSelectionOrUndefined = t.union([agentSelection, t.undefined]); +export type AgentSelectionOrUndefined = t.TypeOf; + export const description = t.string; export type Description = t.TypeOf; export const descriptionOrUndefined = t.union([description, t.undefined]); diff --git a/x-pack/plugins/osquery/common/schemas/routes/action/create_action_request_body_schema.ts b/x-pack/plugins/osquery/common/schemas/routes/action/create_action_request_body_schema.ts new file mode 100644 index 0000000000000..bcbd528c4e749 --- /dev/null +++ b/x-pack/plugins/osquery/common/schemas/routes/action/create_action_request_body_schema.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; + +import { query, agentSelection } from '../../common/schemas'; + +export const createActionRequestBodySchema = t.type({ + agentSelection, + query, +}); + +export type CreateActionRequestBodySchema = t.OutputOf; diff --git a/x-pack/plugins/osquery/common/schemas/routes/action/index.ts b/x-pack/plugins/osquery/common/schemas/routes/action/index.ts new file mode 100644 index 0000000000000..286aa2e5128b2 --- /dev/null +++ b/x-pack/plugins/osquery/common/schemas/routes/action/index.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 * from './create_action_request_body_schema'; diff --git a/x-pack/plugins/osquery/public/action_results/use_action_results.ts b/x-pack/plugins/osquery/public/action_results/use_action_results.ts index 7cad8ca3fc498..1f6da0b3a2a0e 100644 --- a/x-pack/plugins/osquery/public/action_results/use_action_results.ts +++ b/x-pack/plugins/osquery/public/action_results/use_action_results.ts @@ -8,6 +8,7 @@ import { flatten, reverse, uniqBy } from 'lodash/fp'; import { useQuery } from 'react-query'; +import { i18n } from '@kbn/i18n'; import { createFilter } from '../common/helpers'; import { useKibana } from '../common/lib/kibana'; import { @@ -32,7 +33,7 @@ export interface ResultsArgs { totalCount: number; } -interface UseActionResults { +export interface UseActionResults { actionId: string; activePage: number; agentIds?: string[]; @@ -55,7 +56,10 @@ export const useActionResults = ({ skip = false, isLive = false, }: UseActionResults) => { - const { data } = useKibana().services; + const { + data, + notifications: { toasts }, + } = useKibana().services; return useQuery( ['actionResults', { actionId }], @@ -120,6 +124,12 @@ export const useActionResults = ({ refetchInterval: isLive ? 1000 : false, keepPreviousData: true, enabled: !skip && !!agentIds?.length, + onError: (error: Error) => + toasts.addError(error, { + title: i18n.translate('xpack.osquery.action_results.fetchError', { + defaultMessage: 'Error while fetching action results', + }), + }), } ); }; diff --git a/x-pack/plugins/osquery/public/actions/use_action_details.ts b/x-pack/plugins/osquery/public/actions/use_action_details.ts index 2e5fa79cae992..bb260cd78ca76 100644 --- a/x-pack/plugins/osquery/public/actions/use_action_details.ts +++ b/x-pack/plugins/osquery/public/actions/use_action_details.ts @@ -7,6 +7,7 @@ import { useQuery } from 'react-query'; +import { i18n } from '@kbn/i18n'; import { createFilter } from '../common/helpers'; import { useKibana } from '../common/lib/kibana'; import { @@ -32,7 +33,10 @@ interface UseActionDetails { } export const useActionDetails = ({ actionId, filterQuery, skip = false }: UseActionDetails) => { - const { data } = useKibana().services; + const { + data, + notifications: { toasts }, + } = useKibana().services; return useQuery( ['actionDetails', { actionId, filterQuery }], @@ -57,6 +61,12 @@ export const useActionDetails = ({ actionId, filterQuery, skip = false }: UseAct }, { enabled: !skip, + onError: (error: Error) => + toasts.addError(error, { + title: i18n.translate('xpack.osquery.action_details.fetchError', { + defaultMessage: 'Error while fetching action details', + }), + }), } ); }; diff --git a/x-pack/plugins/osquery/public/actions/use_all_actions.ts b/x-pack/plugins/osquery/public/actions/use_all_actions.ts index a58f45b8e99a2..375d108c4dd8b 100644 --- a/x-pack/plugins/osquery/public/actions/use_all_actions.ts +++ b/x-pack/plugins/osquery/public/actions/use_all_actions.ts @@ -7,6 +7,7 @@ import { useQuery } from 'react-query'; +import { i18n } from '@kbn/i18n'; import { createFilter } from '../common/helpers'; import { useKibana } from '../common/lib/kibana'; import { @@ -47,7 +48,10 @@ export const useAllActions = ({ filterQuery, skip = false, }: UseAllActions) => { - const { data } = useKibana().services; + const { + data, + notifications: { toasts }, + } = useKibana().services; return useQuery( ['actions', { activePage, direction, limit, sortField }], @@ -78,6 +82,12 @@ export const useAllActions = ({ { keepPreviousData: true, enabled: !skip, + onError: (error: Error) => + toasts.addError(error, { + title: i18n.translate('xpack.osquery.all_actions.fetchError', { + defaultMessage: 'Error while fetching actions', + }), + }), } ); }; diff --git a/x-pack/plugins/osquery/public/agent_policies/use_agent_policies.ts b/x-pack/plugins/osquery/public/agent_policies/use_agent_policies.ts index 95323dd23f4d2..d4bd0a1f4277f 100644 --- a/x-pack/plugins/osquery/public/agent_policies/use_agent_policies.ts +++ b/x-pack/plugins/osquery/public/agent_policies/use_agent_policies.ts @@ -7,6 +7,7 @@ import { useQuery } from 'react-query'; +import { i18n } from '@kbn/i18n'; import { useKibana } from '../common/lib/kibana'; import { agentPolicyRouteService, @@ -15,7 +16,10 @@ import { } from '../../../fleet/common'; export const useAgentPolicies = () => { - const { http } = useKibana().services; + const { + http, + notifications: { toasts }, + } = useKibana().services; return useQuery( ['agentPolicies'], @@ -30,6 +34,12 @@ export const useAgentPolicies = () => { placeholderData: [], keepPreviousData: true, select: (response) => response.items, + onError: (error) => + toasts.addError(error as Error, { + title: i18n.translate('xpack.osquery.agent_policies.fetchError', { + defaultMessage: 'Error while fetching agent policies', + }), + }), } ); }; diff --git a/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts b/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts index 5fdc317d3f6f1..e87d8d1c9f28e 100644 --- a/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts +++ b/x-pack/plugins/osquery/public/agent_policies/use_agent_policy.ts @@ -7,6 +7,7 @@ import { useQuery } from 'react-query'; +import { i18n } from '@kbn/i18n'; import { useKibana } from '../common/lib/kibana'; import { agentPolicyRouteService } from '../../../fleet/common'; @@ -16,7 +17,10 @@ interface UseAgentPolicy { } export const useAgentPolicy = ({ policyId, skip }: UseAgentPolicy) => { - const { http } = useKibana().services; + const { + http, + notifications: { toasts }, + } = useKibana().services; return useQuery( ['agentPolicy', { policyId }], @@ -25,6 +29,12 @@ export const useAgentPolicy = ({ policyId, skip }: UseAgentPolicy) => { enabled: !skip, keepPreviousData: true, select: (response) => response.item, + onError: (error: Error) => + toasts.addError(error, { + title: i18n.translate('xpack.osquery.agent_policy_details.fetchError', { + defaultMessage: 'Error while fetching agent policy details', + }), + }), } ); }; diff --git a/x-pack/plugins/osquery/public/agents/use_agent_groups.ts b/x-pack/plugins/osquery/public/agents/use_agent_groups.ts index 0853891f1919d..44737af9d3477 100644 --- a/x-pack/plugins/osquery/public/agents/use_agent_groups.ts +++ b/x-pack/plugins/osquery/public/agents/use_agent_groups.ts @@ -6,6 +6,7 @@ */ import { useState } from 'react'; import { useQuery } from 'react-query'; +import { i18n } from '@kbn/i18n'; import { useKibana } from '../common/lib/kibana'; import { useAgentPolicies } from './use_agent_policies'; @@ -24,7 +25,10 @@ interface UseAgentGroups { } export const useAgentGroups = ({ osqueryPolicies, osqueryPoliciesLoading }: UseAgentGroups) => { - const { data } = useKibana().services; + const { + data, + notifications: { toasts }, + } = useKibana().services; const { agentPoliciesLoading, agentPolicyById } = useAgentPolicies(osqueryPolicies); const [platforms, setPlatforms] = useState([]); @@ -96,6 +100,12 @@ export const useAgentGroups = ({ osqueryPolicies, osqueryPoliciesLoading }: UseA }, { enabled: !osqueryPoliciesLoading && !agentPoliciesLoading, + onError: (error) => + toasts.addError(error as Error, { + title: i18n.translate('xpack.osquery.agent_groups.fetchError', { + defaultMessage: 'Error while fetching agent groups', + }), + }), } ); diff --git a/x-pack/plugins/osquery/public/agents/use_agent_policies.ts b/x-pack/plugins/osquery/public/agents/use_agent_policies.ts index c8b3ef064c038..ecb95fff8838e 100644 --- a/x-pack/plugins/osquery/public/agents/use_agent_policies.ts +++ b/x-pack/plugins/osquery/public/agents/use_agent_policies.ts @@ -7,17 +7,27 @@ import { mapKeys } from 'lodash'; import { useQueries, UseQueryResult } from 'react-query'; +import { i18n } from '@kbn/i18n'; import { useKibana } from '../common/lib/kibana'; import { agentPolicyRouteService, GetOneAgentPolicyResponse } from '../../../fleet/common'; export const useAgentPolicies = (policyIds: string[] = []) => { - const { http } = useKibana().services; + const { + http, + notifications: { toasts }, + } = useKibana().services; const agentResponse = useQueries( policyIds.map((policyId) => ({ queryKey: ['agentPolicy', policyId], queryFn: () => http.get(agentPolicyRouteService.getInfoPath(policyId)), enabled: policyIds.length > 0, + onError: (error) => + toasts.addError(error as Error, { + title: i18n.translate('xpack.osquery.action_policy_details.fetchError', { + defaultMessage: 'Error while fetching policy details', + }), + }), })) ) as Array>; diff --git a/x-pack/plugins/osquery/public/agents/use_agent_status.ts b/x-pack/plugins/osquery/public/agents/use_agent_status.ts index c26adb908f6be..4954eb0dc80c4 100644 --- a/x-pack/plugins/osquery/public/agents/use_agent_status.ts +++ b/x-pack/plugins/osquery/public/agents/use_agent_status.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import { useQuery } from 'react-query'; import { GetAgentStatusResponse, agentRouteService } from '../../../fleet/common'; @@ -16,7 +17,10 @@ interface UseAgentStatus { } export const useAgentStatus = ({ policyId, skip }: UseAgentStatus) => { - const { http } = useKibana().services; + const { + http, + notifications: { toasts }, + } = useKibana().services; return useQuery( ['agentStatus', policyId], @@ -34,6 +38,12 @@ export const useAgentStatus = ({ policyId, skip }: UseAgentStatus) => { { enabled: !skip, select: (response) => response.results, + onError: (error) => + toasts.addError(error as Error, { + title: i18n.translate('xpack.osquery.agent_status.fetchError', { + defaultMessage: 'Error while fetching agent status', + }), + }), } ); }; diff --git a/x-pack/plugins/osquery/public/agents/use_all_agents.ts b/x-pack/plugins/osquery/public/agents/use_all_agents.ts index e10bc2a0d9bf6..674deb3b339bd 100644 --- a/x-pack/plugins/osquery/public/agents/use_all_agents.ts +++ b/x-pack/plugins/osquery/public/agents/use_all_agents.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import { useQuery } from 'react-query'; import { GetAgentsResponse, agentRouteService } from '../../../fleet/common'; @@ -27,7 +28,10 @@ export const useAllAgents = ( opts: RequestOptions = { perPage: 9000 } ) => { const { perPage } = opts; - const { http } = useKibana().services; + const { + http, + notifications: { toasts }, + } = useKibana().services; const { isLoading: agentsLoading, data: agentData } = useQuery( ['agents', osqueryPolicies, searchValue, perPage], () => { @@ -52,6 +56,12 @@ export const useAllAgents = ( }, { enabled: !osqueryPoliciesLoading, + onError: (error) => + toasts.addError(error as Error, { + title: i18n.translate('xpack.osquery.agents.fetchError', { + defaultMessage: 'Error while fetching agents', + }), + }), } ); diff --git a/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts b/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts index 2937c57b50a3d..0eb94af73e3a8 100644 --- a/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts +++ b/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts @@ -5,15 +5,21 @@ * 2.0. */ +import { uniq } from 'lodash'; import { useQuery } from 'react-query'; +import { useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; import { useKibana } from '../common/lib/kibana'; import { packagePolicyRouteService, PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../fleet/common'; import { OSQUERY_INTEGRATION_NAME } from '../../common'; export const useOsqueryPolicies = () => { - const { http } = useKibana().services; + const { + http, + notifications: { toasts }, + } = useKibana().services; - const { isLoading: osqueryPoliciesLoading, data: osqueryPolicies } = useQuery( + const { isLoading: osqueryPoliciesLoading, data: osqueryPolicies = [] } = useQuery( ['osqueryPolicies'], () => http.get(packagePolicyRouteService.getListPath(), { @@ -21,8 +27,19 @@ export const useOsqueryPolicies = () => { kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`, }, }), - { select: (data) => data.items.map((p: { policy_id: string }) => p.policy_id) } + { + select: (response) => + uniq(response.items.map((p: { policy_id: string }) => p.policy_id)), + onError: (error: Error) => + toasts.addError(error, { + title: i18n.translate('xpack.osquery.osquery_policies.fetchError', { + defaultMessage: 'Error while fetching osquery policies', + }), + }), + } ); - - return { osqueryPoliciesLoading, osqueryPolicies }; + return useMemo(() => ({ osqueryPoliciesLoading, osqueryPolicies }), [ + osqueryPoliciesLoading, + osqueryPolicies, + ]); }; diff --git a/x-pack/plugins/osquery/public/common/hooks/use_osquery_integration.tsx b/x-pack/plugins/osquery/public/common/hooks/use_osquery_integration.tsx index d8bed30b969ad..ccfb407eab58b 100644 --- a/x-pack/plugins/osquery/public/common/hooks/use_osquery_integration.tsx +++ b/x-pack/plugins/osquery/public/common/hooks/use_osquery_integration.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import { find } from 'lodash/fp'; import { useQuery } from 'react-query'; @@ -13,7 +14,10 @@ import { OSQUERY_INTEGRATION_NAME } from '../../../common'; import { useKibana } from '../lib/kibana'; export const useOsqueryIntegration = () => { - const { http } = useKibana().services; + const { + http, + notifications: { toasts }, + } = useKibana().services; return useQuery( 'integrations', @@ -26,6 +30,12 @@ export const useOsqueryIntegration = () => { { select: ({ response }: GetPackagesResponse) => find(['name', OSQUERY_INTEGRATION_NAME], response), + onError: (error: Error) => + toasts.addError(error, { + title: i18n.translate('xpack.osquery.osquery_integration.fetchError', { + defaultMessage: 'Error while fetching osquery integration', + }), + }), } ); }; diff --git a/x-pack/plugins/osquery/public/common/validations.ts b/x-pack/plugins/osquery/public/common/validations.ts new file mode 100644 index 0000000000000..7ab9de52e35ad --- /dev/null +++ b/x-pack/plugins/osquery/public/common/validations.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +import { ValidationFunc, fieldValidators } from '../shared_imports'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const queryFieldValidation: ValidationFunc = fieldValidators.emptyField( + i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.emptyQueryError', { + defaultMessage: 'Query is a required field', + }) +); diff --git a/x-pack/plugins/osquery/public/live_queries/agent_results/index.tsx b/x-pack/plugins/osquery/public/live_queries/agent_results/index.tsx index 272e65d9cc0fa..d1ef18e2e12ea 100644 --- a/x-pack/plugins/osquery/public/live_queries/agent_results/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/agent_results/index.tsx @@ -22,7 +22,7 @@ const QueryAgentResultsComponent = () => { {data?.actionDetails._source?.data?.query} - + ); }; diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index 056bbc75f3b76..5d1b616c7d88a 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -12,14 +12,18 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { useMemo } from 'react'; import { useMutation } from 'react-query'; -import { UseField, Form, FormData, useForm, useFormData } from '../../shared_imports'; +import { UseField, Form, FormData, useForm, useFormData, FIELD_TYPES } from '../../shared_imports'; import { AgentsTableField } from './agents_table_field'; import { LiveQueryQueryField } from './live_query_query_field'; import { useKibana } from '../../common/lib/kibana'; import { ResultTabs } from '../../queries/edit/tabs'; +import { queryFieldValidation } from '../../common/validations'; +import { fieldValidators } from '../../shared_imports'; const FORM_ID = 'liveQueryForm'; +export const MAX_QUERY_LENGTH = 2000; + interface LiveQueryFormProps { defaultValue?: Partial | undefined; onSubmit?: (payload: Record) => Promise; @@ -50,9 +54,27 @@ const LiveQueryFormComponent: React.FC = ({ } ); + const formSchema = { + query: { + type: FIELD_TYPES.TEXT, + validations: [ + { + validator: fieldValidators.maxLengthField({ + length: MAX_QUERY_LENGTH, + message: i18n.translate('xpack.osquery.liveQuery.queryForm.largeQueryError', { + defaultMessage: 'Query is too large (max {maxLength} characters)', + values: { maxLength: MAX_QUERY_LENGTH }, + }), + }), + }, + { validator: queryFieldValidation }, + ], + }, + }; + const { form } = useForm({ id: FORM_ID, - // schema: formSchema, + schema: formSchema, onSubmit: (payload) => { return mutateAsync(payload); }, @@ -60,10 +82,7 @@ const LiveQueryFormComponent: React.FC = ({ stripEmptyFields: false, }, defaultValue: defaultValue ?? { - query: { - id: null, - query: '', - }, + query: '', }, }); @@ -85,16 +104,16 @@ const LiveQueryFormComponent: React.FC = ({ [agentSelection] ); - const queryValueProvided = useMemo(() => !!query?.query?.length, [query]); + const queryValueProvided = useMemo(() => !!query?.length, [query]); const queryStatus = useMemo(() => { if (!agentSelected) return 'disabled'; - if (isError) return 'danger'; + if (isError || !form.getFields().query.isValid) return 'danger'; if (isLoading) return 'loading'; if (isSuccess) return 'complete'; return 'incomplete'; - }, [agentSelected, isError, isLoading, isSuccess]); + }, [agentSelected, isError, isLoading, isSuccess, form]); const resultsStatus = useMemo(() => (queryStatus === 'complete' ? 'incomplete' : 'disabled'), [ queryStatus, diff --git a/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx b/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx index 68207200dc789..07c13b930e143 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx @@ -5,86 +5,32 @@ * 2.0. */ -// import { find } from 'lodash/fp'; -// import { EuiCodeBlock, EuiSuperSelect, EuiText, EuiSpacer } from '@elastic/eui'; import React, { useCallback } from 'react'; -// import { useQuery } from 'react-query'; +import { EuiFormRow } from '@elastic/eui'; import { FieldHook } from '../../shared_imports'; -// import { useKibana } from '../../common/lib/kibana'; import { OsqueryEditor } from '../../editor'; interface LiveQueryQueryFieldProps { disabled?: boolean; - field: FieldHook<{ - id: string | null; - query: string; - }>; + field: FieldHook; } const LiveQueryQueryFieldComponent: React.FC = ({ disabled, field }) => { - // const { http } = useKibana().services; - // const { data } = useQuery('savedQueryList', () => - // http.get('/internal/osquery/saved_query', { - // query: { - // pageIndex: 0, - // pageSize: 100, - // sortField: 'updated_at', - // sortDirection: 'desc', - // }, - // }) - // ); - - // const queryOptions = - // // @ts-expect-error update types - // data?.saved_objects.map((savedQuery) => ({ - // value: savedQuery, - // inputDisplay: savedQuery.attributes.name, - // dropdownDisplay: ( - // <> - // {savedQuery.attributes.name} - // - //

{savedQuery.attributes.description}

- //
- // - // {savedQuery.attributes.query} - // - // - // ), - // })) ?? []; - - const { value, setValue } = field; - - // const handleSavedQueryChange = useCallback( - // (newValue) => { - // setValue({ - // id: newValue.id, - // query: newValue.attributes.query, - // }); - // }, - // [setValue] - // ); + const { value, setValue, errors } = field; + const error = errors[0]?.message; const handleEditorChange = useCallback( (newValue) => { - setValue({ - id: null, - query: newValue, - }); + setValue(newValue); }, [setValue] ); return ( - <> - {/* - */} - - + + + ); }; diff --git a/x-pack/plugins/osquery/public/queries/edit/tabs.tsx b/x-pack/plugins/osquery/public/queries/edit/tabs.tsx index 1a6b317653c98..f86762e76834b 100644 --- a/x-pack/plugins/osquery/public/queries/edit/tabs.tsx +++ b/x-pack/plugins/osquery/public/queries/edit/tabs.tsx @@ -36,7 +36,7 @@ const ResultTabsComponent: React.FC = ({ actionId, agentIds, is content: ( <> - + ), }, diff --git a/x-pack/plugins/osquery/public/queries/form/code_editor_field.tsx b/x-pack/plugins/osquery/public/queries/form/code_editor_field.tsx index a56e747355c5b..77ffdc4457d3d 100644 --- a/x-pack/plugins/osquery/public/queries/form/code_editor_field.tsx +++ b/x-pack/plugins/osquery/public/queries/form/code_editor_field.tsx @@ -31,15 +31,16 @@ const OsquerySchemaLink = React.memo(() => ( OsquerySchemaLink.displayName = 'OsquerySchemaLink'; const CodeEditorFieldComponent: React.FC = ({ field }) => { - const { value, label, labelAppend, helpText, setValue } = field; + const { value, label, labelAppend, helpText, setValue, errors } = field; + const error = errors[0]?.message; return ( } helpText={helpText} - // isInvalid={typeof error === 'string'} - // error={error} + isInvalid={typeof error === 'string'} + error={error} fullWidth > diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx index d82c45d802520..8b613a336ae73 100644 --- a/x-pack/plugins/osquery/public/results/results_table.tsx +++ b/x-pack/plugins/osquery/public/results/results_table.tsx @@ -12,6 +12,10 @@ import { EuiDataGridProps, EuiDataGridColumn, EuiLink, + EuiTextColor, + EuiBasicTable, + EuiBasicTableColumn, + EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react'; @@ -20,16 +24,89 @@ import { pagePathGetters } from '../../../fleet/public'; import { useAllResults } from './use_all_results'; import { Direction, ResultEdges } from '../../common/search_strategy'; import { useKibana } from '../common/lib/kibana'; +import { useActionResults } from '../action_results/use_action_results'; +import { generateEmptyDataMessage } from './translations'; const DataContext = createContext([]); interface ResultsTableComponentProps { actionId: string; - agentId?: string; + selectedAgent?: string; + agentIds?: string[]; isLive?: boolean; } -const ResultsTableComponent: React.FC = ({ actionId, isLive }) => { +interface SummaryTableValue { + total: number | string; + pending: number | string; + responded: number; + failed: number; +} + +const ResultsTableComponent: React.FC = ({ + actionId, + agentIds, + isLive, +}) => { + const { + // @ts-expect-error update types + data: { aggregations }, + } = useActionResults({ + actionId, + activePage: 0, + agentIds, + limit: 0, + direction: Direction.asc, + sortField: '@timestamp', + isLive, + }); + + const notRespondedCount = useMemo(() => { + if (!agentIds || !aggregations.totalResponded) { + return '-'; + } + + return agentIds.length - aggregations.totalResponded; + }, [aggregations.totalResponded, agentIds]); + + const summaryColumns: Array> = useMemo( + () => [ + { + field: 'total', + name: 'Agents queried', + }, + { + field: 'responded', + name: 'Successful', + }, + { + field: 'pending', + name: 'Not yet responded', + }, + { + field: 'failed', + name: 'Failed', + // eslint-disable-next-line react/display-name + render: (failed: number) => ( + {failed} + ), + }, + ], + [] + ); + + const summaryItems = useMemo( + () => [ + { + total: agentIds?.length ?? '-', + pending: notRespondedCount, + responded: aggregations.totalResponded, + failed: aggregations.failed, + }, + ], + [aggregations, agentIds, notRespondedCount] + ); + const { getUrlForApp } = useKibana().services.application; const getFleetAppUrl = useCallback( @@ -115,30 +192,41 @@ const ResultsTableComponent: React.FC = ({ actionId, const newColumns = keys(allResultsData?.edges[0]?.fields) .sort() - .reduce((acc, fieldName) => { - if (fieldName === 'agent.name') { - acc.push({ - id: fieldName, - displayAsText: i18n.translate('xpack.osquery.liveQueryResults.table.agentColumnTitle', { - defaultMessage: 'agent', - }), - defaultSortDirection: Direction.asc, - }); + .reduce( + (acc, fieldName) => { + const { data, seen } = acc; + if (fieldName === 'agent.name') { + data.push({ + id: fieldName, + displayAsText: i18n.translate( + 'xpack.osquery.liveQueryResults.table.agentColumnTitle', + { + defaultMessage: 'agent', + } + ), + defaultSortDirection: Direction.asc, + }); - return acc; - } - - if (fieldName.startsWith('osquery.')) { - acc.push({ - id: fieldName, - displayAsText: fieldName.split('.')[1], - defaultSortDirection: Direction.asc, - }); - return acc; - } + return acc; + } - return acc; - }, [] as EuiDataGridColumn[]); + if (fieldName.startsWith('osquery.')) { + const displayAsText = fieldName.split('.')[1]; + if (!seen.has(displayAsText)) { + data.push({ + id: fieldName, + displayAsText, + defaultSortDirection: Direction.asc, + }); + seen.add(displayAsText); + } + return acc; + } + + return acc; + }, + { data: [], seen: new Set() } as { data: EuiDataGridColumn[]; seen: Set } + ).data; if (!isEqual(columns, newColumns)) { setColumns(newColumns); @@ -149,16 +237,24 @@ const ResultsTableComponent: React.FC = ({ actionId, return ( // @ts-expect-error update types - + + + {columns.length > 0 ? ( + + ) : ( +
+ {generateEmptyDataMessage(aggregations.totalResponded)} +
+ )}
); }; diff --git a/x-pack/plugins/osquery/public/results/translations.ts b/x-pack/plugins/osquery/public/results/translations.ts index 0f785f0c1f4d1..8e77e78ec76e2 100644 --- a/x-pack/plugins/osquery/public/results/translations.ts +++ b/x-pack/plugins/osquery/public/results/translations.ts @@ -7,6 +7,14 @@ import { i18n } from '@kbn/i18n'; +export const generateEmptyDataMessage = (agentsResponded: number): string => { + return i18n.translate('xpack.osquery.results.multipleAgentsResponded', { + defaultMessage: + '{agentsResponded, plural, one {# agent has} other {# agents have}} responded, but no osquery data has been reported.', + values: { agentsResponded }, + }); +}; + export const ERROR_ALL_RESULTS = i18n.translate('xpack.osquery.results.errorSearchDescription', { defaultMessage: `An error has occurred on all results search`, }); diff --git a/x-pack/plugins/osquery/public/results/use_all_results.ts b/x-pack/plugins/osquery/public/results/use_all_results.ts index 7140f80f510f4..afeb7dadb030c 100644 --- a/x-pack/plugins/osquery/public/results/use_all_results.ts +++ b/x-pack/plugins/osquery/public/results/use_all_results.ts @@ -7,6 +7,7 @@ import { useQuery } from 'react-query'; +import { i18n } from '@kbn/i18n'; import { createFilter } from '../common/helpers'; import { useKibana } from '../common/lib/kibana'; import { @@ -51,7 +52,10 @@ export const useAllResults = ({ skip = false, isLive = false, }: UseAllResults) => { - const { data } = useKibana().services; + const { + data, + notifications: { toasts }, + } = useKibana().services; return useQuery( ['allActionResults', { actionId, activePage, direction, limit, sortField }], @@ -82,6 +86,12 @@ export const useAllResults = ({ { refetchInterval: isLive ? 1000 : false, enabled: !skip, + onError: (error: Error) => + toasts.addError(error, { + title: i18n.translate('xpack.osquery.results.fetchError', { + defaultMessage: 'Error while fetching results', + }), + }), } ); }; diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/add_query_flyout.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/form/add_query_flyout.tsx index b2cfa05e0fc63..808431b68c4ba 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/form/add_query_flyout.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/form/add_query_flyout.tsx @@ -23,6 +23,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { CodeEditorField } from '../../queries/form/code_editor_field'; +import { idFieldValidations, intervalFieldValidation, queryFieldValidation } from './validations'; import { Form, useForm, FormData, getUseField, Field, FIELD_TYPES } from '../../shared_imports'; const FORM_ID = 'addQueryFlyoutForm'; @@ -50,12 +51,14 @@ const AddQueryFlyoutComponent: React.FC = ({ onSave, onClos label: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.idFieldLabel', { defaultMessage: 'ID', }), + validations: idFieldValidations.map((validator) => ({ validator })), }, query: { type: FIELD_TYPES.TEXT, label: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.queryFieldLabel', { defaultMessage: 'Query', }), + validations: [{ validator: queryFieldValidation }], }, interval: { type: FIELD_TYPES.NUMBER, @@ -65,6 +68,7 @@ const AddQueryFlyoutComponent: React.FC = ({ onSave, onClos defaultMessage: 'Interval (s)', } ), + validations: [{ validator: intervalFieldValidation }], }, }, }); diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/confirmation_modal.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/form/confirmation_modal.tsx index e686038430829..65379c9e23626 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/form/confirmation_modal.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/form/confirmation_modal.tsx @@ -74,7 +74,7 @@ const ConfirmDeployAgentPolicyModalComponent: React.FC ); diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/edit_query_flyout.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/form/edit_query_flyout.tsx index 41846636eccd4..767eda01c06df 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/form/edit_query_flyout.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/form/edit_query_flyout.tsx @@ -25,6 +25,7 @@ import { i18n } from '@kbn/i18n'; import { PackagePolicyInputStream } from '../../../../fleet/common'; import { CodeEditorField } from '../../queries/form/code_editor_field'; import { Form, useForm, getUseField, Field, FIELD_TYPES } from '../../shared_imports'; +import { idFieldValidations, intervalFieldValidation, queryFieldValidation } from './validations'; const FORM_ID = 'editQueryFlyoutForm'; @@ -64,12 +65,14 @@ export const EditQueryFlyout: React.FC = ({ label: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.idFieldLabel', { defaultMessage: 'ID', }), + validations: idFieldValidations.map((validator) => ({ validator })), }, query: { type: FIELD_TYPES.TEXT, label: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.queryFieldLabel', { defaultMessage: 'Query', }), + validations: [{ validator: queryFieldValidation }], }, interval: { type: FIELD_TYPES.NUMBER, @@ -79,6 +82,7 @@ export const EditQueryFlyout: React.FC = ({ defaultMessage: 'Interval (s)', } ), + validations: [{ validator: intervalFieldValidation }], }, }, }); diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/translations.ts b/x-pack/plugins/osquery/public/scheduled_query_groups/form/translations.ts new file mode 100644 index 0000000000000..5d00d60ffd8b8 --- /dev/null +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/form/translations.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. + */ + +import { i18n } from '@kbn/i18n'; + +export const INVALID_ID_ERROR = i18n.translate('xpack.osquery.agents.failSearchDescription', { + defaultMessage: `Failed to fetch agents`, +}); diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/validations.ts b/x-pack/plugins/osquery/public/scheduled_query_groups/form/validations.ts new file mode 100644 index 0000000000000..95e3000476a08 --- /dev/null +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/form/validations.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 { i18n } from '@kbn/i18n'; + +import { ValidationFunc, fieldValidators } from '../../shared_imports'; +export { queryFieldValidation } from '../../common/validations'; + +const idPattern = /^[a-zA-Z0-9-_]+$/; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const idSchemaValidation: ValidationFunc = ({ value }) => { + const valueIsValid = idPattern.test(value); + if (!valueIsValid) { + return { + message: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.invalidIdError', { + defaultMessage: 'Characters must be alphanumeric, _, or -', + }), + }; + } +}; + +export const idFieldValidations = [ + fieldValidators.emptyField( + i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.emptyIdError', { + defaultMessage: 'ID is required', + }) + ), + idSchemaValidation, +]; + +export const intervalFieldValidation: ValidationFunc< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + any, + string, + number +> = fieldValidators.numberGreaterThanField({ + than: 0, + message: i18n.translate( + 'xpack.osquery.scheduledQueryGroup.queryFlyoutForm.invalidIntervalField', + { + defaultMessage: 'A positive interval value is required', + } + ), +}); diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx index d501f56b789d7..90ec7e0c2717b 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_table.tsx @@ -148,7 +148,7 @@ const ScheduledQueryGroupQueriesTableComponent: React.FC Promise<{ results: string[]; total: number }> +) => { + const { results, total } = await generator(1, PER_PAGE); + const totalPages = Math.ceil(total / PER_PAGE); + let currPage = 2; + while (currPage <= totalPages) { + const { results: additionalResults } = await generator(currPage++, PER_PAGE); + results.push(...additionalResults); + } + return uniq(results); +}; + export const parseAgentSelection = async ( esClient: ElasticsearchClient, + soClient: SavedObjectsClientContract, context: OsqueryAppContext, agentSelection: AgentSelection ) => { - let selectedAgents: string[] = []; + const selectedAgents: Set = new Set(); + const addAgent = selectedAgents.add.bind(selectedAgents); const { allAgentsSelected, platformsSelected, policiesSelected, agents } = agentSelection; const agentService = context.service.getAgentService(); - if (agentService) { + const packagePolicyService = context.service.getPackagePolicyService(); + const kueryFragments = ['active:true']; + + if (agentService && packagePolicyService) { + const osqueryPolicies = await aggregateResults(async (page, perPage) => { + const { items, total } = await packagePolicyService.list(soClient, { + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`, + perPage, + page, + }); + return { results: items.map((it) => it.policy_id), total }; + }); + kueryFragments.push(`policy_id:(${uniq(osqueryPolicies).join(',')})`); if (allAgentsSelected) { - // TODO: actually fetch all the agents - const { agents: fetchedAgents } = await agentService.listAgents(esClient, { - perPage: 9000, - showInactive: true, + const kuery = kueryFragments.join(' and '); + const fetchedAgents = await aggregateResults(async (page, perPage) => { + const res = await agentService.listAgents(esClient, { + perPage, + page, + kuery, + showInactive: true, + }); + return { results: res.agents.map((agent) => agent.id), total: res.total }; }); - selectedAgents.push(...fetchedAgents.map((a) => a.id)); + fetchedAgents.forEach(addAgent); } else { if (platformsSelected.length > 0 || policiesSelected.length > 0) { - const kueryFragments = []; + const groupFragments = []; if (platformsSelected.length) { - kueryFragments.push( - ...platformsSelected.map((platform) => `local_metadata.os.platform:${platform}`) - ); + groupFragments.push(`local_metadata.os.platform:(${platformsSelected.join(',')})`); } if (policiesSelected.length) { - kueryFragments.push(...policiesSelected.map((policy) => `policy_id:${policy}`)); + groupFragments.push(`policy_id:(${policiesSelected.join(',')})`); } - const kuery = kueryFragments.join(' or '); - // TODO: actually fetch all the agents - const { agents: fetchedAgents } = await agentService.listAgents(esClient, { - kuery, - perPage: 9000, - showInactive: true, + kueryFragments.push(`(${groupFragments.join(' or ')})`); + const kuery = kueryFragments.join(' and '); + const fetchedAgents = await aggregateResults(async (page, perPage) => { + const res = await agentService.listAgents(esClient, { + perPage, + page, + kuery, + showInactive: true, + }); + return { results: res.agents.map((agent) => agent.id), total: res.total }; }); - selectedAgents.push(...fetchedAgents.map((a) => a.id)); + fetchedAgents.forEach(addAgent); } - selectedAgents.push(...agents); - selectedAgents = Array.from(new Set(selectedAgents)); } } - return selectedAgents; + + agents.forEach(addAgent); + + return Array.from(selectedAgents); }; diff --git a/x-pack/plugins/osquery/server/routes/action/create_action_route.ts b/x-pack/plugins/osquery/server/routes/action/create_action_route.ts index 8e741c6a9e3ca..9dcd020f0734e 100644 --- a/x-pack/plugins/osquery/server/routes/action/create_action_route.ts +++ b/x-pack/plugins/osquery/server/routes/action/create_action_route.ts @@ -7,29 +7,42 @@ import uuid from 'uuid'; import moment from 'moment'; -import { schema } from '@kbn/config-schema'; import { IRouter } from '../../../../../../src/core/server'; import { OsqueryAppContext } from '../../lib/osquery_app_context_services'; import { parseAgentSelection, AgentSelection } from '../../lib/parse_agent_groups'; +import { buildRouteValidation } from '../../utils/build_validation/route_validation'; +import { + createActionRequestBodySchema, + CreateActionRequestBodySchema, +} from '../../../common/schemas/routes/action/create_action_request_body_schema'; export const createActionRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => { router.post( { path: '/internal/osquery/action', validate: { - params: schema.object({}, { unknowns: 'allow' }), - body: schema.object({}, { unknowns: 'allow' }), - }, - options: { - tags: ['access:osquery', 'access:osquery_write'], + body: buildRouteValidation< + typeof createActionRequestBodySchema, + CreateActionRequestBodySchema + >(createActionRequestBodySchema), }, }, async (context, request, response) => { const esClient = context.core.elasticsearch.client.asCurrentUser; + const soClient = context.core.savedObjects.client; const { agentSelection } = request.body as { agentSelection: AgentSelection }; - const selectedAgents = await parseAgentSelection(esClient, osqueryContext, agentSelection); + const selectedAgents = await parseAgentSelection( + esClient, + soClient, + osqueryContext, + agentSelection + ); + + if (!selectedAgents.length) { + throw new Error('No agents found for selection, aborting.'); + } const action = { action_id: uuid.v4(), @@ -39,10 +52,8 @@ export const createActionRoute = (router: IRouter, osqueryContext: OsqueryAppCon input_type: 'osquery', agents: selectedAgents, data: { - // @ts-expect-error update validation - id: request.body.query.id ?? uuid.v4(), - // @ts-expect-error update validation - query: request.body.query.query, + id: uuid.v4(), + query: request.body.query, }, }; const actionResponse = await esClient.index<{}, {}>({ From 71145a6778a7a5a1784a6b5eee3c549606f6c05a Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 28 Apr 2021 21:31:13 -0700 Subject: [PATCH 56/70] skip flaky suite (#32240) --- src/cli/serve/integration_tests/invalid_config.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/serve/integration_tests/invalid_config.test.ts b/src/cli/serve/integration_tests/invalid_config.test.ts index 517c8aa946590..b593aa9a73196 100644 --- a/src/cli/serve/integration_tests/invalid_config.test.ts +++ b/src/cli/serve/integration_tests/invalid_config.test.ts @@ -18,7 +18,8 @@ interface LogEntry { type: string; } -describe('cli invalid config support', function () { +// FLAKY: https://github.com/elastic/kibana/issues/32240 +describe.skip('cli invalid config support', function () { it( 'exits with statusCode 64 and logs a single line when config is invalid', function () { From fe48ae396bc1741ee5fb27817318e7868e5c8a3f Mon Sep 17 00:00:00 2001 From: Diana Derevyankina <54894989+DziyanaDzeraviankina@users.noreply.github.com> Date: Thu, 29 Apr 2021 10:38:57 +0300 Subject: [PATCH 57/70] [Timelion] Support of Runtime Fields (#96700) * [Timelion] Support of Runtime Fields * Replace call of getScriptedFields() with getComputedFields().runtimeFields, refactor buildAggBody and es.test.js * Refactor index.js and agg_body.js Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/helpers/arg_value_suggestions.ts | 15 +--- .../server/series_functions/es/es.test.js | 73 ++++++++++--------- .../server/series_functions/es/index.js | 5 +- .../series_functions/es/lib/agg_body.js | 18 +---- .../series_functions/es/lib/build_request.js | 7 +- .../es/lib/create_date_agg.js | 4 +- 6 files changed, 50 insertions(+), 72 deletions(-) diff --git a/src/plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts b/src/plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts index d8ec46eba004f..8685ed3102fa6 100644 --- a/src/plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts +++ b/src/plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts @@ -10,13 +10,7 @@ import { get } from 'lodash'; import { getIndexPatterns } from './plugin_services'; import { TimelionFunctionArgs } from '../../common/types'; import { TimelionExpressionFunction, TimelionExpressionArgument } from '../../common/parser'; -import { - IndexPatternField, - indexPatterns as indexPatternsUtils, - KBN_FIELD_TYPES, -} from '../../../data/public'; - -const isRuntimeField = (field: IndexPatternField) => Boolean(field.runtimeField); +import { indexPatterns as indexPatternsUtils, KBN_FIELD_TYPES } from '../../../data/public'; export function getArgValueSuggestions() { const indexPatterns = getIndexPatterns(); @@ -77,7 +71,6 @@ export function getArgValueSuggestions() { .getByType(KBN_FIELD_TYPES.NUMBER) .filter( (field) => - !isRuntimeField(field) && field.aggregatable && containsFieldName(valueSplit[1], field) && !indexPatternsUtils.isNestedField(field) @@ -101,7 +94,6 @@ export function getArgValueSuggestions() { .getAll() .filter( (field) => - !isRuntimeField(field) && field.aggregatable && [ KBN_FIELD_TYPES.NUMBER, @@ -124,10 +116,7 @@ export function getArgValueSuggestions() { return indexPattern.fields .getByType(KBN_FIELD_TYPES.DATE) .filter( - (field) => - !isRuntimeField(field) && - containsFieldName(partial, field) && - !indexPatternsUtils.isNestedField(field) + (field) => containsFieldName(partial, field) && !indexPatternsUtils.isNestedField(field) ) .map((field) => ({ name: field.name, insertText: field.name })); }, diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js index 3ace745604660..c2940c6d7731a 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js @@ -120,7 +120,7 @@ describe('es', () => { }); describe('metric aggs', () => { - const emptyScriptedFields = []; + const emptyScriptFields = {}; test('adds a metric agg for each metric', () => { config.metric = [ @@ -133,7 +133,7 @@ describe('es', () => { 'percentiles:\\:bytes\\:123:20.0,50.0,100.0', 'percentiles:a:2', ]; - agg = createDateAgg(config, tlConfig, emptyScriptedFields); + agg = createDateAgg(config, tlConfig, emptyScriptFields); expect(agg.time_buckets.aggs['sum(beer)']).toEqual({ sum: { field: 'beer' } }); expect(agg.time_buckets.aggs['avg(bytes)']).toEqual({ avg: { field: 'bytes' } }); expect(agg.time_buckets.aggs['percentiles(bytes)']).toEqual({ @@ -156,14 +156,15 @@ describe('es', () => { test('adds a scripted metric agg for each scripted metric', () => { config.metric = ['avg:scriptedBytes']; - const scriptedFields = [ - { - name: 'scriptedBytes', - script: 'doc["bytes"].value', - lang: 'painless', + const scriptFields = { + scriptedBytes: { + script: { + source: 'doc["bytes"].value', + lang: 'painless', + }, }, - ]; - agg = createDateAgg(config, tlConfig, scriptedFields); + }; + agg = createDateAgg(config, tlConfig, scriptFields); expect(agg.time_buckets.aggs['avg(scriptedBytes)']).toEqual({ avg: { script: { @@ -176,14 +177,14 @@ describe('es', () => { test('has a special `count` metric that uses a script', () => { config.metric = ['count']; - agg = createDateAgg(config, tlConfig, emptyScriptedFields); + agg = createDateAgg(config, tlConfig, emptyScriptFields); expect(typeof agg.time_buckets.aggs.count.bucket_script).toBe('object'); expect(agg.time_buckets.aggs.count.bucket_script.buckets_path).toEqual('_count'); }); test('has a special `count` metric with redundant field which use a script', () => { config.metric = ['count:beer']; - agg = createDateAgg(config, tlConfig, emptyScriptedFields); + agg = createDateAgg(config, tlConfig, emptyScriptFields); expect(typeof agg.time_buckets.aggs.count.bucket_script).toBe('object'); expect(agg.time_buckets.aggs.count.bucket_script.buckets_path).toEqual('_count'); }); @@ -192,7 +193,7 @@ describe('es', () => { describe('buildRequest', () => { const fn = buildRequest; - const emptyScriptedFields = []; + const emptyScriptFields = {}; let tlConfig; let config; beforeEach(() => { @@ -206,20 +207,20 @@ describe('es', () => { test('sets the index on the request', () => { config.index = 'beer'; - const request = fn(config, tlConfig, emptyScriptedFields); + const request = fn(config, tlConfig, emptyScriptFields); expect(request.params.index).toEqual('beer'); }); test('always sets body.size to 0', () => { - const request = fn(config, tlConfig, emptyScriptedFields); + const request = fn(config, tlConfig, emptyScriptFields); expect(request.params.body.size).toEqual(0); }); test('creates a filters agg that contains each of the queries passed', () => { config.q = ['foo', 'bar']; - const request = fn(config, tlConfig, emptyScriptedFields); + const request = fn(config, tlConfig, emptyScriptFields); expect(request.params.body.aggs.q.meta.type).toEqual('split'); @@ -231,14 +232,14 @@ describe('es', () => { describe('timeouts', () => { test('sets the timeout on the request', () => { config.index = 'beer'; - const request = fn(config, tlConfig, emptyScriptedFields, 30000); + const request = fn(config, tlConfig, emptyScriptFields, {}, 30000); expect(request.params.timeout).toEqual('30000ms'); }); test('sets no timeout if elasticsearch.shardTimeout is set to 0', () => { config.index = 'beer'; - const request = fn(config, tlConfig, emptyScriptedFields, 0); + const request = fn(config, tlConfig, emptyScriptFields, {}, 0); expect(request.params).not.toHaveProperty('timeout'); }); @@ -258,7 +259,7 @@ describe('es', () => { test('sets ignore_throttled=true on the request', () => { config.index = 'beer'; tlConfig.settings[UI_SETTINGS.SEARCH_INCLUDE_FROZEN] = false; - const request = fn(config, tlConfig, emptyScriptedFields); + const request = fn(config, tlConfig, emptyScriptFields); expect(request.params.ignore_throttled).toEqual(true); }); @@ -266,7 +267,7 @@ describe('es', () => { test('sets no timeout if elasticsearch.shardTimeout is set to 0', () => { tlConfig.settings[UI_SETTINGS.SEARCH_INCLUDE_FROZEN] = true; config.index = 'beer'; - const request = fn(config, tlConfig, emptyScriptedFields); + const request = fn(config, tlConfig, emptyScriptFields); expect(request.params.ignore_throttled).toEqual(false); }); @@ -301,7 +302,7 @@ describe('es', () => { test('adds the contents of body.extended.es.filter to a filter clause of the bool', () => { config.kibana = true; - const request = fn(config, tlConfig, emptyScriptedFields); + const request = fn(config, tlConfig, emptyScriptFields); const filter = request.params.body.query.bool.filter.bool; expect(filter.must.length).toEqual(1); expect(filter.must_not.length).toEqual(2); @@ -309,12 +310,12 @@ describe('es', () => { test('does not include filters if config.kibana = false', () => { config.kibana = false; - const request = fn(config, tlConfig, emptyScriptedFields); + const request = fn(config, tlConfig, emptyScriptFields); expect(request.params.body.query.bool.filter).toEqual(undefined); }); test('adds a time filter to the bool querys must clause', () => { - let request = fn(config, tlConfig, emptyScriptedFields); + let request = fn(config, tlConfig, emptyScriptFields); expect(request.params.body.query.bool.must.length).toEqual(1); expect(request.params.body.query.bool.must[0]).toEqual({ range: { @@ -327,7 +328,7 @@ describe('es', () => { }); config.kibana = true; - request = fn(config, tlConfig, emptyScriptedFields); + request = fn(config, tlConfig, emptyScriptFields); expect(request.params.body.query.bool.must.length).toEqual(1); }); }); @@ -335,7 +336,7 @@ describe('es', () => { describe('config.split', () => { test('adds terms aggs, in order, under the filters agg', () => { config.split = ['beer:5', 'wine:10', ':lemo:nade::15', ':jui:ce:723::45']; - const request = fn(config, tlConfig, emptyScriptedFields); + const request = fn(config, tlConfig, {}); let aggs = request.params.body.aggs.q.aggs; @@ -362,19 +363,21 @@ describe('es', () => { test('adds scripted terms aggs, in order, under the filters agg', () => { config.split = ['scriptedBeer:5', 'scriptedWine:10']; - const scriptedFields = [ - { - name: 'scriptedBeer', - script: 'doc["beer"].value', - lang: 'painless', + const scriptFields = { + scriptedBeer: { + script: { + source: 'doc["beer"].value', + lang: 'painless', + }, }, - { - name: 'scriptedWine', - script: 'doc["wine"].value', - lang: 'painless', + scriptedWine: { + script: { + source: 'doc["wine"].value', + lang: 'painless', + }, }, - ]; - const request = fn(config, tlConfig, scriptedFields); + }; + const request = fn(config, tlConfig, scriptFields); const aggs = request.params.body.aggs.q.aggs; diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/index.js b/src/plugins/vis_type_timelion/server/series_functions/es/index.js index 75b16fa25c9cd..663d7714774c2 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/index.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/index.js @@ -101,11 +101,10 @@ export default new Datasource('es', { (index) => index.title === config.index ); - const scriptedFields = indexPatternSpec?.getScriptedFields() ?? []; - + const { scriptFields = {}, runtimeFields = {} } = indexPatternSpec?.getComputedFields() ?? {}; const esShardTimeout = tlConfig.esShardTimeout; - const body = buildRequest(config, tlConfig, scriptedFields, esShardTimeout); + const body = buildRequest(config, tlConfig, scriptFields, runtimeFields, esShardTimeout); const resp = await tlConfig.context.search .search( diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js b/src/plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js index cbdc834dd6611..db66cd1efc012 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js @@ -6,21 +6,7 @@ * Side Public License, v 1. */ -export function buildAggBody(fieldName, scriptedFields) { - const scriptedField = scriptedFields.find((field) => { - return field.name === fieldName; - }); - - if (scriptedField) { - return { - script: { - source: scriptedField.script, - lang: scriptedField.lang, - }, - }; - } - - return { +export const buildAggBody = (fieldName, scriptFields) => + scriptFields[fieldName] ?? { field: fieldName, }; -} diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js b/src/plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js index a30b197e46067..7d55a772c7fc1 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js @@ -12,7 +12,7 @@ import { buildAggBody } from './agg_body'; import createDateAgg from './create_date_agg'; import { UI_SETTINGS } from '../../../../../data/server'; -export default function buildRequest(config, tlConfig, scriptedFields, timeout) { +export default function buildRequest(config, tlConfig, scriptFields, runtimeFields, timeout) { const bool = { must: [] }; const timeFilter = { @@ -51,7 +51,7 @@ export default function buildRequest(config, tlConfig, scriptedFields, timeout) (config.split || []).forEach((clause) => { const [field, arg] = clause.split(/:(\d+$)/); if (field && arg) { - const termsAgg = buildAggBody(field, scriptedFields); + const termsAgg = buildAggBody(field, scriptFields); termsAgg.size = parseInt(arg, 10); aggCursor[field] = { meta: { type: 'split' }, @@ -64,7 +64,7 @@ export default function buildRequest(config, tlConfig, scriptedFields, timeout) } }); - _.assign(aggCursor, createDateAgg(config, tlConfig, scriptedFields)); + _.assign(aggCursor, createDateAgg(config, tlConfig, scriptFields)); const request = { index: config.index, @@ -75,6 +75,7 @@ export default function buildRequest(config, tlConfig, scriptedFields, timeout) }, aggs: aggs, size: 0, + runtime_mappings: runtimeFields, }, }; diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js b/src/plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js index 55538fbff4e79..bd6cf8a4b7c5e 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js @@ -11,7 +11,7 @@ import { search, METRIC_TYPES } from '../../../../../data/server'; const { dateHistogramInterval } = search.aggs; -export default function createDateAgg(config, tlConfig, scriptedFields) { +export default function createDateAgg(config, tlConfig, scriptFields) { const dateAgg = { time_buckets: { meta: { type: 'time_buckets' }, @@ -47,7 +47,7 @@ export default function createDateAgg(config, tlConfig, scriptedFields) { const percentArgs = splittedArgs[1]; const metricKey = metricName + '(' + field + ')'; - metricBody[metricKey] = { [metricName]: buildAggBody(field, scriptedFields) }; + metricBody[metricKey] = { [metricName]: buildAggBody(field, scriptFields) }; if (metricName === METRIC_TYPES.PERCENTILES && percentArgs) { let percentList = percentArgs.split(','); From 42d361c644f0a7b6620178a3002be2952279f49e Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 29 Apr 2021 10:17:22 +0200 Subject: [PATCH 58/70] [ML] Adds waiting state for transforms. (#98592) When no transform nodes are available, existing continuous transform end up in a waiting state. This PR adds support for this state in the transforms UI. Without the fix, transforms in a waiting state would fail to show up in the transform list. --- x-pack/plugins/transform/common/api_schemas/common.ts | 2 ++ x-pack/plugins/transform/common/constants.ts | 3 ++- .../components/transform_list/use_columns.tsx | 8 ++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/transform/common/api_schemas/common.ts b/x-pack/plugins/transform/common/api_schemas/common.ts index 3651af69359a9..b84dcd2a4b749 100644 --- a/x-pack/plugins/transform/common/api_schemas/common.ts +++ b/x-pack/plugins/transform/common/api_schemas/common.ts @@ -17,6 +17,7 @@ export const transformIdsSchema = schema.arrayOf( export type TransformIdsSchema = TypeOf; +// reflects https://github.com/elastic/elasticsearch/blob/master/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/transforms/TransformStats.java#L250 export const transformStateSchema = schema.oneOf([ schema.literal(TRANSFORM_STATE.ABORTING), schema.literal(TRANSFORM_STATE.FAILED), @@ -24,6 +25,7 @@ export const transformStateSchema = schema.oneOf([ schema.literal(TRANSFORM_STATE.STARTED), schema.literal(TRANSFORM_STATE.STOPPED), schema.literal(TRANSFORM_STATE.STOPPING), + schema.literal(TRANSFORM_STATE.WAITING), ]); export const indexPatternTitleSchema = schema.object({ diff --git a/x-pack/plugins/transform/common/constants.ts b/x-pack/plugins/transform/common/constants.ts index ce61f27ef2553..423b2d001381c 100644 --- a/x-pack/plugins/transform/common/constants.ts +++ b/x-pack/plugins/transform/common/constants.ts @@ -77,7 +77,7 @@ export const APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES = [ export const APP_INDEX_PRIVILEGES = ['monitor']; -// reflects https://github.com/elastic/elasticsearch/blob/master/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/dataframe/transforms/DataFrameTransformStats.java#L243 +// reflects https://github.com/elastic/elasticsearch/blob/master/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/transforms/TransformStats.java#L250 export const TRANSFORM_STATE = { ABORTING: 'aborting', FAILED: 'failed', @@ -85,6 +85,7 @@ export const TRANSFORM_STATE = { STARTED: 'started', STOPPED: 'stopped', STOPPING: 'stopping', + WAITING: 'waiting', } as const; const transformStates = Object.values(TRANSFORM_STATE); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx index a8f6a9a233c62..e186acf31d34f 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.tsx @@ -30,6 +30,7 @@ import { TRANSFORM_STATE } from '../../../../../../common/constants'; import { getTransformProgress, TransformListRow, TRANSFORM_LIST_COLUMN } from '../../../../common'; import { useActions } from './use_actions'; +// reflects https://github.com/elastic/elasticsearch/blob/master/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/transforms/TransformStats.java#L250 const STATE_COLOR = { aborting: 'warning', failed: 'danger', @@ -37,6 +38,7 @@ const STATE_COLOR = { started: 'primary', stopped: 'hollow', stopping: 'hollow', + waiting: 'hollow', } as const; export const getTaskStateBadge = ( @@ -202,13 +204,15 @@ export const useColumns = ( {!isBatchTransform && ( - {/* If not stopped or failed show the animated progress bar */} + {/* If not stopped, failed or waiting show the animated progress bar */} {item.stats.state !== TRANSFORM_STATE.STOPPED && + item.stats.state !== TRANSFORM_STATE.WAITING && item.stats.state !== TRANSFORM_STATE.FAILED && ( )} - {/* If stopped or failed show an empty (0%) progress bar */} + {/* If stopped, failed or waiting show an empty (0%) progress bar */} {(item.stats.state === TRANSFORM_STATE.STOPPED || + item.stats.state === TRANSFORM_STATE.WAITING || item.stats.state === TRANSFORM_STATE.FAILED) && ( )} From 3f48479376e6cdf50a28feeb677f22b9bd9b054b Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Thu, 29 Apr 2021 10:22:15 +0200 Subject: [PATCH 59/70] [Security Solutions][Timeline] Added createFrom in action to hide (#98144) * added createFrom in action to hide * prettier configured * tests to check timeline modal table actions * test changes and contant extract * removed unused dependency * prevent adding empty column to timeline table when no action need * test updated Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/rules/query_bar/index.test.tsx | 58 ++++++- .../components/rules/query_bar/index.tsx | 6 +- .../flyout/add_timeline_button/index.test.tsx | 142 ++++++++++++++---- .../flyout/add_timeline_button/index.tsx | 7 +- .../open_timeline/timelines_table/index.tsx | 16 +- 5 files changed, 188 insertions(+), 41 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx index 7ef698ae05b36..1e8525f0519ed 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx @@ -6,14 +6,38 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount, shallow } from 'enzyme'; import { QueryBarDefineRule } from './index'; -import { useFormFieldMock } from '../../../../common/mock'; +import { + TestProviders, + useFormFieldMock, + mockOpenTimelineQueryResults, +} from '../../../../common/mock'; +import { mockHistory, Router } from '../../../../cases/components/__mock__/router'; +import { useGetAllTimeline, getAllTimeline } from '../../../../timelines/containers/all'; jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../../timelines/containers/all', () => { + const originalModule = jest.requireActual('../../../../timelines/containers/all'); + return { + ...originalModule, + useGetAllTimeline: jest.fn(), + }; +}); + describe('QueryBarDefineRule', () => { + beforeEach(() => { + ((useGetAllTimeline as unknown) as jest.Mock).mockReturnValue({ + fetchAllTimeline: jest.fn(), + timelines: getAllTimeline('', mockOpenTimelineQueryResults.timeline ?? []), + loading: false, + totalCount: mockOpenTimelineQueryResults.totalCount, + refetch: jest.fn(), + }); + }); + it('renders correctly', () => { const Component = () => { const field = useFormFieldMock(); @@ -32,7 +56,35 @@ describe('QueryBarDefineRule', () => { ); }; const wrapper = shallow(); - expect(wrapper.dive().find('[data-test-subj="query-bar-define-rule"]')).toHaveLength(1); }); + + it('renders import query from saved timeline modal actions hidden correctly', () => { + const Component = () => { + const field = useFormFieldMock(); + + return ( + + ); + }; + const wrapper = mount( + + + + + + ); + + expect(wrapper.find('[data-test-subj="open-duplicate"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="create-from-template"]').exists()).toBeFalsy(); + }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx index f45ff5f1ea1a1..6bda4a0e0f6b8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx @@ -6,7 +6,7 @@ */ import { EuiFormRow, EuiMutationObserver } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Subscription } from 'rxjs'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; @@ -50,6 +50,8 @@ interface QueryBarDefineRuleProps { onValidityChange?: (arg: boolean) => void; } +const actionTimelineToHide: ActionTimelineToShow[] = ['duplicate', 'createFrom']; + const StyledEuiFormRow = styled(EuiFormRow)` .kbnTypeahead__items { max-height: 45vh !important; @@ -253,8 +255,6 @@ export const QueryBarDefineRule = ({ } }; - const actionTimelineToHide = useMemo(() => ['duplicate'], []); - return ( <> { + const originalModule = jest.requireActual('../../open_timeline/use_timeline_status'); + return { + ...originalModule, + useTimelineStatus: jest.fn().mockReturnValue({ + timelineStatus: 'active', + templateTimelineFilter: [], + installPrepackagedTimelines: jest.fn(), + }), + }; +}); -jest.mock('../../../../common/lib/kibana', () => ({ - useKibana: jest.fn(), - useUiSetting$: jest.fn().mockReturnValue([]), -})); +jest.mock('../../../../common/lib/kibana', () => { + const originalModule = jest.requireActual('../../../../common/lib/kibana'); + return { + ...originalModule, + useKibana: jest.fn(), + useUiSetting$: jest.fn().mockReturnValue([]), + }; +}); + +jest.mock('../../../containers/all', () => { + const originalModule = jest.requireActual('../../../containers/all'); + return { + ...originalModule, + useGetAllTimeline: jest.fn(), + }; +}); jest.mock('../../timeline/properties/new_template_timeline', () => ({ NewTemplateTimeline: jest.fn(() =>
), @@ -35,8 +62,7 @@ jest.mock('../../../../common/components/inspect', () => ({ InspectButtonContainer: jest.fn(({ children }) =>
{children}
), })); -// FLAKY: https://github.com/elastic/kibana/issues/96691 -describe.skip('AddTimelineButton', () => { +describe('AddTimelineButton', () => { let wrapper: ReactWrapper; const props = { timelineId: TimelineId.active, @@ -67,24 +93,24 @@ describe.skip('AddTimelineButton', () => { }); test('it renders create timeline btn', async () => { - await waitFor(() => { - wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); - expect(wrapper.find('[data-test-subj="create-default-btn"]').exists()).toBeTruthy(); - }); + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); + await waitFor(() => + expect(wrapper.find('[data-test-subj="create-default-btn"]').exists()).toBeTruthy() + ); }); test('it renders create timeline template btn', async () => { - await waitFor(() => { - wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); - expect(wrapper.find('[data-test-subj="create-template-btn"]').exists()).toBeTruthy(); - }); + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); + await waitFor(() => + expect(wrapper.find('[data-test-subj="create-template-btn"]').exists()).toBeTruthy() + ); }); test('it renders Open timeline btn', async () => { - await waitFor(() => { - wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); - expect(wrapper.find('[data-test-subj="open-timeline-button"]').exists()).toBeTruthy(); - }); + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); + await waitFor(() => + expect(wrapper.find('[data-test-subj="open-timeline-button"]').exists()).toBeTruthy() + ); }); }); @@ -113,24 +139,86 @@ describe.skip('AddTimelineButton', () => { }); test('it renders create timeline btn', async () => { - await waitFor(() => { - wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); - expect(wrapper.find('[data-test-subj="create-default-btn"]').exists()).toBeTruthy(); - }); + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); + await waitFor(() => + expect(wrapper.find('[data-test-subj="create-default-btn"]').exists()).toBeTruthy() + ); }); test('it renders create timeline template btn', async () => { - await waitFor(() => { - wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); - expect(wrapper.find('[data-test-subj="create-template-btn"]').exists()).toBeTruthy(); - }); + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); + await waitFor(() => + expect(wrapper.find('[data-test-subj="create-template-btn"]').exists()).toBeTruthy() + ); }); test('it renders Open timeline btn', async () => { + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); + await waitFor(() => + expect(wrapper.find('[data-test-subj="open-timeline-button"]').exists()).toBeTruthy() + ); + }); + }); + + describe('open modal', () => { + beforeEach(() => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + application: { + getUrlForApp: jest.fn(), + capabilities: { + siem: { + crud: true, + }, + }, + }, + }, + }); + + ((useGetAllTimeline as unknown) as jest.Mock).mockReturnValue({ + fetchAllTimeline: jest.fn(), + timelines: getAllTimeline('', mockOpenTimelineQueryResults.timeline ?? []), + loading: false, + totalCount: mockOpenTimelineQueryResults.totalCount, + refetch: jest.fn(), + }); + + wrapper = mount( + + + + + + ); + }); + + afterEach(() => { + (useKibana as jest.Mock).mockReset(); + }); + + it('should render timelines table', async () => { + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); await waitFor(() => { - wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); expect(wrapper.find('[data-test-subj="open-timeline-button"]').exists()).toBeTruthy(); }); + + wrapper.find('[data-test-subj="open-timeline-button"]').first().simulate('click'); + await waitFor(() => { + expect(wrapper.find('[data-test-subj="timelines-table"]').exists()).toBeTruthy(); + }); + }); + + it('should render correct actions', async () => { + wrapper.find('[data-test-subj="settings-plus-in-circle"]').last().simulate('click'); + await waitFor(() => + expect(wrapper.find('[data-test-subj="open-timeline-button"]').exists()).toBeTruthy() + ); + + wrapper.find('[data-test-subj="open-timeline-button"]').first().simulate('click'); + await waitFor(() => { + expect(wrapper.find('[data-test-subj="open-duplicate"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="create-from-template"]').exists()).toBeFalsy(); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.tsx index 90b1cf09cb6cd..5ea1b60e4f156 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.tsx @@ -10,6 +10,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { OpenTimelineModalButton } from '../../open_timeline/open_timeline_modal/open_timeline_modal_button'; import { OpenTimelineModal } from '../../open_timeline/open_timeline_modal'; +import { ActionTimelineToShow } from '../../open_timeline/types'; import * as i18n from '../../timeline/properties/translations'; import { NewTimeline } from '../../timeline/properties/helpers'; import { NewTemplateTimeline } from '../../timeline/properties/new_template_timeline'; @@ -20,6 +21,8 @@ interface AddTimelineButtonComponentProps { export const ADD_TIMELINE_BUTTON_CLASS_NAME = 'add-timeline-button'; +const actionTimelineToHide: ActionTimelineToShow[] = ['createFrom']; + const AddTimelineButtonComponent: React.FC = ({ timelineId }) => { const [showActions, setShowActions] = useState(false); const [showTimelineModal, setShowTimelineModal] = useState(false); @@ -83,7 +86,9 @@ const AddTimelineButtonComponent: React.FC = ({ - {showTimelineModal ? : null} + {showTimelineModal ? ( + + ) : null} ); }; 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 c1b30f3e68cf4..4aa6fd469de26 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 @@ -83,13 +83,15 @@ export const getTimelinesTableColumns = ({ }), ...getExtendedColumns(showExtendedColumns), ...getIconHeaderColumns({ timelineType }), - ...getActionsColumns({ - actionTimelineToShow, - deleteTimelines, - enableExportTimelineDownloader, - onOpenDeleteTimelineModal, - onOpenTimeline, - }), + ...(actionTimelineToShow.length + ? getActionsColumns({ + actionTimelineToShow, + deleteTimelines, + enableExportTimelineDownloader, + onOpenDeleteTimelineModal, + onOpenTimeline, + }) + : []), ]; }; From 58d4164b6ca798201fa8e551eacb8e26063521d0 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 29 Apr 2021 10:47:17 +0200 Subject: [PATCH 60/70] [Discover][doc] Add view document (#92472) * Adds info about the doc viewer of Discover * Merges with the context view doc Co-authored-by: Gail Chappell --- docs/discover/context.asciidoc | 60 ------------------ .../discover-context-load-newer-documents.png | Bin 8009 -> 5577 bytes .../images/discover-view-single-document.png | Bin 0 -> 66824 bytes docs/discover/images/expand-icon.png | Bin 0 -> 339 bytes docs/discover/view-document.asciidoc | 56 ++++++++++++++++ docs/redirects.asciidoc | 5 ++ docs/user/discover.asciidoc | 6 +- 7 files changed, 64 insertions(+), 63 deletions(-) delete mode 100644 docs/discover/context.asciidoc create mode 100644 docs/discover/images/discover-view-single-document.png create mode 100644 docs/discover/images/expand-icon.png create mode 100644 docs/discover/view-document.asciidoc diff --git a/docs/discover/context.asciidoc b/docs/discover/context.asciidoc deleted file mode 100644 index 9131c81781fc8..0000000000000 --- a/docs/discover/context.asciidoc +++ /dev/null @@ -1,60 +0,0 @@ -[[discover-document-context]] -== View surrounding documents - -Once you've narrowed your search to a specific event in *Discover*, -you can inspect the documents that occurred -immediately before and after the event. -To view the surrounding documents, your index pattern must contain time-based events. - -. In the document table, click the expand icon (>). -. Click *View surrounding documents.* -+ -In the context view, documents are sorted by the time field specified in the index pattern -and displayed using the same set of columns as the *Discover* view from which -the context was opened. The anchor document is highlighted in blue. -+ -[role="screenshot"] -image::images/discover-context.png[Image showing context view feature, with anchor documents highlighted in blue] -+ -The filters you applied in *Discover* are carried over to the context view. Pinned -filters remain active, while normal filters are copied in a disabled state. - -+ -[role="screenshot"] -image::images/discover-context-filters-inactive.png[Filter in context view] - -. To find the documents of interest, add filters. - -. To increase the number of documents that surround the anchor document, click *Load*. -By default, five documents are added with each click. -+ -[role="screenshot"] -image::images/discover-context-load-newer-documents.png[Load button and the number of documents to load] - - -[float] -[[configure-context-ContextView]] -=== Configure the context view - -Configure the appearance and behavior in *Advanced Settings*. - -. Open the main menu, then click *Stack Management > Advanced Settings*. -. Search for `context`, then edit the settings. -+ -[horizontal] -`context:defaultSize`:: The number of documents to display by default. -`context:step`:: The default number of documents to load with each button click. The default is 5. -`context:tieBreakerFields`:: The field to use for tiebreaking in case of equal time field values. -The default is the `_doc` field. -+ -You can enter a comma-separated list of field -names, which is checked in sequence for suitability when a context is -displayed. The first suitable field is used as the tiebreaking -field. A field is suitable if the field exists and is sortable in the index -pattern the context is based on. -+ -Although not required, it is recommended to only -use fields that have {ref}/doc-values.html[doc values] enabled to achieve -good performance and avoid unnecessary {ref}/modules-fielddata.html[field -data] usage. Common examples for suitable fields include log line numbers, -monotonically increasing counters and high-precision timestamps. \ No newline at end of file diff --git a/docs/discover/images/discover-context-load-newer-documents.png b/docs/discover/images/discover-context-load-newer-documents.png index 9c4a74d39b3c9c6efd98b231d4d28b17424dd011..65d168f5ca4af1e9803498ff68dfd3ba0fce0402 100644 GIT binary patch literal 5577 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 literal 8009 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 diff --git a/docs/discover/images/discover-view-single-document.png b/docs/discover/images/discover-view-single-document.png new file mode 100644 index 0000000000000000000000000000000000000000..d803acc49ce24fc80e58239a3baf88557f9b7870 GIT binary patch literal 66824 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

+y5bsOATUg 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 literal 0 HcmV?d00001 diff --git a/docs/discover/view-document.asciidoc b/docs/discover/view-document.asciidoc new file mode 100644 index 0000000000000..b471e238c1a0f --- /dev/null +++ b/docs/discover/view-document.asciidoc @@ -0,0 +1,56 @@ +[[discover-view-document]] +== View a document + +Once you've found a document of interest in *Discover*, you have two more ways to +view it: in a view by itself or in context with surrounding documents. + +[float] +[[discover-view-single-document]] +=== View a single document + +Access a single document so you can bookmark it and share the link. + +. In the document table, click the expand icon (>). +. In the expanded view, click **View single document**. ++ +You can view the document in two ways. The **Table** view displays the document fields row-by-row. +The **JSON** (JavaScript Object Notation) view allows you to look at how {es} returns the document. ++ +[role="screenshot"] +image::images/discover-view-single-document.png[Discover single document view] ++ +The link is valid for the time the document is available in Elasticsearch. To create a customized view of the document, +you can create <>. + +[float] +[[discover-view-surrounding-documents]] +=== View surrounding documents + +To inspect the documents that occurred immediately before and after a document, +your index pattern must contain time-based events. + +. In the document table, click the expand icon (>). +. In the expanded view, click **View surrounding documents**. ++ +Documents are displayed using the same set of columns as the *Discover* view from which +the context was opened. The anchor document is highlighted in blue. ++ +[role="screenshot"] +image::images/discover-context.png[Image showing context view feature, with anchor documents highlighted in blue] ++ +The filters you applied in *Discover* are carried over to the context view. Pinned +filters remain active, while normal filters are copied in a disabled state. ++ +[role="screenshot"] +image::images/discover-context-filters-inactive.png[Filter in context view] + +. To find the documents of interest, add filters. + +. To increase the number of documents that surround the anchor document, click *Load*. +By default, five documents are added with each click. ++ +[role="screenshot"] +image::images/discover-context-load-newer-documents.png[Load button and the number of documents to load] +. To configure the number of documents to display and +the number of documents to load with each button click, go to *Stack Management > Advanced Settings* +and edit the <>. diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 15b353223452a..4aedb0f516b20 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -297,3 +297,8 @@ This content has moved. refer to <>. == Search your data This content has moved. refer to <>. + +[role="exclude",id="discover-document-context"] +== View surrounding documents + +This content has moved. refer to <>. diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index 4565f7c9616c3..0a8fefa3c0693 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -173,9 +173,9 @@ image:images/document-table-expanded.png[Table view with document expanded] hover of its name for filters and other controls. . To view documents that occurred before or after the event you are looking at, click -<>. +<>. -. For direct access to a particular document, click **View single document**. +. For direct access to a particular document, click **<>**. + You can bookmark this document and share the link. @@ -243,7 +243,7 @@ the table columns that display by default, and more. -- -include::{kib-repo-dir}/discover/context.asciidoc[] +include::{kib-repo-dir}/discover/view-document.asciidoc[] include::{kib-repo-dir}/discover/search-for-relevance.asciidoc[] From b322f20cfe32e952b522235fe1733942c935454d Mon Sep 17 00:00:00 2001 From: Katrin Freihofner Date: Thu, 29 Apr 2021 11:56:48 +0200 Subject: [PATCH 61/70] Polish: wording, experimental badge and button sizes (#98655) --- .../shared/exploratory_view/components/empty_view.tsx | 6 +++--- .../components/shared/exploratory_view/header/header.tsx | 3 +++ .../series_editor/columns/series_filter.tsx | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx index e7a6874870fb2..ea69a371cedae 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/empty_view.tsx @@ -77,18 +77,18 @@ export const EMPTY_LABEL = i18n.translate('xpack.observability.expView.seriesBui export const CHOOSE_REPORT_DEFINITION = i18n.translate( 'xpack.observability.expView.seriesBuilder.emptyReportDefinition', { - defaultMessage: 'Please choose a report definition below to visualize.', + defaultMessage: 'Select a report type to create a visualization.', } ); export const SELECT_REPORT_TYPE_BELOW = i18n.translate( 'xpack.observability.expView.seriesBuilder.selectReportType.empty', { - defaultMessage: 'Please Select a report type below to define visualization.', + defaultMessage: 'Select a report type to create a visualization.', } ); const SELECTED_DATA_TYPE_FOR_REPORT = i18n.translate( 'xpack.observability.expView.reportType.selectDataType', - { defaultMessage: 'Please Select a data type below to start building a series.' } + { defaultMessage: 'Select a data type to create a visualization.' } ); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx index 7ac0961532b65..9d051e89e1a38 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx @@ -36,6 +36,9 @@ export function ExploratoryViewHeader({ seriesId, lensAttributes }: Props) { defaultMessage: 'Exploratory view', })}{' '} { setSeries(seriesId, { ...urlSeries, filters: undefined }); }} - size="xs" + size="s" > {i18n.translate('xpack.observability.expView.seriesEditor.clearFilter', { defaultMessage: 'Clear filters', From 6cde07e3c9b57420f0e89ee608e3bf51f5b676e6 Mon Sep 17 00:00:00 2001 From: Peter Pisljar Date: Thu, 29 Apr 2021 12:01:58 +0200 Subject: [PATCH 62/70] adding reference extraction/injection for canvas functions (#95895) --- .../functions/external/saved_lens.ts | 26 +++++++++++++++++++ .../functions/external/saved_map.ts | 26 +++++++++++++++++++ .../functions/external/saved_search.ts | 26 +++++++++++++++++++ .../functions/external/saved_visualization.ts | 26 +++++++++++++++++++ 4 files changed, 104 insertions(+) diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.ts index 5a019f2dd9a2b..3ffa20de55aaf 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.ts @@ -17,6 +17,7 @@ import { EmbeddableExpression, } from '../../expression_types'; import { getFunctionHelp } from '../../../i18n'; +import { SavedObjectReference } from '../../../../../../src/core/types'; interface Arguments { id: string; @@ -90,5 +91,30 @@ export function savedLens(): ExpressionFunctionDefinition< generatedAt: Date.now(), }; }, + extract(state) { + const refName = 'savedLens.id'; + const references: SavedObjectReference[] = [ + { + name: refName, + type: 'lens', + id: state.id[0] as string, + }, + ]; + return { + state: { + ...state, + id: [refName], + }, + references, + }; + }, + + inject(state, references) { + const reference = references.find((ref) => ref.name === 'savedLens.id'); + if (reference) { + state.id[0] = reference.id; + } + return state; + }, }; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts index 1c17929c704c8..395c6e112f753 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts @@ -15,6 +15,7 @@ import { } from '../../expression_types'; import { getFunctionHelp } from '../../../i18n'; import { MapEmbeddableInput } from '../../../../../plugins/maps/public/embeddable'; +import { SavedObjectReference } from '../../../../../../src/core/types'; interface Arguments { id: string; @@ -103,5 +104,30 @@ export function savedMap(): ExpressionFunctionDefinition< generatedAt: Date.now(), }; }, + extract(state) { + const refName = 'savedMap.id'; + const references: SavedObjectReference[] = [ + { + name: refName, + type: 'map', + id: state.id[0] as string, + }, + ]; + return { + state: { + ...state, + id: [refName], + }, + references, + }; + }, + + inject(state, references) { + const reference = references.find((ref) => ref.name === 'savedMap.id'); + if (reference) { + state.id[0] = reference.id; + } + return state; + }, }; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_search.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_search.ts index 8d7e1da95487e..8e3ec9dc9e186 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_search.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_search.ts @@ -16,6 +16,7 @@ import { import { buildEmbeddableFilters } from '../../../public/lib/build_embeddable_filters'; import { ExpressionValueFilter } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; +import { SavedObjectReference } from '../../../../../../src/core/types'; interface Arguments { id: string; @@ -53,5 +54,30 @@ export function savedSearch(): ExpressionFunctionDefinition< generatedAt: Date.now(), }; }, + extract(state) { + const refName = 'savedSearch.id'; + const references: SavedObjectReference[] = [ + { + name: refName, + type: 'search', + id: state.id[0] as string, + }, + ]; + return { + state: { + ...state, + id: [refName], + }, + references, + }; + }, + + inject(state, references) { + const reference = references.find((ref) => ref.name === 'savedSearch.id'); + if (reference) { + state.id[0] = reference.id; + } + return state; + }, }; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.ts index 796038540262d..92ddf6420f0e0 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.ts @@ -15,6 +15,7 @@ import { import { getQueryFilters } from '../../../public/lib/build_embeddable_filters'; import { ExpressionValueFilter, TimeRange as TimeRangeArg, SeriesStyle } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; +import { SavedObjectReference } from '../../../../../../src/core/types'; interface Arguments { id: string; @@ -103,5 +104,30 @@ export function savedVisualization(): ExpressionFunctionDefinition< generatedAt: Date.now(), }; }, + extract(state) { + const refName = 'savedVisualization.id'; + const references: SavedObjectReference[] = [ + { + name: refName, + type: 'visualization', + id: state.id[0] as string, + }, + ]; + return { + state: { + ...state, + id: [refName], + }, + references, + }; + }, + + inject(state, references) { + const reference = references.find((ref) => ref.name === 'savedVisualization.id'); + if (reference) { + state.id[0] = reference.id; + } + return state; + }, }; } From 1d5fa6f53a2328ac8857ccd6ac61c38612593037 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Thu, 29 Apr 2021 06:24:57 -0500 Subject: [PATCH 63/70] [Workplace Search] Remove MVP and expose beta (#98680) * Remove references to `/alpha` route * Delete all files and references to them for the existing MVP * Remove conditional MVP Personal dashboard link * Remove extra Route component * Wrap header actions in EuiHeaderLinks for mobile This is an add-on and should be reviewed with white space changes hidden. --- .../layout/account_header/account_header.tsx | 11 +- .../layout/kibana_header_actions.test.tsx | 13 -- .../layout/kibana_header_actions.tsx | 63 +++--- .../components/layout/nav.test.tsx | 4 +- .../components/layout/nav.tsx | 3 +- .../workplace_search/index.test.tsx | 4 +- .../applications/workplace_search/index.tsx | 15 +- .../applications/workplace_search/routes.ts | 1 - .../views/overview_mvp/__mocks__/index.ts | 8 - .../__mocks__/overview_logic.mock.ts | 37 ---- .../views/overview_mvp/index.ts | 8 - .../overview_mvp/onboarding_card.test.tsx | 55 ------ .../views/overview_mvp/onboarding_card.tsx | 92 --------- .../overview_mvp/onboarding_steps.test.tsx | 135 ------------- .../views/overview_mvp/onboarding_steps.tsx | 182 ------------------ .../overview_mvp/organization_stats.test.tsx | 35 ---- .../views/overview_mvp/organization_stats.tsx | 79 -------- .../views/overview_mvp/overview.test.tsx | 66 ------- .../views/overview_mvp/overview.tsx | 93 --------- .../views/overview_mvp/overview_logic.test.ts | 72 ------- .../views/overview_mvp/overview_logic.ts | 114 ----------- .../views/overview_mvp/recent_activity.scss | 38 ---- .../overview_mvp/recent_activity.test.tsx | 79 -------- .../views/overview_mvp/recent_activity.tsx | 126 ------------ .../overview_mvp/statistic_card.test.tsx | 34 ---- .../views/overview_mvp/statistic_card.tsx | 45 ----- 26 files changed, 38 insertions(+), 1374 deletions(-) delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/__mocks__/index.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/__mocks__/overview_logic.mock.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/index.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_card.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_card.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_steps.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_steps.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/organization_stats.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/organization_stats.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview_logic.test.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview_logic.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/recent_activity.scss delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/recent_activity.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/recent_activity.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/statistic_card.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/statistic_card.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/account_header/account_header.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/account_header/account_header.tsx index 87ee108f21c73..92a936fcdbefe 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/account_header/account_header.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/account_header/account_header.tsx @@ -27,12 +27,7 @@ import { getWorkplaceSearchUrl } from '../../../../shared/enterprise_search_url' import { EuiButtonEmptyTo } from '../../../../shared/react_router_helpers'; import { AppLogic } from '../../../app_logic'; import { WORKPLACE_SEARCH_TITLE, ACCOUNT_NAV } from '../../../constants'; -import { - ALPHA_PATH, - PERSONAL_SOURCES_PATH, - LOGOUT_ROUTE, - KIBANA_ACCOUNT_ROUTE, -} from '../../../routes'; +import { PERSONAL_SOURCES_PATH, LOGOUT_ROUTE, KIBANA_ACCOUNT_ROUTE } from '../../../routes'; export const AccountHeader: React.FC = () => { const [isPopoverOpen, setPopover] = useState(false); @@ -84,9 +79,7 @@ export const AccountHeader: React.FC = () => { - {isAdmin && ( - {ACCOUNT_NAV.ORG_DASHBOARD} - )} + {isAdmin && {ACCOUNT_NAV.ORG_DASHBOARD}} { expect(wrapper.find('[data-test-subj="PersonalDashboardButton"]').prop('to')).toEqual( '/p/sources' ); - expect(wrapper.find('[data-test-subj="PersonalDashboardMVPButton"]')).toHaveLength(0); }); it('renders a link to the search application', () => { @@ -41,15 +39,4 @@ describe('WorkplaceSearchHeaderActions', () => { 'http://localhost:3002/ws/search' ); }); - - it('renders an MVP link back to the legacy dashboard on the MVP page', () => { - window.history.pushState({}, 'Overview', WORKPLACE_SEARCH_URL_PREFIX); - externalUrl.enterpriseSearchUrl = ENT_SEARCH_URL; - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="PersonalDashboardMVPButton"]').prop('href')).toEqual( - `${ENT_SEARCH_URL}/ws/sources` - ); - expect(wrapper.find('[data-test-subj="PersonalDashboardButton"]')).toHaveLength(0); - }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx index 7d594ce66aea1..0875e8cf0ec08 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/kibana_header_actions.tsx @@ -7,52 +7,39 @@ import React from 'react'; -import { EuiButtonEmpty, EuiText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiButtonEmpty, EuiText, EuiFlexGroup, EuiFlexItem, EuiHeaderLinks } from '@elastic/eui'; import { externalUrl, getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; import { EuiButtonEmptyTo } from '../../../shared/react_router_helpers'; -import { NAV, WORKPLACE_SEARCH_URL_PREFIX } from '../../constants'; +import { NAV } from '../../constants'; import { PERSONAL_SOURCES_PATH } from '../../routes'; export const WorkplaceSearchHeaderActions: React.FC = () => { if (!externalUrl.enterpriseSearchUrl) return null; - const isMVP = window.location.pathname.endsWith(WORKPLACE_SEARCH_URL_PREFIX); - - const personalDashboardMVPButton = ( - - {NAV.PERSONAL_DASHBOARD} - - ); - - const personalDashboardButton = ( - - {NAV.PERSONAL_DASHBOARD} - - ); - return ( - - {isMVP ? personalDashboardMVPButton : personalDashboardButton} - - - {NAV.SEARCH} - - - + + + + + {NAV.PERSONAL_DASHBOARD} + + + + + {NAV.SEARCH} + + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx index bac27bddf075a..8f37f608f4e28 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.test.tsx @@ -13,8 +13,6 @@ import { shallow } from 'enzyme'; import { SideNav, SideNavLink } from '../../../shared/layout'; -import { ALPHA_PATH } from '../../routes'; - import { WorkplaceSearchNav } from './'; describe('WorkplaceSearchNav', () => { @@ -22,7 +20,7 @@ describe('WorkplaceSearchNav', () => { const wrapper = shallow(); expect(wrapper.find(SideNav)).toHaveLength(1); - expect(wrapper.find(SideNavLink).first().prop('to')).toEqual(ALPHA_PATH); + expect(wrapper.find(SideNavLink).first().prop('to')).toEqual('/'); expect(wrapper.find(SideNavLink)).toHaveLength(6); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx index 51cdcc688e682..fb3c8556029b2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx @@ -13,7 +13,6 @@ import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; import { SideNav, SideNavLink } from '../../../shared/layout'; import { NAV } from '../../constants'; import { - ALPHA_PATH, SOURCES_PATH, SECURITY_PATH, ROLE_MAPPINGS_PATH, @@ -33,7 +32,7 @@ export const WorkplaceSearchNav: React.FC = ({ settingsSubNav, }) => ( - + {NAV.OVERVIEW} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx index a2c0ec18def4b..2c2859e8f4427 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx @@ -18,7 +18,7 @@ import { Layout } from '../shared/layout'; import { WorkplaceSearchHeaderActions } from './components/layout'; import { SourceAdded } from './views/content_sources/components/source_added'; import { ErrorState } from './views/error_state'; -import { Overview as OverviewMVP } from './views/overview_mvp'; +import { Overview } from './views/overview'; import { SetupGuide } from './views/setup_guide'; import { WorkplaceSearch, WorkplaceSearchUnconfigured, WorkplaceSearchConfigured } from './'; @@ -61,7 +61,7 @@ describe('WorkplaceSearchConfigured', () => { const wrapper = shallow(); expect(wrapper.find(Layout).first().prop('readOnlyMode')).toBeFalsy(); - expect(wrapper.find(OverviewMVP)).toHaveLength(1); + expect(wrapper.find(Overview)).toHaveLength(1); expect(mockKibanaValues.setChromeIsVisible).toHaveBeenCalledWith(true); expect(mockKibanaValues.renderHeaderActions).toHaveBeenCalledWith(WorkplaceSearchHeaderActions); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx index a8d6fc54f7924..54085a9cd4467 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx @@ -20,7 +20,6 @@ import { NotFound } from '../shared/not_found'; import { AppLogic } from './app_logic'; import { WorkplaceSearchNav, WorkplaceSearchHeaderActions } from './components/layout'; import { - ALPHA_PATH, GROUPS_PATH, SETUP_GUIDE_PATH, SOURCES_PATH, @@ -38,7 +37,6 @@ import { ErrorState } from './views/error_state'; import { GroupsRouter } from './views/groups'; import { GroupSubNav } from './views/groups/components/group_sub_nav'; import { Overview } from './views/overview'; -import { Overview as OverviewMVP } from './views/overview_mvp'; import { RoleMappingsRouter } from './views/role_mappings'; import { Security } from './views/security'; import { SettingsRouter } from './views/settings'; @@ -92,7 +90,13 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { - {errorConnecting ? : } + {errorConnecting ? ( + + ) : ( + } restrictWidth readOnlyMode={readOnlyMode}> + + + )} @@ -108,11 +112,6 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { - - } restrictWidth readOnlyMode={readOnlyMode}> - - - } />} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts index 59e43b103db40..0a6b6ef89b2a4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts @@ -60,7 +60,6 @@ export const GROUPS_PATH = '/groups'; export const GROUP_PATH = `${GROUPS_PATH}/:groupId`; export const GROUP_SOURCE_PRIORITIZATION_PATH = `${GROUPS_PATH}/:groupId/source_prioritization`; -export const ALPHA_PATH = '/alpha'; export const SOURCES_PATH = '/sources'; export const PERSONAL_SOURCES_PATH = `${PERSONAL_PATH}${SOURCES_PATH}`; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/__mocks__/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/__mocks__/index.ts deleted file mode 100644 index 3a1bbfcae75ba..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/__mocks__/index.ts +++ /dev/null @@ -1,8 +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. - */ - -export { setMockValues, mockOverviewValues, mockActions } from './overview_logic.mock'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/__mocks__/overview_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/__mocks__/overview_logic.mock.ts deleted file mode 100644 index 787354974cb31..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/__mocks__/overview_logic.mock.ts +++ /dev/null @@ -1,37 +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 { DEFAULT_INITIAL_APP_DATA } from '../../../../../../common/__mocks__'; -import { setMockValues as setMockKeaValues, setMockActions } from '../../../../__mocks__/kea.mock'; - -const { workplaceSearch: mockAppValues } = DEFAULT_INITIAL_APP_DATA; - -export const mockOverviewValues = { - accountsCount: 0, - activityFeed: [], - canCreateContentSources: false, - hasOrgSources: false, - hasUsers: false, - isOldAccount: false, - pendingInvitationsCount: 0, - personalSourcesCount: 0, - sourcesCount: 0, - dataLoading: true, -}; - -export const mockActions = { - initializeOverview: jest.fn(() => ({})), -}; - -const mockValues = { ...mockOverviewValues, ...mockAppValues, isFederatedAuth: true }; - -setMockActions({ ...mockActions }); -setMockKeaValues({ ...mockValues }); - -export const setMockValues = (values: object) => { - setMockKeaValues({ ...mockValues, ...values }); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/index.ts deleted file mode 100644 index 69c843fe3821e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/index.ts +++ /dev/null @@ -1,8 +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. - */ - -export { Overview } from './overview'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_card.test.tsx deleted file mode 100644 index 68dece976a09c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_card.test.tsx +++ /dev/null @@ -1,55 +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 '../../../__mocks__/kea.mock'; -import '../../../__mocks__/enterprise_search_url.mock'; -import { mockTelemetryActions } from '../../../__mocks__'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiEmptyPrompt, EuiButton, EuiButtonEmpty } from '@elastic/eui'; - -import { OnboardingCard } from './onboarding_card'; - -const cardProps = { - title: 'My card', - icon: 'icon', - description: 'this is a card', - actionTitle: 'action', - testSubj: 'actionButton', -}; - -describe('OnboardingCard', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); - }); - - it('renders an action button', () => { - const wrapper = shallow(); - const prompt = wrapper.find(EuiEmptyPrompt).dive(); - - expect(prompt.find(EuiButton)).toHaveLength(1); - expect(prompt.find(EuiButtonEmpty)).toHaveLength(0); - - const button = prompt.find('[data-test-subj="actionButton"]'); - expect(button.prop('href')).toBe('http://localhost:3002/ws/some_path'); - - button.simulate('click'); - expect(mockTelemetryActions.sendWorkplaceSearchTelemetry).toHaveBeenCalled(); - }); - - it('renders an empty button when onboarding is completed', () => { - const wrapper = shallow(); - const prompt = wrapper.find(EuiEmptyPrompt).dive(); - - expect(prompt.find(EuiButton)).toHaveLength(0); - expect(prompt.find(EuiButtonEmpty)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_card.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_card.tsx deleted file mode 100644 index 2f8d06b71fc27..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_card.tsx +++ /dev/null @@ -1,92 +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 { useActions } from 'kea'; - -import { - EuiButton, - EuiButtonEmpty, - EuiFlexItem, - EuiPanel, - EuiEmptyPrompt, - IconType, - EuiButtonProps, - EuiButtonEmptyProps, - EuiLinkProps, -} from '@elastic/eui'; - -import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; -import { TelemetryLogic } from '../../../shared/telemetry'; - -interface OnboardingCardProps { - title: React.ReactNode; - icon: React.ReactNode; - description: React.ReactNode; - actionTitle: React.ReactNode; - testSubj: string; - actionPath?: string; - complete?: boolean; -} - -export const OnboardingCard: React.FC = ({ - title, - icon, - description, - actionTitle, - testSubj, - actionPath, - complete, -}) => { - const { sendWorkplaceSearchTelemetry } = useActions(TelemetryLogic); - - const onClick = () => - sendWorkplaceSearchTelemetry({ - action: 'clicked', - metric: 'onboarding_card_button', - }); - const buttonActionProps = actionPath - ? { - onClick, - href: getWorkplaceSearchUrl(actionPath), - target: '_blank', - 'data-test-subj': testSubj, - } - : { - 'data-test-subj': testSubj, - }; - - const emptyButtonProps = { - ...buttonActionProps, - } as EuiButtonEmptyProps & EuiLinkProps; - const fillButtonProps = { - ...buttonActionProps, - color: 'secondary', - fill: true, - } as EuiButtonProps & EuiLinkProps; - - return ( - - - {title}} - body={description} - actions={ - complete ? ( - {actionTitle} - ) : ( - {actionTitle} - ) - } - /> - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_steps.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_steps.test.tsx deleted file mode 100644 index 5059533519a6f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_steps.test.tsx +++ /dev/null @@ -1,135 +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 { mockTelemetryActions } from '../../../__mocks__'; -import { setMockValues } from './__mocks__'; -import './__mocks__/overview_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { SOURCES_PATH, USERS_PATH } from '../../routes'; - -import { OnboardingCard } from './onboarding_card'; -import { OnboardingSteps, OrgNameOnboarding } from './onboarding_steps'; - -const account = { - id: '1', - isAdmin: true, - canCreatePersonalSources: true, - groups: [], - isCurated: false, - canCreateInvitations: true, -}; - -describe('OnboardingSteps', () => { - describe('Shared Sources', () => { - it('renders 0 sources state', () => { - setMockValues({ canCreateContentSources: true }); - const wrapper = shallow(); - - expect(wrapper.find(OnboardingCard)).toHaveLength(1); - expect(wrapper.find(OnboardingCard).prop('actionPath')).toBe(SOURCES_PATH); - expect(wrapper.find(OnboardingCard).prop('description')).toBe( - 'Add shared sources for your organization to start searching.' - ); - }); - - it('renders completed sources state', () => { - setMockValues({ sourcesCount: 2, hasOrgSources: true }); - const wrapper = shallow(); - - expect(wrapper.find(OnboardingCard).prop('description')).toEqual( - 'You have added 2 shared sources. Happy searching.' - ); - }); - - it('disables link when the user cannot create sources', () => { - setMockValues({ canCreateContentSources: false }); - const wrapper = shallow(); - - expect(wrapper.find(OnboardingCard).prop('actionPath')).toBe(undefined); - }); - }); - - describe('Users & Invitations', () => { - it('renders 0 users when not on federated auth', () => { - setMockValues({ - isFederatedAuth: false, - account, - accountsCount: 0, - hasUsers: false, - }); - const wrapper = shallow(); - - expect(wrapper.find(OnboardingCard)).toHaveLength(2); - expect(wrapper.find(OnboardingCard).last().prop('actionPath')).toBe(USERS_PATH); - expect(wrapper.find(OnboardingCard).last().prop('description')).toEqual( - 'Invite your colleagues into this organization to search with you.' - ); - }); - - it('renders completed users state', () => { - setMockValues({ - isFederatedAuth: false, - account, - accountsCount: 1, - hasUsers: true, - }); - const wrapper = shallow(); - - expect(wrapper.find(OnboardingCard).last().prop('description')).toEqual( - 'Nice, you’ve invited colleagues to search with you.' - ); - }); - - it('disables link when the user cannot create invitations', () => { - setMockValues({ - isFederatedAuth: false, - account: { - ...account, - canCreateInvitations: false, - }, - }); - const wrapper = shallow(); - expect(wrapper.find(OnboardingCard).last().prop('actionPath')).toBe(undefined); - }); - }); - - describe('Org Name', () => { - it('renders button to change name', () => { - setMockValues({ - organization: { - name: 'foo', - defaultOrgName: 'foo', - }, - }); - const wrapper = shallow(); - - const button = wrapper - .find(OrgNameOnboarding) - .dive() - .find('[data-test-subj="orgNameChangeButton"]'); - - button.simulate('click'); - expect(mockTelemetryActions.sendWorkplaceSearchTelemetry).toHaveBeenCalled(); - }); - - it('hides card when name has been changed', () => { - setMockValues({ - organization: { - name: 'foo', - defaultOrgName: 'bar', - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(OrgNameOnboarding)).toHaveLength(0); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_steps.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_steps.tsx deleted file mode 100644 index fc3998fcdfeec..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/onboarding_steps.tsx +++ /dev/null @@ -1,182 +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 { useValues, useActions } from 'kea'; - -import { - EuiSpacer, - EuiButtonEmpty, - EuiTitle, - EuiPanel, - EuiIcon, - EuiFlexGrid, - EuiFlexItem, - EuiFlexGroup, - EuiButtonEmptyProps, - EuiLinkProps, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; -import { TelemetryLogic } from '../../../shared/telemetry'; -import { AppLogic } from '../../app_logic'; -import sharedSourcesIcon from '../../components/shared/assets/source_icons/share_circle.svg'; -import { ContentSection } from '../../components/shared/content_section'; -import { SOURCES_PATH, USERS_PATH, ORG_SETTINGS_PATH } from '../../routes'; - -import { OnboardingCard } from './onboarding_card'; -import { OverviewLogic } from './overview_logic'; - -const SOURCES_TITLE = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.overviewOnboardingSourcesCard.title', - { defaultMessage: 'Shared sources' } -); - -const USERS_TITLE = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.title', - { defaultMessage: 'Users & invitations' } -); - -const ONBOARDING_SOURCES_CARD_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.overviewOnboardingSourcesCard.description', - { defaultMessage: 'Add shared sources for your organization to start searching.' } -); - -const USERS_CARD_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.overviewUsersCard.title', - { defaultMessage: 'Nice, you’ve invited colleagues to search with you.' } -); - -const ONBOARDING_USERS_CARD_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.description', - { defaultMessage: 'Invite your colleagues into this organization to search with you.' } -); - -export const OnboardingSteps: React.FC = () => { - const { - isFederatedAuth, - organization: { name, defaultOrgName }, - account: { isCurated, canCreateInvitations }, - } = useValues(AppLogic); - - const { - hasUsers, - hasOrgSources, - canCreateContentSources, - accountsCount, - sourcesCount, - } = useValues(OverviewLogic); - - const accountsPath = - !isFederatedAuth && (canCreateInvitations || isCurated) ? USERS_PATH : undefined; - const sourcesPath = canCreateContentSources || isCurated ? SOURCES_PATH : undefined; - - const SOURCES_CARD_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.description', - { - defaultMessage: - 'You have added {sourcesCount, number} shared {sourcesCount, plural, one {source} other {sources}}. Happy searching.', - values: { sourcesCount }, - } - ); - - return ( - - - 0 ? 'more' : '' }, - } - )} - actionPath={sourcesPath} - complete={hasOrgSources} - /> - {!isFederatedAuth && ( - 0 ? 'more' : '' }, - } - )} - actionPath={accountsPath} - complete={hasUsers} - /> - )} - - {name === defaultOrgName && ( - <> - - - - )} - - ); -}; - -export const OrgNameOnboarding: React.FC = () => { - const { sendWorkplaceSearchTelemetry } = useActions(TelemetryLogic); - - const onClick = () => - sendWorkplaceSearchTelemetry({ - action: 'clicked', - metric: 'org_name_change_button', - }); - - const buttonProps = { - onClick, - target: '_blank', - color: 'primary', - href: getWorkplaceSearchUrl(ORG_SETTINGS_PATH), - 'data-test-subj': 'orgNameChangeButton', - } as EuiButtonEmptyProps & EuiLinkProps; - - return ( - - - - - - - -

- -

- - - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/organization_stats.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/organization_stats.test.tsx deleted file mode 100644 index 110557ac4087a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/organization_stats.test.tsx +++ /dev/null @@ -1,35 +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 { setMockValues } from './__mocks__'; -import './__mocks__/overview_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiFlexGrid } from '@elastic/eui'; - -import { OrganizationStats } from './organization_stats'; -import { StatisticCard } from './statistic_card'; - -describe('OrganizationStats', () => { - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(StatisticCard)).toHaveLength(2); - expect(wrapper.find(EuiFlexGrid).prop('columns')).toEqual(2); - }); - - it('renders additional cards for federated auth', () => { - setMockValues({ isFederatedAuth: false }); - const wrapper = shallow(); - - expect(wrapper.find(StatisticCard)).toHaveLength(4); - expect(wrapper.find(EuiFlexGrid).prop('columns')).toEqual(4); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/organization_stats.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/organization_stats.tsx deleted file mode 100644 index 525035030b8cc..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/organization_stats.tsx +++ /dev/null @@ -1,79 +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 { useValues } from 'kea'; - -import { EuiFlexGrid } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { AppLogic } from '../../app_logic'; -import { ContentSection } from '../../components/shared/content_section'; -import { SOURCES_PATH, USERS_PATH } from '../../routes'; - -import { OverviewLogic } from './overview_logic'; -import { StatisticCard } from './statistic_card'; - -export const OrganizationStats: React.FC = () => { - const { isFederatedAuth } = useValues(AppLogic); - - const { sourcesCount, pendingInvitationsCount, accountsCount, personalSourcesCount } = useValues( - OverviewLogic - ); - - return ( - - } - headerSpacer="m" - > - - - {!isFederatedAuth && ( - <> - - - - )} - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview.test.tsx deleted file mode 100644 index 19c893bec81ea..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview.test.tsx +++ /dev/null @@ -1,66 +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 '../../../__mocks__/react_router_history.mock'; -import './__mocks__/overview_logic.mock'; -import { mockActions, setMockValues } from './__mocks__'; - -import React from 'react'; - -import { shallow, mount } from 'enzyme'; - -import { Loading } from '../../../shared/loading'; -import { ViewContentHeader } from '../../components/shared/view_content_header'; - -import { OnboardingSteps } from './onboarding_steps'; -import { OrganizationStats } from './organization_stats'; -import { Overview } from './overview'; -import { RecentActivity } from './recent_activity'; - -describe('Overview', () => { - describe('non-happy-path states', () => { - it('isLoading', () => { - const wrapper = shallow(); - - expect(wrapper.find(Loading)).toHaveLength(1); - }); - }); - - describe('happy-path states', () => { - it('calls initialize function', async () => { - mount(); - - expect(mockActions.initializeOverview).toHaveBeenCalled(); - }); - - it('renders onboarding state', () => { - setMockValues({ dataLoading: false }); - const wrapper = shallow(); - - expect(wrapper.find(ViewContentHeader)).toHaveLength(1); - expect(wrapper.find(OnboardingSteps)).toHaveLength(1); - expect(wrapper.find(OrganizationStats)).toHaveLength(1); - expect(wrapper.find(RecentActivity)).toHaveLength(1); - }); - - it('renders when onboarding complete', () => { - setMockValues({ - dataLoading: false, - hasUsers: true, - hasOrgSources: true, - isOldAccount: true, - organization: { - name: 'foo', - defaultOrgName: 'bar', - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(OnboardingSteps)).toHaveLength(0); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview.tsx deleted file mode 100644 index 6bf84b585da80..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview.tsx +++ /dev/null @@ -1,93 +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. - */ - -// TODO: Remove EuiPage & EuiPageBody before exposing full app - -import React, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { EuiPage, EuiPageBody, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; -import { Loading } from '../../../shared/loading'; -import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; -import { AppLogic } from '../../app_logic'; -import { ProductButton } from '../../components/shared/product_button'; -import { ViewContentHeader } from '../../components/shared/view_content_header'; - -import { OnboardingSteps } from './onboarding_steps'; -import { OrganizationStats } from './organization_stats'; -import { OverviewLogic } from './overview_logic'; -import { RecentActivity } from './recent_activity'; - -const ONBOARDING_HEADER_TITLE = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.overviewOnboardingHeader.title', - { defaultMessage: 'Get started with Workplace Search' } -); - -const HEADER_TITLE = i18n.translate('xpack.enterpriseSearch.workplaceSearch.overviewHeader.title', { - defaultMessage: 'Organization overview', -}); - -const ONBOARDING_HEADER_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.overviewOnboardingHeader.description', - { defaultMessage: 'Complete the following to set up your organization.' } -); - -const HEADER_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.overviewHeader.description', - { defaultMessage: "Your organizations's statistics and activity" } -); - -export const Overview: React.FC = () => { - const { - organization: { name: orgName, defaultOrgName }, - } = useValues(AppLogic); - - const { initializeOverview } = useActions(OverviewLogic); - const { dataLoading, hasUsers, hasOrgSources, isOldAccount } = useValues(OverviewLogic); - - useEffect(() => { - initializeOverview(); - }, [initializeOverview]); - - // TODO: Remove div wrapper once the Overview page is using the full Layout - if (dataLoading) { - return ( -
- -
- ); - } - - const hideOnboarding = hasUsers && hasOrgSources && isOldAccount && orgName !== defaultOrgName; - - const headerTitle = hideOnboarding ? HEADER_TITLE : ONBOARDING_HEADER_TITLE; - const headerDescription = hideOnboarding ? HEADER_DESCRIPTION : ONBOARDING_HEADER_DESCRIPTION; - - return ( - - - - - - } - /> - {!hideOnboarding && } - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview_logic.test.ts deleted file mode 100644 index 75a41216ffbb7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview_logic.test.ts +++ /dev/null @@ -1,72 +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 { LogicMounter, mockHttpValues } from '../../../__mocks__'; -import { mockOverviewValues } from './__mocks__'; - -import { OverviewLogic } from './overview_logic'; - -describe('OverviewLogic', () => { - const { mount } = new LogicMounter(OverviewLogic); - const { http } = mockHttpValues; - - beforeEach(() => { - jest.clearAllMocks(); - mount(); - }); - - it('has expected default values', () => { - expect(OverviewLogic.values).toEqual(mockOverviewValues); - }); - - describe('setServerData', () => { - const feed = [{ foo: 'bar' }] as any; - - const data = { - accountsCount: 1, - activityFeed: feed, - canCreateContentSources: true, - hasOrgSources: true, - hasUsers: true, - isOldAccount: true, - pendingInvitationsCount: 1, - personalSourcesCount: 1, - sourcesCount: 1, - }; - - beforeEach(() => { - OverviewLogic.actions.setServerData(data); - }); - - it('will set `dataLoading` to false', () => { - expect(OverviewLogic.values.dataLoading).toEqual(false); - }); - - it('will set server values', () => { - expect(OverviewLogic.values.hasUsers).toEqual(true); - expect(OverviewLogic.values.hasOrgSources).toEqual(true); - expect(OverviewLogic.values.canCreateContentSources).toEqual(true); - expect(OverviewLogic.values.isOldAccount).toEqual(true); - expect(OverviewLogic.values.sourcesCount).toEqual(1); - expect(OverviewLogic.values.pendingInvitationsCount).toEqual(1); - expect(OverviewLogic.values.accountsCount).toEqual(1); - expect(OverviewLogic.values.personalSourcesCount).toEqual(1); - expect(OverviewLogic.values.activityFeed).toEqual(feed); - }); - }); - - describe('initializeOverview', () => { - it('calls API and sets values', async () => { - const setServerDataSpy = jest.spyOn(OverviewLogic.actions, 'setServerData'); - - await OverviewLogic.actions.initializeOverview(); - - expect(http.get).toHaveBeenCalledWith('/api/workplace_search/overview'); - expect(setServerDataSpy).toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview_logic.ts deleted file mode 100644 index 7d8bc95529483..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/overview_logic.ts +++ /dev/null @@ -1,114 +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 { kea, MakeLogicType } from 'kea'; - -import { flashAPIErrors } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; - -import { FeedActivity } from './recent_activity'; - -interface OverviewServerData { - hasUsers: boolean; - hasOrgSources: boolean; - canCreateContentSources: boolean; - isOldAccount: boolean; - sourcesCount: number; - pendingInvitationsCount: number; - accountsCount: number; - personalSourcesCount: number; - activityFeed: FeedActivity[]; -} - -interface OverviewActions { - setServerData(serverData: OverviewServerData): OverviewServerData; - initializeOverview(): void; -} - -interface OverviewValues extends OverviewServerData { - dataLoading: boolean; -} - -export const OverviewLogic = kea>({ - path: ['enterprise_search', 'workplace_search', 'overview_logic'], - actions: { - setServerData: (serverData) => serverData, - initializeOverview: () => null, - }, - reducers: { - hasUsers: [ - false, - { - setServerData: (_, { hasUsers }) => hasUsers, - }, - ], - hasOrgSources: [ - false, - { - setServerData: (_, { hasOrgSources }) => hasOrgSources, - }, - ], - canCreateContentSources: [ - false, - { - setServerData: (_, { canCreateContentSources }) => canCreateContentSources, - }, - ], - isOldAccount: [ - false, - { - setServerData: (_, { isOldAccount }) => isOldAccount, - }, - ], - sourcesCount: [ - 0, - { - setServerData: (_, { sourcesCount }) => sourcesCount, - }, - ], - pendingInvitationsCount: [ - 0, - { - setServerData: (_, { pendingInvitationsCount }) => pendingInvitationsCount, - }, - ], - accountsCount: [ - 0, - { - setServerData: (_, { accountsCount }) => accountsCount, - }, - ], - personalSourcesCount: [ - 0, - { - setServerData: (_, { personalSourcesCount }) => personalSourcesCount, - }, - ], - activityFeed: [ - [], - { - setServerData: (_, { activityFeed }) => activityFeed, - }, - ], - dataLoading: [ - true, - { - setServerData: () => false, - }, - ], - }, - listeners: ({ actions }) => ({ - initializeOverview: async () => { - try { - const response = await HttpLogic.values.http.get('/api/workplace_search/overview'); - actions.setServerData(response); - } catch (e) { - flashAPIErrors(e); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/recent_activity.scss b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/recent_activity.scss deleted file mode 100644 index 822ba64c91237..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/recent_activity.scss +++ /dev/null @@ -1,38 +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. - */ - -.activity { - display: flex; - justify-content: space-between; - padding: $euiSizeM; - font-size: $euiFontSizeS; - - &--error { - font-weight: $euiFontWeightSemiBold; - color: $euiColorDanger; - background: rgba($euiColorDanger, .1); - - &__label { - margin-left: $euiSizeS * 1.75; - font-weight: $euiFontWeightRegular; - text-decoration: underline; - opacity: .7; - } - } - - &__message { - flex-grow: 1; - } - - &__date { - flex-grow: 0; - } - - & + & { - border-top: $euiBorderThin; - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/recent_activity.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/recent_activity.test.tsx deleted file mode 100644 index 7213526c8864a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/recent_activity.test.tsx +++ /dev/null @@ -1,79 +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 { mockTelemetryActions } from '../../../__mocks__'; -import { setMockValues } from './__mocks__'; -import './__mocks__/overview_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiEmptyPrompt, EuiLink } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { RecentActivity, RecentActivityItem } from './recent_activity'; - -const organization = { name: 'foo', defaultOrgName: 'bar' }; - -const activityFeed = [ - { - id: 'demo', - sourceId: 'd2d2d23d', - message: 'was successfully connected', - target: 'http://localhost:3002/ws/org/sources', - timestamp: '2020-06-24 16:34:16', - }, -]; - -describe('RecentActivity', () => { - it('renders with no activityFeed data', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); - - // Branch coverage - renders without error for custom org name - setMockValues({ organization }); - shallow(); - }); - - it('renders an activityFeed with links', () => { - setMockValues({ activityFeed }); - const wrapper = shallow(); - const activity = wrapper.find(RecentActivityItem).dive(); - - expect(activity).toHaveLength(1); - - const link = activity.find('[data-test-subj="viewSourceDetailsLink"]'); - link.simulate('click'); - expect(mockTelemetryActions.sendWorkplaceSearchTelemetry).toHaveBeenCalled(); - }); - - it('renders activity item error state', () => { - const props = { ...activityFeed[0], status: 'error' }; - const wrapper = shallow(); - - expect(wrapper.find('.activity--error')).toHaveLength(1); - expect(wrapper.find('.activity--error__label')).toHaveLength(1); - expect(wrapper.find(EuiLink).prop('color')).toEqual('danger'); - }); - - it('renders recent activity message for default org name', () => { - setMockValues({ - organization: { - name: 'foo', - defaultOrgName: 'foo', - }, - }); - const wrapper = shallow(); - const emptyPrompt = wrapper.find(EuiEmptyPrompt).dive(); - - expect(emptyPrompt.find(FormattedMessage).prop('defaultMessage')).toEqual( - 'Your organization has no recent activity' - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/recent_activity.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/recent_activity.tsx deleted file mode 100644 index 43d3f880feef4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/recent_activity.tsx +++ /dev/null @@ -1,126 +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 { useValues, useActions } from 'kea'; -import moment from 'moment'; - -import { EuiEmptyPrompt, EuiLink, EuiPanel, EuiSpacer, EuiLinkProps } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; -import { TelemetryLogic } from '../../../shared/telemetry'; -import { AppLogic } from '../../app_logic'; -import { ContentSection } from '../../components/shared/content_section'; -import { RECENT_ACTIVITY_TITLE } from '../../constants'; -import { SOURCE_DETAILS_PATH, getContentSourcePath } from '../../routes'; - -import { OverviewLogic } from './overview_logic'; - -import './recent_activity.scss'; - -export interface FeedActivity { - status?: string; - id: string; - message: string; - timestamp: string; - sourceId: string; -} - -export const RecentActivity: React.FC = () => { - const { - organization: { name, defaultOrgName }, - } = useValues(AppLogic); - - const { activityFeed } = useValues(OverviewLogic); - - return ( - - - {activityFeed.length > 0 ? ( - <> - {activityFeed.map((props: FeedActivity, index) => ( - - ))} - - ) : ( - <> - - - {name === defaultOrgName ? ( - - ) : ( - - )} - - } - /> - - - )} - - - ); -}; - -export const RecentActivityItem: React.FC = ({ - id, - status, - message, - timestamp, - sourceId, -}) => { - const { sendWorkplaceSearchTelemetry } = useActions(TelemetryLogic); - - const onClick = () => - sendWorkplaceSearchTelemetry({ - action: 'clicked', - metric: 'recent_activity_source_details_link', - }); - - const linkProps = { - onClick, - target: '_blank', - href: getWorkplaceSearchUrl(getContentSourcePath(SOURCE_DETAILS_PATH, sourceId, true)), - external: true, - color: status === 'error' ? 'danger' : 'primary', - 'data-test-subj': 'viewSourceDetailsLink', - } as EuiLinkProps; - - return ( -
-
- - {id} {message} - {status === 'error' && ( - - {' '} - - - )} - -
-
{moment.utc(timestamp).fromNow()}
-
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/statistic_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/statistic_card.test.tsx deleted file mode 100644 index ff1d69e406830..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/statistic_card.test.tsx +++ /dev/null @@ -1,34 +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 '../../../__mocks__/enterprise_search_url.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiCard } from '@elastic/eui'; - -import { StatisticCard } from './statistic_card'; - -const props = { - title: 'foo', -}; - -describe('StatisticCard', () => { - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiCard)).toHaveLength(1); - }); - - it('renders clickable card', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiCard).prop('href')).toBe('http://localhost:3002/ws/foo'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/statistic_card.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/statistic_card.tsx deleted file mode 100644 index 346debb1c5251..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview_mvp/statistic_card.tsx +++ /dev/null @@ -1,45 +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 { EuiCard, EuiFlexItem, EuiTitle, EuiTextColor } from '@elastic/eui'; - -import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; - -interface StatisticCardProps { - title: string; - count?: number; - actionPath?: string; -} - -export const StatisticCard: React.FC = ({ title, count = 0, actionPath }) => { - const linkProps = actionPath - ? { - href: getWorkplaceSearchUrl(actionPath), - target: '_blank', - rel: 'noopener', - } - : {}; - // TODO: When we port this destination to Kibana, we'll want to create a EuiReactRouterCard component (see shared/react_router_helpers/eui_link.tsx) - - return ( - - - {count} - - } - /> - - ); -}; From 2d5ff8ab70c0682a9cef1118dd0301cf28868e59 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 29 Apr 2021 05:41:46 -0600 Subject: [PATCH 64/70] [Security Solution] [Cases] Cases UI Plugin for RAC (#97646) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: spalger Co-authored-by: Steph Milovic Co-authored-by: Michael Olorunnisola Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> Co-authored-by: David Sánchez Co-authored-by: Spencer Co-authored-by: Dmitry --- docs/developer/plugin-list.asciidoc | 2 +- packages/kbn-optimizer/limits.yml | 3 +- tsconfig.json | 2 +- tsconfig.refs.json | 1 + x-pack/plugins/cases/README.md | 188 ++++- x-pack/plugins/cases/common/api/index.ts | 1 + .../plugins/cases/common/api/runtime_types.ts | 8 +- x-pack/plugins/cases/common/constants.ts | 7 +- x-pack/plugins/cases/common/index.ts | 10 + .../common/ui/index.ts} | 2 +- .../containers => cases/common/ui}/types.ts | 75 +- x-pack/plugins/cases/images/all_cases.png | Bin 0 -> 260832 bytes .../cases/images/all_cases_selector_modal.png | Bin 0 -> 387277 bytes x-pack/plugins/cases/images/case_view.png | Bin 0 -> 554933 bytes x-pack/plugins/cases/images/configure.png | Bin 0 -> 285182 bytes x-pack/plugins/cases/images/create.png | Bin 0 -> 300838 bytes x-pack/plugins/cases/images/logo.png | Bin 0 -> 4692 bytes x-pack/plugins/cases/images/recent_cases.png | Bin 0 -> 50037 bytes x-pack/plugins/cases/kibana.json | 5 +- x-pack/plugins/cases/public/common/errors.ts | 39 + .../common/lib/kibana/__mocks__/index.ts | 30 + .../cases/public/common/lib/kibana/hooks.ts | 132 ++++ .../cases/public/common/lib/kibana/index.ts | 10 + .../common/lib/kibana/kibana_react.mock.ts | 35 + .../public/common/lib/kibana/kibana_react.ts | 18 + .../public/common/lib/kibana/services.ts | 42 ++ .../plugins/cases/public/common/mock/index.ts | 8 + .../cases/public/common/mock/match_media.ts | 16 + .../public/common/mock/test_providers.tsx | 61 ++ .../cases/public/common/shared_imports.ts | 33 + .../plugins/cases/public/common/test_utils.ts | 12 + .../cases/public/common/translations.ts | 259 +++++++ .../cases/public/components/__mock__/form.ts | 50 ++ .../public/components/__mock__/router.ts | 40 ++ .../public/components/__mock__/timeline.tsx | 33 + .../components/add_comment/index.test.tsx | 48 +- .../public}/components/add_comment/index.tsx | 17 +- .../public}/components/add_comment/schema.tsx | 4 +- .../components/add_comment/translations.ts | 8 + .../public}/components/all_cases/actions.tsx | 4 +- .../all_cases/all_cases_generic.tsx | 321 +++++++++ .../components/all_cases/columns.test.tsx | 2 +- .../public}/components/all_cases/columns.tsx | 154 +++- .../public/components/all_cases/count.tsx | 58 ++ .../components/all_cases/expanded_row.tsx | 8 +- .../public/components/all_cases/header.tsx | 66 ++ .../public}/components/all_cases/helpers.ts | 2 +- .../components/all_cases/index.test.tsx | 230 +++--- .../public/components/all_cases/index.tsx | 23 + .../components/all_cases/nav_buttons.tsx | 55 ++ .../all_cases/selector_modal/index.test.tsx | 83 +++ .../all_cases/selector_modal/index.tsx | 69 ++ .../all_cases/status_filter.test.tsx | 3 +- .../components/all_cases/status_filter.tsx | 3 +- .../public/components/all_cases/table.tsx | 148 ++++ .../all_cases/table_filters.test.tsx | 4 +- .../components/all_cases/table_filters.tsx | 42 +- .../components/all_cases/translations.ts | 91 +++ .../public/components/all_cases/types.ts | 26 + .../components/all_cases/utility_bar.tsx | 173 +++++ .../public}/components/bulk_actions/index.tsx | 4 +- .../components/bulk_actions/translations.ts | 2 +- .../components/callout/callout.test.tsx | 90 +++ .../public/components/callout/callout.tsx | 54 ++ .../components/callout/helpers.test.tsx | 29 + .../public/components/callout/helpers.tsx | 22 + .../public/components/callout/index.test.tsx | 217 ++++++ .../cases/public/components/callout/index.tsx | 103 +++ .../public/components/callout/translations.ts | 24 + .../cases/public/components/callout/types.ts | 13 + .../case_action_bar/actions.test.tsx | 2 +- .../components/case_action_bar/actions.tsx | 25 +- .../case_action_bar/helpers.test.ts | 2 +- .../components/case_action_bar/helpers.ts | 2 +- .../components/case_action_bar/index.test.tsx | 2 +- .../components/case_action_bar/index.tsx | 4 +- .../status_context_menu.test.tsx | 2 +- .../case_action_bar/status_context_menu.tsx | 2 +- .../components/case_header_page/index.tsx | 14 + .../case_settings/sync_alerts_switch.test.tsx | 0 .../case_settings/sync_alerts_switch.tsx | 2 +- .../components/case_view/helpers.test.tsx | 58 ++ .../public/components/case_view/helpers.ts | 22 + .../components/case_view/index.test.tsx | 349 +++++---- .../public/components/case_view/index.tsx | 538 ++++++++++++++ .../components/case_view/translations.ts | 130 ++++ .../configure_cases/__mock__/index.tsx | 3 +- .../configure_cases/button.test.tsx | 14 +- .../components/configure_cases/button.tsx | 30 +- .../configure_cases/closure_options.test.tsx | 2 +- .../configure_cases/closure_options.tsx | 0 .../closure_options_radio.test.tsx | 2 +- .../configure_cases/closure_options_radio.tsx | 0 .../configure_cases/connectors.test.tsx | 4 +- .../components/configure_cases/connectors.tsx | 2 +- .../connectors_dropdown.test.tsx | 2 +- .../configure_cases/connectors_dropdown.tsx | 2 +- .../configure_cases/field_mapping.test.tsx | 4 +- .../configure_cases/field_mapping.tsx | 2 +- .../field_mapping_row_static.tsx | 6 +- .../components/configure_cases/index.test.tsx | 16 +- .../components/configure_cases/index.tsx | 30 +- .../configure_cases/mapping.test.tsx | 2 +- .../components/configure_cases/mapping.tsx | 0 .../configure_cases/translations.ts | 111 ++- .../components/configure_cases/utils.test.tsx | 0 .../components/configure_cases/utils.ts | 2 +- .../components/confirm_delete_case/index.tsx | 0 .../confirm_delete_case/translations.ts | 28 +- .../connector_selector/form.test.tsx | 6 +- .../components/connector_selector/form.tsx | 4 +- .../public}/components/connectors/card.tsx | 2 +- .../connectors/case/alert_fields.tsx | 6 +- .../connectors/case/cases_dropdown.tsx | 0 .../connectors/case/existing_case.tsx | 2 +- .../components/connectors/case/index.ts | 2 +- .../connectors/case/translations.ts | 30 +- .../components/connectors/case/types.ts | 0 .../public}/components/connectors/config.ts | 2 +- .../connectors/connectors_registry.ts | 32 +- .../components/connectors/fields_form.tsx | 2 +- .../public}/components/connectors/index.ts | 4 +- .../connectors/jira/__mocks__/api.ts | 0 .../components/connectors/jira/api.test.ts | 2 +- .../public}/components/connectors/jira/api.ts | 2 +- .../connectors/jira/case_fields.test.tsx | 3 +- .../connectors/jira/case_fields.tsx | 4 +- .../components/connectors/jira/index.ts | 2 +- .../connectors/jira/search_issues.tsx | 4 +- .../connectors/jira/translations.ts | 41 +- .../components/connectors/jira/types.ts | 0 .../use_get_fields_by_issue_type.test.tsx | 4 +- .../jira/use_get_fields_by_issue_type.tsx | 2 +- .../jira/use_get_issue_types.test.tsx | 4 +- .../connectors/jira/use_get_issue_types.tsx | 2 +- .../connectors/jira/use_get_issues.test.tsx | 4 +- .../connectors/jira/use_get_issues.tsx | 2 +- .../jira/use_get_single_issue.test.tsx | 4 +- .../connectors/jira/use_get_single_issue.tsx | 2 +- .../public}/components/connectors/mock.ts | 0 .../connectors/resilient/__mocks__/api.ts | 0 .../components/connectors/resilient/api.ts | 2 +- .../connectors/resilient/case_fields.test.tsx | 2 +- .../connectors/resilient/case_fields.tsx | 4 +- .../components/connectors/resilient/index.ts | 2 +- .../connectors/resilient/translations.ts | 17 +- .../components/connectors/resilient/types.ts | 0 .../resilient/use_get_incident_types.test.tsx | 4 +- .../resilient/use_get_incident_types.tsx | 2 +- .../resilient/use_get_severity.test.tsx | 4 +- .../connectors/resilient/use_get_severity.tsx | 2 +- .../connectors/servicenow/__mocks__/api.ts | 0 .../connectors/servicenow/api.test.ts | 2 +- .../components/connectors/servicenow/api.ts | 2 +- .../connectors/servicenow/helpers.ts | 0 .../components/connectors/servicenow/index.ts | 5 +- .../servicenow_itsm_case_fields.test.tsx | 2 +- .../servicenow_itsm_case_fields.tsx | 7 +- .../servicenow_sir_case_fields.test.tsx | 2 +- .../servicenow/servicenow_sir_case_fields.tsx | 7 +- .../connectors/servicenow/translations.ts | 75 ++ .../components/connectors/servicenow/types.ts | 0 .../servicenow/use_get_choices.test.tsx | 6 +- .../connectors/servicenow/use_get_choices.tsx | 2 +- .../public}/components/connectors/types.ts | 4 +- .../components/create/connector.test.tsx | 46 +- .../public}/components/create/connector.tsx | 28 +- .../components/create/description.test.tsx | 2 +- .../public}/components/create/description.tsx | 5 +- .../public/components/create/flyout.test.tsx} | 37 +- .../cases/public/components/create/flyout.tsx | 71 ++ .../public}/components/create/form.test.tsx | 2 +- .../public}/components/create/form.tsx | 18 +- .../components/create/form_context.test.tsx | 53 +- .../components/create/form_context.tsx | 34 +- .../public/components/create/index.test.tsx | 126 ++++ .../cases/public/components/create/index.tsx | 90 +++ .../public}/components/create/mock.ts | 3 +- .../optional_field_label/index.test.tsx | 0 .../create/optional_field_label/index.tsx | 2 +- .../public}/components/create/schema.tsx | 4 +- .../components/create/submit_button.test.tsx | 28 +- .../components/create/submit_button.tsx | 2 +- .../create/sync_alerts_toggle.test.tsx | 2 +- .../components/create/sync_alerts_toggle.tsx | 2 +- .../public}/components/create/tags.test.tsx | 2 +- .../public}/components/create/tags.tsx | 2 +- .../public}/components/create/title.test.tsx | 2 +- .../public}/components/create/title.tsx | 2 +- .../public/components/create/translations.ts | 26 + .../components/edit_connector/helpers.ts | 0 .../components/edit_connector/index.test.tsx | 6 +- .../components/edit_connector/index.tsx | 5 +- .../components/edit_connector/schema.tsx | 2 +- .../components/edit_connector/translations.ts | 4 +- .../__snapshots__/empty_value.test.tsx.snap | 7 + .../empty_value/empty_value.test.tsx | 166 +++++ .../public/components/empty_value/index.tsx | 49 ++ .../components/empty_value}/translations.ts | 5 +- .../components/filter_popover/index.tsx | 0 .../__snapshots__/index.test.tsx.snap | 9 + .../components/formatted_date/index.test.tsx | 170 +++++ .../components/formatted_date/index.tsx | 173 +++++ .../formatted_date/maybe_date.test.ts | 46 ++ .../components/formatted_date/maybe_date.ts | 22 + .../editable_title.test.tsx.snap | 27 + .../__snapshots__/index.test.tsx.snap | 40 ++ .../__snapshots__/title.test.tsx.snap | 19 + .../header_page/editable_title.test.tsx | 172 +++++ .../components/header_page/editable_title.tsx | 123 ++++ .../components/header_page/index.test.tsx | 157 ++++ .../public/components/header_page/index.tsx | 128 ++++ .../components/header_page/title.test.tsx | 39 + .../public/components/header_page/title.tsx | 54 ++ .../components/header_page/translations.ts | 22 + .../public/components/header_page/types.ts | 20 + .../components/insert_timeline/index.test.tsx | 73 ++ .../components/insert_timeline/index.tsx | 24 + .../__snapshots__/index.test.tsx.snap | 20 + .../components/link_icon/index.test.tsx | 95 +++ .../public/components/link_icon/index.tsx | 106 +++ .../cases/public/components/links/index.tsx | 70 ++ .../public/components/links/translations.ts | 14 + .../localized_date_tooltip/index.test.tsx | 49 ++ .../localized_date_tooltip/index.tsx | 48 ++ .../components/markdown_editor/editor.tsx | 62 ++ .../components/markdown_editor/eui_form.tsx | 65 ++ .../components/markdown_editor/index.tsx | 11 + .../markdown_editor/markdown_link.tsx | 35 + .../markdown_editor/renderer.test.tsx | 63 ++ .../components/markdown_editor/renderer.tsx | 40 ++ .../markdown_editor/translations.ts | 19 + .../components/markdown_editor/types.ts | 30 + .../components/markdown_editor/use_plugins.ts | 40 ++ .../public/components/panel/index.test.tsx | 17 + .../cases/public/components/panel/index.tsx | 37 + .../components/property_actions/index.tsx | 0 .../property_actions/translations.ts | 9 +- .../components/recent_cases/filters/index.tsx | 4 +- .../recent_cases/icon_with_count.tsx | 42 ++ .../components/recent_cases/index.test.tsx | 81 +++ .../public/components/recent_cases/index.tsx | 91 +++ .../recent_cases/loading_placeholders.tsx | 27 + .../recent_cases/no_cases/index.test.tsx | 26 + .../recent_cases/no_cases/index.tsx | 26 + .../components/recent_cases/recent_cases.tsx | 96 +++ .../components/recent_cases/translations.ts | 46 ++ .../public}/components/recent_cases/types.ts | 0 .../public}/components/status/button.test.tsx | 2 +- .../public}/components/status/button.tsx | 2 +- .../public}/components/status/config.ts | 4 +- .../public}/components/status/index.ts | 0 .../public}/components/status/stats.test.tsx | 2 +- .../public}/components/status/stats.tsx | 2 +- .../public}/components/status/status.test.tsx | 2 +- .../public}/components/status/status.tsx | 2 +- .../public/components/status/translations.ts | 69 ++ .../public}/components/status/types.ts | 7 +- .../__snapshots__/index.test.tsx.snap | 11 + .../public/components/subtitle/index.test.tsx | 70 ++ .../public/components/subtitle/index.tsx | 75 ++ .../components/tag_list/index.test.tsx | 11 +- .../public}/components/tag_list/index.tsx | 2 +- .../public}/components/tag_list/schema.tsx | 2 +- .../public}/components/tag_list/tags.tsx | 0 .../components/tag_list/translations.ts | 11 +- .../components/timeline_context/index.tsx | 65 ++ .../timeline_context/use_timeline_context.ts | 13 + .../create_case_modal.test.tsx | 76 ++ .../create_case_modal.tsx | 46 +- .../use_create_case_modal/index.test.tsx | 61 +- .../use_create_case_modal/index.tsx | 7 +- .../use_push_to_service/helpers.tsx | 4 +- .../use_push_to_service/index.test.tsx | 17 +- .../components/use_push_to_service/index.tsx | 35 +- .../use_push_to_service/translations.ts | 40 +- .../user_action_tree/helpers.test.tsx | 43 +- .../components/user_action_tree/helpers.tsx | 138 +--- .../user_action_tree/index.test.tsx | 151 ++-- .../components/user_action_tree/index.tsx | 77 +- .../components/user_action_tree/schema.ts | 4 +- .../user_action_tree/translations.ts | 50 +- .../user_action_alert_comment_event.test.tsx | 19 +- .../user_action_alert_comment_event.tsx | 27 +- .../user_action_avatar.test.tsx | 0 .../user_action_tree/user_action_avatar.tsx | 0 .../user_action_content_toolbar.test.tsx | 5 +- .../user_action_content_toolbar.tsx | 7 +- .../user_action_copy_link.test.tsx | 18 +- .../user_action_copy_link.tsx | 20 +- .../user_action_markdown.test.tsx | 73 ++ .../user_action_tree/user_action_markdown.tsx | 4 +- .../user_action_move_to_reference.test.tsx | 0 .../user_action_move_to_reference.tsx | 0 .../user_action_property_actions.test.tsx | 23 +- .../user_action_property_actions.tsx | 0 .../user_action_show_alert.test.tsx | 12 - .../user_action_show_alert.tsx | 0 .../user_action_timestamp.test.tsx | 2 +- .../user_action_timestamp.tsx | 2 +- .../user_action_username.test.tsx | 0 .../user_action_tree/user_action_username.tsx | 0 .../user_action_username_with_avatar.test.tsx | 0 .../user_action_username_with_avatar.tsx | 0 .../components/user_list/index.test.tsx | 6 +- .../public}/components/user_list/index.tsx | 0 .../components/user_list/translations.ts | 2 +- .../__snapshots__/utility_bar.test.tsx.snap | 30 + .../utility_bar_action.test.tsx.snap | 9 + .../utility_bar_group.test.tsx.snap | 9 + .../utility_bar_section.test.tsx.snap | 11 + .../utility_bar_text.test.tsx.snap | 7 + .../public/components/utility_bar/index.ts | 13 + .../public/components/utility_bar/styles.tsx | 144 ++++ .../utility_bar/utility_bar.test.tsx | 109 +++ .../components/utility_bar/utility_bar.tsx | 20 + .../utility_bar/utility_bar_action.test.tsx | 36 + .../utility_bar/utility_bar_action.tsx | 97 +++ .../utility_bar/utility_bar_group.test.tsx | 26 + .../utility_bar/utility_bar_group.tsx | 20 + .../utility_bar/utility_bar_section.test.tsx | 28 + .../utility_bar/utility_bar_section.tsx | 20 + .../utility_bar/utility_bar_spacer.tsx | 20 + .../utility_bar/utility_bar_text.test.tsx | 24 + .../utility_bar/utility_bar_text.tsx | 21 + .../public/components/wrappers/index.tsx | 26 + .../public}/containers/__mocks__/api.ts | 2 +- .../public}/containers/api.test.tsx | 8 +- .../cases => cases/public}/containers/api.ts | 10 +- .../containers/configure/__mocks__/api.ts | 2 +- .../public}/containers/configure/api.test.ts | 6 +- .../public}/containers/configure/api.ts | 15 +- .../public}/containers/configure/mock.ts | 2 +- .../containers/configure/translations.ts | 9 +- .../public}/containers/configure/types.ts | 2 +- .../configure/use_action_types.test.tsx | 1 + .../containers/configure/use_action_types.tsx | 10 +- .../configure/use_configure.test.tsx | 28 +- .../containers/configure/use_configure.tsx | 47 +- .../configure/use_connectors.test.tsx | 1 + .../containers/configure/use_connectors.tsx | 45 +- .../public}/containers/constants.ts | 0 .../cases => cases/public}/containers/mock.ts | 20 +- .../public}/containers/translations.ts | 38 +- .../plugins/cases/public/containers/types.ts | 8 + .../containers/use_bulk_update_case.test.tsx | 3 +- .../containers/use_bulk_update_case.tsx | 21 +- .../containers/use_delete_cases.test.tsx | 3 +- .../public}/containers/use_delete_cases.tsx | 22 +- .../use_get_action_license.test.tsx | 1 + .../containers/use_get_action_license.tsx | 13 +- .../public}/containers/use_get_case.test.tsx | 1 + .../public}/containers/use_get_case.tsx | 13 +- .../use_get_case_user_actions.test.tsx | 1 + .../containers/use_get_case_user_actions.tsx | 22 +- .../public}/containers/use_get_cases.test.tsx | 3 +- .../public}/containers/use_get_cases.tsx | 85 ++- .../containers/use_get_cases_status.test.tsx | 1 + .../containers/use_get_cases_status.tsx | 13 +- .../containers/use_get_reporters.test.tsx | 1 + .../public}/containers/use_get_reporters.tsx | 18 +- .../public}/containers/use_get_tags.test.tsx | 1 + .../public}/containers/use_get_tags.tsx | 13 +- .../containers/use_messages_storage.test.tsx | 97 +++ .../containers/use_messages_storage.tsx | 64 ++ .../public}/containers/use_post_case.test.tsx | 3 +- .../public}/containers/use_post_case.tsx | 15 +- .../containers/use_post_comment.test.tsx | 3 +- .../public}/containers/use_post_comment.tsx | 17 +- .../use_post_push_to_service.test.tsx | 3 +- .../containers/use_post_push_to_service.tsx | 24 +- .../containers/use_update_case.test.tsx | 1 + .../public}/containers/use_update_case.tsx | 22 +- .../containers/use_update_comment.test.tsx | 1 + .../public}/containers/use_update_comment.tsx | 13 +- .../public}/containers/utils.test.ts | 46 +- .../public}/containers/utils.ts | 21 +- x-pack/plugins/cases/public/index.tsx | 17 + .../cases/public/methods/get_all_cases.tsx | 17 + .../methods/get_all_cases_selector_modal.tsx | 17 + .../cases/public/methods/get_case_view.tsx | 17 + .../public/methods/get_configure_cases.tsx | 17 + .../cases/public/methods/get_create_case.tsx | 17 + .../cases/public/methods/get_recent_cases.tsx | 17 + x-pack/plugins/cases/public/methods/index.ts | 13 + x-pack/plugins/cases/public/plugin.ts | 93 +++ x-pack/plugins/cases/public/types.ts | 51 ++ .../cases/public/utils/use_mount_appended.ts | 31 + .../client/alerts/update_status.test.ts | 2 +- .../cases/server/client/cases/create.test.ts | 7 +- .../cases/server/client/cases/create.ts | 2 +- .../plugins/cases/server/client/cases/get.ts | 2 +- .../plugins/cases/server/client/cases/mock.ts | 2 +- .../plugins/cases/server/client/cases/push.ts | 2 +- .../cases/server/client/cases/types.ts | 2 +- .../cases/server/client/cases/update.test.ts | 2 +- .../cases/server/client/cases/update.ts | 2 +- .../cases/server/client/cases/utils.test.ts | 4 +- .../cases/server/client/cases/utils.ts | 14 +- x-pack/plugins/cases/server/client/client.ts | 2 +- .../cases/server/client/comments/add.test.ts | 2 +- .../cases/server/client/comments/add.ts | 7 +- .../client/configure/get_fields.test.ts | 2 +- .../server/client/configure/get_fields.ts | 2 +- .../client/configure/get_mappings.test.ts | 27 +- .../server/client/configure/get_mappings.ts | 8 +- .../cases/server/client/configure/mock.ts | 6 +- .../cases/server/client/configure/utils.ts | 2 +- x-pack/plugins/cases/server/client/types.ts | 2 +- .../cases/server/client/user_actions/get.ts | 2 +- .../server/common/models/commentable_case.ts | 2 +- .../plugins/cases/server/common/utils.test.ts | 2 +- x-pack/plugins/cases/server/common/utils.ts | 8 +- .../server/connectors/case/index.test.ts | 2 +- .../cases/server/connectors/case/index.ts | 7 +- .../cases/server/connectors/case/schema.ts | 2 +- .../cases/server/connectors/case/types.ts | 2 +- .../plugins/cases/server/connectors/index.ts | 2 +- .../jira/external_service_formatter.test.ts | 2 +- .../jira/external_service_formatter.ts | 2 +- .../external_service_formatter.test.ts | 2 +- .../resilient/external_service_formatter.ts | 2 +- .../connectors/servicenow/itsm_formatter.ts | 2 +- .../servicenow/itsm_formmater.test.ts | 2 +- .../servicenow/sir_formatter.test.ts | 2 +- .../connectors/servicenow/sir_formatter.ts | 2 +- .../plugins/cases/server/connectors/types.ts | 2 +- x-pack/plugins/cases/server/plugin.ts | 2 +- .../api/__fixtures__/mock_saved_objects.ts | 22 +- .../routes/api/__mocks__/request_responses.ts | 2 +- .../api/cases/comments/delete_all_comments.ts | 3 +- .../api/cases/comments/delete_comment.test.ts | 2 +- .../api/cases/comments/find_comments.ts | 4 +- .../api/cases/comments/get_all_comment.ts | 4 +- .../api/cases/comments/get_comment.test.ts | 2 +- .../routes/api/cases/comments/get_comment.ts | 4 +- .../api/cases/comments/patch_comment.test.ts | 4 +- .../api/cases/comments/patch_comment.ts | 4 +- .../api/cases/comments/post_comment.test.ts | 4 +- .../routes/api/cases/comments/post_comment.ts | 3 +- .../api/cases/configure/get_configure.test.ts | 3 +- .../api/cases/configure/get_configure.ts | 4 +- .../cases/configure/get_connectors.test.ts | 2 +- .../api/cases/configure/get_connectors.ts | 5 +- .../cases/configure/patch_configure.test.ts | 3 +- .../api/cases/configure/patch_configure.ts | 4 +- .../cases/configure/post_configure.test.ts | 3 +- .../api/cases/configure/post_configure.ts | 4 +- .../routes/api/cases/delete_cases.test.ts | 2 +- .../server/routes/api/cases/delete_cases.ts | 2 +- .../routes/api/cases/find_cases.test.ts | 2 +- .../server/routes/api/cases/find_cases.ts | 4 +- .../server/routes/api/cases/get_case.test.ts | 4 +- .../cases/server/routes/api/cases/get_case.ts | 2 +- .../server/routes/api/cases/helpers.test.ts | 2 +- .../cases/server/routes/api/cases/helpers.ts | 12 +- .../routes/api/cases/patch_cases.test.ts | 2 +- .../server/routes/api/cases/patch_cases.ts | 4 +- .../server/routes/api/cases/post_case.test.ts | 4 +- .../server/routes/api/cases/post_case.ts | 4 +- .../server/routes/api/cases/push_case.test.ts | 2 +- .../server/routes/api/cases/push_case.ts | 4 +- .../api/cases/reporters/get_reporters.ts | 4 +- .../api/cases/status/get_status.test.ts | 4 +- .../routes/api/cases/status/get_status.ts | 4 +- .../api/cases/sub_case/delete_sub_cases.ts | 2 +- .../api/cases/sub_case/find_sub_cases.ts | 4 +- .../routes/api/cases/sub_case/get_sub_case.ts | 4 +- .../api/cases/sub_case/patch_sub_cases.ts | 4 +- .../server/routes/api/cases/tags/get_tags.ts | 2 +- .../user_actions/get_all_user_actions.ts | 2 +- .../cases/server/routes/api/utils.test.ts | 2 +- .../plugins/cases/server/routes/api/utils.ts | 2 +- .../server/saved_object_types/migrations.ts | 2 +- .../cases/server/scripts/sub_cases/index.ts | 4 +- .../server/services/alerts/index.test.ts | 2 +- .../cases/server/services/alerts/index.ts | 2 +- .../cases/server/services/configure/index.ts | 2 +- .../services/connector_mappings/index.ts | 2 +- x-pack/plugins/cases/server/services/index.ts | 4 +- .../services/reporters/read_reporters.ts | 2 +- .../cases/server/services/tags/read_tags.ts | 2 +- .../server/services/user_actions/helpers.ts | 2 +- .../server/services/user_actions/index.ts | 2 +- x-pack/plugins/cases/server/types.ts | 4 - x-pack/plugins/cases/tsconfig.json | 30 + .../security_solution/common/constants.ts | 4 +- .../integration/cases/attach_timeline.spec.ts | 3 +- .../integration/cases/creation.spec.ts | 4 +- x-pack/plugins/security_solution/kibana.json | 1 + .../cases/components/all_cases/index.tsx | 613 ++-------------- .../components/all_cases/translations.ts | 109 --- .../components/case_view/helpers.test.tsx | 50 +- .../cases/components/case_view/helpers.ts | 120 +++- .../cases/components/case_view/index.tsx | 668 ++++++------------ .../components/case_view/translations.ts | 149 +--- .../connectors/servicenow/translations.ts | 99 --- .../cases/components/create/flyout.test.tsx | 79 +-- .../public/cases/components/create/flyout.tsx | 30 +- .../cases/components/create/index.test.tsx | 113 +-- .../public/cases/components/create/index.tsx | 71 +- .../cases/components/create/translations.ts | 38 - .../cases/components/status/translations.ts | 72 -- .../add_to_case_action.test.tsx | 246 ++----- .../timeline_actions/add_to_case_action.tsx | 133 ++-- .../timeline_actions/helpers.test.tsx | 2 +- .../components/timeline_actions/helpers.tsx | 2 +- .../all_cases_modal.test.tsx | 108 --- .../use_all_cases_modal/all_cases_modal.tsx | 60 -- .../use_all_cases_modal/index.test.tsx | 135 ---- .../components/use_all_cases_modal/index.tsx | 59 -- .../user_action_markdown.test.tsx | 83 --- .../cases/components/wrappers/index.tsx | 5 - .../public/cases/pages/configure_cases.tsx | 8 +- .../markdown_editor/plugins/index.ts | 30 +- .../local_storage/use_messages_storage.tsx | 2 +- .../public/common/lib/kibana/hooks.ts | 24 +- .../common/lib/kibana/kibana_react.mock.ts | 7 + .../use_manage_case_action.tsx | 2 +- .../plugins/security_solution/public/index.ts | 4 +- .../components/recent_cases/index.tsx | 127 ++-- .../recent_cases/no_cases/index.test.tsx | 40 -- .../recent_cases/no_cases/index.tsx | 53 -- .../components/recent_cases/recent_cases.tsx | 70 -- .../components/recent_cases/translations.ts | 51 -- .../overview/components/sidebar/index.tsx | 6 - .../overview/components/sidebar/sidebar.tsx | 103 +-- .../security_solution/public/plugin.tsx | 3 - .../flyout/add_to_case_button/index.test.tsx | 61 +- .../flyout/add_to_case_button/index.tsx | 51 +- .../public/timelines/containers/api.ts | 7 +- .../plugins/security_solution/public/types.ts | 2 + .../translations/translations/ja-JP.json | 184 ----- .../translations/translations/zh-CN.json | 188 ----- x-pack/test/tsconfig.json | 1 + 535 files changed, 10931 insertions(+), 5131 deletions(-) create mode 100644 x-pack/plugins/cases/common/index.ts rename x-pack/plugins/{security_solution/public/cases/components/add_comment/translations.ts => cases/common/ui/index.ts} (87%) rename x-pack/plugins/{security_solution/public/cases/containers => cases/common/ui}/types.ts (74%) create mode 100644 x-pack/plugins/cases/images/all_cases.png create mode 100644 x-pack/plugins/cases/images/all_cases_selector_modal.png create mode 100644 x-pack/plugins/cases/images/case_view.png create mode 100644 x-pack/plugins/cases/images/configure.png create mode 100644 x-pack/plugins/cases/images/create.png create mode 100644 x-pack/plugins/cases/images/logo.png create mode 100644 x-pack/plugins/cases/images/recent_cases.png create mode 100644 x-pack/plugins/cases/public/common/errors.ts create mode 100644 x-pack/plugins/cases/public/common/lib/kibana/__mocks__/index.ts create mode 100644 x-pack/plugins/cases/public/common/lib/kibana/hooks.ts create mode 100644 x-pack/plugins/cases/public/common/lib/kibana/index.ts create mode 100644 x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.ts create mode 100644 x-pack/plugins/cases/public/common/lib/kibana/kibana_react.ts create mode 100644 x-pack/plugins/cases/public/common/lib/kibana/services.ts create mode 100644 x-pack/plugins/cases/public/common/mock/index.ts create mode 100644 x-pack/plugins/cases/public/common/mock/match_media.ts create mode 100644 x-pack/plugins/cases/public/common/mock/test_providers.tsx create mode 100644 x-pack/plugins/cases/public/common/shared_imports.ts create mode 100644 x-pack/plugins/cases/public/common/test_utils.ts create mode 100644 x-pack/plugins/cases/public/common/translations.ts create mode 100644 x-pack/plugins/cases/public/components/__mock__/form.ts create mode 100644 x-pack/plugins/cases/public/components/__mock__/router.ts create mode 100644 x-pack/plugins/cases/public/components/__mock__/timeline.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/add_comment/index.test.tsx (79%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/add_comment/index.tsx (87%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/add_comment/schema.tsx (88%) create mode 100644 x-pack/plugins/cases/public/components/add_comment/translations.ts rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/all_cases/actions.tsx (96%) create mode 100644 x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/all_cases/columns.test.tsx (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/all_cases/columns.tsx (65%) create mode 100644 x-pack/plugins/cases/public/components/all_cases/count.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/all_cases/expanded_row.tsx (85%) create mode 100644 x-pack/plugins/cases/public/components/all_cases/header.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/all_cases/helpers.ts (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/all_cases/index.test.tsx (81%) create mode 100644 x-pack/plugins/cases/public/components/all_cases/index.tsx create mode 100644 x-pack/plugins/cases/public/components/all_cases/nav_buttons.tsx create mode 100644 x-pack/plugins/cases/public/components/all_cases/selector_modal/index.test.tsx create mode 100644 x-pack/plugins/cases/public/components/all_cases/selector_modal/index.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/all_cases/status_filter.test.tsx (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/all_cases/status_filter.tsx (93%) create mode 100644 x-pack/plugins/cases/public/components/all_cases/table.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/all_cases/table_filters.test.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/all_cases/table_filters.tsx (91%) create mode 100644 x-pack/plugins/cases/public/components/all_cases/translations.ts create mode 100644 x-pack/plugins/cases/public/components/all_cases/types.ts create mode 100644 x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/bulk_actions/index.tsx (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/bulk_actions/translations.ts (83%) create mode 100644 x-pack/plugins/cases/public/components/callout/callout.test.tsx create mode 100644 x-pack/plugins/cases/public/components/callout/callout.tsx create mode 100644 x-pack/plugins/cases/public/components/callout/helpers.test.tsx create mode 100644 x-pack/plugins/cases/public/components/callout/helpers.tsx create mode 100644 x-pack/plugins/cases/public/components/callout/index.test.tsx create mode 100644 x-pack/plugins/cases/public/components/callout/index.tsx create mode 100644 x-pack/plugins/cases/public/components/callout/translations.ts create mode 100644 x-pack/plugins/cases/public/components/callout/types.ts rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/case_action_bar/actions.test.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/case_action_bar/actions.tsx (92%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/case_action_bar/helpers.test.ts (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/case_action_bar/helpers.ts (92%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/case_action_bar/index.test.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/case_action_bar/index.tsx (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/case_action_bar/status_context_menu.test.tsx (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/case_action_bar/status_context_menu.tsx (96%) create mode 100644 x-pack/plugins/cases/public/components/case_header_page/index.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/case_settings/sync_alerts_switch.test.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/case_settings/sync_alerts_switch.tsx (96%) create mode 100644 x-pack/plugins/cases/public/components/case_view/helpers.test.tsx create mode 100644 x-pack/plugins/cases/public/components/case_view/helpers.ts rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/case_view/index.test.tsx (73%) create mode 100644 x-pack/plugins/cases/public/components/case_view/index.tsx create mode 100644 x-pack/plugins/cases/public/components/case_view/translations.ts rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/__mock__/index.tsx (94%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/button.test.tsx (92%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/button.tsx (62%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/closure_options.test.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/closure_options.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/closure_options_radio.test.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/closure_options_radio.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/connectors.test.tsx (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/connectors.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/connectors_dropdown.test.tsx (99%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/connectors_dropdown.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/field_mapping.test.tsx (92%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/field_mapping.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/field_mapping_row_static.tsx (92%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/index.test.tsx (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/index.tsx (89%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/mapping.test.tsx (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/mapping.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/translations.ts (51%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/utils.test.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/configure_cases/utils.ts (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/confirm_delete_case/index.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/confirm_delete_case/translations.ts (50%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connector_selector/form.test.tsx (91%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connector_selector/form.tsx (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/card.tsx (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/case/alert_fields.tsx (94%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/case/cases_dropdown.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/case/existing_case.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/case/index.ts (93%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/case/translations.ts (64%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/case/types.ts (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/config.ts (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/connectors_registry.ts (61%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/fields_form.tsx (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/index.ts (93%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/__mocks__/api.ts (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/api.test.ts (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/api.ts (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/case_fields.test.tsx (99%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/case_fields.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/index.ts (89%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/search_issues.tsx (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/translations.ts (50%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/types.ts (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/use_get_fields_by_issue_type.test.tsx (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/use_get_fields_by_issue_type.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/use_get_issue_types.test.tsx (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/use_get_issue_types.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/use_get_issues.test.tsx (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/use_get_issues.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/use_get_single_issue.test.tsx (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/jira/use_get_single_issue.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/mock.ts (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/resilient/__mocks__/api.ts (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/resilient/api.ts (93%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/resilient/case_fields.test.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/resilient/case_fields.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/resilient/index.ts (88%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/resilient/translations.ts (60%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/resilient/types.ts (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/resilient/use_get_incident_types.test.tsx (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/resilient/use_get_incident_types.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/resilient/use_get_severity.test.tsx (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/resilient/use_get_severity.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/servicenow/__mocks__/api.ts (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/servicenow/api.test.ts (93%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/servicenow/api.ts (91%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/servicenow/helpers.ts (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/servicenow/index.ts (88%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/servicenow/servicenow_itsm_case_fields.test.tsx (99%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/servicenow/servicenow_itsm_case_fields.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/servicenow/servicenow_sir_case_fields.test.tsx (99%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/servicenow/servicenow_sir_case_fields.tsx (98%) create mode 100644 x-pack/plugins/cases/public/components/connectors/servicenow/translations.ts rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/servicenow/types.ts (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/servicenow/use_get_choices.test.tsx (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/servicenow/use_get_choices.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/connectors/types.ts (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/connector.test.tsx (76%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/connector.tsx (81%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/description.test.tsx (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/description.tsx (84%) rename x-pack/plugins/{security_solution/public/cases/components/use_create_case_modal/create_case_modal.test.tsx => cases/public/components/create/flyout.test.tsx} (68%) create mode 100644 x-pack/plugins/cases/public/components/create/flyout.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/form.test.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/form.tsx (84%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/form_context.test.tsx (94%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/form_context.tsx (75%) create mode 100644 x-pack/plugins/cases/public/components/create/index.test.tsx create mode 100644 x-pack/plugins/cases/public/components/create/index.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/mock.ts (92%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/optional_field_label/index.test.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/optional_field_label/index.tsx (89%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/schema.tsx (92%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/submit_button.test.tsx (77%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/submit_button.tsx (92%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/sync_alerts_toggle.test.tsx (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/sync_alerts_toggle.tsx (93%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/tags.test.tsx (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/tags.tsx (94%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/title.test.tsx (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/create/title.tsx (92%) create mode 100644 x-pack/plugins/cases/public/components/create/translations.ts rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/edit_connector/helpers.ts (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/edit_connector/index.test.tsx (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/edit_connector/index.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/edit_connector/schema.tsx (85%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/edit_connector/translations.ts (79%) create mode 100644 x-pack/plugins/cases/public/components/empty_value/__snapshots__/empty_value.test.tsx.snap create mode 100644 x-pack/plugins/cases/public/components/empty_value/empty_value.test.tsx create mode 100644 x-pack/plugins/cases/public/components/empty_value/index.tsx rename x-pack/plugins/{security_solution/public/cases/components/use_all_cases_modal => cases/public/components/empty_value}/translations.ts (69%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/filter_popover/index.tsx (100%) create mode 100644 x-pack/plugins/cases/public/components/formatted_date/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/plugins/cases/public/components/formatted_date/index.test.tsx create mode 100644 x-pack/plugins/cases/public/components/formatted_date/index.tsx create mode 100644 x-pack/plugins/cases/public/components/formatted_date/maybe_date.test.ts create mode 100644 x-pack/plugins/cases/public/components/formatted_date/maybe_date.ts create mode 100644 x-pack/plugins/cases/public/components/header_page/__snapshots__/editable_title.test.tsx.snap create mode 100644 x-pack/plugins/cases/public/components/header_page/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/plugins/cases/public/components/header_page/__snapshots__/title.test.tsx.snap create mode 100644 x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx create mode 100644 x-pack/plugins/cases/public/components/header_page/editable_title.tsx create mode 100644 x-pack/plugins/cases/public/components/header_page/index.test.tsx create mode 100644 x-pack/plugins/cases/public/components/header_page/index.tsx create mode 100644 x-pack/plugins/cases/public/components/header_page/title.test.tsx create mode 100644 x-pack/plugins/cases/public/components/header_page/title.tsx create mode 100644 x-pack/plugins/cases/public/components/header_page/translations.ts create mode 100644 x-pack/plugins/cases/public/components/header_page/types.ts create mode 100644 x-pack/plugins/cases/public/components/insert_timeline/index.test.tsx create mode 100644 x-pack/plugins/cases/public/components/insert_timeline/index.tsx create mode 100644 x-pack/plugins/cases/public/components/link_icon/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/plugins/cases/public/components/link_icon/index.test.tsx create mode 100644 x-pack/plugins/cases/public/components/link_icon/index.tsx create mode 100644 x-pack/plugins/cases/public/components/links/index.tsx create mode 100644 x-pack/plugins/cases/public/components/links/translations.ts create mode 100644 x-pack/plugins/cases/public/components/localized_date_tooltip/index.test.tsx create mode 100644 x-pack/plugins/cases/public/components/localized_date_tooltip/index.tsx create mode 100644 x-pack/plugins/cases/public/components/markdown_editor/editor.tsx create mode 100644 x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx create mode 100644 x-pack/plugins/cases/public/components/markdown_editor/index.tsx create mode 100644 x-pack/plugins/cases/public/components/markdown_editor/markdown_link.tsx create mode 100644 x-pack/plugins/cases/public/components/markdown_editor/renderer.test.tsx create mode 100644 x-pack/plugins/cases/public/components/markdown_editor/renderer.tsx create mode 100644 x-pack/plugins/cases/public/components/markdown_editor/translations.ts create mode 100644 x-pack/plugins/cases/public/components/markdown_editor/types.ts create mode 100644 x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts create mode 100644 x-pack/plugins/cases/public/components/panel/index.test.tsx create mode 100644 x-pack/plugins/cases/public/components/panel/index.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/property_actions/index.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/property_actions/translations.ts (63%) rename x-pack/plugins/{security_solution/public/overview => cases/public}/components/recent_cases/filters/index.tsx (93%) create mode 100644 x-pack/plugins/cases/public/components/recent_cases/icon_with_count.tsx create mode 100644 x-pack/plugins/cases/public/components/recent_cases/index.test.tsx create mode 100644 x-pack/plugins/cases/public/components/recent_cases/index.tsx create mode 100644 x-pack/plugins/cases/public/components/recent_cases/loading_placeholders.tsx create mode 100644 x-pack/plugins/cases/public/components/recent_cases/no_cases/index.test.tsx create mode 100644 x-pack/plugins/cases/public/components/recent_cases/no_cases/index.tsx create mode 100644 x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx create mode 100644 x-pack/plugins/cases/public/components/recent_cases/translations.ts rename x-pack/plugins/{security_solution/public/overview => cases/public}/components/recent_cases/types.ts (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/status/button.test.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/status/button.tsx (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/status/config.ts (93%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/status/index.ts (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/status/stats.test.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/status/stats.tsx (94%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/status/status.test.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/status/status.tsx (94%) create mode 100644 x-pack/plugins/cases/public/components/status/translations.ts rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/status/types.ts (78%) create mode 100644 x-pack/plugins/cases/public/components/subtitle/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/plugins/cases/public/components/subtitle/index.test.tsx create mode 100644 x-pack/plugins/cases/public/components/subtitle/index.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/tag_list/index.test.tsx (89%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/tag_list/index.tsx (99%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/tag_list/schema.tsx (86%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/tag_list/tags.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/tag_list/translations.ts (59%) create mode 100644 x-pack/plugins/cases/public/components/timeline_context/index.tsx create mode 100644 x-pack/plugins/cases/public/components/timeline_context/use_timeline_context.ts create mode 100644 x-pack/plugins/cases/public/components/use_create_case_modal/create_case_modal.test.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/use_create_case_modal/create_case_modal.tsx (54%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/use_create_case_modal/index.test.tsx (66%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/use_create_case_modal/index.tsx (91%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/use_push_to_service/helpers.tsx (92%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/use_push_to_service/index.test.tsx (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/use_push_to_service/index.tsx (83%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/use_push_to_service/translations.ts (57%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/helpers.test.tsx (82%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/helpers.tsx (72%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/index.test.tsx (77%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/index.tsx (89%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/schema.ts (88%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/translations.ts (52%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_alert_comment_event.test.tsx (76%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_alert_comment_event.tsx (70%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_avatar.test.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_avatar.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_content_toolbar.test.tsx (90%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_content_toolbar.tsx (85%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_copy_link.test.tsx (65%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_copy_link.tsx (59%) create mode 100644 x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.test.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_markdown.tsx (94%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_move_to_reference.test.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_move_to_reference.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_property_actions.test.tsx (69%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_property_actions.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_show_alert.test.tsx (77%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_show_alert.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_timestamp.test.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_timestamp.tsx (94%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_username.test.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_username.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_username_with_avatar.test.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_action_tree/user_action_username_with_avatar.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_list/index.test.tsx (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_list/index.tsx (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/components/user_list/translations.ts (84%) create mode 100644 x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar.test.tsx.snap create mode 100644 x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_action.test.tsx.snap create mode 100644 x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap create mode 100644 x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap create mode 100644 x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap create mode 100644 x-pack/plugins/cases/public/components/utility_bar/index.ts create mode 100644 x-pack/plugins/cases/public/components/utility_bar/styles.tsx create mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.tsx create mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar.tsx create mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.test.tsx create mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.tsx create mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.test.tsx create mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.tsx create mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.test.tsx create mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.tsx create mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_spacer.tsx create mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.test.tsx create mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.tsx create mode 100644 x-pack/plugins/cases/public/components/wrappers/index.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/__mocks__/api.ts (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/api.test.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/api.ts (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/configure/__mocks__/api.ts (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/configure/api.test.ts (96%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/configure/api.ts (94%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/configure/mock.ts (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/configure/translations.ts (64%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/configure/types.ts (95%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/configure/use_action_types.test.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/configure/use_action_types.tsx (85%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/configure/use_configure.test.tsx (94%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/configure/use_configure.tsx (89%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/configure/use_connectors.test.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/configure/use_connectors.tsx (68%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/constants.ts (100%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/mock.ts (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/translations.ts (64%) create mode 100644 x-pack/plugins/cases/public/containers/types.ts rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_bulk_update_case.test.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_bulk_update_case.tsx (89%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_delete_cases.test.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_delete_cases.tsx (90%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_get_action_license.test.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_get_action_license.tsx (86%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_get_case.test.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_get_case.tsx (89%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_get_case_user_actions.test.tsx (99%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_get_case_user_actions.tsx (94%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_get_cases.test.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_get_cases.tsx (79%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_get_cases_status.test.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_get_cases_status.tsx (86%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_get_reporters.test.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_get_reporters.tsx (82%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_get_tags.test.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_get_tags.tsx (87%) create mode 100644 x-pack/plugins/cases/public/containers/use_messages_storage.test.tsx create mode 100644 x-pack/plugins/cases/public/containers/use_messages_storage.tsx rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_post_case.test.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_post_case.tsx (85%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_post_comment.test.tsx (98%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_post_comment.tsx (85%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_post_push_to_service.test.tsx (97%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_post_push_to_service.tsx (81%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_update_case.test.tsx (99%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_update_case.tsx (86%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_update_comment.test.tsx (99%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/use_update_comment.tsx (90%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/utils.test.ts (77%) rename x-pack/plugins/{security_solution/public/cases => cases/public}/containers/utils.ts (92%) create mode 100644 x-pack/plugins/cases/public/index.tsx create mode 100644 x-pack/plugins/cases/public/methods/get_all_cases.tsx create mode 100644 x-pack/plugins/cases/public/methods/get_all_cases_selector_modal.tsx create mode 100644 x-pack/plugins/cases/public/methods/get_case_view.tsx create mode 100644 x-pack/plugins/cases/public/methods/get_configure_cases.tsx create mode 100644 x-pack/plugins/cases/public/methods/get_create_case.tsx create mode 100644 x-pack/plugins/cases/public/methods/get_recent_cases.tsx create mode 100644 x-pack/plugins/cases/public/methods/index.ts create mode 100644 x-pack/plugins/cases/public/plugin.ts create mode 100644 x-pack/plugins/cases/public/types.ts create mode 100644 x-pack/plugins/cases/public/utils/use_mount_appended.ts create mode 100644 x-pack/plugins/cases/tsconfig.json delete mode 100644 x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts delete mode 100644 x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/translations.ts delete mode 100644 x-pack/plugins/security_solution/public/cases/components/create/translations.ts delete mode 100644 x-pack/plugins/security_solution/public/cases/components/status/translations.ts delete mode 100644 x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx delete mode 100644 x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/index.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/overview/components/recent_cases/recent_cases.tsx delete mode 100644 x-pack/plugins/security_solution/public/overview/components/recent_cases/translations.ts diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 6f54e924769b8..ad58cd040ff35 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -338,7 +338,7 @@ Failure to have auth enabled in Kibana will make for a broken UI. UI-based error |{kib-repo}blob/{branch}/x-pack/plugins/cases/README.md[cases] -|Experimental Feature +|Case management in Kibana |{kib-repo}blob/{branch}/x-pack/plugins/cloud/README.md[cloud] diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 95bf3f8f251b7..0bb4594244a75 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -69,7 +69,7 @@ pageLoadAssetSize: searchprofiler: 67080 security: 189428 securityOss: 30806 - securitySolution: 235402 + securitySolution: 187863 share: 99061 snapshotRestore: 79032 spaces: 387915 @@ -110,3 +110,4 @@ pageLoadAssetSize: banners: 17946 mapsEms: 26072 timelines: 28613 + cases: 162385 diff --git a/tsconfig.json b/tsconfig.json index ac15fe14b4d2c..87ee067002109 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,6 @@ "x-pack/mocks.ts", "x-pack/typings/**/*", "x-pack/tasks/**/*", - "x-pack/plugins/cases/**/*", "x-pack/plugins/lists/**/*", "x-pack/plugins/security_solution/**/*", ], @@ -84,6 +83,7 @@ { "path": "./x-pack/plugins/apm/tsconfig.json" }, { "path": "./x-pack/plugins/beats_management/tsconfig.json" }, { "path": "./x-pack/plugins/canvas/tsconfig.json" }, + { "path": "./x-pack/plugins/cases/tsconfig.json" }, { "path": "./x-pack/plugins/cloud/tsconfig.json" }, { "path": "./x-pack/plugins/console_extensions/tsconfig.json" }, { "path": "./x-pack/plugins/data_enhanced/tsconfig.json" }, diff --git a/tsconfig.refs.json b/tsconfig.refs.json index f13455a14b4df..b5e73e50f8b81 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -60,6 +60,7 @@ { "path": "./x-pack/plugins/apm/tsconfig.json" }, { "path": "./x-pack/plugins/beats_management/tsconfig.json" }, { "path": "./x-pack/plugins/canvas/tsconfig.json" }, + { "path": "./x-pack/plugins/cases/tsconfig.json" }, { "path": "./x-pack/plugins/cloud/tsconfig.json" }, { "path": "./x-pack/plugins/console_extensions/tsconfig.json" }, { "path": "./x-pack/plugins/dashboard_enhanced/tsconfig.json" }, diff --git a/x-pack/plugins/cases/README.md b/x-pack/plugins/cases/README.md index 069441ab640ee..14afe89829a68 100644 --- a/x-pack/plugins/cases/README.md +++ b/x-pack/plugins/cases/README.md @@ -1,18 +1,160 @@ -# Case Workflow - -*Experimental Feature* - -Elastic is developing a Case Management Workflow. Follow our progress: - -- [Case API Documentation](https://www.elastic.co/guide/en/security/master/cases-overview.html) - - -# Action types - +Case management in Kibana + +[![Issues][issues-shield]][issues-url] +[![Pull Requests][pr-shield]][pr-url] + +# Cases Plugin Docs + +![Cases Logo][cases-logo] + +[Report Bug](https://github.com/elastic/kibana/issues/new?assignees=&labels=bug&template=Bug_report.md) +· +[Request Feature](https://github.com/elastic/kibana/issues/new?assignees=&labels=&template=Feature_request.md) + +## Table of Contents + +- [Cases API](#cases-api) +- [Cases UI](#cases-ui) +- [Case Action Type](#case-action-type) _feature in development, disabled by default_ + + +## Cases API +[**Explore the API docs »**](https://www.elastic.co/guide/en/security/current/cases-api-overview.html) + +## Cases UI + +#### Embed Cases UI components in any Kibana plugin +- Add `CasesUiStart` to Kibana plugin `StartServices` dependencies: + +```ts +cases: CasesUiStart; +``` + +#### Cases UI Methods + +- From the UI component, get the component from the `useKibana` hook start services +```tsx + const { cases } = useKibana().services; + // call in the return as you would any component + cases.getCreateCase({ + onCancel: handleSetIsCancel, + onSuccess, + timelineIntegration?: { + plugins: { + parsingPlugin, + processingPluginRenderer, + uiPlugin, + }, + hooks: { + useInsertTimeline, + }, + }, + }) +``` + +##### Methods: +### `getAllCases` +Arguments: + +|Property|Description| +|---|---| +|caseDetailsNavigation|`CasesNavigation` route configuration to generate the case details url for the case details page +|configureCasesNavigation|`CasesNavigation` route configuration for configure cases page +|createCaseNavigation|`CasesNavigation` route configuration for create cases page +|userCanCrud|`boolean;` user permissions to crud + +UI component: +![All Cases Component][all-cases-img] + +### `getAllCasesSelectorModal` +Arguments: + +|Property|Description| +|---|---| +|alertData?|`Omit;` alert data to post to case +|createCaseNavigation|`CasesNavigation` route configuration for create cases page +|disabledStatuses?|`CaseStatuses[];` array of disabled statuses +|onRowClick|(theCase?: Case | SubCase) => void; callback for row click, passing case in row +|updateCase?|(theCase: Case | SubCase) => void; callback after case has been updated +|userCanCrud|`boolean;` user permissions to crud + +UI component: +![All Cases Selector Modal Component][all-cases-modal-img] + +### `getCaseView` +Arguments: + +|Property|Description| +|---|---| +|caseDetailsNavigation|`CasesNavigation` route configuration to generate the case details url for the case details page +|caseId|`string;` ID of the case +|configureCasesNavigation|`CasesNavigation` route configuration for configure cases page +|createCaseNavigation|`CasesNavigation` route configuration for create cases page +|getCaseDetailHrefWithCommentId|`(commentId: string) => string;` callback to generate the case details url with a comment id reference from the case id and comment id +|onComponentInitialized?|`() => void;` callback when component has initialized +|onCaseDataSuccess?| `(data: Case) => void;` optional callback to handle case data in consuming application +|ruleDetailsNavigation| CasesNavigation +|showAlertDetails| `(alertId: string, index: string) => void;` callback to show alert details +|subCaseId?| `string;` subcase id +|timelineIntegration?.editor_plugins| Plugins needed for integrating timeline into markdown editor. +|timelineIntegration?.editor_plugins.parsingPlugin| `Plugin;` +|timelineIntegration?.editor_plugins.processingPluginRenderer| `React.FC` +|timelineIntegration?.editor_plugins.uiPlugin?| `EuiMarkdownEditorUiPlugin` +|timelineIntegration?.hooks.useInsertTimeline| `(value: string, onChange: (newValue: string) => void): UseInsertTimelineReturn` +|timelineIntegration?.ui?.renderInvestigateInTimelineActionComponent?| `(alertIds: string[]) => JSX.Element;` space to render `InvestigateInTimelineActionComponent` +|timelineIntegration?.ui?renderTimelineDetailsPanel?| `() => JSX.Element;` space to render `TimelineDetailsPanel` +|useFetchAlertData| `(alertIds: string[]) => [boolean, Record];` fetch alerts +|userCanCrud| `boolean;` user permissions to crud + +UI component: + ![Case View Component][case-view-img] + +### `getCreateCase` +Arguments: + +|Property|Description| +|---|---| +|afterCaseCreated?|`(theCase: Case) => Promise;` callback passing newly created case before pushCaseToExternalService is called +|onCancel|`() => void;` callback when create case is canceled +|onSuccess|`(theCase: Case) => Promise;` callback passing newly created case after pushCaseToExternalService is called +|timelineIntegration?.editor_plugins| Plugins needed for integrating timeline into markdown editor. +|timelineIntegration?.editor_plugins.parsingPlugin| `Plugin;` +|timelineIntegration?.editor_plugins.processingPluginRenderer| `React.FC` +|timelineIntegration?.editor_plugins.uiPlugin?| `EuiMarkdownEditorUiPlugin` +|timelineIntegration?.hooks.useInsertTimeline| `(value: string, onChange: (newValue: string) => void): UseInsertTimelineReturn` + +UI component: + ![Create Component][create-img] + + ### `getConfigureCases` + Arguments: + + |Property|Description| + |---|---| + |userCanCrud|`boolean;` user permissions to crud + + UI component: + ![Configure Component][configure-img] + +### `getRecentCases` +Arguments: + +|Property|Description| +|---|---| +|allCasesNavigation|`CasesNavigation` route configuration for configure cases page +|caseDetailsNavigation|`CasesNavigation` route configuration to generate the case details url for the case details page +|createCaseNavigation|`CasesNavigation` route configuration for create case page +|maxCasesToShow|`number;` number of cases to show in widget + +UI component: + ![Recent Cases Component][recent-cases-img] + +## Case Action Type + +_***Feature in development, disabled by default**_ See [Kibana Actions](https://github.com/elastic/kibana/tree/master/x-pack/plugins/actions) for more information. -## Case ID: `.case` @@ -101,4 +243,24 @@ For IBM Resilient connectors: | Property | Description | Type | | ---------- | ------------------------------ | ------- | -| syncAlerts | Turn on or off alert synching. | boolean | \ No newline at end of file +| syncAlerts | Turn on or off alert synching. | boolean | + + + + + + + + +[pr-shield]: https://img.shields.io/github/issues-pr/elangosundar/awesome-README-templates?style=for-the-badge +[pr-url]: https://github.com/elastic/kibana/pulls?q=is%3Apr+label%3AFeature%3ACases+-is%3Adraft+is%3Aopen+ +[issues-shield]: https://img.shields.io/github/issues/othneildrew/Best-README-Template.svg?style=for-the-badge +[issues-url]: https://github.com/elastic/kibana/issues?q=is%3Aopen+is%3Aissue+label%3AFeature%3ACases +[cases-logo]: images/logo.png +[configure-img]: images/configure.png +[create-img]: images/create.png +[all-cases-img]: images/all_cases.png +[all-cases-modal-img]: images/all_cases_selector_modal.png +[recent-cases-img]: images/recent_cases.png +[case-view-img]: images/case_view.png + diff --git a/x-pack/plugins/cases/common/api/index.ts b/x-pack/plugins/cases/common/api/index.ts index 7780564089d3d..2ef03dd96e315 100644 --- a/x-pack/plugins/cases/common/api/index.ts +++ b/x-pack/plugins/cases/common/api/index.ts @@ -7,6 +7,7 @@ export * from './cases'; export * from './connectors'; +export * from './helpers'; export * from './runtime_types'; export * from './saved_object'; export * from './user'; diff --git a/x-pack/plugins/cases/common/api/runtime_types.ts b/x-pack/plugins/cases/common/api/runtime_types.ts index b2ff763838287..8001eb80cec73 100644 --- a/x-pack/plugins/cases/common/api/runtime_types.ts +++ b/x-pack/plugins/cases/common/api/runtime_types.ts @@ -25,7 +25,13 @@ export const formatErrors = (errors: rt.Errors): string[] => { .map((entry) => entry.key) .join(','); - const nameContext = error.context.find((entry) => entry.type?.name?.length > 0); + const nameContext = error.context.find((entry) => { + // TODO: Put in fix for optional chaining https://github.com/cypress-io/cypress/issues/9298 + if (entry.type && entry.type.name) { + return entry.type.name.length > 0; + } + return false; + }); const suppliedValue = keyContext !== '' ? keyContext : nameContext != null ? nameContext.type.name : ''; const value = isObject(error.value) ? JSON.stringify(error.value) : error.value; diff --git a/x-pack/plugins/cases/common/constants.ts b/x-pack/plugins/cases/common/constants.ts index 148b81c346b6e..f9fae2466a59b 100644 --- a/x-pack/plugins/cases/common/constants.ts +++ b/x-pack/plugins/cases/common/constants.ts @@ -4,6 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +export const DEFAULT_DATE_FORMAT = 'dateFormat'; +export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz'; export const APP_ID = 'cases'; @@ -50,11 +52,8 @@ export const SUPPORTED_CONNECTORS = [ /** * Alerts */ - -// this value is from x-pack/plugins/security_solution/common/constants.ts -const DEFAULT_MAX_SIGNALS = 100; export const MAX_ALERTS_PER_SUB_CASE = 5000; -export const MAX_GENERATED_ALERTS_PER_SUB_CASE = MAX_ALERTS_PER_SUB_CASE / DEFAULT_MAX_SIGNALS; +export const MAX_GENERATED_ALERTS_PER_SUB_CASE = 50; /** * This flag governs enabling the case as a connector feature. It is disabled by default as the feature is not complete. diff --git a/x-pack/plugins/cases/common/index.ts b/x-pack/plugins/cases/common/index.ts new file mode 100644 index 0000000000000..3d277d12d6826 --- /dev/null +++ b/x-pack/plugins/cases/common/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 './constants'; +export * from './api'; +export * from './ui/types'; diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/translations.ts b/x-pack/plugins/cases/common/ui/index.ts similarity index 87% rename from x-pack/plugins/security_solution/public/cases/components/add_comment/translations.ts rename to x-pack/plugins/cases/common/ui/index.ts index d94a4a8607d1e..6cc0ccaa93a6d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/translations.ts +++ b/x-pack/plugins/cases/common/ui/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export * from '../../translations'; +export * from './types'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/types.ts b/x-pack/plugins/cases/common/ui/types.ts similarity index 74% rename from x-pack/plugins/security_solution/public/cases/containers/types.ts rename to x-pack/plugins/cases/common/ui/types.ts index ac60f2999c510..43e3453500b17 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/types.ts +++ b/x-pack/plugins/cases/common/ui/types.ts @@ -6,20 +6,22 @@ */ import { - User, - UserActionField, - UserAction, - CaseConnector, - CommentRequest, - CaseStatuses, + AssociationType, CaseAttributes, + CaseConnector, CasePatchRequest, + CaseStatuses, CaseType, - AssociationType, -} from '../../../../cases/common/api'; -import { CaseStatusWithAllStatus } from '../components/status'; + CommentRequest, + User, + UserAction, + UserActionField, +} from '../api'; + +export const StatusAll = 'all' as const; +export type StatusAllType = typeof StatusAll; -export { CaseConnector, ActionConnector, CaseStatuses } from '../../../../cases/common/api'; +export type CaseStatusWithAllStatus = CaseStatuses | StatusAllType; export type Comment = CommentRequest & { associationType: AssociationType; @@ -172,3 +174,56 @@ export interface UpdateByKey { onSuccess?: () => void; onError?: () => void; } + +export interface RuleEcs { + id?: string[]; + rule_id?: string[]; + name?: string[]; + false_positives: string[]; + saved_id?: string[]; + timeline_id?: string[]; + timeline_title?: string[]; + max_signals?: number[]; + risk_score?: string[]; + output_index?: string[]; + description?: string[]; + from?: string[]; + immutable?: boolean[]; + index?: string[]; + interval?: string[]; + language?: string[]; + query?: string[]; + references?: string[]; + severity?: string[]; + tags?: string[]; + threat?: unknown; + threshold?: unknown; + type?: string[]; + size?: string[]; + to?: string[]; + enabled?: boolean[]; + filters?: unknown; + created_at?: string[]; + updated_at?: string[]; + created_by?: string[]; + updated_by?: string[]; + version?: string[]; + note?: string[]; + building_block_type?: string[]; +} + +export interface SignalEcs { + rule?: RuleEcs; + original_time?: string[]; + status?: string[]; + group?: { + id?: string[]; + }; + threshold_result?: unknown; +} + +export interface Ecs { + _id: string; + _index?: string; + signal?: SignalEcs; +} diff --git a/x-pack/plugins/cases/images/all_cases.png b/x-pack/plugins/cases/images/all_cases.png new file mode 100644 index 0000000000000000000000000000000000000000..3c6adf8ff2de28129cb34eecb278ae0a93b0e8e4 GIT binary patch literal 260832 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

L`NG58_Wb; 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�dPD{XCB$r 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

rDP6pc<6E-9R(eC-3~z+i3m9J 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 literal 0 HcmV?d00001 diff --git a/x-pack/plugins/cases/images/all_cases_selector_modal.png b/x-pack/plugins/cases/images/all_cases_selector_modal.png new file mode 100644 index 0000000000000000000000000000000000000000..f24ad32509dd1fd2436ebe210eac7d3b65746395 GIT binary patch literal 387277 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 z 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 literal 0 HcmV?d00001 diff --git a/x-pack/plugins/cases/images/case_view.png b/x-pack/plugins/cases/images/case_view.png new file mode 100644 index 0000000000000000000000000000000000000000..4fb14d7b41b26e8b40b6dc938b9682e592de105a GIT binary patch literal 554933 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 {i;K0h}%vABv7sCLC(Tp*Mrm)eTg!9N5F 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

    HJ+p*ukddJ{P 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

    b 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 IdiVbS07Qc#;s5{u literal 0 HcmV?d00001 diff --git a/x-pack/plugins/cases/images/create.png b/x-pack/plugins/cases/images/create.png new file mode 100644 index 0000000000000000000000000000000000000000..df9bac09d53459e3263c78417a16acd527fc766b GIT binary patch literal 300838 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%0D>W}0??49JsYEmUdd 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 literal 0 HcmV?d00001 diff --git a/x-pack/plugins/cases/kibana.json b/x-pack/plugins/cases/kibana.json index 1aaf84decbe36..4a534c29de804 100644 --- a/x-pack/plugins/cases/kibana.json +++ b/x-pack/plugins/cases/kibana.json @@ -2,12 +2,13 @@ "configPath": ["xpack", "cases"], "id": "cases", "kibanaVersion": "kibana", - "requiredPlugins": ["actions", "securitySolution"], + "extraPublicDirs": ["common"], + "requiredPlugins": ["actions", "esUiShared", "kibanaReact", "kibanaUtils", "triggersActionsUi"], "optionalPlugins": [ "spaces", "security" ], "server": true, - "ui": false, + "ui": true, "version": "8.0.0" } diff --git a/x-pack/plugins/cases/public/common/errors.ts b/x-pack/plugins/cases/public/common/errors.ts new file mode 100644 index 0000000000000..6edef08c1f4b1 --- /dev/null +++ b/x-pack/plugins/cases/public/common/errors.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 { has } from 'lodash/fp'; + +export interface AppError { + name: string; + message: string; + body: { + message: string; + }; +} + +export interface KibanaError extends AppError { + body: { + message: string; + statusCode: number; + }; +} + +export interface CasesAppError extends AppError { + body: { + message: string; + status_code: number; + }; +} + +export const isKibanaError = (error: unknown): error is KibanaError => + has('message', error) && has('body.message', error) && has('body.statusCode', error); + +export const isCasesAppError = (error: unknown): error is CasesAppError => + has('message', error) && has('body.message', error) && has('body.status_code', error); + +export const isAppError = (error: unknown): error is AppError => + isKibanaError(error) || isCasesAppError(error); diff --git a/x-pack/plugins/cases/public/common/lib/kibana/__mocks__/index.ts b/x-pack/plugins/cases/public/common/lib/kibana/__mocks__/index.ts new file mode 100644 index 0000000000000..392b71befe2b4 --- /dev/null +++ b/x-pack/plugins/cases/public/common/lib/kibana/__mocks__/index.ts @@ -0,0 +1,30 @@ +/* + * 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 { notificationServiceMock } from '../../../../../../../../src/core/public/mocks'; +import { + createKibanaContextProviderMock, + createStartServicesMock, + createWithKibanaMock, +} from '../kibana_react.mock'; + +export const KibanaServices = { get: jest.fn(), getKibanaVersion: jest.fn(() => '8.0.0') }; +export const useKibana = jest.fn().mockReturnValue({ + services: createStartServicesMock(), +}); + +export const useHttp = jest.fn().mockReturnValue(createStartServicesMock().http); +export const useTimeZone = jest.fn(); +export const useDateFormat = jest.fn(); +export const useBasePath = jest.fn(() => '/test/base/path'); +export const useToasts = jest + .fn() + .mockReturnValue(notificationServiceMock.createStartContract().toasts); +export const useCurrentUser = jest.fn(); +export const withKibana = jest.fn(createWithKibanaMock()); +export const KibanaContextProvider = jest.fn(createKibanaContextProviderMock()); +export const useGetUserSavedObjectPermissions = jest.fn(); diff --git a/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts b/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts new file mode 100644 index 0000000000000..cb90568982282 --- /dev/null +++ b/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts @@ -0,0 +1,132 @@ +/* + * 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 moment from 'moment-timezone'; + +import { useCallback, useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../../../common/constants'; +import { AuthenticatedUser } from '../../../../../security/common/model'; +import { convertToCamelCase } from '../../../containers/utils'; +import { StartServices } from '../../../types'; +import { useUiSetting, useKibana } from './kibana_react'; + +export const useDateFormat = (): string => useUiSetting(DEFAULT_DATE_FORMAT); + +export const useTimeZone = (): string => { + const timeZone = useUiSetting(DEFAULT_DATE_FORMAT_TZ); + return timeZone === 'Browser' ? moment.tz.guess() : timeZone; +}; + +export const useBasePath = (): string => useKibana().services.http.basePath.get(); + +export const useToasts = (): StartServices['notifications']['toasts'] => + useKibana().services.notifications.toasts; + +export const useHttp = (): StartServices['http'] => useKibana().services.http; + +interface UserRealm { + name: string; + type: string; +} + +export interface AuthenticatedElasticUser { + username: string; + email: string; + fullName: string; + roles: string[]; + enabled: boolean; + metadata?: { + _reserved: boolean; + }; + authenticationRealm: UserRealm; + lookupRealm: UserRealm; + authenticationProvider: string; +} + +export const useCurrentUser = (): AuthenticatedElasticUser | null => { + const [user, setUser] = useState(null); + + const toasts = useToasts(); + + const { security } = useKibana().services; + + const fetchUser = useCallback(() => { + let didCancel = false; + const fetchData = async () => { + try { + if (security != null) { + const response = await security.authc.getCurrentUser(); + if (!didCancel) { + setUser(convertToCamelCase(response)); + } + } else { + setUser({ + username: i18n.translate('xpack.cases.getCurrentUser.unknownUser', { + defaultMessage: 'Unknown', + }), + email: '', + fullName: '', + roles: [], + enabled: false, + authenticationRealm: { name: '', type: '' }, + lookupRealm: { name: '', type: '' }, + authenticationProvider: '', + }); + } + } catch (error) { + if (!didCancel) { + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { + title: i18n.translate('xpack.cases.getCurrentUser.Error', { + defaultMessage: 'Error getting user', + }), + } + ); + setUser(null); + } + } + }; + fetchData(); + return () => { + didCancel = true; + }; + }, [security, toasts]); + + useEffect(() => { + fetchUser(); + }, [fetchUser]); + return user; +}; + +export interface UseGetUserSavedObjectPermissions { + crud: boolean; + read: boolean; +} + +export const useGetUserSavedObjectPermissions = () => { + const [ + savedObjectsPermissions, + setSavedObjectsPermissions, + ] = useState(null); + const uiCapabilities = useKibana().services.application.capabilities; + + useEffect(() => { + const capabilitiesCanUserCRUD: boolean = + typeof uiCapabilities.siem.crud === 'boolean' ? uiCapabilities.siem.crud : false; + const capabilitiesCanUserRead: boolean = + typeof uiCapabilities.siem.show === 'boolean' ? uiCapabilities.siem.show : false; + setSavedObjectsPermissions({ + crud: capabilitiesCanUserCRUD, + read: capabilitiesCanUserRead, + }); + }, [uiCapabilities]); + + return savedObjectsPermissions; +}; diff --git a/x-pack/plugins/cases/public/common/lib/kibana/index.ts b/x-pack/plugins/cases/public/common/lib/kibana/index.ts new file mode 100644 index 0000000000000..5a89cbca9e471 --- /dev/null +++ b/x-pack/plugins/cases/public/common/lib/kibana/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 './hooks'; +export * from './kibana_react'; +export * from './services'; diff --git a/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.ts new file mode 100644 index 0000000000000..326163f6cdc03 --- /dev/null +++ b/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.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 React from 'react'; + +import { RecursivePartial } from '@elastic/eui/src/components/common'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public'; +import { StartServices } from '../../../types'; +import { EuiTheme } from '../../../../../../../src/plugins/kibana_react/common'; + +export const createStartServicesMock = (): StartServices => + (coreMock.createStart() as unknown) as StartServices; + +export const createWithKibanaMock = () => { + const services = createStartServicesMock(); + + return (Component: unknown) => (props: unknown) => { + return React.createElement(Component as string, { ...(props as object), kibana: { services } }); + }; +}; + +export const createKibanaContextProviderMock = () => { + const services = createStartServicesMock(); + + return ({ children }: { children: React.ReactNode }) => + React.createElement(KibanaContextProvider, { services }, children); +}; + +export const getMockTheme = (partialTheme: RecursivePartial): EuiTheme => + partialTheme as EuiTheme; diff --git a/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.ts b/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.ts new file mode 100644 index 0000000000000..921463c4c41ab --- /dev/null +++ b/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.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 { + KibanaContextProvider, + useKibana, + useUiSetting, + useUiSetting$, +} from '../../../../../../../src/plugins/kibana_react/public'; +import { StartServices } from '../../../types'; + +const useTypedKibana = () => useKibana(); + +export { KibanaContextProvider, useTypedKibana as useKibana, useUiSetting, useUiSetting$ }; diff --git a/x-pack/plugins/cases/public/common/lib/kibana/services.ts b/x-pack/plugins/cases/public/common/lib/kibana/services.ts new file mode 100644 index 0000000000000..94487bd3ca5e9 --- /dev/null +++ b/x-pack/plugins/cases/public/common/lib/kibana/services.ts @@ -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 { CoreStart } from 'kibana/public'; + +type GlobalServices = Pick; + +export class KibanaServices { + private static kibanaVersion?: string; + private static services?: GlobalServices; + + public static init({ http, kibanaVersion }: GlobalServices & { kibanaVersion: string }) { + this.services = { http }; + this.kibanaVersion = kibanaVersion; + } + + public static get(): GlobalServices { + if (!this.services) { + this.throwUninitializedError(); + } + + return this.services; + } + + public static getKibanaVersion(): string { + if (!this.kibanaVersion) { + this.throwUninitializedError(); + } + + return this.kibanaVersion; + } + + private static throwUninitializedError(): never { + throw new Error( + 'Kibana services not initialized - are you trying to import this module from outside of the Cases app?' + ); + } +} diff --git a/x-pack/plugins/cases/public/common/mock/index.ts b/x-pack/plugins/cases/public/common/mock/index.ts new file mode 100644 index 0000000000000..add4c1c206dd4 --- /dev/null +++ b/x-pack/plugins/cases/public/common/mock/index.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 * from './test_providers'; diff --git a/x-pack/plugins/cases/public/common/mock/match_media.ts b/x-pack/plugins/cases/public/common/mock/match_media.ts new file mode 100644 index 0000000000000..722f4c3917ea0 --- /dev/null +++ b/x-pack/plugins/cases/public/common/mock/match_media.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. + */ + +window.matchMedia = jest.fn().mockImplementation((query) => { + return { + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), + removeListener: jest.fn(), + }; +}); diff --git a/x-pack/plugins/cases/public/common/mock/test_providers.tsx b/x-pack/plugins/cases/public/common/mock/test_providers.tsx new file mode 100644 index 0000000000000..94ee5dd4f2743 --- /dev/null +++ b/x-pack/plugins/cases/public/common/mock/test_providers.tsx @@ -0,0 +1,61 @@ +/* + * 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 euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { I18nProvider } from '@kbn/i18n/react'; +import React from 'react'; +import { BehaviorSubject } from 'rxjs'; +import { ThemeProvider } from 'styled-components'; +import { + createKibanaContextProviderMock, + createStartServicesMock, +} from '../lib/kibana/kibana_react.mock'; +import { FieldHook } from '../shared_imports'; + +interface Props { + children: React.ReactNode; +} + +export const kibanaObservable = new BehaviorSubject(createStartServicesMock()); + +window.scrollTo = jest.fn(); +const MockKibanaContextProvider = createKibanaContextProviderMock(); + +/** A utility for wrapping children in the providers required to run most tests */ +const TestProvidersComponent: React.FC = ({ children }) => ( + + + ({ eui: euiDarkVars, darkMode: true })}>{children} + + +); + +export const TestProviders = React.memo(TestProvidersComponent); + +export const useFormFieldMock = (options?: Partial>): FieldHook => { + return { + path: 'path', + type: 'type', + value: ('mockedValue' as unknown) as T, + isPristine: false, + isValidating: false, + isValidated: false, + isChangingValue: false, + errors: [], + isValid: true, + getErrorsMessages: jest.fn(), + onChange: jest.fn(), + setValue: jest.fn(), + setErrors: jest.fn(), + clearErrors: jest.fn(), + validate: jest.fn(), + reset: jest.fn(), + __isIncludedInOutput: true, + __serializeValue: jest.fn(), + ...options, + }; +}; diff --git a/x-pack/plugins/cases/public/common/shared_imports.ts b/x-pack/plugins/cases/public/common/shared_imports.ts new file mode 100644 index 0000000000000..675204076b02a --- /dev/null +++ b/x-pack/plugins/cases/public/common/shared_imports.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. + */ + +export { + getUseField, + getFieldValidityAndErrorMessage, + FieldHook, + FieldValidateResponse, + FIELD_TYPES, + Form, + FormData, + FormDataProvider, + FormHook, + FormSchema, + UseField, + UseMultiFields, + useForm, + useFormContext, + useFormData, + ValidationError, + ValidationFunc, + VALIDATION_TYPES, +} from '../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; +export { + Field, + SelectField, +} from '../../../../../src/plugins/es_ui_shared/static/forms/components'; +export { fieldValidators } from '../../../../../src/plugins/es_ui_shared/static/forms/helpers'; +export { ERROR_CODE } from '../../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types'; diff --git a/x-pack/plugins/cases/public/common/test_utils.ts b/x-pack/plugins/cases/public/common/test_utils.ts new file mode 100644 index 0000000000000..f6ccf28bcb643 --- /dev/null +++ b/x-pack/plugins/cases/public/common/test_utils.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. + */ + +/** + * Convenience utility to remove text appended to links by EUI + */ +export const removeExternalLinkText = (str: string) => + str.replace(/\(opens in a new tab or window\)/g, ''); diff --git a/x-pack/plugins/cases/public/common/translations.ts b/x-pack/plugins/cases/public/common/translations.ts new file mode 100644 index 0000000000000..834bd1292ccdd --- /dev/null +++ b/x-pack/plugins/cases/public/common/translations.ts @@ -0,0 +1,259 @@ +/* + * 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 SAVED_OBJECT_NO_PERMISSIONS_TITLE = i18n.translate( + 'xpack.cases.caseSavedObjectNoPermissionsTitle', + { + defaultMessage: 'Kibana feature privileges required', + } +); + +export const SAVED_OBJECT_NO_PERMISSIONS_MSG = i18n.translate( + 'xpack.cases.caseSavedObjectNoPermissionsMessage', + { + defaultMessage: + 'To view cases, you must have privileges for the Saved Object Management feature in the Kibana space. For more information, contact your Kibana administrator.', + } +); + +export const BACK_TO_ALL = i18n.translate('xpack.cases.caseView.backLabel', { + defaultMessage: 'Back to cases', +}); + +export const CANCEL = i18n.translate('xpack.cases.caseView.cancel', { + defaultMessage: 'Cancel', +}); + +export const DELETE_CASE = i18n.translate('xpack.cases.confirmDeleteCase.deleteCase', { + defaultMessage: 'Delete case', +}); + +export const DELETE_CASES = i18n.translate('xpack.cases.confirmDeleteCase.deleteCases', { + defaultMessage: 'Delete cases', +}); + +export const NAME = i18n.translate('xpack.cases.caseView.name', { + defaultMessage: 'Name', +}); + +export const OPENED_ON = i18n.translate('xpack.cases.caseView.openedOn', { + defaultMessage: 'Opened on', +}); + +export const CLOSED_ON = i18n.translate('xpack.cases.caseView.closedOn', { + defaultMessage: 'Closed on', +}); + +export const REPORTER = i18n.translate('xpack.cases.caseView.reporterLabel', { + defaultMessage: 'Reporter', +}); + +export const PARTICIPANTS = i18n.translate('xpack.cases.caseView.particpantsLabel', { + defaultMessage: 'Participants', +}); + +export const CREATE_BC_TITLE = i18n.translate('xpack.cases.caseView.breadcrumb', { + defaultMessage: 'Create', +}); + +export const CREATE_TITLE = i18n.translate('xpack.cases.caseView.create', { + defaultMessage: 'Create new case', +}); + +export const DESCRIPTION = i18n.translate('xpack.cases.caseView.description', { + defaultMessage: 'Description', +}); + +export const DESCRIPTION_REQUIRED = i18n.translate( + 'xpack.cases.createCase.descriptionFieldRequiredError', + { + defaultMessage: 'A description is required.', + } +); + +export const COMMENT_REQUIRED = i18n.translate('xpack.cases.caseView.commentFieldRequiredError', { + defaultMessage: 'A comment is required.', +}); + +export const REQUIRED_FIELD = i18n.translate('xpack.cases.caseView.fieldRequiredError', { + defaultMessage: 'Required field', +}); + +export const EDIT = i18n.translate('xpack.cases.caseView.edit', { + defaultMessage: 'Edit', +}); + +export const OPTIONAL = i18n.translate('xpack.cases.caseView.optional', { + defaultMessage: 'Optional', +}); + +export const PAGE_TITLE = i18n.translate('xpack.cases.pageTitle', { + defaultMessage: 'Cases', +}); + +export const CREATE_CASE = i18n.translate('xpack.cases.caseView.createCase', { + defaultMessage: 'Create case', +}); + +export const CLOSE_CASE = i18n.translate('xpack.cases.caseView.closeCase', { + defaultMessage: 'Close case', +}); + +export const MARK_CASE_IN_PROGRESS = i18n.translate('xpack.cases.caseView.markInProgress', { + defaultMessage: 'Mark in progress', +}); + +export const REOPEN_CASE = i18n.translate('xpack.cases.caseView.reopenCase', { + defaultMessage: 'Reopen case', +}); + +export const OPEN_CASE = i18n.translate('xpack.cases.caseView.openCase', { + defaultMessage: 'Open case', +}); + +export const CASE_NAME = i18n.translate('xpack.cases.caseView.caseName', { + defaultMessage: 'Case name', +}); + +export const TO = i18n.translate('xpack.cases.caseView.to', { + defaultMessage: 'to', +}); + +export const TAGS = i18n.translate('xpack.cases.caseView.tags', { + defaultMessage: 'Tags', +}); + +export const ACTIONS = i18n.translate('xpack.cases.allCases.actions', { + defaultMessage: 'Actions', +}); + +export const NO_TAGS_AVAILABLE = i18n.translate('xpack.cases.allCases.noTagsAvailable', { + defaultMessage: 'No tags available', +}); + +export const NO_REPORTERS_AVAILABLE = i18n.translate('xpack.cases.caseView.noReportersAvailable', { + defaultMessage: 'No reporters available.', +}); + +export const COMMENTS = i18n.translate('xpack.cases.allCases.comments', { + defaultMessage: 'Comments', +}); + +export const TAGS_HELP = i18n.translate('xpack.cases.createCase.fieldTagsHelpText', { + defaultMessage: + 'Type one or more custom identifying tags for this case. Press enter after each tag to begin a new one.', +}); + +export const NO_TAGS = i18n.translate('xpack.cases.caseView.noTags', { + defaultMessage: 'No tags are currently assigned to this case.', +}); + +export const TITLE_REQUIRED = i18n.translate('xpack.cases.createCase.titleFieldRequiredError', { + defaultMessage: 'A title is required.', +}); + +export const CONFIGURE_CASES_PAGE_TITLE = i18n.translate('xpack.cases.configureCases.headerTitle', { + defaultMessage: 'Configure cases', +}); + +export const CONFIGURE_CASES_BUTTON = i18n.translate('xpack.cases.configureCasesButton', { + defaultMessage: 'Edit external connection', +}); + +export const ADD_COMMENT = i18n.translate('xpack.cases.caseView.comment.addComment', { + defaultMessage: 'Add comment', +}); + +export const ADD_COMMENT_HELP_TEXT = i18n.translate( + 'xpack.cases.caseView.comment.addCommentHelpText', + { + defaultMessage: 'Add a new comment...', + } +); + +export const SAVE = i18n.translate('xpack.cases.caseView.description.save', { + defaultMessage: 'Save', +}); + +export const GO_TO_DOCUMENTATION = i18n.translate('xpack.cases.caseView.goToDocumentationButton', { + defaultMessage: 'View documentation', +}); + +export const CONNECTORS = i18n.translate('xpack.cases.caseView.connectors', { + defaultMessage: 'External Incident Management System', +}); + +export const EDIT_CONNECTOR = i18n.translate('xpack.cases.caseView.editConnector', { + defaultMessage: 'Change external incident management system', +}); + +export const NO_CONNECTOR = i18n.translate('xpack.cases.common.noConnector', { + defaultMessage: 'No connector selected', +}); + +export const UNKNOWN = i18n.translate('xpack.cases.caseView.unknown', { + defaultMessage: 'Unknown', +}); + +export const MARKED_CASE_AS = i18n.translate('xpack.cases.caseView.markedCaseAs', { + defaultMessage: 'marked case as', +}); + +export const OPEN_CASES = i18n.translate('xpack.cases.caseTable.openCases', { + defaultMessage: 'Open cases', +}); + +export const CLOSED_CASES = i18n.translate('xpack.cases.caseTable.closedCases', { + defaultMessage: 'Closed cases', +}); + +export const IN_PROGRESS_CASES = i18n.translate('xpack.cases.caseTable.inProgressCases', { + defaultMessage: 'In progress cases', +}); + +export const SYNC_ALERTS_SWITCH_LABEL_ON = i18n.translate( + 'xpack.cases.settings.syncAlertsSwitchLabelOn', + { + defaultMessage: 'On', + } +); + +export const SYNC_ALERTS_SWITCH_LABEL_OFF = i18n.translate( + 'xpack.cases.settings.syncAlertsSwitchLabelOff', + { + defaultMessage: 'Off', + } +); + +export const SYNC_ALERTS_HELP = i18n.translate('xpack.cases.components.create.syncAlertHelpText', { + defaultMessage: + 'Enabling this option will sync the status of alerts in this case with the case status.', +}); + +export const ALERT = i18n.translate('xpack.cases.common.alertLabel', { + defaultMessage: 'Alert', +}); + +export const ALERTS = i18n.translate('xpack.cases.common.alertsLabel', { + defaultMessage: 'Alerts', +}); + +export const ALERT_ADDED_TO_CASE = i18n.translate('xpack.cases.common.alertAddedToCase', { + defaultMessage: 'added to case', +}); + +export const SELECTABLE_MESSAGE_COLLECTIONS = i18n.translate( + 'xpack.cases.common.allCases.table.selectableMessageCollections', + { + defaultMessage: 'Cases with sub-cases cannot be selected', + } +); +export const SELECT_CASE_TITLE = i18n.translate('xpack.cases.common.allCases.caseModal.title', { + defaultMessage: 'Select case', +}); diff --git a/x-pack/plugins/cases/public/components/__mock__/form.ts b/x-pack/plugins/cases/public/components/__mock__/form.ts new file mode 100644 index 0000000000000..6d3e8353e630a --- /dev/null +++ b/x-pack/plugins/cases/public/components/__mock__/form.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useForm } from '../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'; +import { useFormData } from '../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data'; + +jest.mock('../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'); +jest.mock( + '../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data' +); + +export const mockFormHook = { + isSubmitted: false, + isSubmitting: false, + isValid: true, + submit: jest.fn(), + subscribe: jest.fn(), + setFieldValue: jest.fn(), + setFieldErrors: jest.fn(), + getFields: jest.fn(), + getFormData: jest.fn(), + /* Returns a list of all errors in the form */ + getErrors: jest.fn(), + reset: jest.fn(), + __options: {}, + __formData$: {}, + __addField: jest.fn(), + __removeField: jest.fn(), + __validateFields: jest.fn(), + __updateFormDataAt: jest.fn(), + __readFieldConfigFromSchema: jest.fn(), + __getFieldDefaultValue: jest.fn(), +}; + +export const getFormMock = (sampleData: any) => ({ + ...mockFormHook, + submit: () => + Promise.resolve({ + data: sampleData, + isValid: true, + }), + getFormData: () => sampleData, +}); + +export const useFormMock = useForm as jest.Mock; +export const useFormDataMock = useFormData as jest.Mock; diff --git a/x-pack/plugins/cases/public/components/__mock__/router.ts b/x-pack/plugins/cases/public/components/__mock__/router.ts new file mode 100644 index 0000000000000..58b7bb0ac2688 --- /dev/null +++ b/x-pack/plugins/cases/public/components/__mock__/router.ts @@ -0,0 +1,40 @@ +/* + * 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 { Router } from 'react-router-dom'; +// eslint-disable-next-line @kbn/eslint/module_migration +import routeData from 'react-router'; +type Action = 'PUSH' | 'POP' | 'REPLACE'; +const pop: Action = 'POP'; +const location = { + pathname: '/network', + search: '', + state: '', + hash: '', +}; +export const mockHistory = { + length: 2, + location, + action: pop, + push: jest.fn(), + replace: jest.fn(), + go: jest.fn(), + goBack: jest.fn(), + goForward: jest.fn(), + block: jest.fn(), + createHref: jest.fn(), + listen: jest.fn(), +}; + +export const mockLocation = { + pathname: '/welcome', + hash: '', + search: '', + state: '', +}; + +export { Router, routeData }; diff --git a/x-pack/plugins/cases/public/components/__mock__/timeline.tsx b/x-pack/plugins/cases/public/components/__mock__/timeline.tsx new file mode 100644 index 0000000000000..0aeda0f08302d --- /dev/null +++ b/x-pack/plugins/cases/public/components/__mock__/timeline.tsx @@ -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 React from 'react'; +import { useTimelineContext } from '../timeline_context/use_timeline_context'; +jest.mock('../timeline_context'); + +const mockTimelineComponent = (name: string) => {name}; + +export const timelineIntegrationMock = { + editor_plugins: { + parsingPlugin: jest.fn(), + processingPluginRenderer: () => mockTimelineComponent('plugin-renderer'), + uiPlugin: { + name: 'mock-timeline', + button: { label: 'mock-timeline-button', iconType: 'mock-timeline-icon' }, + editor: () => mockTimelineComponent('plugin-timeline-editor'), + }, + }, + hooks: { + useInsertTimeline: jest.fn(), + }, + ui: { + renderInvestigateInTimelineActionComponent: () => + mockTimelineComponent('investigate-in-timeline'), + renderTimelineDetailsPanel: () => mockTimelineComponent('timeline-details-panel'), + }, +}; + +export const useTimelineContextMock = useTimelineContext as jest.Mock; diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx similarity index 79% rename from x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx rename to x-pack/plugins/cases/public/components/add_comment/index.test.tsx index 9c06fc032f819..d35a3dc6a7462 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx @@ -10,19 +10,18 @@ import { mount } from 'enzyme'; import { waitFor, act } from '@testing-library/react'; import { noop } from 'lodash/fp'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; -import { CommentRequest, CommentType } from '../../../../../cases/common/api'; -import { useInsertTimeline } from '../use_insert_timeline'; +import { CommentRequest, CommentType } from '../../../common'; import { usePostComment } from '../../containers/use_post_comment'; import { AddComment, AddCommentRefObject } from '.'; +import { CasesTimelineIntegrationProvider } from '../timeline_context'; +import { timelineIntegrationMock } from '../__mock__/timeline'; jest.mock('../../containers/use_post_comment'); -jest.mock('../use_insert_timeline'); const usePostCommentMock = usePostComment as jest.Mock; -const useInsertTimelineMock = useInsertTimeline as jest.Mock; const onCommentSaving = jest.fn(); const onCommentPosted = jest.fn(); const postComment = jest.fn(); @@ -49,7 +48,7 @@ const sampleData: CommentRequest = { describe('AddComment ', () => { beforeEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); usePostCommentMock.mockImplementation(() => defaultPostComment); jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); }); @@ -63,20 +62,15 @@ describe('AddComment ', () => { ); - await act(async () => { - wrapper - .find(`[data-test-subj="add-comment"] textarea`) - .first() - .simulate('change', { target: { value: sampleData.comment } }); - }); + wrapper + .find(`[data-test-subj="add-comment"] textarea`) + .first() + .simulate('change', { target: { value: sampleData.comment } }); expect(wrapper.find(`[data-test-subj="add-comment"]`).exists()).toBeTruthy(); expect(wrapper.find(`[data-test-subj="loading-spinner"]`).exists()).toBeFalsy(); - await act(async () => { - wrapper.find(`[data-test-subj="submit-comment"]`).first().simulate('click'); - }); - + wrapper.find(`[data-test-subj="submit-comment"]`).first().simulate('click'); await waitFor(() => { expect(onCommentSaving).toBeCalled(); expect(postComment).toBeCalledWith({ @@ -131,12 +125,10 @@ describe('AddComment ', () => { ); - await act(async () => { - wrapper - .find(`[data-test-subj="add-comment"] textarea`) - .first() - .simulate('change', { target: { value: sampleData.comment } }); - }); + wrapper + .find(`[data-test-subj="add-comment"] textarea`) + .first() + .simulate('change', { target: { value: sampleData.comment } }); await act(async () => { ref.current!.addQuote(sampleQuote); @@ -148,16 +140,22 @@ describe('AddComment ', () => { }); it('it should insert a timeline', async () => { + const useInsertTimelineMock = jest.fn(); let attachTimeline = noop; useInsertTimelineMock.mockImplementation((comment, onTimelineAttached) => { attachTimeline = onTimelineAttached; }); + const mockTimelineIntegration = { ...timelineIntegrationMock }; + mockTimelineIntegration.hooks.useInsertTimeline = useInsertTimelineMock; + const wrapper = mount( - - - + + + + + ); diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx b/x-pack/plugins/cases/public/components/add_comment/index.tsx similarity index 87% rename from x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx rename to x-pack/plugins/cases/public/components/add_comment/index.tsx index acd27e99a857f..b4aadc85ad5a7 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.tsx @@ -9,16 +9,15 @@ import { EuiButton, EuiLoadingSpinner } from '@elastic/eui'; import React, { useCallback, forwardRef, useImperativeHandle } from 'react'; import styled from 'styled-components'; -import { CommentType } from '../../../../../cases/common/api'; +import { CommentType } from '../../../common'; import { usePostComment } from '../../containers/use_post_comment'; import { Case } from '../../containers/types'; -import { MarkdownEditorForm } from '../../../common/components/markdown_editor/eui_form'; -import { Form, useForm, UseField, useFormData } from '../../../shared_imports'; +import { MarkdownEditorForm } from '../markdown_editor'; +import { Form, useForm, UseField, useFormData } from '../../common/shared_imports'; import * as i18n from './translations'; import { schema, AddCommentFormSchema } from './schema'; -import { useInsertTimeline } from '../use_insert_timeline'; - +import { InsertTimeline } from '../insert_timeline'; const MySpinner = styled(EuiLoadingSpinner)` position: absolute; top: 50%; @@ -71,13 +70,6 @@ export const AddComment = React.memo( addQuote, })); - const onTimelineAttached = useCallback( - (newValue: string) => setFieldValue(fieldName, newValue), - [setFieldValue] - ); - - useInsertTimeline(comment ?? '', onTimelineAttached); - const onSubmit = useCallback(async () => { const { isValid, data } = await submit(); if (isValid) { @@ -120,6 +112,7 @@ export const AddComment = React.memo( ), }} /> + ); diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/schema.tsx b/x-pack/plugins/cases/public/components/add_comment/schema.tsx similarity index 88% rename from x-pack/plugins/security_solution/public/cases/components/add_comment/schema.tsx rename to x-pack/plugins/cases/public/components/add_comment/schema.tsx index 2cf7d3c6c555b..9693219dd5196 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/schema.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/schema.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { CommentRequestUserType } from '../../../../../cases/common/api'; -import { FIELD_TYPES, fieldValidators, FormSchema } from '../../../shared_imports'; +import { CommentRequestUserType } from '../../../common'; +import { FIELD_TYPES, fieldValidators, FormSchema } from '../../common/shared_imports'; import * as i18n from './translations'; const { emptyField } = fieldValidators; diff --git a/x-pack/plugins/cases/public/components/add_comment/translations.ts b/x-pack/plugins/cases/public/components/add_comment/translations.ts new file mode 100644 index 0000000000000..a3d96a3b9b5b6 --- /dev/null +++ b/x-pack/plugins/cases/public/components/add_comment/translations.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 * from '../../common/translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx b/x-pack/plugins/cases/public/components/all_cases/actions.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx rename to x-pack/plugins/cases/public/components/all_cases/actions.tsx index daa988641fbab..8742b8fea23a4 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/actions.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/actions.tsx @@ -8,7 +8,7 @@ import { Dispatch } from 'react'; import { DefaultItemIconButtonAction } from '@elastic/eui/src/components/basic_table/action_types'; -import { CaseStatuses } from '../../../../../cases/common/api'; +import { CaseStatuses } from '../../../common'; import { Case, SubCase } from '../../containers/types'; import { UpdateCase } from '../../containers/use_get_cases'; import { statuses } from '../status'; @@ -16,13 +16,11 @@ import * as i18n from './translations'; import { isIndividual } from './helpers'; interface GetActions { - caseStatus: string; dispatchUpdate: Dispatch>; deleteCaseOnClick: (deleteCase: Case) => void; } export const getActions = ({ - caseStatus, dispatchUpdate, deleteCaseOnClick, }: GetActions): Array> => { diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx new file mode 100644 index 0000000000000..83f38aab21aa4 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx @@ -0,0 +1,321 @@ +/* + * 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, useRef, useState } from 'react'; +import { EuiProgress } from '@elastic/eui'; +import { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types'; +import { isEmpty, memoize } from 'lodash/fp'; +import styled, { css } from 'styled-components'; +import classnames from 'classnames'; + +import { + Case, + CaseStatuses, + CaseType, + CommentRequestAlertType, + CommentType, + FilterOptions, + SortFieldCase, + SubCase, +} from '../../../common'; +import { SELECTABLE_MESSAGE_COLLECTIONS } from '../../common/translations'; +import { useGetActionLicense } from '../../containers/use_get_action_license'; +import { useGetCases } from '../../containers/use_get_cases'; +import { usePostComment } from '../../containers/use_post_comment'; +import { CaseCallOut } from '../callout'; +import { CaseDetailsHrefSchema, CasesNavigation } from '../links'; +import { Panel } from '../panel'; +import { getActionLicenseError } from '../use_push_to_service/helpers'; +import { ERROR_PUSH_SERVICE_CALLOUT_TITLE } from '../use_push_to_service/translations'; +import { useCasesColumns } from './columns'; +import { getExpandedRowMap } from './expanded_row'; +import { CasesTableHeader } from './header'; +import { CasesTableFilters } from './table_filters'; +import { EuiBasicTableOnChange } from './types'; + +import { CasesTable } from './table'; +const ProgressLoader = styled(EuiProgress)` + ${({ $isShow }: { $isShow: boolean }) => + $isShow + ? css` + top: 2px; + border-radius: ${({ theme }) => theme.eui.euiBorderRadius}; + z-index: ${({ theme }) => theme.eui.euiZHeader}; + ` + : ` + display: none; + `} +`; + +const getSortField = (field: string): SortFieldCase => + field === SortFieldCase.closedAt ? SortFieldCase.closedAt : SortFieldCase.createdAt; + +interface AllCasesGenericProps { + alertData?: Omit; + caseDetailsNavigation?: CasesNavigation; // if not passed, case name is not displayed as a link (Formerly dependant on isSelectorView) + configureCasesNavigation?: CasesNavigation; // if not passed, header with nav is not displayed (Formerly dependant on isSelectorView) + createCaseNavigation: CasesNavigation; + disabledStatuses?: CaseStatuses[]; + isSelectorView?: boolean; + onRowClick?: (theCase?: Case | SubCase) => void; + updateCase?: (newCase: Case) => void; + userCanCrud: boolean; +} + +export const AllCasesGeneric = React.memo( + ({ + alertData, + caseDetailsNavigation, + configureCasesNavigation, + createCaseNavigation, + disabledStatuses, + isSelectorView, + onRowClick, + updateCase, + userCanCrud, + }) => { + const { actionLicense } = useGetActionLicense(); + const { + data, + dispatchUpdateCaseProperty, + filterOptions, + loading, + queryParams, + selectedCases, + refetchCases, + setFilters, + setQueryParams, + setSelectedCases, + } = useGetCases(); + + // Post Comment to Case + const { postComment, isLoading: isCommentUpdating } = usePostComment(); + + const sorting = useMemo( + () => ({ + sort: { field: queryParams.sortField, direction: queryParams.sortOrder }, + }), + [queryParams.sortField, queryParams.sortOrder] + ); + + const filterRefetch = useRef<() => void>(); + const setFilterRefetch = useCallback( + (refetchFilter: () => void) => { + filterRefetch.current = refetchFilter; + }, + [filterRefetch] + ); + const [refresh, doRefresh] = useState(0); + const [isLoading, handleIsLoading] = useState(false); + const refreshCases = useCallback( + (dataRefresh = true) => { + if (dataRefresh) refetchCases(); + doRefresh((prev) => prev + 1); + setSelectedCases([]); + if (filterRefetch.current != null) { + filterRefetch.current(); + } + }, + [filterRefetch, refetchCases, setSelectedCases] + ); + + const { onClick: onCreateCaseNavClick } = createCaseNavigation; + const goToCreateCase = useCallback( + (ev) => { + ev.preventDefault(); + if (isSelectorView && onRowClick != null) { + onRowClick(); + } else if (onCreateCaseNavClick) { + onCreateCaseNavClick(ev); + } + }, + [isSelectorView, onCreateCaseNavClick, onRowClick] + ); + const actionsErrors = useMemo(() => getActionLicenseError(actionLicense), [actionLicense]); + + const tableOnChangeCallback = useCallback( + ({ page, sort }: EuiBasicTableOnChange) => { + let newQueryParams = queryParams; + if (sort) { + newQueryParams = { + ...newQueryParams, + sortField: getSortField(sort.field), + sortOrder: sort.direction, + }; + } + if (page) { + newQueryParams = { + ...newQueryParams, + page: page.index + 1, + perPage: page.size, + }; + } + setQueryParams(newQueryParams); + refreshCases(false); + }, + [queryParams, refreshCases, setQueryParams] + ); + + const onFilterChangedCallback = useCallback( + (newFilterOptions: Partial) => { + if (newFilterOptions.status && newFilterOptions.status === CaseStatuses.closed) { + setQueryParams({ sortField: SortFieldCase.closedAt }); + } else if (newFilterOptions.status && newFilterOptions.status === CaseStatuses.open) { + setQueryParams({ sortField: SortFieldCase.createdAt }); + } else if ( + newFilterOptions.status && + newFilterOptions.status === CaseStatuses['in-progress'] + ) { + setQueryParams({ sortField: SortFieldCase.createdAt }); + } + setFilters(newFilterOptions); + refreshCases(false); + }, + [refreshCases, setQueryParams, setFilters] + ); + + const showActions = userCanCrud && !isSelectorView; + + const columns = useCasesColumns({ + caseDetailsNavigation, + dispatchUpdateCaseProperty, + filterStatus: filterOptions.status, + handleIsLoading, + isLoadingCases: loading, + refreshCases, + showActions, + }); + + const itemIdToExpandedRowMap = useMemo( + () => + getExpandedRowMap({ + columns, + data: data.cases, + onSubCaseClick: onRowClick, + }), + [data.cases, columns, onRowClick] + ); + + const pagination = useMemo( + () => ({ + pageIndex: queryParams.page - 1, + pageSize: queryParams.perPage, + totalItemCount: data.total, + pageSizeOptions: [5, 10, 15, 20, 25], + }), + [data, queryParams] + ); + + const euiBasicTableSelectionProps = useMemo>( + () => ({ + onSelectionChange: setSelectedCases, + selectableMessage: (selectable) => (!selectable ? SELECTABLE_MESSAGE_COLLECTIONS : ''), + initialSelected: selectedCases, + }), + [selectedCases, setSelectedCases] + ); + const isCasesLoading = useMemo(() => loading.indexOf('cases') > -1, [loading]); + const isDataEmpty = useMemo(() => data.total === 0, [data]); + + const TableWrap = useMemo(() => (isSelectorView ? 'span' : Panel), [isSelectorView]); + + const tableRowProps = useCallback( + (theCase: Case) => { + const onTableRowClick = memoize(async () => { + if (alertData != null) { + await postComment({ + caseId: theCase.id, + data: { + type: CommentType.alert, + ...alertData, + }, + updateCase, + }); + } + if (onRowClick) { + onRowClick(theCase); + } + }); + + return { + 'data-test-subj': `cases-table-row-${theCase.id}`, + className: classnames({ isDisabled: theCase.type === CaseType.collection }), + ...(isSelectorView && theCase.type !== CaseType.collection + ? { onClick: onTableRowClick } + : {}), + }; + }, + [isSelectorView, alertData, onRowClick, postComment, updateCase] + ); + + return ( + <> + {!isEmpty(actionsErrors) && ( + + )} + {configureCasesNavigation != null && ( + + )} + + + + + + + ); + } +); + +AllCasesGeneric.displayName = 'AllCasesGeneric'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.test.tsx b/x-pack/plugins/cases/public/components/all_cases/columns.test.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/all_cases/columns.test.tsx rename to x-pack/plugins/cases/public/components/all_cases/columns.test.tsx index ac877b9fae381..c7a255da9dda6 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/columns.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import '../../../common/mock/match_media'; +import '../../common/mock/match_media'; import { ExternalServiceColumn } from './columns'; import { useGetCasesMockState } from '../../containers/mock'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx b/x-pack/plugins/cases/public/components/all_cases/columns.tsx similarity index 65% rename from x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx rename to x-pack/plugins/cases/public/components/all_cases/columns.tsx index 1efcdf2d792f4..cf5da3928446e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/columns.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/columns.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EuiAvatar, EuiBadgeGroup, @@ -19,22 +19,24 @@ import { } from '@elastic/eui'; import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; import styled from 'styled-components'; -import { DefaultItemIconButtonAction } from '@elastic/eui/src/components/basic_table/action_types'; -import { CaseStatuses, CaseType } from '../../../../../cases/common/api'; -import { getEmptyTagValue } from '../../../common/components/empty_value'; -import { Case, SubCase } from '../../containers/types'; -import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date'; -import { CaseDetailsLink } from '../../../common/components/links'; +import { CaseStatuses, CaseType, DeleteCase, Case, SubCase } from '../../../common'; +import { getEmptyTagValue } from '../empty_value'; +import { FormattedRelativePreferenceDate } from '../formatted_date'; +import { CaseDetailsHrefSchema, CaseDetailsLink, CasesNavigation } from '../links'; import * as i18n from './translations'; import { Status } from '../status'; import { getSubCasesStatusCountsBadges, isSubCase } from './helpers'; -import { ALERTS } from '../../../app/home/translations'; +import { ALERTS } from '../../common/translations'; +import { getActions } from './actions'; +import { UpdateCase } from '../../containers/use_get_cases'; +import { useDeleteCases } from '../../containers/use_delete_cases'; +import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; export type CasesColumns = - | EuiTableFieldDataColumnType + | EuiTableActionsColumnType | EuiTableComputedColumnType - | EuiTableActionsColumnType; + | EuiTableFieldDataColumnType; const MediumShadeText = styled.p` color: ${({ theme }) => theme.eui.euiColorMediumShade}; @@ -51,27 +53,98 @@ const TagWrapper = styled(EuiBadgeGroup)` const renderStringField = (field: string, dataTestSubj: string) => field != null ? {field} : getEmptyTagValue(); -export const getCasesColumns = ( - actions: Array>, - filterStatus: string, - isModal: boolean -): CasesColumns[] => { - const columns = [ +export interface GetCasesColumn { + caseDetailsNavigation?: CasesNavigation; + dispatchUpdateCaseProperty: (u: UpdateCase) => void; + filterStatus: string; + handleIsLoading: (a: boolean) => void; + isLoadingCases: string[]; + refreshCases?: (a?: boolean) => void; + showActions: boolean; +} +export const useCasesColumns = ({ + caseDetailsNavigation, + dispatchUpdateCaseProperty, + filterStatus, + handleIsLoading, + isLoadingCases, + refreshCases, + showActions, +}: GetCasesColumn): CasesColumns[] => { + // Delete case + const { + dispatchResetIsDeleted, + handleOnDeleteConfirm, + handleToggleModal, + isDeleted, + isDisplayConfirmDeleteModal, + isLoading: isDeleting, + } = useDeleteCases(); + + const [deleteThisCase, setDeleteThisCase] = useState({ + id: '', + title: '', + type: null, + }); + + const toggleDeleteModal = useCallback( + (deleteCase: Case) => { + handleToggleModal(); + setDeleteThisCase({ id: deleteCase.id, title: deleteCase.title, type: deleteCase.type }); + }, + [handleToggleModal] + ); + + const handleDispatchUpdate = useCallback( + (args: Omit) => { + dispatchUpdateCaseProperty({ + ...args, + refetchCasesStatus: () => { + if (refreshCases != null) refreshCases(); + }, + }); + }, + [dispatchUpdateCaseProperty, refreshCases] + ); + + const actions = useMemo( + () => + getActions({ + deleteCaseOnClick: toggleDeleteModal, + dispatchUpdate: handleDispatchUpdate, + }), + [toggleDeleteModal, handleDispatchUpdate] + ); + + useEffect(() => { + handleIsLoading(isDeleting || isLoadingCases.indexOf('caseUpdate') > -1); + }, [handleIsLoading, isDeleting, isLoadingCases]); + + useEffect(() => { + if (isDeleted) { + if (refreshCases != null) refreshCases(); + dispatchResetIsDeleted(); + } + }, [isDeleted, dispatchResetIsDeleted, refreshCases]); + + return [ { name: i18n.NAME, render: (theCase: Case | SubCase) => { if (theCase.id != null && theCase.title != null) { - const caseDetailsLinkComponent = !isModal ? ( - - {theCase.title} - - ) : ( - {theCase.title} - ); + const caseDetailsLinkComponent = + caseDetailsNavigation != null ? ( + + {theCase.title} + + ) : ( + {theCase.title} + ); return theCase.status !== CaseStatuses.closed ? ( caseDetailsLinkComponent ) : ( @@ -218,15 +291,26 @@ export const getCasesColumns = ( )); }, }, - { - name: i18n.ACTIONS, - actions, - }, + ...(showActions + ? [ + { + name: ( + <> + {i18n.ACTIONS} + + + ), + actions, + }, + ] + : []), ]; - if (isModal) { - columns.pop(); // remove actions if in modal - } - return columns; }; interface Props { diff --git a/x-pack/plugins/cases/public/components/all_cases/count.tsx b/x-pack/plugins/cases/public/components/all_cases/count.tsx new file mode 100644 index 0000000000000..e42e52cfdc934 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/count.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 React, { FunctionComponent, useEffect } from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { CaseStatuses } from '../../../common'; +import { Stats } from '../status'; +import { useGetCasesStatus } from '../../containers/use_get_cases_status'; + +interface CountProps { + refresh?: number; +} +export const Count: FunctionComponent = ({ refresh }) => { + const { + countOpenCases, + countInProgressCases, + countClosedCases, + isLoading: isCasesStatusLoading, + fetchCasesStatus, + } = useGetCasesStatus(); + useEffect(() => { + if (refresh != null) { + fetchCasesStatus(); + } + }, [fetchCasesStatus, refresh]); + return ( + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx b/x-pack/plugins/cases/public/components/all_cases/expanded_row.tsx similarity index 85% rename from x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx rename to x-pack/plugins/cases/public/components/all_cases/expanded_row.tsx index 43f0d9df49e94..59efcf868c9ee 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/expanded_row.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/expanded_row.tsx @@ -10,11 +10,11 @@ import { EuiBasicTable as _EuiBasicTable } from '@elastic/eui'; import styled from 'styled-components'; import { Case, SubCase } from '../../containers/types'; import { CasesColumns } from './columns'; -import { AssociationType } from '../../../../../cases/common/api'; +import { AssociationType } from '../../../common'; type ExpandedRowMap = Record | {}; -const EuiBasicTable: any = _EuiBasicTable; // eslint-disable-line @typescript-eslint/no-explicit-any +const EuiBasicTable: any = _EuiBasicTable; const BasicTable = styled(EuiBasicTable)` thead { display: none; @@ -34,12 +34,10 @@ BasicTable.displayName = 'BasicTable'; export const getExpandedRowMap = ({ data, columns, - isModal, onSubCaseClick, }: { data: Case[] | null; columns: CasesColumns[]; - isModal: boolean; onSubCaseClick?: (theSubCase: SubCase) => void; }): ExpandedRowMap => { if (data == null) { @@ -48,7 +46,7 @@ export const getExpandedRowMap = ({ const rowProps = (theSubCase: SubCase) => { return { - ...(isModal && onSubCaseClick ? { onClick: () => onSubCaseClick(theSubCase) } : {}), + ...(onSubCaseClick ? { onClick: () => onSubCaseClick(theSubCase) } : {}), className: 'subCase', }; }; diff --git a/x-pack/plugins/cases/public/components/all_cases/header.tsx b/x-pack/plugins/cases/public/components/all_cases/header.tsx new file mode 100644 index 0000000000000..a6737b987e2c4 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/header.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, { FunctionComponent } from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import styled, { css } from 'styled-components'; +import { CaseHeaderPage } from '../case_header_page'; +import * as i18n from './translations'; +import { Count } from './count'; +import { CasesNavigation } from '../links'; +import { ErrorMessage } from '../callout/types'; +import { NavButtons } from './nav_buttons'; + +interface OwnProps { + actionsErrors: ErrorMessage[]; + configureCasesNavigation: CasesNavigation; + createCaseNavigation: CasesNavigation; + refresh: number; + userCanCrud: boolean; +} + +type Props = OwnProps; + +const FlexItemDivider = styled(EuiFlexItem)` + ${({ theme }) => css` + .euiFlexGroup--gutterMedium > &.euiFlexItem { + border-right: ${theme.eui.euiBorderThin}; + padding-right: ${theme.eui.euiSize}; + margin-right: ${theme.eui.euiSize}; + } + `} +`; + +export const CasesTableHeader: FunctionComponent = ({ + actionsErrors, + configureCasesNavigation, + createCaseNavigation, + refresh, + userCanCrud, +}) => ( + + + + + + + + + + +); diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/helpers.ts b/x-pack/plugins/cases/public/components/all_cases/helpers.ts similarity index 98% rename from x-pack/plugins/security_solution/public/cases/components/all_cases/helpers.ts rename to x-pack/plugins/cases/public/components/all_cases/helpers.ts index 8962d67319371..1751d478a5d9c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/helpers.ts +++ b/x-pack/plugins/cases/public/components/all_cases/helpers.ts @@ -6,7 +6,7 @@ */ import { filter } from 'lodash/fp'; -import { AssociationType, CaseStatuses, CaseType } from '../../../../../cases/common/api'; +import { AssociationType, CaseStatuses, CaseType } from '../../../common'; import { Case, SubCase } from '../../containers/types'; import { statuses } from '../status'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx similarity index 81% rename from x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx rename to x-pack/plugins/cases/public/components/all_cases/index.test.tsx index c7dd392bf801c..82db4a63115e4 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx @@ -9,41 +9,52 @@ import React from 'react'; import { mount } from 'enzyme'; import moment from 'moment-timezone'; import { waitFor } from '@testing-library/react'; -import '../../../common/mock/match_media'; -import { TestProviders } from '../../../common/mock'; +import '../../common/mock/match_media'; +import { TestProviders } from '../../common/mock'; import { casesStatus, useGetCasesMockState, collectionCase } from '../../containers/mock'; -import * as i18n from './translations'; -import { CaseStatuses, CaseType } from '../../../../../cases/common/api'; -import { useKibana } from '../../../common/lib/kibana'; -import { getEmptyTagValue } from '../../../common/components/empty_value'; +import { CaseStatuses, CaseType, StatusAll } from '../../../common'; +import { getEmptyTagValue } from '../empty_value'; import { useDeleteCases } from '../../containers/use_delete_cases'; import { useGetCases } from '../../containers/use_get_cases'; import { useGetCasesStatus } from '../../containers/use_get_cases_status'; import { useUpdateCases } from '../../containers/use_bulk_update_case'; import { useGetActionLicense } from '../../containers/use_get_action_license'; -import { getCasesColumns } from './columns'; -import { AllCases } from '.'; -import { StatusAll } from '../status'; - +import { AllCasesGeneric as AllCases } from './all_cases_generic'; +import { AllCasesProps } from '.'; +import { CasesColumns, GetCasesColumn, useCasesColumns } from './columns'; +import { renderHook } from '@testing-library/react-hooks'; jest.mock('../../containers/use_bulk_update_case'); jest.mock('../../containers/use_delete_cases'); jest.mock('../../containers/use_get_cases'); jest.mock('../../containers/use_get_cases_status'); jest.mock('../../containers/use_get_action_license'); -const useKibanaMock = useKibana as jest.Mocked; const useDeleteCasesMock = useDeleteCases as jest.Mock; const useGetCasesMock = useGetCases as jest.Mock; const useGetCasesStatusMock = useGetCasesStatus as jest.Mock; const useUpdateCasesMock = useUpdateCases as jest.Mock; const useGetActionLicenseMock = useGetActionLicense as jest.Mock; -jest.mock('../../../common/components/link_to'); - -jest.mock('../../../common/lib/kibana'); +jest.mock('../../common/lib/kibana'); + +describe('AllCasesGeneric', () => { + const defaultAllCasesProps: AllCasesProps = { + configureCasesNavigation: { + href: 'blah', + onClick: jest.fn(), + }, + caseDetailsNavigation: { + href: jest.fn().mockReturnValue('testHref'), // string + onClick: jest.fn(), + }, + createCaseNavigation: { + href: 'bleh', + onClick: jest.fn(), + }, + userCanCrud: true, + }; -describe('AllCases', () => { const dispatchResetIsDeleted = jest.fn(); const dispatchResetIsUpdated = jest.fn(); const dispatchUpdateCaseProperty = jest.fn(); @@ -97,12 +108,20 @@ describe('AllCases', () => { isError: false, }; - let navigateToApp: jest.Mock; + const defaultColumnArgs = { + caseDetailsNavigation: { + href: jest.fn(), + onClick: jest.fn(), + }, + dispatchUpdateCaseProperty: jest.fn, + filterStatus: CaseStatuses.open, + handleIsLoading: jest.fn(), + isLoadingCases: [], + showActions: true, + }; beforeEach(() => { jest.clearAllMocks(); - navigateToApp = jest.fn(); - useKibanaMock().services.application.navigateToApp = navigateToApp; useUpdateCasesMock.mockReturnValue(defaultUpdateCases); useGetCasesMock.mockReturnValue(defaultGetCases); useDeleteCasesMock.mockReturnValue(defaultDeleteCases); @@ -119,13 +138,13 @@ describe('AllCases', () => { const wrapper = mount( - + ); await waitFor(() => { expect(wrapper.find(`a[data-test-subj="case-details-link"]`).first().prop('href')).toEqual( - `/${useGetCasesMockState.data.cases[0].id}` + `testHref` ); expect(wrapper.find(`a[data-test-subj="case-details-link"]`).first().text()).toEqual( useGetCasesMockState.data.cases[0].title @@ -157,7 +176,7 @@ describe('AllCases', () => { const wrapper = mount( - + ); @@ -193,7 +212,7 @@ describe('AllCases', () => { const wrapper = mount( - + ); @@ -234,20 +253,22 @@ describe('AllCases', () => { }); const wrapper = mount( - + ); const checkIt = (columnName: string, key: number) => { const column = wrapper.find('[data-test-subj="cases-table"] tbody .euiTableRowCell').at(key); - if (columnName === i18n.ACTIONS) { - return; - } expect(column.find('.euiTableRowCell--hideForDesktop').text()).toEqual(columnName); expect(column.find('span').text()).toEqual(emptyTag); }; + + const { result } = renderHook(() => + useCasesColumns(defaultColumnArgs) + ); + await waitFor(() => { - getCasesColumns([], CaseStatuses.open, false).map( - (i, key) => i.name != null && checkIt(`${i.name}`, key) + result.current.map( + (i, key) => i.name != null && !i.hasOwnProperty('actions') && checkIt(`${i.name}`, key) ); }); }); @@ -259,7 +280,7 @@ describe('AllCases', () => { }); const wrapper = mount( - + ); wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click'); @@ -301,7 +322,7 @@ describe('AllCases', () => { }); const wrapper = mount( - + ); @@ -326,19 +347,24 @@ describe('AllCases', () => { }); }); - it('should not render case link or actions on modal=true', async () => { + it('should not render case link when caseDetailsNavigation is not passed or actions on showActions=false', async () => { + const { caseDetailsNavigation, ...rest } = defaultAllCasesProps; const wrapper = mount( - + ); + const { result } = renderHook(() => + useCasesColumns({ + dispatchUpdateCaseProperty: jest.fn, + isLoadingCases: [], + filterStatus: CaseStatuses.open, + handleIsLoading: jest.fn(), + showActions: false, + }) + ); await waitFor(() => { - const checkIt = (columnName: string) => { - expect(columnName).not.toEqual(i18n.ACTIONS); - }; - getCasesColumns([], CaseStatuses.open, true).map( - (i, key) => i.name != null && checkIt(`${i.name}`) - ); + result.current.map((i) => i.name != null && !i.hasOwnProperty('actions')); expect(wrapper.find(`a[data-test-subj="case-details-link"]`).exists()).toBeFalsy(); }); }); @@ -346,7 +372,7 @@ describe('AllCases', () => { it('should tableHeaderSortButton AllCases', async () => { const wrapper = mount( - + ); wrapper.find('[data-test-subj="tableHeaderSortButton"]').first().simulate('click'); @@ -363,7 +389,7 @@ describe('AllCases', () => { it('closes case when row action icon clicked', async () => { const wrapper = mount( - + ); wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click'); @@ -371,13 +397,14 @@ describe('AllCases', () => { await waitFor(() => { const firstCase = useGetCasesMockState.data.cases[0]; - expect(dispatchUpdateCaseProperty).toBeCalledWith({ - caseId: firstCase.id, - updateKey: 'status', - updateValue: CaseStatuses.closed, - refetchCasesStatus: fetchCasesStatus, - version: firstCase.version, - }); + expect(dispatchUpdateCaseProperty.mock.calls[0][0]).toEqual( + expect.objectContaining({ + caseId: firstCase.id, + updateKey: 'status', + updateValue: CaseStatuses.closed, + version: firstCase.version, + }) + ); }); }); @@ -398,7 +425,7 @@ describe('AllCases', () => { const wrapper = mount( - + ); @@ -407,20 +434,21 @@ describe('AllCases', () => { await waitFor(() => { const firstCase = useGetCasesMockState.data.cases[0]; - expect(dispatchUpdateCaseProperty).toBeCalledWith({ - caseId: firstCase.id, - updateKey: 'status', - updateValue: CaseStatuses.open, - refetchCasesStatus: fetchCasesStatus, - version: firstCase.version, - }); + expect(dispatchUpdateCaseProperty.mock.calls[0][0]).toEqual( + expect.objectContaining({ + caseId: firstCase.id, + updateKey: 'status', + updateValue: CaseStatuses.open, + version: firstCase.version, + }) + ); }); }); it('put case in progress when row action icon clicked', async () => { const wrapper = mount( - + ); @@ -429,13 +457,14 @@ describe('AllCases', () => { await waitFor(() => { const firstCase = useGetCasesMockState.data.cases[0]; - expect(dispatchUpdateCaseProperty).toBeCalledWith({ - caseId: firstCase.id, - updateKey: 'status', - updateValue: CaseStatuses['in-progress'], - refetchCasesStatus: fetchCasesStatus, - version: firstCase.version, - }); + expect(dispatchUpdateCaseProperty.mock.calls[0][0]).toEqual( + expect.objectContaining({ + caseId: firstCase.id, + updateKey: 'status', + updateValue: CaseStatuses['in-progress'], + version: firstCase.version, + }) + ); }); }); @@ -458,7 +487,7 @@ describe('AllCases', () => { const wrapper = mount( - + ); @@ -495,7 +524,7 @@ describe('AllCases', () => { const wrapper = mount( - + ); @@ -513,7 +542,7 @@ describe('AllCases', () => { }); }); - it('Renders correct bulk actoins for case collection when filter status is set to all - enable only bulk delete if any collection is selected', async () => { + it('Renders correct bulk actions for case collection when filter status is set to all - enable only bulk delete if any collection is selected', async () => { useGetCasesMock.mockReturnValue({ ...defaultGetCases, filterOptions: { ...defaultGetCases.filterOptions, status: CaseStatuses.open }, @@ -538,7 +567,7 @@ describe('AllCases', () => { const wrapper = mount( - + ); wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click'); @@ -565,7 +594,7 @@ describe('AllCases', () => { const wrapper = mount( - + ); wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click'); @@ -588,7 +617,7 @@ describe('AllCases', () => { const wrapper = mount( - + ); wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click'); @@ -607,7 +636,7 @@ describe('AllCases', () => { const wrapper = mount( - + ); wrapper.find('[data-test-subj="case-table-bulk-actions"] button').first().simulate('click'); @@ -628,7 +657,7 @@ describe('AllCases', () => { mount( - + ); await waitFor(() => { @@ -646,7 +675,7 @@ describe('AllCases', () => { mount( - + ); await waitFor(() => { @@ -656,10 +685,11 @@ describe('AllCases', () => { }); }); - it('should not render header when modal=true', async () => { + it('should not render header when configureCasesNavigation are not present', async () => { + const { configureCasesNavigation, ...restProps } = defaultAllCasesProps; const wrapper = mount( - + ); await waitFor(() => { @@ -667,23 +697,24 @@ describe('AllCases', () => { }); }); - it('should not render table utility bar when modal=true', async () => { + it('should not render table utility bar when isSelectorView=true', async () => { const wrapper = mount( - + ); await waitFor(() => { - expect(wrapper.find('[data-test-subj="case-table-utility-bar-actions"]').exists()).toBe( + expect(wrapper.find('[data-test-subj="case-table-selected-case-count"]').exists()).toBe( false ); + expect(wrapper.find('[data-test-subj="case-table-bulk-actions"]').exists()).toBe(false); }); }); - it('case table should not be selectable when modal=true', async () => { + it('case table should not be selectable when isSelectorView=true', async () => { const wrapper = mount( - + ); await waitFor(() => { @@ -693,7 +724,7 @@ describe('AllCases', () => { }); }); - it('should call onRowClick with no cases and modal=true', async () => { + it('should call onRowClick with no cases and isSelectorView=true', async () => { useGetCasesMock.mockReturnValue({ ...defaultGetCases, data: { @@ -705,7 +736,12 @@ describe('AllCases', () => { const wrapper = mount( - + ); wrapper.find('[data-test-subj="cases-table-add-case"]').first().simulate('click'); @@ -714,7 +750,8 @@ describe('AllCases', () => { }); }); - it('should call navigateToApp with no cases and modal=false', async () => { + it('should call createCaseNavigation.onClick with no cases and isSelectorView=false', async () => { + const createCaseNavigation = { href: '', onClick: jest.fn() }; useGetCasesMock.mockReturnValue({ ...defaultGetCases, data: { @@ -726,19 +763,28 @@ describe('AllCases', () => { const wrapper = mount( - + ); wrapper.find('[data-test-subj="cases-table-add-case"]').first().simulate('click'); await waitFor(() => { - expect(navigateToApp).toHaveBeenCalledWith('securitySolution:case', { path: '/create' }); + expect(createCaseNavigation.onClick).toHaveBeenCalled(); }); }); it('should call onRowClick when clicking a case with modal=true', async () => { const wrapper = mount( - + ); wrapper.find('[data-test-subj="cases-table-row-1"]').first().simulate('click'); @@ -793,7 +839,7 @@ describe('AllCases', () => { it('should NOT call onRowClick when clicking a case with modal=true', async () => { const wrapper = mount( - + ); wrapper.find('[data-test-subj="cases-table-row-1"]').first().simulate('click'); @@ -805,7 +851,7 @@ describe('AllCases', () => { it('should change the status to closed', async () => { const wrapper = mount( - + ); wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click'); @@ -820,7 +866,7 @@ describe('AllCases', () => { it('should change the status to in-progress', async () => { const wrapper = mount( - + ); wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click'); @@ -835,7 +881,7 @@ describe('AllCases', () => { it('should change the status to open', async () => { const wrapper = mount( - + ); wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click'); @@ -850,7 +896,7 @@ describe('AllCases', () => { it('should show the correct count on stats', async () => { const wrapper = mount( - + ); wrapper.find('button[data-test-subj="case-status-filter"]').simulate('click'); @@ -882,7 +928,7 @@ describe('AllCases', () => { const wrapper = mount( - + ); @@ -908,7 +954,7 @@ describe('AllCases', () => { const wrapper = mount( - + ); diff --git a/x-pack/plugins/cases/public/components/all_cases/index.tsx b/x-pack/plugins/cases/public/components/all_cases/index.tsx new file mode 100644 index 0000000000000..2c506cd2da411 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/index.tsx @@ -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 React from 'react'; +import { CaseDetailsHrefSchema, CasesNavigation } from '../links'; +import { AllCasesGeneric } from './all_cases_generic'; +export interface AllCasesProps { + caseDetailsNavigation: CasesNavigation; // if not passed, case name is not displayed as a link (Formerly dependant on isSelector) + configureCasesNavigation: CasesNavigation; // if not passed, header with nav is not displayed (Formerly dependant on isSelector) + createCaseNavigation: CasesNavigation; + userCanCrud: boolean; +} + +export const AllCases: React.FC = (props) => { + return ; +}; + +// eslint-disable-next-line import/no-default-export +export { AllCases as default }; diff --git a/x-pack/plugins/cases/public/components/all_cases/nav_buttons.tsx b/x-pack/plugins/cases/public/components/all_cases/nav_buttons.tsx new file mode 100644 index 0000000000000..e29551f43c2bd --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/nav_buttons.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, { FunctionComponent } from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; +import { ConfigureCaseButton } from '../configure_cases/button'; +import * as i18n from './translations'; +import { CasesNavigation, LinkButton } from '../links'; +import { ErrorMessage } from '../callout/types'; + +interface OwnProps { + actionsErrors: ErrorMessage[]; + configureCasesNavigation: CasesNavigation; + createCaseNavigation: CasesNavigation; + userCanCrud: boolean; +} + +type Props = OwnProps; + +export const NavButtons: FunctionComponent = ({ + actionsErrors, + configureCasesNavigation, + createCaseNavigation, + userCanCrud, +}) => ( + + + } + titleTooltip={!isEmpty(actionsErrors) ? actionsErrors[0].title : ''} + /> + + + + {i18n.CREATE_TITLE} + + + +); diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/index.test.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/index.test.tsx new file mode 100644 index 0000000000000..aaec37335c699 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/index.test.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { AllCasesSelectorModal } from '.'; +import { TestProviders } from '../../../common/mock'; +import { AllCasesGeneric } from '../all_cases_generic'; + +jest.mock('../../../methods'); +jest.mock('../all_cases_generic'); +const onRowClick = jest.fn(); +const createCaseNavigation = { href: '', onClick: jest.fn() }; +const defaultProps = { + createCaseNavigation, + onRowClick, + userCanCrud: true, +}; +const updateCase = jest.fn(); + +describe('AllCasesSelectorModal', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('renders', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj='all-cases-modal']`).exists()).toBeTruthy(); + }); + + it('Closing modal calls onCloseCaseModal', () => { + const wrapper = mount( + + + + ); + + wrapper.find('.euiModal__closeIcon').first().simulate('click'); + expect(wrapper.find(`[data-test-subj='all-cases-modal']`).exists()).toBeFalsy(); + }); + + it('pass the correct props to getAllCases method', () => { + const fullProps = { + ...defaultProps, + alertData: { + rule: { + id: 'rule-id', + name: 'rule', + }, + index: 'index-id', + alertId: 'alert-id', + }, + disabledStatuses: [], + updateCase, + }; + mount( + + + + ); + // @ts-ignore idk what this mock style is but it works ¯\_(ツ)_/¯ + expect(AllCasesGeneric.type.mock.calls[0][0]).toEqual( + expect.objectContaining({ + alertData: fullProps.alertData, + createCaseNavigation, + disabledStatuses: fullProps.disabledStatuses, + isSelectorView: true, + userCanCrud: fullProps.userCanCrud, + updateCase, + }) + ); + }); +}); diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/index.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/index.tsx new file mode 100644 index 0000000000000..0a83ef13e8ee6 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/index.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 React, { useState, useCallback } from 'react'; +import { EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui'; +import styled from 'styled-components'; +import { Case, CaseStatuses, CommentRequestAlertType, SubCase } from '../../../../common'; +import { CasesNavigation } from '../../links'; +import * as i18n from '../../../common/translations'; +import { AllCasesGeneric } from '../all_cases_generic'; + +export interface AllCasesSelectorModalProps { + alertData?: Omit; + createCaseNavigation: CasesNavigation; + disabledStatuses?: CaseStatuses[]; + onRowClick: (theCase?: Case | SubCase) => void; + updateCase?: (newCase: Case) => void; + userCanCrud: boolean; +} + +const Modal = styled(EuiModal)` + ${({ theme }) => ` + width: ${theme.eui.euiBreakpoints.l}; + max-width: ${theme.eui.euiBreakpoints.l}; + `} +`; + +export const AllCasesSelectorModal: React.FC = ({ + alertData, + createCaseNavigation, + disabledStatuses, + onRowClick, + updateCase, + userCanCrud, +}) => { + const [isModalOpen, setIsModalOpen] = useState(true); + const closeModal = useCallback(() => setIsModalOpen(false), []); + const onClick = useCallback( + (theCase?: Case | SubCase) => { + closeModal(); + onRowClick(theCase); + }, + [closeModal, onRowClick] + ); + return isModalOpen ? ( + + + {i18n.SELECT_CASE_TITLE} + + + + + + ) : null; +}; +// eslint-disable-next-line import/no-default-export +export { AllCasesSelectorModal as default }; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.test.tsx b/x-pack/plugins/cases/public/components/all_cases/status_filter.test.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.test.tsx rename to x-pack/plugins/cases/public/components/all_cases/status_filter.test.tsx index 5c9f11d1e3a83..1a9dd9c772294 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/status_filter.test.tsx @@ -9,9 +9,8 @@ import React from 'react'; import { mount } from 'enzyme'; import { waitFor } from '@testing-library/react'; -import { CaseStatuses } from '../../../../../cases/common/api'; +import { CaseStatuses, StatusAll } from '../../../common'; import { StatusFilter } from './status_filter'; -import { StatusAll } from '../status'; const stats = { [StatusAll]: 0, diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.tsx b/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx similarity index 93% rename from x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.tsx rename to x-pack/plugins/cases/public/components/all_cases/status_filter.tsx index 34186a201cc05..9fb00933f0307 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/status_filter.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx @@ -7,7 +7,8 @@ import React, { memo } from 'react'; import { EuiSuperSelect, EuiSuperSelectOption, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { Status, statuses, StatusAll, CaseStatusWithAllStatus } from '../status'; +import { Status, statuses } from '../status'; +import { CaseStatusWithAllStatus, StatusAll } from '../../../common'; interface Props { stats: Record; diff --git a/x-pack/plugins/cases/public/components/all_cases/table.tsx b/x-pack/plugins/cases/public/components/all_cases/table.tsx new file mode 100644 index 0000000000000..4b786e320d50c --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/table.tsx @@ -0,0 +1,148 @@ +/* + * 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, { FunctionComponent } from 'react'; +import { + EuiEmptyPrompt, + EuiLoadingContent, + EuiTableSelectionType, + EuiBasicTable as _EuiBasicTable, + EuiBasicTableProps, +} from '@elastic/eui'; +import classnames from 'classnames'; +import styled from 'styled-components'; + +import { CasesTableUtilityBar } from './utility_bar'; +import { CasesNavigation, LinkButton } from '../links'; +import { AllCases, Case, FilterOptions } from '../../../common'; +import * as i18n from './translations'; + +interface CasesTableProps { + columns: EuiBasicTableProps['columns']; // CasesColumns[]; + createCaseNavigation: CasesNavigation; + data: AllCases; + filterOptions: FilterOptions; + goToCreateCase: (e: React.MouseEvent) => void; + handleIsLoading: (a: boolean) => void; + isCasesLoading: boolean; + isCommentUpdating: boolean; + isDataEmpty: boolean; + isSelectorView?: boolean; + itemIdToExpandedRowMap: EuiBasicTableProps['itemIdToExpandedRowMap']; + onChange: EuiBasicTableProps['onChange']; + pagination: EuiBasicTableProps['pagination']; + refreshCases: (a?: boolean) => void; + selectedCases: Case[]; + selection: EuiTableSelectionType; + showActions: boolean; + sorting: EuiBasicTableProps['sorting']; + tableRowProps: EuiBasicTableProps['rowProps']; + userCanCrud: boolean; +} + +const EuiBasicTable: any = _EuiBasicTable; +const BasicTable = styled(EuiBasicTable)` + ${({ theme }) => ` + .euiTableRow-isExpandedRow.euiTableRow-isSelectable .euiTableCellContent { + padding: 8px 0 8px 32px; + } + + &.isSelectorView .euiTableRow.isDisabled { + cursor: not-allowed; + background-color: ${theme.eui.euiTableHoverClickableColor}; + } + + &.isSelectorView .euiTableRow.euiTableRow-isExpandedRow .euiTableRowCell, + &.isSelectorView .euiTableRow.euiTableRow-isExpandedRow:hover { + background-color: transparent; + } + + &.isSelectorView .euiTableRow.euiTableRow-isExpandedRow { + .subCase:hover { + background-color: ${theme.eui.euiTableHoverClickableColor}; + } + } + `} +`; + +const Div = styled.div` + margin-top: ${({ theme }) => theme.eui.paddingSizes.m}; +`; + +export const CasesTable: FunctionComponent = ({ + columns, + createCaseNavigation, + data, + filterOptions, + goToCreateCase, + handleIsLoading, + isCasesLoading, + isCommentUpdating, + isDataEmpty, + isSelectorView, + itemIdToExpandedRowMap, + onChange, + pagination, + refreshCases, + selectedCases, + selection, + showActions, + sorting, + tableRowProps, + userCanCrud, +}) => + isCasesLoading && isDataEmpty ? ( +

    + ) : ( +
    + + {i18n.NO_CASES}} + titleSize="xs" + body={i18n.NO_CASES_BODY} + actions={ + + {i18n.ADD_NEW_CASE} + + } + /> + } + onChange={onChange} + pagination={pagination} + rowProps={tableRowProps} + selection={showActions ? selection : undefined} + sorting={sorting} + className={classnames({ isSelectorView })} + /> +
    + ); diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx b/x-pack/plugins/cases/public/components/all_cases/table_filters.test.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx rename to x-pack/plugins/cases/public/components/all_cases/table_filters.test.tsx index 48a642aaf51a9..20892ce8e9c5d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/table_filters.test.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../../../cases/common/api'; -import { TestProviders } from '../../../common/mock'; +import { CaseStatuses } from '../../../common'; +import { TestProviders } from '../../common/mock'; import { useGetTags } from '../../containers/use_get_tags'; import { useGetReporters } from '../../containers/use_get_reporters'; import { DEFAULT_FILTER_OPTIONS } from '../../containers/use_get_cases'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.tsx b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx similarity index 91% rename from x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.tsx rename to x-pack/plugins/cases/public/components/all_cases/table_filters.tsx index ff5b511ef9026..9428a374a0314 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/table_filters.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx @@ -10,12 +10,11 @@ import { isEqual } from 'lodash/fp'; import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiFilterGroup } from '@elastic/eui'; -import { CaseStatuses } from '../../../../../cases/common/api'; +import { CaseStatuses, CaseStatusWithAllStatus, StatusAll } from '../../../common'; import { FilterOptions } from '../../containers/types'; import { useGetTags } from '../../containers/use_get_tags'; import { useGetReporters } from '../../containers/use_get_reporters'; import { FilterPopover } from '../filter_popover'; -import { CaseStatusWithAllStatus, StatusAll } from '../status'; import { StatusFilter } from './status_filter'; import * as i18n from './translations'; @@ -78,22 +77,6 @@ const CasesTableFiltersComponent = ({ } }, [refetch, setFilterRefetch]); - useEffect(() => { - if (selectedReporters.length) { - const newReporters = selectedReporters.filter((r) => reporters.includes(r)); - handleSelectedReporters(newReporters); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [reporters]); - - useEffect(() => { - if (selectedTags.length) { - const newTags = selectedTags.filter((t) => tags.includes(t)); - handleSelectedTags(newTags); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [tags]); - const handleSelectedReporters = useCallback( (newReporters) => { if (!isEqual(newReporters, selectedReporters)) { @@ -104,10 +87,16 @@ const CasesTableFiltersComponent = ({ onFilterChanged({ reporters: reportersObj }); } }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [selectedReporters, respReporters] + [selectedReporters, respReporters, onFilterChanged] ); + useEffect(() => { + if (selectedReporters.length) { + const newReporters = selectedReporters.filter((r) => reporters.includes(r)); + handleSelectedReporters(newReporters); + } + }, [handleSelectedReporters, reporters, selectedReporters]); + const handleSelectedTags = useCallback( (newTags) => { if (!isEqual(newTags, selectedTags)) { @@ -115,10 +104,16 @@ const CasesTableFiltersComponent = ({ onFilterChanged({ tags: newTags }); } }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [selectedTags] + [onFilterChanged, selectedTags] ); + useEffect(() => { + if (selectedTags.length) { + const newTags = selectedTags.filter((t) => tags.includes(t)); + handleSelectedTags(newTags); + } + }, [handleSelectedTags, selectedTags, tags]); + const handleOnSearch = useCallback( (newSearch) => { const trimSearch = newSearch.trim(); @@ -127,8 +122,7 @@ const CasesTableFiltersComponent = ({ onFilterChanged({ search: trimSearch }); } }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [search] + [onFilterChanged, search] ); const onStatusChanged = useCallback( diff --git a/x-pack/plugins/cases/public/components/all_cases/translations.ts b/x-pack/plugins/cases/public/components/all_cases/translations.ts new file mode 100644 index 0000000000000..0f535b771ec8a --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/translations.ts @@ -0,0 +1,91 @@ +/* + * 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 NO_CASES = i18n.translate('xpack.cases.caseTable.noCases.title', { + defaultMessage: 'No Cases', +}); +export const NO_CASES_BODY = i18n.translate('xpack.cases.caseTable.noCases.body', { + defaultMessage: + 'There are no cases to display. Please create a new case or change your filter settings above.', +}); + +export const ADD_NEW_CASE = i18n.translate('xpack.cases.caseTable.addNewCase', { + defaultMessage: 'Add New Case', +}); + +export const SHOWING_SELECTED_CASES = (totalRules: number) => + i18n.translate('xpack.cases.caseTable.selectedCasesTitle', { + values: { totalRules }, + defaultMessage: 'Selected {totalRules} {totalRules, plural, =1 {case} other {cases}}', + }); + +export const SHOWING_CASES = (totalRules: number) => + i18n.translate('xpack.cases.caseTable.showingCasesTitle', { + values: { totalRules }, + defaultMessage: 'Showing {totalRules} {totalRules, plural, =1 {case} other {cases}}', + }); + +export const UNIT = (totalCount: number) => + i18n.translate('xpack.cases.caseTable.unit', { + values: { totalCount }, + defaultMessage: `{totalCount, plural, =1 {case} other {cases}}`, + }); + +export const SEARCH_CASES = i18n.translate('xpack.cases.caseTable.searchAriaLabel', { + defaultMessage: 'Search cases', +}); + +export const BULK_ACTIONS = i18n.translate('xpack.cases.caseTable.bulkActions', { + defaultMessage: 'Bulk actions', +}); + +export const EXTERNAL_INCIDENT = i18n.translate('xpack.cases.caseTable.snIncident', { + defaultMessage: 'External Incident', +}); + +export const INCIDENT_MANAGEMENT_SYSTEM = i18n.translate('xpack.cases.caseTable.incidentSystem', { + defaultMessage: 'Incident Management System', +}); + +export const SEARCH_PLACEHOLDER = i18n.translate('xpack.cases.caseTable.searchPlaceholder', { + defaultMessage: 'e.g. case name', +}); + +export const CLOSED = i18n.translate('xpack.cases.caseTable.closed', { + defaultMessage: 'Closed', +}); + +export const DELETE = i18n.translate('xpack.cases.caseTable.delete', { + defaultMessage: 'Delete', +}); + +export const REQUIRES_UPDATE = i18n.translate('xpack.cases.caseTable.requiresUpdate', { + defaultMessage: ' requires update', +}); + +export const UP_TO_DATE = i18n.translate('xpack.cases.caseTable.upToDate', { + defaultMessage: ' is up to date', +}); +export const NOT_PUSHED = i18n.translate('xpack.cases.caseTable.notPushed', { + defaultMessage: 'Not pushed', +}); + +export const REFRESH = i18n.translate('xpack.cases.caseTable.refreshTitle', { + defaultMessage: 'Refresh', +}); + +export const SERVICENOW_LINK_ARIA = i18n.translate('xpack.cases.caseTable.serviceNowLinkAria', { + defaultMessage: 'click to view the incident on servicenow', +}); + +export const STATUS = i18n.translate('xpack.cases.caseTable.status', { + defaultMessage: 'Status', +}); diff --git a/x-pack/plugins/cases/public/components/all_cases/types.ts b/x-pack/plugins/cases/public/components/all_cases/types.ts new file mode 100644 index 0000000000000..5014522177570 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/types.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 * as t from 'io-ts'; + +/* eslint-disable @typescript-eslint/naming-convention */ + +export const sort_order = t.keyof({ asc: null, desc: null }); +export type SortOrder = t.TypeOf; + +export interface EuiBasicTableSortTypes { + field: string; + direction: SortOrder; +} + +export interface EuiBasicTableOnChange { + page: { + index: number; + size: number; + }; + sort?: EuiBasicTableSortTypes; +} diff --git a/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx new file mode 100644 index 0000000000000..d0981c38385e9 --- /dev/null +++ b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx @@ -0,0 +1,173 @@ +/* + * 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, { FunctionComponent, useCallback, useEffect, useState } from 'react'; +import { EuiContextMenuPanel } from '@elastic/eui'; +import { + UtilityBar, + UtilityBarAction, + UtilityBarGroup, + UtilityBarSection, + UtilityBarText, +} from '../utility_bar'; +import * as i18n from './translations'; +import { AllCases, Case, DeleteCase, FilterOptions } from '../../../common'; +import { getBulkItems } from '../bulk_actions'; +import { isSelectedCasesIncludeCollections } from './helpers'; +import { useDeleteCases } from '../../containers/use_delete_cases'; +import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; +import { useUpdateCases } from '../../containers/use_bulk_update_case'; + +interface OwnProps { + data: AllCases; + enableBulkActions: boolean; + filterOptions: FilterOptions; + handleIsLoading: (a: boolean) => void; + refreshCases?: (a?: boolean) => void; + selectedCases: Case[]; +} + +type Props = OwnProps; + +export const CasesTableUtilityBar: FunctionComponent = ({ + data, + enableBulkActions = false, + filterOptions, + handleIsLoading, + refreshCases, + selectedCases, +}) => { + const [deleteBulk, setDeleteBulk] = useState([]); + const [deleteThisCase, setDeleteThisCase] = useState({ + title: '', + id: '', + type: null, + }); + // Delete case + const { + dispatchResetIsDeleted, + handleOnDeleteConfirm, + handleToggleModal, + isLoading: isDeleting, + isDeleted, + isDisplayConfirmDeleteModal, + } = useDeleteCases(); + + // Update case + const { + dispatchResetIsUpdated, + isLoading: isUpdating, + isUpdated, + updateBulkStatus, + } = useUpdateCases(); + + useEffect(() => { + handleIsLoading(isDeleting); + }, [handleIsLoading, isDeleting]); + + useEffect(() => { + handleIsLoading(isUpdating); + }, [handleIsLoading, isUpdating]); + useEffect(() => { + if (isDeleted) { + if (refreshCases != null) refreshCases(); + dispatchResetIsDeleted(); + } + if (isUpdated) { + if (refreshCases != null) refreshCases(); + dispatchResetIsUpdated(); + } + }, [isDeleted, isUpdated, refreshCases, dispatchResetIsDeleted, dispatchResetIsUpdated]); + + const toggleBulkDeleteModal = useCallback( + (cases: Case[]) => { + handleToggleModal(); + if (cases.length === 1) { + const singleCase = cases[0]; + if (singleCase) { + return setDeleteThisCase({ + id: singleCase.id, + title: singleCase.title, + type: singleCase.type, + }); + } + } + const convertToDeleteCases: DeleteCase[] = cases.map(({ id, title, type }) => ({ + id, + title, + type, + })); + setDeleteBulk(convertToDeleteCases); + }, + [setDeleteBulk, handleToggleModal] + ); + + const handleUpdateCaseStatus = useCallback( + (status: string) => { + updateBulkStatus(selectedCases, status); + }, + [selectedCases, updateBulkStatus] + ); + const getBulkItemsPopoverContent = useCallback( + (closePopover: () => void) => ( + + ), + [selectedCases, filterOptions.status, toggleBulkDeleteModal, handleUpdateCaseStatus] + ); + return ( + + + + + {i18n.SHOWING_CASES(data.total ?? 0)} + + + + {enableBulkActions && ( + <> + + {i18n.SHOWING_SELECTED_CASES(selectedCases.length)} + + + + {i18n.BULK_ACTIONS} + + + )} + + {i18n.REFRESH} + + + + 0} + onCancel={handleToggleModal} + onConfirm={handleOnDeleteConfirm.bind( + null, + deleteBulk.length > 0 ? deleteBulk : [deleteThisCase] + )} + /> + + ); +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx b/x-pack/plugins/cases/public/components/bulk_actions/index.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx rename to x-pack/plugins/cases/public/components/bulk_actions/index.tsx index 24897a14f0754..fae1c4909ffe2 100644 --- a/x-pack/plugins/security_solution/public/cases/components/bulk_actions/index.tsx +++ b/x-pack/plugins/cases/public/components/bulk_actions/index.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { EuiContextMenuItem } from '@elastic/eui'; -import { CaseStatuses } from '../../../../../cases/common/api'; -import { statuses, CaseStatusWithAllStatus } from '../status'; +import { CaseStatuses, CaseStatusWithAllStatus } from '../../../common'; +import { statuses } from '../status'; import * as i18n from './translations'; import { Case } from '../../containers/types'; diff --git a/x-pack/plugins/security_solution/public/cases/components/bulk_actions/translations.ts b/x-pack/plugins/cases/public/components/bulk_actions/translations.ts similarity index 83% rename from x-pack/plugins/security_solution/public/cases/components/bulk_actions/translations.ts rename to x-pack/plugins/cases/public/components/bulk_actions/translations.ts index 1171495f4a202..c5bc5d7cde66b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/bulk_actions/translations.ts +++ b/x-pack/plugins/cases/public/components/bulk_actions/translations.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; export const BULK_ACTION_DELETE_SELECTED = i18n.translate( - 'xpack.securitySolution.cases.caseTable.bulkActions.deleteSelectedTitle', + 'xpack.cases.caseTable.bulkActions.deleteSelectedTitle', { defaultMessage: 'Delete selected', } diff --git a/x-pack/plugins/cases/public/components/callout/callout.test.tsx b/x-pack/plugins/cases/public/components/callout/callout.test.tsx new file mode 100644 index 0000000000000..926fe7b63fb5a --- /dev/null +++ b/x-pack/plugins/cases/public/components/callout/callout.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 React from 'react'; +import { mount } from 'enzyme'; + +import { CallOut, CallOutProps } from './callout'; + +describe('Callout', () => { + const defaultProps: CallOutProps = { + id: 'md5-hex', + type: 'primary', + title: 'a tittle', + messages: [ + { + id: 'generic-error', + title: 'message-one', + description:

    {'error'}

    , + }, + ], + showCallOut: true, + handleDismissCallout: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('It renders the callout', () => { + const wrapper = mount(); + expect(wrapper.find(`[data-test-subj="case-callout-md5-hex"]`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="callout-messages-md5-hex"]`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="callout-dismiss-md5-hex"]`).exists()).toBeTruthy(); + }); + + it('hides the callout', () => { + const wrapper = mount(); + expect(wrapper.find(`[data-test-subj="case-callout-md5-hex"]`).exists()).toBeFalsy(); + }); + + it('does not shows any messages when the list is empty', () => { + const wrapper = mount(); + expect(wrapper.find(`[data-test-subj="callout-messages-md5-hex"]`).exists()).toBeFalsy(); + }); + + it('transform the button color correctly - primary', () => { + const wrapper = mount(); + const className = + wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ?? + ''; + expect(className.includes('euiButton--primary')).toBeTruthy(); + }); + + it('transform the button color correctly - success', () => { + const wrapper = mount(); + const className = + wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ?? + ''; + expect(className.includes('euiButton--secondary')).toBeTruthy(); + }); + + it('transform the button color correctly - warning', () => { + const wrapper = mount(); + const className = + wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ?? + ''; + expect(className.includes('euiButton--warning')).toBeTruthy(); + }); + + it('transform the button color correctly - danger', () => { + const wrapper = mount(); + const className = + wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ?? + ''; + expect(className.includes('euiButton--danger')).toBeTruthy(); + }); + + it('dismiss the callout correctly', () => { + const wrapper = mount(); + expect(wrapper.find(`[data-test-subj="callout-dismiss-md5-hex"]`).exists()).toBeTruthy(); + wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).simulate('click'); + wrapper.update(); + + expect(defaultProps.handleDismissCallout).toHaveBeenCalledWith('md5-hex', 'primary'); + }); +}); diff --git a/x-pack/plugins/cases/public/components/callout/callout.tsx b/x-pack/plugins/cases/public/components/callout/callout.tsx new file mode 100644 index 0000000000000..8e2f439f02c4b --- /dev/null +++ b/x-pack/plugins/cases/public/components/callout/callout.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 { EuiCallOut, EuiButton, EuiDescriptionList } from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; +import React, { memo, useCallback } from 'react'; + +import { ErrorMessage } from './types'; +import * as i18n from './translations'; + +export interface CallOutProps { + id: string; + type: NonNullable; + title: string; + messages: ErrorMessage[]; + showCallOut: boolean; + handleDismissCallout: (id: string, type: NonNullable) => void; +} + +const CallOutComponent = ({ + id, + type, + title, + messages, + showCallOut, + handleDismissCallout, +}: CallOutProps) => { + const handleCallOut = useCallback(() => handleDismissCallout(id, type), [ + handleDismissCallout, + id, + type, + ]); + + return showCallOut ? ( + + {!isEmpty(messages) && ( + + )} + + {i18n.DISMISS_CALLOUT} + + + ) : null; +}; + +export const CallOut = memo(CallOutComponent); diff --git a/x-pack/plugins/cases/public/components/callout/helpers.test.tsx b/x-pack/plugins/cases/public/components/callout/helpers.test.tsx new file mode 100644 index 0000000000000..b5b92a3374874 --- /dev/null +++ b/x-pack/plugins/cases/public/components/callout/helpers.test.tsx @@ -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 md5 from 'md5'; +import { createCalloutId } from './helpers'; + +describe('createCalloutId', () => { + it('creates id correctly with one id', () => { + const digest = md5('one'); + const id = createCalloutId(['one']); + expect(id).toBe(digest); + }); + + it('creates id correctly with multiples ids', () => { + const digest = md5('one|two|three'); + const id = createCalloutId(['one', 'two', 'three']); + expect(id).toBe(digest); + }); + + it('creates id correctly with multiples ids and delimiter', () => { + const digest = md5('one,two,three'); + const id = createCalloutId(['one', 'two', 'three'], ','); + expect(id).toBe(digest); + }); +}); diff --git a/x-pack/plugins/cases/public/components/callout/helpers.tsx b/x-pack/plugins/cases/public/components/callout/helpers.tsx new file mode 100644 index 0000000000000..2a7804579a57e --- /dev/null +++ b/x-pack/plugins/cases/public/components/callout/helpers.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 from 'react'; +import md5 from 'md5'; + +import * as i18n from './translations'; +import { ErrorMessage } from './types'; + +export const savedObjectReadOnlyErrorMessage: ErrorMessage = { + id: 'read-only-privileges-error', + title: i18n.READ_ONLY_SAVED_OBJECT_TITLE, + description: <>{i18n.READ_ONLY_SAVED_OBJECT_MSG}, + errorType: 'warning', +}; + +export const createCalloutId = (ids: string[], delimiter: string = '|'): string => + md5(ids.join(delimiter)); diff --git a/x-pack/plugins/cases/public/components/callout/index.test.tsx b/x-pack/plugins/cases/public/components/callout/index.test.tsx new file mode 100644 index 0000000000000..c46ec1b5606c9 --- /dev/null +++ b/x-pack/plugins/cases/public/components/callout/index.test.tsx @@ -0,0 +1,217 @@ +/* + * 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 { mount } from 'enzyme'; + +import { useMessagesStorage } from '../../containers/use_messages_storage'; +import { TestProviders } from '../../common/mock'; +import { createCalloutId } from './helpers'; +import { CaseCallOut, CaseCallOutProps } from '.'; + +jest.mock('../../containers/use_messages_storage'); + +const useSecurityLocalStorageMock = useMessagesStorage as jest.Mock; +const securityLocalStorageMock = { + getMessages: jest.fn(() => []), + addMessage: jest.fn(), +}; + +describe('CaseCallOut ', () => { + beforeEach(() => { + jest.clearAllMocks(); + useSecurityLocalStorageMock.mockImplementation(() => securityLocalStorageMock); + }); + + it('renders a callout correctly', () => { + const props: CaseCallOutProps = { + title: 'hey title', + messages: [ + { id: 'message-one', title: 'title', description:

    {'we have two messages'}

    }, + { id: 'message-two', title: 'title', description:

    {'for real'}

    }, + ], + }; + const wrapper = mount( + + + + ); + + const id = createCalloutId(['message-one', 'message-two']); + expect(wrapper.find(`[data-test-subj="callout-messages-${id}"]`).last().exists()).toBeTruthy(); + }); + + it('groups the messages correctly', () => { + const props: CaseCallOutProps = { + title: 'hey title', + messages: [ + { + id: 'message-one', + title: 'title one', + description:

    {'we have two messages'}

    , + errorType: 'danger', + }, + { id: 'message-two', title: 'title two', description:

    {'for real'}

    }, + ], + }; + + const wrapper = mount( + + + + ); + + const idDanger = createCalloutId(['message-one']); + const idPrimary = createCalloutId(['message-two']); + + expect( + wrapper.find(`[data-test-subj="case-callout-${idPrimary}"]`).last().exists() + ).toBeTruthy(); + expect( + wrapper.find(`[data-test-subj="case-callout-${idDanger}"]`).last().exists() + ).toBeTruthy(); + }); + + it('dismisses the callout correctly', () => { + const props: CaseCallOutProps = { + title: 'hey title', + messages: [ + { id: 'message-one', title: 'title', description:

    {'we have two messages'}

    }, + ], + }; + const wrapper = mount( + + + + ); + + const id = createCalloutId(['message-one']); + + expect(wrapper.find(`[data-test-subj="case-callout-${id}"]`).last().exists()).toBeTruthy(); + wrapper.find(`[data-test-subj="callout-dismiss-${id}"]`).last().simulate('click'); + expect(wrapper.find(`[data-test-subj="case-callout-${id}"]`).exists()).toBeFalsy(); + }); + + it('persist the callout of type primary when dismissed', () => { + const props: CaseCallOutProps = { + title: 'hey title', + messages: [ + { id: 'message-one', title: 'title', description:

    {'we have two messages'}

    }, + ], + }; + + const wrapper = mount( + + + + ); + + const id = createCalloutId(['message-one']); + expect(securityLocalStorageMock.getMessages).toHaveBeenCalledWith('case'); + wrapper.find(`[data-test-subj="callout-dismiss-${id}"]`).last().simulate('click'); + expect(securityLocalStorageMock.addMessage).toHaveBeenCalledWith('case', id); + }); + + it('do not show the callout if is in the localStorage', () => { + const props: CaseCallOutProps = { + title: 'hey title', + messages: [ + { id: 'message-one', title: 'title', description:

    {'we have two messages'}

    }, + ], + }; + + const id = createCalloutId(['message-one']); + + useSecurityLocalStorageMock.mockImplementation(() => ({ + ...securityLocalStorageMock, + getMessages: jest.fn(() => [id]), + })); + + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="case-callout-${id}"]`).last().exists()).toBeFalsy(); + }); + + it('do not persist a callout of type danger', () => { + const props: CaseCallOutProps = { + title: 'hey title', + messages: [ + { + id: 'message-one', + title: 'title one', + description:

    {'we have two messages'}

    , + errorType: 'danger', + }, + ], + }; + + const wrapper = mount( + + + + ); + + const id = createCalloutId(['message-one']); + wrapper.find(`button[data-test-subj="callout-dismiss-${id}"]`).simulate('click'); + wrapper.update(); + expect(securityLocalStorageMock.addMessage).not.toHaveBeenCalled(); + }); + + it('do not persist a callout of type warning', () => { + const props: CaseCallOutProps = { + title: 'hey title', + messages: [ + { + id: 'message-one', + title: 'title one', + description:

    {'we have two messages'}

    , + errorType: 'warning', + }, + ], + }; + + const wrapper = mount( + + + + ); + + const id = createCalloutId(['message-one']); + wrapper.find(`button[data-test-subj="callout-dismiss-${id}"]`).simulate('click'); + wrapper.update(); + expect(securityLocalStorageMock.addMessage).not.toHaveBeenCalled(); + }); + + it('do not persist a callout of type success', () => { + const props: CaseCallOutProps = { + title: 'hey title', + messages: [ + { + id: 'message-one', + title: 'title one', + description:

    {'we have two messages'}

    , + errorType: 'success', + }, + ], + }; + + const wrapper = mount( + + + + ); + + const id = createCalloutId(['message-one']); + wrapper.find(`button[data-test-subj="callout-dismiss-${id}"]`).simulate('click'); + wrapper.update(); + expect(securityLocalStorageMock.addMessage).not.toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/callout/index.tsx b/x-pack/plugins/cases/public/components/callout/index.tsx new file mode 100644 index 0000000000000..1994617d62801 --- /dev/null +++ b/x-pack/plugins/cases/public/components/callout/index.tsx @@ -0,0 +1,103 @@ +/* + * 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 { EuiSpacer } from '@elastic/eui'; +import React, { memo, useCallback, useState, useMemo } from 'react'; + +import { useMessagesStorage } from '../../containers/use_messages_storage'; +import { CallOut } from './callout'; +import { ErrorMessage } from './types'; +import { createCalloutId } from './helpers'; + +export * from './helpers'; + +export interface CaseCallOutProps { + title: string; + messages?: ErrorMessage[]; +} + +type GroupByTypeMessages = { + [key in NonNullable]: { + messagesId: string[]; + messages: ErrorMessage[]; + }; +}; + +interface CalloutVisibility { + [index: string]: boolean; +} + +const CaseCallOutComponent = ({ title, messages = [] }: CaseCallOutProps) => { + const { getMessages, addMessage } = useMessagesStorage(); + + const caseMessages = useMemo(() => getMessages('case'), [getMessages]); + const dismissedCallouts = useMemo( + () => + caseMessages.reduce( + (acc, id) => ({ + ...acc, + [id]: false, + }), + {} + ), + [caseMessages] + ); + + const [calloutVisibility, setCalloutVisibility] = useState(dismissedCallouts); + const handleCallOut = useCallback( + (id, type) => { + setCalloutVisibility((prevState) => ({ ...prevState, [id]: false })); + if (type === 'primary') { + addMessage('case', id); + } + }, + [setCalloutVisibility, addMessage] + ); + + const groupedByTypeErrorMessages = useMemo( + () => + messages.reduce( + (acc: GroupByTypeMessages, currentMessage: ErrorMessage) => { + const type = currentMessage.errorType == null ? 'primary' : currentMessage.errorType; + return { + ...acc, + [type]: { + messagesId: [...(acc[type]?.messagesId ?? []), currentMessage.id], + messages: [...(acc[type]?.messages ?? []), currentMessage], + }, + }; + }, + {} as GroupByTypeMessages + ), + [messages] + ); + + return ( + <> + {(Object.keys(groupedByTypeErrorMessages) as Array).map( + (type: NonNullable) => { + const id = createCalloutId(groupedByTypeErrorMessages[type].messagesId); + return ( + + + + + ); + } + )} + + ); +}; + +export const CaseCallOut = memo(CaseCallOutComponent); diff --git a/x-pack/plugins/cases/public/components/callout/translations.ts b/x-pack/plugins/cases/public/components/callout/translations.ts new file mode 100644 index 0000000000000..3f551c5cf0170 --- /dev/null +++ b/x-pack/plugins/cases/public/components/callout/translations.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 { i18n } from '@kbn/i18n'; + +export const READ_ONLY_SAVED_OBJECT_TITLE = i18n.translate('xpack.cases.readOnlySavedObjectTitle', { + defaultMessage: 'You cannot open new or update existing cases', +}); + +export const READ_ONLY_SAVED_OBJECT_MSG = i18n.translate( + 'xpack.cases.readOnlySavedObjectDescription', + { + defaultMessage: + 'You only have permissions to view cases. If you need to open and update cases, contact your Kibana administrator.', + } +); + +export const DISMISS_CALLOUT = i18n.translate('xpack.cases.dismissErrorsPushServiceCallOutTitle', { + defaultMessage: 'Dismiss', +}); diff --git a/x-pack/plugins/cases/public/components/callout/types.ts b/x-pack/plugins/cases/public/components/callout/types.ts new file mode 100644 index 0000000000000..84d79ee391b8f --- /dev/null +++ b/x-pack/plugins/cases/public/components/callout/types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface ErrorMessage { + id: string; + title: string; + description: JSX.Element; + errorType?: 'primary' | 'success' | 'warning' | 'danger'; +} diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/actions.test.tsx b/x-pack/plugins/cases/public/components/case_action_bar/actions.test.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/components/case_action_bar/actions.test.tsx rename to x-pack/plugins/cases/public/components/case_action_bar/actions.test.tsx index ba0c725f99460..886e740d56447 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/actions.test.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/actions.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { useDeleteCases } from '../../containers/use_delete_cases'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; import { basicCase, basicPush } from '../../containers/mock'; import { Actions } from './actions'; import * as i18n from '../case_view/translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/actions.tsx b/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx similarity index 92% rename from x-pack/plugins/security_solution/public/cases/components/case_action_bar/actions.tsx rename to x-pack/plugins/cases/public/components/case_action_bar/actions.tsx index 74d2a40f1ceb9..b8d9d7f85a9ef 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/actions.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx @@ -35,21 +35,6 @@ const ActionsComponent: React.FC = ({ isDisplayConfirmDeleteModal, } = useDeleteCases(); - const confirmDeleteModal = useMemo( - () => ( - - ), - // eslint-disable-next-line react-hooks/exhaustive-deps - [isDisplayConfirmDeleteModal, caseData] - ); const propertyActions = useMemo( () => [ { @@ -78,7 +63,15 @@ const ActionsComponent: React.FC = ({ return ( <> - {confirmDeleteModal} + ); }; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/helpers.test.ts b/x-pack/plugins/cases/public/components/case_action_bar/helpers.test.ts similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/case_action_bar/helpers.test.ts rename to x-pack/plugins/cases/public/components/case_action_bar/helpers.test.ts index 8e26c0fd7a7ff..ed5832d19b4da 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/helpers.test.ts +++ b/x-pack/plugins/cases/public/components/case_action_bar/helpers.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseStatuses } from '../../../../../cases/common/api'; +import { CaseStatuses } from '../../../common'; import { basicCase } from '../../containers/mock'; import { getStatusDate, getStatusTitle } from './helpers'; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/helpers.ts b/x-pack/plugins/cases/public/components/case_action_bar/helpers.ts similarity index 92% rename from x-pack/plugins/security_solution/public/cases/components/case_action_bar/helpers.ts rename to x-pack/plugins/cases/public/components/case_action_bar/helpers.ts index 68a243040145a..35cfdae3abe21 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/helpers.ts +++ b/x-pack/plugins/cases/public/components/case_action_bar/helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseStatuses } from '../../../../../cases/common/api'; +import { CaseStatuses } from '../../../common'; import { Case } from '../../containers/types'; import { statuses } from '../status'; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/index.test.tsx b/x-pack/plugins/cases/public/components/case_action_bar/index.test.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/components/case_action_bar/index.test.tsx rename to x-pack/plugins/cases/public/components/case_action_bar/index.test.tsx index b6158946aa82d..0d29335ea730e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/index.test.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/index.test.tsx @@ -10,7 +10,7 @@ import { mount } from 'enzyme'; import { basicCase } from '../../containers/mock'; import { CaseActionBar } from '.'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; describe('CaseActionBar', () => { const onRefresh = jest.fn(); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/index.tsx b/x-pack/plugins/cases/public/components/case_action_bar/index.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/case_action_bar/index.tsx rename to x-pack/plugins/cases/public/components/case_action_bar/index.tsx index 63ce441732251..0f06dde6a86d1 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/index.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/index.tsx @@ -16,9 +16,9 @@ import { EuiFlexItem, EuiIconTip, } from '@elastic/eui'; -import { CaseStatuses, CaseType } from '../../../../../cases/common/api'; +import { CaseStatuses, CaseType } from '../../../common'; import * as i18n from '../case_view/translations'; -import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date'; +import { FormattedRelativePreferenceDate } from '../formatted_date'; import { Actions } from './actions'; import { Case } from '../../containers/types'; import { CaseService } from '../../containers/use_get_case_user_actions'; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/status_context_menu.test.tsx b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.test.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/case_action_bar/status_context_menu.test.tsx rename to x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.test.tsx index 4e414706d1fd7..29cca46d372f0 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/status_context_menu.test.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../../../cases/common/api'; +import { CaseStatuses } from '../../../common'; import { StatusContextMenu } from './status_context_menu'; describe('SyncAlertsSwitch', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/status_context_menu.tsx b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/case_action_bar/status_context_menu.tsx rename to x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx index 92dcd16a86193..2922b797f9d40 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/status_context_menu.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx @@ -8,7 +8,7 @@ import React, { memo, useCallback, useMemo, useState } from 'react'; import { memoize } from 'lodash/fp'; import { EuiPopover, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; -import { caseStatuses, CaseStatuses } from '../../../../../cases/common/api'; +import { caseStatuses, CaseStatuses } from '../../../common'; import { Status } from '../status'; interface Props { diff --git a/x-pack/plugins/cases/public/components/case_header_page/index.tsx b/x-pack/plugins/cases/public/components/case_header_page/index.tsx new file mode 100644 index 0000000000000..7e60db1030587 --- /dev/null +++ b/x-pack/plugins/cases/public/components/case_header_page/index.tsx @@ -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 React from 'react'; + +import { HeaderPage, HeaderPageProps } from '../header_page'; + +const CaseHeaderPageComponent: React.FC = (props) => ; + +export const CaseHeaderPage = React.memo(CaseHeaderPageComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_settings/sync_alerts_switch.test.tsx b/x-pack/plugins/cases/public/components/case_settings/sync_alerts_switch.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/case_settings/sync_alerts_switch.test.tsx rename to x-pack/plugins/cases/public/components/case_settings/sync_alerts_switch.test.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/case_settings/sync_alerts_switch.tsx b/x-pack/plugins/cases/public/components/case_settings/sync_alerts_switch.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/case_settings/sync_alerts_switch.tsx rename to x-pack/plugins/cases/public/components/case_settings/sync_alerts_switch.tsx index a19640339acc6..406b8dbe51ced 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_settings/sync_alerts_switch.tsx +++ b/x-pack/plugins/cases/public/components/case_settings/sync_alerts_switch.tsx @@ -8,7 +8,7 @@ import React, { memo, useCallback, useState } from 'react'; import { EuiSwitch } from '@elastic/eui'; -import * as i18n from '../../translations'; +import * as i18n from '../../common/translations'; interface Props { disabled: boolean; diff --git a/x-pack/plugins/cases/public/components/case_view/helpers.test.tsx b/x-pack/plugins/cases/public/components/case_view/helpers.test.tsx new file mode 100644 index 0000000000000..f266c574c27da --- /dev/null +++ b/x-pack/plugins/cases/public/components/case_view/helpers.test.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 { AssociationType, CommentType } from '../../../common'; +import { Comment } from '../../containers/types'; + +import { getManualAlertIdsWithNoRuleId } from './helpers'; + +const comments: Comment[] = [ + { + associationType: AssociationType.case, + type: CommentType.alert, + alertId: 'alert-id-1', + index: 'alert-index-1', + id: 'comment-id', + createdAt: '2020-02-19T23:06:33.798Z', + createdBy: { username: 'elastic' }, + rule: { + id: null, + name: null, + }, + pushedAt: null, + pushedBy: null, + updatedAt: null, + updatedBy: null, + version: 'WzQ3LDFc', + }, + { + associationType: AssociationType.case, + type: CommentType.alert, + alertId: 'alert-id-2', + index: 'alert-index-2', + id: 'comment-id', + createdAt: '2020-02-19T23:06:33.798Z', + createdBy: { username: 'elastic' }, + pushedAt: null, + pushedBy: null, + rule: { + id: 'rule-id-2', + name: 'rule-name-2', + }, + updatedAt: null, + updatedBy: null, + version: 'WzQ3LDFc', + }, +]; + +describe('Case view helpers', () => { + describe('getAlertIdsFromComments', () => { + it('it returns the alert id from the comments where rule is not defined', () => { + expect(getManualAlertIdsWithNoRuleId(comments)).toEqual(['alert-id-1']); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/case_view/helpers.ts b/x-pack/plugins/cases/public/components/case_view/helpers.ts new file mode 100644 index 0000000000000..ab26b132e0489 --- /dev/null +++ b/x-pack/plugins/cases/public/components/case_view/helpers.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 { isEmpty } from 'lodash'; +import { CommentType } from '../../../common'; +import { Comment } from '../../containers/types'; + +export const getManualAlertIdsWithNoRuleId = (comments: Comment[]): string[] => { + const dedupeAlerts = comments.reduce((alertIds, comment: Comment) => { + if (comment.type === CommentType.alert && isEmpty(comment.rule.id)) { + const ids = Array.isArray(comment.alertId) ? comment.alertId : [comment.alertId]; + ids.forEach((id) => alertIds.add(id)); + return alertIds; + } + return alertIds; + }, new Set()); + return [...dedupeAlerts]; +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx b/x-pack/plugins/cases/public/components/case_view/index.test.tsx similarity index 73% rename from x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx rename to x-pack/plugins/cases/public/components/case_view/index.test.tsx index 0daa62bf735e8..d13e3978ce618 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/index.test.tsx @@ -8,9 +8,9 @@ import React from 'react'; import { mount } from 'enzyme'; -import '../../../common/mock/match_media'; -import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; -import { CaseComponent, CaseProps, CaseView } from '.'; +import '../../common/mock/match_media'; +import { Router, mockHistory } from '../__mock__/router'; +import { CaseComponent, CaseComponentProps, CaseView } from '.'; import { basicCase, basicCaseClosed, @@ -18,7 +18,7 @@ import { alertComment, getAlertUserAction, } from '../../containers/mock'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; import { useUpdateCase } from '../../containers/use_update_case'; import { useGetCase } from '../../containers/use_get_case'; import { useGetCaseUserActions } from '../../containers/use_get_case_user_actions'; @@ -27,54 +27,19 @@ import { waitFor } from '@testing-library/react'; import { useConnectors } from '../../containers/configure/use_connectors'; import { connectorsMock } from '../../containers/configure/mock'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; -import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query'; -import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; -import { CaseType } from '../../../../../cases/common/api'; - -const mockDispatch = jest.fn(); -jest.mock('react-redux', () => { - const original = jest.requireActual('react-redux'); - return { - ...original, - useDispatch: () => mockDispatch, - }; -}); +import { CaseType, ConnectorTypes } from '../../../common'; jest.mock('../../containers/use_update_case'); jest.mock('../../containers/use_get_case_user_actions'); jest.mock('../../containers/use_get_case'); jest.mock('../../containers/configure/use_connectors'); jest.mock('../../containers/use_post_push_to_service'); -jest.mock('../../../detections/containers/detection_engine/alerts/use_query'); jest.mock('../user_action_tree/user_action_timestamp'); const useUpdateCaseMock = useUpdateCase as jest.Mock; const useGetCaseUserActionsMock = useGetCaseUserActions as jest.Mock; const useConnectorsMock = useConnectors as jest.Mock; const usePostPushToServiceMock = usePostPushToService as jest.Mock; -const useQueryAlertsMock = useQueryAlerts as jest.Mock; - -export const caseProps: CaseProps = { - caseId: basicCase.id, - userCanCrud: true, - caseData: { - ...basicCase, - comments: [...basicCase.comments, alertComment], - connector: { - id: 'resilient-2', - name: 'Resilient', - type: ConnectorTypes.resilient, - fields: null, - }, - }, - fetchCase: jest.fn(), - updateCase: jest.fn(), -}; - -export const caseClosedProps: CaseProps = { - ...caseProps, - caseData: basicCaseClosed, -}; const alertsHit = [ { @@ -103,6 +68,54 @@ const alertsHit = [ }, ]; +export const caseProps: CaseComponentProps = { + allCasesNavigation: { + href: 'all-cases-href', + onClick: jest.fn(), + }, + caseDetailsNavigation: { + href: 'case-details-href', + onClick: jest.fn(), + }, + caseId: basicCase.id, + configureCasesNavigation: { + href: 'configure-cases-href', + onClick: jest.fn(), + }, + getCaseDetailHrefWithCommentId: jest.fn(), + onComponentInitialized: jest.fn(), + ruleDetailsNavigation: { + href: jest.fn(), + onClick: jest.fn(), + }, + showAlertDetails: jest.fn(), + useFetchAlertData: () => [ + false, + { + 'alert-id-1': alertsHit[0], + 'alert-id-2': alertsHit[1], + }, + ], + userCanCrud: true, + caseData: { + ...basicCase, + comments: [...basicCase.comments, alertComment], + connector: { + id: 'resilient-2', + name: 'Resilient', + type: ConnectorTypes.resilient, + fields: null, + }, + }, + fetchCase: jest.fn(), + updateCase: jest.fn(), +}; + +export const caseClosedProps: CaseComponentProps = { + ...caseProps, + caseData: basicCaseClosed, +}; + describe('CaseView ', () => { const updateCaseProperty = jest.fn(); const fetchCaseUserActions = jest.fn(); @@ -139,20 +152,14 @@ describe('CaseView ', () => { }; beforeEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); useUpdateCaseMock.mockImplementation(() => defaultUpdateCaseState); - - jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); useGetCaseUserActionsMock.mockImplementation(() => defaultUseGetCaseUserActions); usePostPushToServiceMock.mockImplementation(() => ({ isLoading: false, pushCaseToExternalService, })); useConnectorsMock.mockImplementation(() => ({ connectors: connectorsMock, loading: false })); - useQueryAlertsMock.mockImplementation(() => ({ - loading: false, - data: { hits: { hits: alertsHit } }, - })); }); it('should render CaseComponent', async () => { @@ -168,44 +175,44 @@ describe('CaseView ', () => { expect(wrapper.find(`[data-test-subj="case-view-title"]`).first().prop('title')).toEqual( data.title ); + }); - expect(wrapper.find(`[data-test-subj="case-view-status-dropdown"]`).first().text()).toEqual( - 'Open' - ); + expect(wrapper.find(`[data-test-subj="case-view-status-dropdown"]`).first().text()).toEqual( + 'Open' + ); - expect( - wrapper - .find(`[data-test-subj="case-view-tag-list"] [data-test-subj="tag-coke"]`) - .first() - .text() - ).toEqual(data.tags[0]); + expect( + wrapper + .find(`[data-test-subj="case-view-tag-list"] [data-test-subj="tag-coke"]`) + .first() + .text() + ).toEqual(data.tags[0]); - expect( - wrapper - .find(`[data-test-subj="case-view-tag-list"] [data-test-subj="tag-pepsi"]`) - .first() - .text() - ).toEqual(data.tags[1]); + expect( + wrapper + .find(`[data-test-subj="case-view-tag-list"] [data-test-subj="tag-pepsi"]`) + .first() + .text() + ).toEqual(data.tags[1]); - expect(wrapper.find(`[data-test-subj="case-view-username"]`).first().text()).toEqual( - data.createdBy.username - ); + expect(wrapper.find(`[data-test-subj="case-view-username"]`).first().text()).toEqual( + data.createdBy.username + ); - expect( - wrapper.find(`[data-test-subj="case-action-bar-status-date"]`).first().prop('value') - ).toEqual(data.createdAt); + expect( + wrapper.find(`[data-test-subj="case-action-bar-status-date"]`).first().prop('value') + ).toEqual(data.createdAt); - expect( - wrapper - .find(`[data-test-subj="description-action"] [data-test-subj="user-action-markdown"]`) - .first() - .text() - ).toBe(data.description); + expect( + wrapper + .find(`[data-test-subj="description-action"] [data-test-subj="user-action-markdown"]`) + .first() + .text() + ).toBe(data.description); - expect( - wrapper.find('button[data-test-subj="case-view-status-action-button"]').first().text() - ).toBe('Mark in progress'); - }); + expect( + wrapper.find('button[data-test-subj="case-view-status-action-button"]').first().text() + ).toBe('Mark in progress'); }); it('should show closed indicators in header when case is closed', async () => { @@ -341,20 +348,17 @@ describe('CaseView ', () => { ); - await waitFor(() => { - const newTitle = 'The new title'; - wrapper.find(`[data-test-subj="editable-title-edit-icon"]`).first().simulate('click'); - wrapper.update(); - wrapper - .find(`[data-test-subj="editable-title-input-field"]`) - .last() - .simulate('change', { target: { value: newTitle } }); + const newTitle = 'The new title'; + wrapper.find(`[data-test-subj="editable-title-edit-icon"]`).first().simulate('click'); + wrapper + .find(`[data-test-subj="editable-title-input-field"]`) + .last() + .simulate('change', { target: { value: newTitle } }); - wrapper.update(); - wrapper.find(`[data-test-subj="editable-title-submit-btn"]`).first().simulate('click'); + wrapper.find(`[data-test-subj="editable-title-submit-btn"]`).first().simulate('click'); - wrapper.update(); - const updateObject = updateCaseProperty.mock.calls[0][0]; + const updateObject = updateCaseProperty.mock.calls[0][0]; + await waitFor(() => { expect(updateObject.updateKey).toEqual('title'); expect(updateObject.updateValue).toEqual(newTitle); }); @@ -378,11 +382,10 @@ describe('CaseView ', () => { expect( wrapper.find('[data-test-subj="has-data-to-push-button"]').first().exists() ).toBeTruthy(); + }); + wrapper.find('[data-test-subj="push-to-external-service"]').first().simulate('click'); - wrapper.find('[data-test-subj="push-to-external-service"]').first().simulate('click'); - - wrapper.update(); - + await waitFor(() => { expect(pushCaseToExternalService).toHaveBeenCalled(); }); }); @@ -397,7 +400,27 @@ describe('CaseView ', () => { @@ -419,7 +442,27 @@ describe('CaseView ', () => { @@ -438,7 +481,27 @@ describe('CaseView ', () => { @@ -457,15 +520,35 @@ describe('CaseView ', () => { ); + wrapper.find('[data-test-subj="case-refresh"]').first().simulate('click'); await waitFor(() => { - wrapper.find('[data-test-subj="case-refresh"]').first().simulate('click'); expect(fetchCaseUserActions).toBeCalledWith('1234', 'resilient-2', undefined); expect(fetchCase).toBeCalled(); }); @@ -497,7 +580,7 @@ describe('CaseView ', () => { }); }); - // TO DO fix when the useEffects in edit_connector are cleaned up + // TODO: fix when the useEffects in edit_connector are cleaned up it.skip('should revert to the initial connector in case of failure', async () => { updateCaseProperty.mockImplementation(({ onError }) => { onError(); @@ -526,18 +609,13 @@ describe('CaseView ', () => { .first() .text(); - await waitFor(() => { - wrapper.find('[data-test-subj="connector-edit"] button').simulate('click'); - }); - - await waitFor(() => { - wrapper.update(); - wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); - wrapper.update(); - wrapper.find('button[data-test-subj="dropdown-connector-resilient-2"]').simulate('click'); - wrapper.update(); - wrapper.find(`[data-test-subj="edit-connectors-submit"]`).last().simulate('click'); - }); + wrapper.find('[data-test-subj="connector-edit"] button').simulate('click'); + await waitFor(() => wrapper.update()); + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + await waitFor(() => wrapper.update()); + wrapper.find('button[data-test-subj="dropdown-connector-resilient-2"]').simulate('click'); + await waitFor(() => wrapper.update()); + wrapper.find(`[data-test-subj="edit-connectors-submit"]`).last().simulate('click'); await waitFor(() => { wrapper.update(); @@ -548,7 +626,6 @@ describe('CaseView ', () => { ).toBe(connectorName); }); }); - it('should update connector', async () => { const wrapper = mount( @@ -572,14 +649,12 @@ describe('CaseView ', () => { wrapper.find('[data-test-subj="connector-edit"] button').simulate('click'); wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); - await waitFor(() => { - wrapper.find('button[data-test-subj="dropdown-connector-resilient-2"]').simulate('click'); - }); + wrapper.find('button[data-test-subj="dropdown-connector-resilient-2"]').simulate('click'); + await waitFor(() => wrapper.update()); wrapper.find(`button[data-test-subj="edit-connectors-submit"]`).first().simulate('click'); await waitFor(() => { - wrapper.update(); const updateObject = updateCaseProperty.mock.calls[0][0]; expect(updateCaseProperty).toHaveBeenCalledTimes(1); expect(updateObject.updateKey).toEqual('connector'); @@ -595,34 +670,23 @@ describe('CaseView ', () => { }); }); - it('it should create a new timeline on mount', async () => { + it('it should call onComponentInitialized on mount', async () => { + const onComponentInitialized = jest.fn(); mount( - + ); await waitFor(() => { - expect(mockDispatch).toHaveBeenCalledWith({ - type: 'x-pack/security_solution/local/timeline/CREATE_TIMELINE', - payload: { - columns: [], - expandedDetail: {}, - id: 'timeline-case', - indexNames: [], - show: false, - }, - }); + expect(onComponentInitialized).toHaveBeenCalled(); }); }); it('should show loading content when loading alerts', async () => { - useQueryAlertsMock.mockImplementation(() => ({ - loading: true, - data: { hits: { hits: [] } }, - })); + const useFetchAlertData = jest.fn().mockReturnValue([true]); useGetCaseUserActionsMock.mockReturnValue({ caseServices: {}, caseUserActions: [], @@ -635,7 +699,7 @@ describe('CaseView ', () => { const wrapper = mount( - + ); @@ -648,28 +712,22 @@ describe('CaseView ', () => { }); }); - it('should open the alert flyout', async () => { + it('should call show alert details with expected arguments', async () => { + const showAlertDetails = jest.fn(); const wrapper = mount( - + ); + wrapper + .find('[data-test-subj="comment-action-show-alert-alert-action-id"] button') + .first() + .simulate('click'); await waitFor(() => { - wrapper - .find('[data-test-subj="comment-action-show-alert-alert-action-id"] button') - .first() - .simulate('click'); - expect(mockDispatch).toHaveBeenCalledWith({ - type: 'x-pack/security_solution/local/timeline/TOGGLE_DETAIL_PANEL', - payload: { - panelView: 'eventDetail', - params: { eventId: 'alert-id-1', indexName: 'alert-index-1' }, - timelineId: 'timeline-case', - }, - }); + expect(showAlertDetails).toHaveBeenCalledWith('alert-id-1', 'alert-index-1'); }); }); @@ -703,9 +761,8 @@ describe('CaseView ', () => { ); + wrapper.find('button[data-test-subj="sync-alerts-switch"]').first().simulate('click'); await waitFor(() => { - wrapper.find('button[data-test-subj="sync-alerts-switch"]').first().simulate('click'); - wrapper.update(); const updateObject = updateCaseProperty.mock.calls[0][0]; expect(updateObject.updateKey).toEqual('settings'); diff --git a/x-pack/plugins/cases/public/components/case_view/index.tsx b/x-pack/plugins/cases/public/components/case_view/index.tsx new file mode 100644 index 0000000000000..557f736c513b9 --- /dev/null +++ b/x-pack/plugins/cases/public/components/case_view/index.tsx @@ -0,0 +1,538 @@ +/* + * 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, useEffect, useMemo, useState, useRef } from 'react'; +// import { useDispatch } from 'react-redux'; +import styled from 'styled-components'; +import { isEmpty } from 'lodash/fp'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiLoadingContent, + EuiLoadingSpinner, + EuiHorizontalRule, +} from '@elastic/eui'; + +import { CaseStatuses, CaseAttributes, CaseType, Case, CaseConnector } from '../../../common'; +import { HeaderPage } from '../header_page'; +import { EditableTitle } from '../header_page/editable_title'; +import { TagList } from '../tag_list'; +import { useGetCase } from '../../containers/use_get_case'; +import { UserActionTree } from '../user_action_tree'; +import { UserList } from '../user_list'; +import { useUpdateCase } from '../../containers/use_update_case'; +import { getTypedPayload } from '../../containers/utils'; +import { WhitePageWrapper, HeaderWrapper } from '../wrappers'; +import { CaseActionBar } from '../case_action_bar'; +import { useGetCaseUserActions } from '../../containers/use_get_case_user_actions'; +import { usePushToService } from '../use_push_to_service'; +import { EditConnector } from '../edit_connector'; +import { useConnectors } from '../../containers/configure/use_connectors'; +import { + getConnectorById, + normalizeActionConnector, + getNoneConnector, +} from '../configure_cases/utils'; +import { StatusActionButton } from '../status/button'; +import * as i18n from './translations'; +import { Ecs } from '../../../common'; +import { CasesTimelineIntegration, CasesTimelineIntegrationProvider } from '../timeline_context'; +import { useTimelineContext } from '../timeline_context/use_timeline_context'; +import { CasesNavigation } from '../links'; + +const gutterTimeline = '70px'; // seems to be a timeline reference from the original file +export interface CaseViewComponentProps { + allCasesNavigation: CasesNavigation; + caseDetailsNavigation: CasesNavigation; + caseId: string; + configureCasesNavigation: CasesNavigation; + getCaseDetailHrefWithCommentId: (commentId: string) => string; + onComponentInitialized?: () => void; + ruleDetailsNavigation: CasesNavigation; + showAlertDetails: (alertId: string, index: string) => void; + subCaseId?: string; + useFetchAlertData: (alertIds: string[]) => [boolean, Record]; + userCanCrud: boolean; +} + +export interface CaseViewProps extends CaseViewComponentProps { + onCaseDataSuccess?: (data: Case) => void; + timelineIntegration?: CasesTimelineIntegration; +} +export interface OnUpdateFields { + key: keyof Case; + value: Case[keyof Case]; + onSuccess?: () => void; + onError?: () => void; +} + +const MyWrapper = styled.div` + padding: ${({ theme }) => + `${theme.eui.paddingSizes.l} ${theme.eui.paddingSizes.l} ${gutterTimeline} ${theme.eui.paddingSizes.l}`}; +`; + +const MyEuiFlexGroup = styled(EuiFlexGroup)` + height: 100%; +`; + +const MyEuiHorizontalRule = styled(EuiHorizontalRule)` + margin-left: 48px; + &.euiHorizontalRule--full { + width: calc(100% - 48px); + } +`; + +export interface CaseComponentProps extends CaseViewComponentProps { + fetchCase: () => void; + caseData: Case; + updateCase: (newCase: Case) => void; +} + +export const CaseComponent = React.memo( + ({ + allCasesNavigation, + caseData, + caseDetailsNavigation, + caseId, + configureCasesNavigation, + getCaseDetailHrefWithCommentId, + fetchCase, + onComponentInitialized, + ruleDetailsNavigation, + showAlertDetails, + subCaseId, + updateCase, + useFetchAlertData, + userCanCrud, + }) => { + const [initLoadingData, setInitLoadingData] = useState(true); + const init = useRef(true); + const timelineUi = useTimelineContext()?.ui; + + const { + caseUserActions, + fetchCaseUserActions, + caseServices, + hasDataToPush, + isLoading: isLoadingUserActions, + participants, + } = useGetCaseUserActions(caseId, caseData.connector.id, subCaseId); + + const { isLoading, updateKey, updateCaseProperty } = useUpdateCase({ + caseId, + subCaseId, + }); + + // Update Fields + const onUpdateField = useCallback( + ({ key, value, onSuccess, onError }: OnUpdateFields) => { + const handleUpdateNewCase = (newCase: Case) => + updateCase({ ...newCase, comments: caseData.comments }); + switch (key) { + case 'title': + const titleUpdate = getTypedPayload(value); + if (titleUpdate.length > 0) { + updateCaseProperty({ + fetchCaseUserActions, + updateKey: 'title', + updateValue: titleUpdate, + updateCase: handleUpdateNewCase, + caseData, + onSuccess, + onError, + }); + } + break; + case 'connector': + const connector = getTypedPayload(value); + if (connector != null) { + updateCaseProperty({ + fetchCaseUserActions, + updateKey: 'connector', + updateValue: connector, + updateCase: handleUpdateNewCase, + caseData, + onSuccess, + onError, + }); + } + break; + case 'description': + const descriptionUpdate = getTypedPayload(value); + if (descriptionUpdate.length > 0) { + updateCaseProperty({ + fetchCaseUserActions, + updateKey: 'description', + updateValue: descriptionUpdate, + updateCase: handleUpdateNewCase, + caseData, + onSuccess, + onError, + }); + } + break; + case 'tags': + const tagsUpdate = getTypedPayload(value); + updateCaseProperty({ + fetchCaseUserActions, + updateKey: 'tags', + updateValue: tagsUpdate, + updateCase: handleUpdateNewCase, + caseData, + onSuccess, + onError, + }); + break; + case 'status': + const statusUpdate = getTypedPayload(value); + if (caseData.status !== value) { + updateCaseProperty({ + fetchCaseUserActions, + updateKey: 'status', + updateValue: statusUpdate, + updateCase: handleUpdateNewCase, + caseData, + onSuccess, + onError, + }); + } + break; + case 'settings': + const settingsUpdate = getTypedPayload(value); + if (caseData.settings !== value) { + updateCaseProperty({ + fetchCaseUserActions, + updateKey: 'settings', + updateValue: settingsUpdate, + updateCase: handleUpdateNewCase, + caseData, + onSuccess, + onError, + }); + } + break; + default: + return null; + } + }, + [fetchCaseUserActions, updateCaseProperty, updateCase, caseData] + ); + + const handleUpdateCase = useCallback( + (newCase: Case) => { + updateCase(newCase); + fetchCaseUserActions(caseId, newCase.connector.id, subCaseId); + }, + [updateCase, fetchCaseUserActions, caseId, subCaseId] + ); + + const { loading: isLoadingConnectors, connectors } = useConnectors(); + + const [connectorName, isValidConnector] = useMemo(() => { + const connector = connectors.find((c) => c.id === caseData.connector.id); + return [connector?.name ?? '', !!connector]; + }, [connectors, caseData.connector]); + + const currentExternalIncident = useMemo( + () => + caseServices != null && caseServices[caseData.connector.id] != null + ? caseServices[caseData.connector.id] + : null, + [caseServices, caseData.connector] + ); + + const { pushButton, pushCallouts } = usePushToService({ + configureCasesNavigation, + connector: { + ...caseData.connector, + name: isEmpty(connectorName) ? caseData.connector.name : connectorName, + }, + caseServices, + caseId: caseData.id, + caseStatus: caseData.status, + connectors, + updateCase: handleUpdateCase, + userCanCrud, + isValidConnector: isLoadingConnectors ? true : isValidConnector, + }); + + const onSubmitConnector = useCallback( + (connectorId, connectorFields, onError, onSuccess) => { + const connector = getConnectorById(connectorId, connectors); + const connectorToUpdate = connector + ? normalizeActionConnector(connector) + : getNoneConnector(); + + onUpdateField({ + key: 'connector', + value: { ...connectorToUpdate, fields: connectorFields }, + onSuccess, + onError, + }); + }, + [onUpdateField, connectors] + ); + + const onSubmitTags = useCallback((newTags) => onUpdateField({ key: 'tags', value: newTags }), [ + onUpdateField, + ]); + + const onSubmitTitle = useCallback( + (newTitle) => onUpdateField({ key: 'title', value: newTitle }), + [onUpdateField] + ); + + const changeStatus = useCallback( + (status: CaseStatuses) => + onUpdateField({ + key: 'status', + value: status, + }), + [onUpdateField] + ); + + const handleRefresh = useCallback(() => { + fetchCaseUserActions(caseId, caseData.connector.id, subCaseId); + fetchCase(); + }, [caseData.connector.id, caseId, fetchCase, fetchCaseUserActions, subCaseId]); + + const emailContent = useMemo( + () => ({ + subject: i18n.EMAIL_SUBJECT(caseData.title), + body: i18n.EMAIL_BODY(caseDetailsNavigation.href), + }), + [caseDetailsNavigation.href, caseData.title] + ); + + useEffect(() => { + if (initLoadingData && !isLoadingUserActions) { + setInitLoadingData(false); + } + }, [initLoadingData, isLoadingUserActions]); + + const backOptions = useMemo( + () => ({ + href: allCasesNavigation.href, + text: i18n.BACK_TO_ALL, + dataTestSubj: 'backToCases', + onClick: allCasesNavigation.onClick, + }), + [allCasesNavigation] + ); + + const onShowAlertDetails = useCallback( + (alertId: string, index: string) => { + showAlertDetails(alertId, index); + }, + [showAlertDetails] + ); + + // useEffect used for component's initialization + useEffect(() => { + if (init.current) { + init.current = false; + if (onComponentInitialized) { + onComponentInitialized(); + } + } + }, [onComponentInitialized]); + + return ( + <> + + + } + title={caseData.title} + > + + + + + + {!initLoadingData && pushCallouts != null && pushCallouts} + + + {initLoadingData && ( + + )} + {!initLoadingData && ( + <> + + {(caseData.type !== CaseType.collection || hasDataToPush) && ( + <> + + + {caseData.type !== CaseType.collection && ( + + + + )} + {hasDataToPush && ( + + {pushButton} + + )} + + + )} + + )} + + + + + + + + + + + {timelineUi?.renderTimelineDetailsPanel ? timelineUi.renderTimelineDetailsPanel() : null} + + ); + } +); + +export const CaseView = React.memo( + ({ + allCasesNavigation, + caseDetailsNavigation, + caseId, + configureCasesNavigation, + getCaseDetailHrefWithCommentId, + onCaseDataSuccess, + onComponentInitialized, + ruleDetailsNavigation, + showAlertDetails, + subCaseId, + timelineIntegration, + useFetchAlertData, + userCanCrud, + }: CaseViewProps) => { + const { data, isLoading, isError, fetchCase, updateCase } = useGetCase(caseId, subCaseId); + if (isError) { + return null; + } + if (isLoading) { + return ( + + + + + + ); + } + if (onCaseDataSuccess && data) { + onCaseDataSuccess(data); + } + + return ( + data && ( + + + + ) + ); + } +); + +CaseComponent.displayName = 'CaseComponent'; +CaseView.displayName = 'CaseView'; + +// eslint-disable-next-line import/no-default-export +export { CaseView as default }; diff --git a/x-pack/plugins/cases/public/components/case_view/translations.ts b/x-pack/plugins/cases/public/components/case_view/translations.ts new file mode 100644 index 0000000000000..41ffbbd9342da --- /dev/null +++ b/x-pack/plugins/cases/public/components/case_view/translations.ts @@ -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 { i18n } from '@kbn/i18n'; + +export * from '../../common/translations'; + +export const SHOWING_CASES = (actionDate: string, actionName: string, userName: string) => + i18n.translate('xpack.cases.caseView.actionHeadline', { + values: { + actionDate, + actionName, + userName, + }, + defaultMessage: '{userName} {actionName} on {actionDate}', + }); + +export const ADDED_FIELD = i18n.translate('xpack.cases.caseView.actionLabel.addedField', { + defaultMessage: 'added', +}); + +export const CHANGED_FIELD = i18n.translate('xpack.cases.caseView.actionLabel.changededField', { + defaultMessage: 'changed', +}); + +export const SELECTED_THIRD_PARTY = (thirdParty: string) => + i18n.translate('xpack.cases.caseView.actionLabel.selectedThirdParty', { + values: { + thirdParty, + }, + defaultMessage: 'selected { thirdParty } as incident management system', + }); + +export const REMOVED_THIRD_PARTY = i18n.translate( + 'xpack.cases.caseView.actionLabel.removedThirdParty', + { + defaultMessage: 'removed external incident management system', + } +); + +export const EDITED_FIELD = i18n.translate('xpack.cases.caseView.actionLabel.editedField', { + defaultMessage: 'edited', +}); + +export const REMOVED_FIELD = i18n.translate('xpack.cases.caseView.actionLabel.removedField', { + defaultMessage: 'removed', +}); + +export const VIEW_INCIDENT = (incidentNumber: string) => + i18n.translate('xpack.cases.caseView.actionLabel.viewIncident', { + defaultMessage: 'View {incidentNumber}', + values: { + incidentNumber, + }, + }); + +export const PUSHED_NEW_INCIDENT = i18n.translate( + 'xpack.cases.caseView.actionLabel.pushedNewIncident', + { + defaultMessage: 'pushed as new incident', + } +); + +export const UPDATE_INCIDENT = i18n.translate('xpack.cases.caseView.actionLabel.updateIncident', { + defaultMessage: 'updated incident', +}); + +export const ADDED_DESCRIPTION = i18n.translate('xpack.cases.caseView.actionLabel.addDescription', { + defaultMessage: 'added description', +}); + +export const EDIT_DESCRIPTION = i18n.translate('xpack.cases.caseView.edit.description', { + defaultMessage: 'Edit description', +}); + +export const QUOTE = i18n.translate('xpack.cases.caseView.edit.quote', { + defaultMessage: 'Quote', +}); + +export const EDIT_COMMENT = i18n.translate('xpack.cases.caseView.edit.comment', { + defaultMessage: 'Edit comment', +}); + +export const ON = i18n.translate('xpack.cases.caseView.actionLabel.on', { + defaultMessage: 'on', +}); + +export const ADDED_COMMENT = i18n.translate('xpack.cases.caseView.actionLabel.addComment', { + defaultMessage: 'added comment', +}); + +export const STATUS = i18n.translate('xpack.cases.caseView.statusLabel', { + defaultMessage: 'Status', +}); + +export const CASE = i18n.translate('xpack.cases.caseView.case', { + defaultMessage: 'case', +}); + +export const COMMENT = i18n.translate('xpack.cases.caseView.comment', { + defaultMessage: 'comment', +}); + +export const CASE_REFRESH = i18n.translate('xpack.cases.caseView.caseRefresh', { + defaultMessage: 'Refresh case', +}); + +export const EMAIL_SUBJECT = (caseTitle: string) => + i18n.translate('xpack.cases.caseView.emailSubject', { + values: { caseTitle }, + defaultMessage: 'Security Case - {caseTitle}', + }); + +export const EMAIL_BODY = (caseUrl: string) => + i18n.translate('xpack.cases.caseView.emailBody', { + values: { caseUrl }, + defaultMessage: 'Case reference: {caseUrl}', + }); + +export const CHANGED_CONNECTOR_FIELD = i18n.translate('xpack.cases.caseView.fieldChanged', { + defaultMessage: `changed connector field`, +}); + +export const SYNC_ALERTS = i18n.translate('xpack.cases.caseView.syncAlertsLabel', { + defaultMessage: `Sync alerts`, +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/__mock__/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx similarity index 94% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/__mock__/index.tsx rename to x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx index ccc697a2ae84e..e3abbeadd2d3c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/__mock__/index.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorTypes } from '../../../../../../cases/common/api'; +import { ConnectorTypes } from '../../../../common'; import { ActionConnector } from '../../../containers/configure/types'; import { UseConnectorsResponse } from '../../../containers/configure/use_connectors'; import { ReturnUseCaseConfigure } from '../../../containers/configure/use_configure'; @@ -14,7 +14,6 @@ import { connectorsMock, actionTypesMock } from '../../../containers/configure/m export { mappings } from '../../../containers/configure/mock'; export const connectors: ActionConnector[] = connectorsMock; -// x - pack / plugins / triggers_actions_ui; 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)))'; diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/button.test.tsx similarity index 92% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx rename to x-pack/plugins/cases/public/components/configure_cases/button.test.tsx index 4b2d72cf86dd6..a3f95e60dc2ae 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/button.test.tsx @@ -9,10 +9,9 @@ import React from 'react'; import { ReactWrapper, mount } from 'enzyme'; import { EuiText } from '@elastic/eui'; -import '../../../common/mock/match_media'; +import '../../common/mock/match_media'; import { ConfigureCaseButton, ConfigureCaseButtonProps } from './button'; -import { TestProviders } from '../../../common/mock'; -import { searchURL } from './__mock__'; +import { TestProviders } from '../../common/mock'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -25,17 +24,18 @@ jest.mock('react-router-dom', () => { }; }); -jest.mock('../../../common/components/link_to'); - describe('Configuration button', () => { let wrapper: ReactWrapper; const props: ConfigureCaseButtonProps = { + configureCasesNavigation: { + href: 'testHref', + onClick: jest.fn(), + }, isDisabled: false, label: 'My label', msgTooltip: <>, showToolTip: false, titleTooltip: '', - urlSearch: searchURL, }; beforeAll(() => { @@ -50,7 +50,7 @@ describe('Configuration button', () => { test('it pass the correct props to the button', () => { expect(wrapper.find('[data-test-subj="configure-case-button"]').first().props()).toMatchObject({ - href: `/configure`, + href: `testHref`, iconType: 'controlsHorizontal', isDisabled: false, 'aria-label': 'My label', diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.tsx b/x-pack/plugins/cases/public/components/configure_cases/button.tsx similarity index 62% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/button.tsx rename to x-pack/plugins/cases/public/components/configure_cases/button.tsx index 2e116e16df52b..1830380be3765 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/button.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/button.tsx @@ -6,45 +6,33 @@ */ import { EuiToolTip } from '@elastic/eui'; -import React, { memo, useCallback, useMemo } from 'react'; -import { useHistory } from 'react-router-dom'; +import React, { memo, useMemo } from 'react'; +import { CasesNavigation, LinkButton } from '../links'; -import { getConfigureCasesUrl, useFormatUrl } from '../../../common/components/link_to'; -import { LinkButton } from '../../../common/components/links'; -import { SecurityPageName } from '../../../app/types'; +// TODO: Potentially move into links component? export interface ConfigureCaseButtonProps { - label: string; + configureCasesNavigation: CasesNavigation; isDisabled: boolean; + label: string; msgTooltip: JSX.Element; showToolTip: boolean; titleTooltip: string; - urlSearch: string; } const ConfigureCaseButtonComponent: React.FC = ({ + configureCasesNavigation: { href, onClick }, isDisabled, label, msgTooltip, showToolTip, titleTooltip, - urlSearch, }: ConfigureCaseButtonProps) => { - const history = useHistory(); - const { formatUrl } = useFormatUrl(SecurityPageName.case); - const goToCaseConfigure = useCallback( - (ev) => { - ev.preventDefault(); - history.push(getConfigureCasesUrl(urlSearch)); - }, - [history, urlSearch] - ); - const configureCaseButton = useMemo( () => ( = ({ {label} ), - [label, isDisabled, formatUrl, goToCaseConfigure] + [label, isDisabled, onClick, href] ); return showToolTip ? ( diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/closure_options.test.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options.test.tsx rename to x-pack/plugins/cases/public/components/configure_cases/closure_options.test.tsx index a7d9805bc77b4..56123a934d51f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/closure_options.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; import { ClosureOptions, ClosureOptionsProps } from './closure_options'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; import { ClosureOptionsRadio } from './closure_options_radio'; describe('ClosureOptions', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options.tsx b/x-pack/plugins/cases/public/components/configure_cases/closure_options.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options.tsx rename to x-pack/plugins/cases/public/components/configure_cases/closure_options.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options_radio.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/closure_options_radio.test.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options_radio.test.tsx rename to x-pack/plugins/cases/public/components/configure_cases/closure_options_radio.test.tsx index e26444590da46..b9885b4e07d48 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options_radio.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/closure_options_radio.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { ReactWrapper, mount } from 'enzyme'; import { ClosureOptionsRadio, ClosureOptionsRadioComponentProps } from './closure_options_radio'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; describe('ClosureOptionsRadio', () => { let wrapper: ReactWrapper; diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options_radio.tsx b/x-pack/plugins/cases/public/components/configure_cases/closure_options_radio.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/closure_options_radio.tsx rename to x-pack/plugins/cases/public/components/configure_cases/closure_options_radio.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.test.tsx rename to x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx index c34651c3e1dc4..d5b9a885f2c6d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx @@ -9,10 +9,10 @@ import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; import { Connectors, Props } from './connectors'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; import { ConnectorsDropdown } from './connectors_dropdown'; import { connectors } from './__mock__'; -import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; +import { ConnectorTypes } from '../../../common'; describe('Connectors', () => { let wrapper: ReactWrapper; diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.tsx rename to x-pack/plugins/cases/public/components/configure_cases/connectors.tsx index 1e0ae95ff901c..45be02e05e1f0 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx @@ -21,7 +21,7 @@ import * as i18n from './translations'; import { ActionConnector, CaseConnectorMapping } from '../../containers/configure/types'; import { Mapping } from './mapping'; -import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; +import { ConnectorTypes } from '../../../common'; const EuiFormRowExtended = styled(EuiFormRow)` .euiFormRow__labelWrapper { diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors_dropdown.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.test.tsx similarity index 99% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors_dropdown.test.tsx rename to x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.test.tsx index ac0bb1f1c742f..0070bc18dfe12 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors_dropdown.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.test.tsx @@ -10,7 +10,7 @@ import { mount, ReactWrapper } from 'enzyme'; import { EuiSuperSelect } from '@elastic/eui'; import { ConnectorsDropdown, Props } from './connectors_dropdown'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; import { connectors } from './__mock__'; describe('ConnectorsDropdown', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors_dropdown.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors_dropdown.tsx rename to x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.tsx index 4971ed43d5974..8c3a0f7ae1961 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/connectors_dropdown.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.tsx @@ -9,7 +9,7 @@ import React, { useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSuperSelect } from '@elastic/eui'; import styled from 'styled-components'; -import { ConnectorTypes } from '../../../../../cases/common/api'; +import { ConnectorTypes } from '../../../common'; import { ActionConnector } from '../../containers/configure/types'; import { connectorsConfiguration } from '../connectors'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/field_mapping.test.tsx similarity index 92% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx rename to x-pack/plugins/cases/public/components/configure_cases/field_mapping.test.tsx index 35f5e1fe058dd..8c2a66ad7ee53 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/field_mapping.test.tsx @@ -10,7 +10,7 @@ import { mount, ReactWrapper } from 'enzyme'; import { FieldMapping, FieldMappingProps } from './field_mapping'; import { mappings } from './__mock__'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; import { FieldMappingRowStatic } from './field_mapping_row_static'; describe('FieldMappingRow', () => { @@ -47,7 +47,7 @@ describe('FieldMappingRow', () => { test('it pass the corrects props to mapping row', () => { const rows = wrapper.find(FieldMappingRowStatic); rows.forEach((row, index) => { - expect(row.prop('securitySolutionField')).toEqual(mappings[index].source); + expect(row.prop('casesField')).toEqual(mappings[index].source); expect(row.prop('selectedActionType')).toEqual(mappings[index].actionType); expect(row.prop('selectedThirdParty')).toEqual(mappings[index].target); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx b/x-pack/plugins/cases/public/components/configure_cases/field_mapping.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx rename to x-pack/plugins/cases/public/components/configure_cases/field_mapping.tsx index 6792f5d9ab49f..7d5b72b583fae 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/field_mapping.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/field_mapping.tsx @@ -58,7 +58,7 @@ const FieldMappingComponent: React.FC = ({ {mappings.map((item) => ( = ({ isLoading, - securitySolutionField, + casesField, selectedActionType, selectedThirdParty, }) => { @@ -32,7 +32,7 @@ const FieldMappingRowComponent: React.FC = ({ - {securitySolutionField} + {casesField} diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/index.test.tsx rename to x-pack/plugins/cases/public/components/configure_cases/index.test.tsx index 8dbefdb731141..898d6cde19a77 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { ReactWrapper, mount } from 'enzyme'; import { ConfigureCases } from '.'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; import { Connectors } from './connectors'; import { ClosureOptions } from './closure_options'; import { @@ -17,14 +17,13 @@ import { ConnectorAddFlyout, ConnectorEditFlyout, TriggersAndActionsUIPublicPluginStart, -} from '../../../../../triggers_actions_ui/public'; -import { actionTypeRegistryMock } from '../../../../../triggers_actions_ui/public/application/action_type_registry.mock'; +} from '../../../../triggers_actions_ui/public'; +import { actionTypeRegistryMock } from '../../../../triggers_actions_ui/public/application/action_type_registry.mock'; -import { useKibana } from '../../../common/lib/kibana'; +import { useKibana } from '../../common/lib/kibana'; import { useConnectors } from '../../containers/configure/use_connectors'; import { useCaseConfigure } from '../../containers/configure/use_configure'; import { useActionTypes } from '../../containers/configure/use_action_types'; -import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; import { connectors, @@ -33,18 +32,17 @@ import { useConnectorsResponse, useActionTypesResponse, } from './__mock__'; -import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; +import { ConnectorTypes } from '../../../common'; -jest.mock('../../../common/lib/kibana'); +jest.mock('../../common/lib/kibana'); jest.mock('../../containers/configure/use_connectors'); jest.mock('../../containers/configure/use_configure'); jest.mock('../../containers/configure/use_action_types'); -jest.mock('../../../common/components/navigation/use_get_url_search'); const useKibanaMock = useKibana as jest.Mocked; const useConnectorsMock = useConnectors as jest.Mock; const useCaseConfigureMock = useCaseConfigure as jest.Mock; -const useGetUrlSearchMock = useGetUrlSearch as jest.Mock; +const useGetUrlSearchMock = jest.fn(); const useActionTypesMock = useActionTypes as jest.Mock; describe('ConfigureCases', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.tsx similarity index 89% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/index.tsx rename to x-pack/plugins/cases/public/components/configure_cases/index.tsx index 25155ff77c2d0..fdba148e5c61e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.tsx @@ -10,8 +10,8 @@ import styled, { css } from 'styled-components'; import { EuiCallOut } from '@elastic/eui'; -import { SUPPORTED_CONNECTORS } from '../../../../../cases/common/constants'; -import { useKibana } from '../../../common/lib/kibana'; +import { SUPPORTED_CONNECTORS } from '../../../common'; +import { useKibana } from '../../common/lib/kibana'; import { useConnectors } from '../../containers/configure/use_connectors'; import { useActionTypes } from '../../containers/configure/use_action_types'; import { useCaseConfigure } from '../../containers/configure/use_configure'; @@ -19,7 +19,7 @@ import { useCaseConfigure } from '../../containers/configure/use_configure'; import { ClosureType } from '../../containers/configure/types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ActionConnectorTableItem } from '../../../../../triggers_actions_ui/public/types'; +import { ActionConnectorTableItem } from '../../../../triggers_actions_ui/public/types'; import { SectionWrapper } from '../wrappers'; import { Connectors } from './connectors'; @@ -50,11 +50,11 @@ const FormWrapper = styled.div` `} `; -interface ConfigureCasesComponentProps { +export interface ConfigureCasesProps { userCanCrud: boolean; } -const ConfigureCasesComponent: React.FC = ({ userCanCrud }) => { +const ConfigureCasesComponent: React.FC = ({ userCanCrud }) => { const { triggersActionsUi } = useKibana().services; const [connectorIsValid, setConnectorIsValid] = useState(true); @@ -158,14 +158,16 @@ const ConfigureCasesComponent: React.FC = ({ userC const ConnectorAddFlyout = useMemo( () => - triggersActionsUi.getAddConnectorFlyout({ - consumer: 'case', - onClose: onCloseAddFlyout, - actionTypes: supportedActionTypes, - reloadConnectors: onConnectorUpdate, - }), + addFlyoutVisible + ? triggersActionsUi.getAddConnectorFlyout({ + consumer: 'case', + onClose: onCloseAddFlyout, + actionTypes: supportedActionTypes, + reloadConnectors: onConnectorUpdate, + }) + : null, // eslint-disable-next-line react-hooks/exhaustive-deps - [supportedActionTypes] + [addFlyoutVisible, supportedActionTypes] ); const ConnectorEditFlyout = useMemo( @@ -215,10 +217,12 @@ const ConfigureCasesComponent: React.FC = ({ userC updateConnectorDisabled={updateConnectorDisabled || !userCanCrud} /> - {addFlyoutVisible && ConnectorAddFlyout} + {ConnectorAddFlyout} {ConnectorEditFlyout} ); }; export const ConfigureCases = React.memo(ConfigureCasesComponent); +// eslint-disable-next-line import/no-default-export +export default ConfigureCases; diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/mapping.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/mapping.test.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/mapping.test.tsx rename to x-pack/plugins/cases/public/components/configure_cases/mapping.test.tsx index 115481c5e7302..75b2410dde957 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/mapping.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/mapping.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; import { Mapping, MappingProps } from './mapping'; import { mappings } from './__mock__'; diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/mapping.tsx b/x-pack/plugins/cases/public/components/configure_cases/mapping.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/mapping.tsx rename to x-pack/plugins/cases/public/components/configure_cases/mapping.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts b/x-pack/plugins/cases/public/components/configure_cases/translations.ts similarity index 51% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts rename to x-pack/plugins/cases/public/components/configure_cases/translations.ts index 697d5e1a7adfa..2fb2133ba470c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/translations.ts +++ b/x-pack/plugins/cases/public/components/configure_cases/translations.ts @@ -7,182 +7,175 @@ import { i18n } from '@kbn/i18n'; -export * from '../../translations'; +export * from '../../common/translations'; export const INCIDENT_MANAGEMENT_SYSTEM_TITLE = i18n.translate( - 'xpack.securitySolution.cases.configureCases.incidentManagementSystemTitle', + 'xpack.cases.configureCases.incidentManagementSystemTitle', { defaultMessage: 'Connect to external incident management system', } ); export const INCIDENT_MANAGEMENT_SYSTEM_DESC = i18n.translate( - 'xpack.securitySolution.cases.configureCases.incidentManagementSystemDesc', + 'xpack.cases.configureCases.incidentManagementSystemDesc', { defaultMessage: - 'You may optionally connect Security cases to an external incident management system of your choosing. This will allow you to push case data as an incident in your chosen third-party system.', + 'You may optionally connect cases to an external incident management system of your choosing. This will allow you to push case data as an incident in your chosen third-party system.', } ); export const INCIDENT_MANAGEMENT_SYSTEM_LABEL = i18n.translate( - 'xpack.securitySolution.cases.configureCases.incidentManagementSystemLabel', + 'xpack.cases.configureCases.incidentManagementSystemLabel', { defaultMessage: 'Incident management system', } ); -export const ADD_NEW_CONNECTOR = i18n.translate( - 'xpack.securitySolution.cases.configureCases.addNewConnector', - { - defaultMessage: 'Add new connector', - } -); +export const ADD_NEW_CONNECTOR = i18n.translate('xpack.cases.configureCases.addNewConnector', { + defaultMessage: 'Add new connector', +}); export const CASE_CLOSURE_OPTIONS_TITLE = i18n.translate( - 'xpack.securitySolution.cases.configureCases.caseClosureOptionsTitle', + 'xpack.cases.configureCases.caseClosureOptionsTitle', { defaultMessage: 'Case Closures', } ); export const CASE_CLOSURE_OPTIONS_DESC = i18n.translate( - 'xpack.securitySolution.cases.configureCases.caseClosureOptionsDesc', + 'xpack.cases.configureCases.caseClosureOptionsDesc', { defaultMessage: - 'Define how you wish Security cases to be closed. Automated case closures require an established connection to an external incident management system.', + 'Define how you wish cases to be closed. Automated case closures require an established connection to an external incident management system.', } ); export const CASE_COLSURE_OPTIONS_SUB_CASES = i18n.translate( - 'xpack.securitySolution.cases.configureCases.caseClosureOptionsSubCases', + 'xpack.cases.configureCases.caseClosureOptionsSubCases', { defaultMessage: 'Automated closures of sub-cases is not currently supported.', } ); export const CASE_CLOSURE_OPTIONS_LABEL = i18n.translate( - 'xpack.securitySolution.cases.configureCases.caseClosureOptionsLabel', + 'xpack.cases.configureCases.caseClosureOptionsLabel', { defaultMessage: 'Case closure options', } ); export const CASE_CLOSURE_OPTIONS_MANUAL = i18n.translate( - 'xpack.securitySolution.cases.configureCases.caseClosureOptionsManual', + 'xpack.cases.configureCases.caseClosureOptionsManual', { - defaultMessage: 'Manually close Security cases', + defaultMessage: 'Manually close cases', } ); export const CASE_CLOSURE_OPTIONS_NEW_INCIDENT = i18n.translate( - 'xpack.securitySolution.cases.configureCases.caseClosureOptionsNewIncident', + 'xpack.cases.configureCases.caseClosureOptionsNewIncident', { - defaultMessage: - 'Automatically close Security cases when pushing new incident to external system', + defaultMessage: 'Automatically close cases when pushing new incident to external system', } ); export const CASE_CLOSURE_OPTIONS_CLOSED_INCIDENT = i18n.translate( - 'xpack.securitySolution.cases.configureCases.caseClosureOptionsClosedIncident', + 'xpack.cases.configureCases.caseClosureOptionsClosedIncident', { - defaultMessage: 'Automatically close Security cases when incident is closed in external system', + defaultMessage: 'Automatically close cases when incident is closed in external system', } ); export const FIELD_MAPPING_TITLE = (thirdPartyName: string): string => { - return i18n.translate('xpack.securitySolution.cases.configureCases.fieldMappingTitle', { + return i18n.translate('xpack.cases.configureCases.fieldMappingTitle', { values: { thirdPartyName }, defaultMessage: '{ thirdPartyName } field mappings', }); }; export const FIELD_MAPPING_DESC = (thirdPartyName: string): string => { - return i18n.translate('xpack.securitySolution.cases.configureCases.fieldMappingDesc', { + return i18n.translate('xpack.cases.configureCases.fieldMappingDesc', { values: { thirdPartyName }, defaultMessage: - 'Map Security Case fields to { thirdPartyName } fields when pushing data to { thirdPartyName }. Field mappings require an established connection to { thirdPartyName }.', + 'Map Case fields to { thirdPartyName } fields when pushing data to { thirdPartyName }. Field mappings require an established connection to { thirdPartyName }.', }); }; export const FIELD_MAPPING_DESC_ERR = (thirdPartyName: string): string => { - return i18n.translate('xpack.securitySolution.cases.configureCases.fieldMappingDescErr', { + return i18n.translate('xpack.cases.configureCases.fieldMappingDescErr', { values: { thirdPartyName }, defaultMessage: 'Field mappings require an established connection to { thirdPartyName }. Please check your connection credentials.', }); }; export const EDIT_FIELD_MAPPING_TITLE = (thirdPartyName: string): string => { - return i18n.translate('xpack.securitySolution.cases.configureCases.editFieldMappingTitle', { + return i18n.translate('xpack.cases.configureCases.editFieldMappingTitle', { values: { thirdPartyName }, defaultMessage: 'Edit { thirdPartyName } field mappings', }); }; export const FIELD_MAPPING_FIRST_COL = i18n.translate( - 'xpack.securitySolution.cases.configureCases.fieldMappingFirstCol', + 'xpack.cases.configureCases.fieldMappingFirstCol', { - defaultMessage: 'Security case field', + defaultMessage: 'Kibana case field', } ); export const FIELD_MAPPING_SECOND_COL = (thirdPartyName: string): string => { - return i18n.translate('xpack.securitySolution.cases.configureCases.fieldMappingSecondCol', { + return i18n.translate('xpack.cases.configureCases.fieldMappingSecondCol', { values: { thirdPartyName }, defaultMessage: '{ thirdPartyName } field', }); }; export const FIELD_MAPPING_THIRD_COL = i18n.translate( - 'xpack.securitySolution.cases.configureCases.fieldMappingThirdCol', + 'xpack.cases.configureCases.fieldMappingThirdCol', { defaultMessage: 'On edit and update', } ); export const FIELD_MAPPING_EDIT_NOTHING = i18n.translate( - 'xpack.securitySolution.cases.configureCases.fieldMappingEditNothing', + 'xpack.cases.configureCases.fieldMappingEditNothing', { defaultMessage: 'Nothing', } ); export const FIELD_MAPPING_EDIT_OVERWRITE = i18n.translate( - 'xpack.securitySolution.cases.configureCases.fieldMappingEditOverwrite', + 'xpack.cases.configureCases.fieldMappingEditOverwrite', { defaultMessage: 'Overwrite', } ); export const FIELD_MAPPING_EDIT_APPEND = i18n.translate( - 'xpack.securitySolution.cases.configureCases.fieldMappingEditAppend', + 'xpack.cases.configureCases.fieldMappingEditAppend', { defaultMessage: 'Append', } ); -export const CANCEL = i18n.translate('xpack.securitySolution.cases.configureCases.cancelButton', { +export const CANCEL = i18n.translate('xpack.cases.configureCases.cancelButton', { defaultMessage: 'Cancel', }); -export const SAVE = i18n.translate('xpack.securitySolution.cases.configureCases.saveButton', { +export const SAVE = i18n.translate('xpack.cases.configureCases.saveButton', { defaultMessage: 'Save', }); -export const SAVE_CLOSE = i18n.translate( - 'xpack.securitySolution.cases.configureCases.saveAndCloseButton', - { - defaultMessage: 'Save & close', - } -); +export const SAVE_CLOSE = i18n.translate('xpack.cases.configureCases.saveAndCloseButton', { + defaultMessage: 'Save & close', +}); export const WARNING_NO_CONNECTOR_TITLE = i18n.translate( - 'xpack.securitySolution.cases.configureCases.warningTitle', + 'xpack.cases.configureCases.warningTitle', { defaultMessage: 'Warning', } ); export const WARNING_NO_CONNECTOR_MESSAGE = i18n.translate( - 'xpack.securitySolution.cases.configureCases.warningMessage', + 'xpack.cases.configureCases.warningMessage', { defaultMessage: 'The selected connector has been deleted. Either select a different connector or create a new one.', @@ -190,21 +183,18 @@ export const WARNING_NO_CONNECTOR_MESSAGE = i18n.translate( ); export const MAPPING_FIELD_NOT_MAPPED = i18n.translate( - 'xpack.securitySolution.cases.configureCases.mappingFieldNotMapped', + 'xpack.cases.configureCases.mappingFieldNotMapped', { defaultMessage: 'Not mapped', } ); -export const COMMENT = i18n.translate( - 'xpack.securitySolution.cases.configureCases.commentMapping', - { - defaultMessage: 'Comments', - } -); +export const COMMENT = i18n.translate('xpack.cases.configureCases.commentMapping', { + defaultMessage: 'Comments', +}); export const NO_FIELDS_ERROR = (connectorName: string): string => { - return i18n.translate('xpack.securitySolution.cases.configureCases.noFieldsError', { + return i18n.translate('xpack.cases.configureCases.noFieldsError', { values: { connectorName }, defaultMessage: 'No { connectorName } fields found. Please check your { connectorName } connector settings or your { connectorName } instance settings to resolve.', @@ -212,28 +202,25 @@ export const NO_FIELDS_ERROR = (connectorName: string): string => { }; export const BLANK_MAPPINGS = (connectorName: string): string => { - return i18n.translate('xpack.securitySolution.cases.configureCases.blankMappings', { + return i18n.translate('xpack.cases.configureCases.blankMappings', { values: { connectorName }, defaultMessage: 'At least one field needs to be mapped to { connectorName }', }); }; export const REQUIRED_MAPPINGS = (connectorName: string, fields: string): string => { - return i18n.translate('xpack.securitySolution.cases.configureCases.requiredMappings', { + return i18n.translate('xpack.cases.configureCases.requiredMappings', { values: { connectorName, fields }, defaultMessage: 'At least one Case field needs to be mapped to the following required { connectorName } fields: { fields }', }); }; -export const UPDATE_FIELD_MAPPINGS = i18n.translate( - 'xpack.securitySolution.cases.configureCases.updateConnector', - { - defaultMessage: 'Update field mappings', - } -); +export const UPDATE_FIELD_MAPPINGS = i18n.translate('xpack.cases.configureCases.updateConnector', { + defaultMessage: 'Update field mappings', +}); export const UPDATE_SELECTED_CONNECTOR = (connectorName: string): string => { - return i18n.translate('xpack.securitySolution.cases.configureCases.updateSelectedConnector', { + return i18n.translate('xpack.cases.configureCases.updateSelectedConnector', { values: { connectorName }, defaultMessage: 'Update { connectorName }', }); diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/utils.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/utils.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/utils.test.tsx rename to x-pack/plugins/cases/public/components/configure_cases/utils.test.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/utils.ts b/x-pack/plugins/cases/public/components/configure_cases/utils.ts similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/configure_cases/utils.ts rename to x-pack/plugins/cases/public/components/configure_cases/utils.ts index db14371b625d8..ade1a5e0c2bba 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/utils.ts +++ b/x-pack/plugins/cases/public/components/configure_cases/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorTypeFields, ConnectorTypes } from '../../../../../cases/common/api'; +import { ConnectorTypeFields, ConnectorTypes } from '../../../common'; import { CaseField, ActionType, diff --git a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx b/x-pack/plugins/cases/public/components/confirm_delete_case/index.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx rename to x-pack/plugins/cases/public/components/confirm_delete_case/index.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts b/x-pack/plugins/cases/public/components/confirm_delete_case/translations.ts similarity index 50% rename from x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts rename to x-pack/plugins/cases/public/components/confirm_delete_case/translations.ts index 07bf6966e953c..0400c4c7fef41 100644 --- a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/translations.ts +++ b/x-pack/plugins/cases/public/components/confirm_delete_case/translations.ts @@ -6,35 +6,29 @@ */ import { i18n } from '@kbn/i18n'; -export * from '../../translations'; +export * from '../../common/translations'; export const DELETE_TITLE = (caseTitle: string) => - i18n.translate('xpack.securitySolution.cases.confirmDeleteCase.deleteTitle', { + i18n.translate('xpack.cases.confirmDeleteCase.deleteTitle', { values: { caseTitle }, defaultMessage: 'Delete "{caseTitle}"', }); export const DELETE_THIS_CASE = (caseTitle: string) => - i18n.translate('xpack.securitySolution.cases.confirmDeleteCase.deleteThisCase', { + i18n.translate('xpack.cases.confirmDeleteCase.deleteThisCase', { defaultMessage: 'Delete this case', }); -export const CONFIRM_QUESTION = i18n.translate( - 'xpack.securitySolution.cases.confirmDeleteCase.confirmQuestion', - { - defaultMessage: - 'By deleting this case, all related case data will be permanently removed and you will no longer be able to push data to an external incident management system. Are you sure you wish to proceed?', - } -); -export const DELETE_SELECTED_CASES = i18n.translate( - 'xpack.securitySolution.cases.confirmDeleteCase.selectedCases', - { - defaultMessage: 'Delete selected cases', - } -); +export const CONFIRM_QUESTION = i18n.translate('xpack.cases.confirmDeleteCase.confirmQuestion', { + defaultMessage: + 'By deleting this case, all related case data will be permanently removed and you will no longer be able to push data to an external incident management system. Are you sure you wish to proceed?', +}); +export const DELETE_SELECTED_CASES = i18n.translate('xpack.cases.confirmDeleteCase.selectedCases', { + defaultMessage: 'Delete selected cases', +}); export const CONFIRM_QUESTION_PLURAL = i18n.translate( - 'xpack.securitySolution.cases.confirmDeleteCase.confirmQuestionPlural', + 'xpack.cases.confirmDeleteCase.confirmQuestionPlural', { defaultMessage: 'By deleting these cases, all related case data will be permanently removed and you will no longer be able to push data to an external incident management system. Are you sure you wish to proceed?', diff --git a/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.test.tsx b/x-pack/plugins/cases/public/components/connector_selector/form.test.tsx similarity index 91% rename from x-pack/plugins/security_solution/public/cases/components/connector_selector/form.test.tsx rename to x-pack/plugins/cases/public/components/connector_selector/form.test.tsx index 00e827b62a34e..ec136989dd937 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.test.tsx +++ b/x-pack/plugins/cases/public/components/connector_selector/form.test.tsx @@ -7,14 +7,12 @@ import React from 'react'; import { mount } from 'enzyme'; -import { UseField, Form, useForm, FormHook } from '../../../shared_imports'; +import { UseField, Form, useForm, FormHook } from '../../common/shared_imports'; import { ConnectorSelector } from './form'; import { connectorsMock } from '../../containers/mock'; import { getFormMock } from '../__mock__/form'; -jest.mock( - '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' -); +jest.mock('../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'); const useFormMock = useForm as jest.Mock; diff --git a/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx b/x-pack/plugins/cases/public/components/connector_selector/form.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx rename to x-pack/plugins/cases/public/components/connector_selector/form.tsx index 63c6f265b1ab2..210334e93adb8 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx +++ b/x-pack/plugins/cases/public/components/connector_selector/form.tsx @@ -9,9 +9,9 @@ import React, { useCallback } from 'react'; import { isEmpty } from 'lodash/fp'; import { EuiFormRow } from '@elastic/eui'; -import { FieldHook, getFieldValidityAndErrorMessage } from '../../../shared_imports'; +import { FieldHook, getFieldValidityAndErrorMessage } from '../../common/shared_imports'; import { ConnectorsDropdown } from '../configure_cases/connectors_dropdown'; -import { ActionConnector } from '../../../../../cases/common/api'; +import { ActionConnector } from '../../../common'; interface ConnectorSelectorProps { connectors: ActionConnector[]; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/card.tsx b/x-pack/plugins/cases/public/components/connectors/card.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/cases/components/connectors/card.tsx rename to x-pack/plugins/cases/public/components/connectors/card.tsx index af9a86b0b711b..82a508ccf3432 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/card.tsx +++ b/x-pack/plugins/cases/public/components/connectors/card.tsx @@ -10,7 +10,7 @@ import { EuiCard, EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; import styled from 'styled-components'; import { connectorsConfiguration } from '.'; -import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; +import { ConnectorTypes } from '../../../common'; interface ConnectorCardProps { connectorType: ConnectorTypes; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/alert_fields.tsx b/x-pack/plugins/cases/public/components/connectors/case/alert_fields.tsx similarity index 94% rename from x-pack/plugins/security_solution/public/cases/components/connectors/case/alert_fields.tsx rename to x-pack/plugins/cases/public/components/connectors/case/alert_fields.tsx index 05161456976c6..0c44bcab70679 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/case/alert_fields.tsx +++ b/x-pack/plugins/cases/public/components/connectors/case/alert_fields.tsx @@ -11,8 +11,8 @@ import React, { useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; import { EuiCallOut, EuiSpacer } from '@elastic/eui'; -import { ActionParamsProps } from '../../../../../../triggers_actions_ui/public/types'; -import { CommentType } from '../../../../../../cases/common/api'; +import { ActionParamsProps } from '../../../../../triggers_actions_ui/public/types'; +import { CommentType } from '../../../../common'; import { CaseActionParams } from './types'; import { ExistingCase } from './existing_case'; @@ -36,8 +36,6 @@ const CaseParamsFields: React.FunctionComponent { const { caseId = null, comment = defaultAlertComment } = actionParams.subActionParams ?? {}; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/cases_dropdown.tsx b/x-pack/plugins/cases/public/components/connectors/case/cases_dropdown.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/connectors/case/cases_dropdown.tsx rename to x-pack/plugins/cases/public/components/connectors/case/cases_dropdown.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx b/x-pack/plugins/cases/public/components/connectors/case/existing_case.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx rename to x-pack/plugins/cases/public/components/connectors/case/existing_case.tsx index 3c6c5f47c6d12..22798843dd856 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx +++ b/x-pack/plugins/cases/public/components/connectors/case/existing_case.tsx @@ -6,7 +6,7 @@ */ import React, { memo, useMemo, useCallback } from 'react'; -import { CaseType } from '../../../../../../cases/common/api'; +import { CaseType } from '../../../../common'; import { useGetCases, DEFAULT_QUERY_PARAMS, diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/index.ts b/x-pack/plugins/cases/public/components/connectors/case/index.ts similarity index 93% rename from x-pack/plugins/security_solution/public/cases/components/connectors/case/index.ts rename to x-pack/plugins/cases/public/components/connectors/case/index.ts index 4f7a720ea6410..c2cf4980da7ec 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/case/index.ts +++ b/x-pack/plugins/cases/public/components/connectors/case/index.ts @@ -8,7 +8,7 @@ import { lazy } from 'react'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ActionTypeModel } from '../../../../../../triggers_actions_ui/public/types'; +import { ActionTypeModel } from '../../../../../triggers_actions_ui/public/types'; import { CaseActionParams } from './types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts b/x-pack/plugins/cases/public/components/connectors/case/translations.ts similarity index 64% rename from x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts rename to x-pack/plugins/cases/public/components/connectors/case/translations.ts index 1d15a3da496a6..8304aaef5765c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts +++ b/x-pack/plugins/cases/public/components/connectors/case/translations.ts @@ -7,80 +7,80 @@ import { i18n } from '@kbn/i18n'; -export * from '../../../translations'; +export * from '../../../common/translations'; export const CASE_CONNECTOR_DESC = i18n.translate( - 'xpack.securitySolution.cases.components.connectors.cases.selectMessageText', + 'xpack.cases.components.connectors.cases.selectMessageText', { defaultMessage: 'Create or update a case.', } ); export const CASE_CONNECTOR_TITLE = i18n.translate( - 'xpack.securitySolution.cases.components.connectors.cases.actionTypeTitle', + 'xpack.cases.components.connectors.cases.actionTypeTitle', { defaultMessage: 'Cases', } ); export const CASE_CONNECTOR_COMMENT_LABEL = i18n.translate( - 'xpack.securitySolution.cases.components.connectors.cases.commentLabel', + 'xpack.cases.components.connectors.cases.commentLabel', { defaultMessage: 'Comment', } ); export const CASE_CONNECTOR_COMMENT_REQUIRED = i18n.translate( - 'xpack.securitySolution.cases.components.connectors.cases.commentRequired', + 'xpack.cases.components.connectors.cases.commentRequired', { defaultMessage: 'Comment is required.', } ); export const CASE_CONNECTOR_CASES_DROPDOWN_ROW_LABEL = i18n.translate( - 'xpack.securitySolution.cases.components.connectors.cases.casesDropdownRowLabel', + 'xpack.cases.components.connectors.cases.casesDropdownRowLabel', { defaultMessage: 'Case allowing sub-cases', } ); export const CASE_CONNECTOR_CASES_DROPDOWN_PLACEHOLDER = i18n.translate( - 'xpack.securitySolution.cases.components.connectors.cases.casesDropdownPlaceholder', + 'xpack.cases.components.connectors.cases.casesDropdownPlaceholder', { defaultMessage: 'Select case', } ); export const CASE_CONNECTOR_CASES_OPTION_NEW_CASE = i18n.translate( - 'xpack.securitySolution.cases.components.connectors.cases.optionAddNewCase', + 'xpack.cases.components.connectors.cases.optionAddNewCase', { defaultMessage: 'Add to a new case', } ); export const CASE_CONNECTOR_CASES_OPTION_EXISTING_CASE = i18n.translate( - 'xpack.securitySolution.cases.components.connectors.cases.optionAddToExistingCase', + 'xpack.cases.components.connectors.cases.optionAddToExistingCase', { defaultMessage: 'Add to existing case', } ); export const CASE_CONNECTOR_CASE_REQUIRED = i18n.translate( - 'xpack.securitySolution.cases.components.connectors.cases.caseRequired', + 'xpack.cases.components.connectors.cases.caseRequired', { defaultMessage: 'You must select a case.', } ); export const CASE_CONNECTOR_CALL_OUT_TITLE = i18n.translate( - 'xpack.securitySolution.cases.components.connectors.cases.callOutTitle', + 'xpack.cases.components.connectors.cases.callOutTitle', { defaultMessage: 'Generated alerts will be attached to sub-cases', } ); export const CASE_CONNECTOR_CALL_OUT_MSG = i18n.translate( - 'xpack.securitySolution.cases.components.connectors.cases.callOutMsg', + 'xpack.cases.components.connectors.cases.callOutMsg', { defaultMessage: 'A case can contain multiple sub-cases to allow grouping of generated alerts. Sub-cases will give more granular control over the status of these generated alerts and prevents having too many alerts attached to one case.', @@ -88,21 +88,21 @@ export const CASE_CONNECTOR_CALL_OUT_MSG = i18n.translate( ); export const CASE_CONNECTOR_ADD_NEW_CASE = i18n.translate( - 'xpack.securitySolution.cases.components.connectors.cases.addNewCaseOption', + 'xpack.cases.components.connectors.cases.addNewCaseOption', { defaultMessage: 'Add new case', } ); export const CREATE_CASE = i18n.translate( - 'xpack.securitySolution.cases.components.connectors.cases.createCaseLabel', + 'xpack.cases.components.connectors.cases.createCaseLabel', { defaultMessage: 'Create case', } ); export const CONNECTED_CASE = i18n.translate( - 'xpack.securitySolution.cases.components.connectors.cases.connectedCaseLabel', + 'xpack.cases.components.connectors.cases.connectedCaseLabel', { defaultMessage: 'Connected case', } diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/types.ts b/x-pack/plugins/cases/public/components/connectors/case/types.ts similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/connectors/case/types.ts rename to x-pack/plugins/cases/public/components/connectors/case/types.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/config.ts b/x-pack/plugins/cases/public/components/connectors/config.ts similarity index 95% rename from x-pack/plugins/security_solution/public/cases/components/connectors/config.ts rename to x-pack/plugins/cases/public/components/connectors/config.ts index 1d12d4b98a823..e8d87511c7e17 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/config.ts +++ b/x-pack/plugins/cases/public/components/connectors/config.ts @@ -11,7 +11,7 @@ import { getServiceNowSIRActionType, getJiraActionType, // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../../triggers_actions_ui/public/common'; +} from '../../../../triggers_actions_ui/public/common'; import { ConnectorConfiguration } from './types'; const resilient = getResilientActionType(); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/connectors_registry.ts b/x-pack/plugins/cases/public/components/connectors/connectors_registry.ts similarity index 61% rename from x-pack/plugins/security_solution/public/cases/components/connectors/connectors_registry.ts rename to x-pack/plugins/cases/public/components/connectors/connectors_registry.ts index d6896a8ac8c80..2e02cb290c3c8 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/connectors_registry.ts +++ b/x-pack/plugins/cases/public/components/connectors/connectors_registry.ts @@ -8,8 +8,6 @@ import { i18n } from '@kbn/i18n'; import { CaseConnector, CaseConnectorsRegistry } from './types'; -/* eslint-disable @typescript-eslint/no-explicit-any */ - export const createCaseConnectorsRegistry = (): CaseConnectorsRegistry => { const connectors: Map> = new Map(); @@ -18,15 +16,12 @@ export const createCaseConnectorsRegistry = (): CaseConnectorsRegistry => { register: (connector: CaseConnector) => { if (connectors.has(connector.id)) { throw new Error( - i18n.translate( - 'xpack.securitySolution.caseConnectorsRegistry.register.duplicateCaseConnectorErrorMessage', - { - defaultMessage: 'Object type "{id}" is already registered.', - values: { - id: connector.id, - }, - } - ) + i18n.translate('xpack.cases.connecors.register.duplicateCaseConnectorErrorMessage', { + defaultMessage: 'Object type "{id}" is already registered.', + values: { + id: connector.id, + }, + }) ); } @@ -35,15 +30,12 @@ export const createCaseConnectorsRegistry = (): CaseConnectorsRegistry => { get: (id: string): CaseConnector => { if (!connectors.has(id)) { throw new Error( - i18n.translate( - 'xpack.securitySolution.caseConnectorsRegistry.get.missingCaseConnectorErrorMessage', - { - defaultMessage: 'Object type "{id}" is not registered.', - values: { - id, - }, - } - ) + i18n.translate('xpack.cases.connecors.get.missingCaseConnectorErrorMessage', { + defaultMessage: 'Object type "{id}" is not registered.', + values: { + id, + }, + }) ); } return connectors.get(id)!; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/fields_form.tsx b/x-pack/plugins/cases/public/components/connectors/fields_form.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/cases/components/connectors/fields_form.tsx rename to x-pack/plugins/cases/public/components/connectors/fields_form.tsx index 841c2a9e38f6d..d71da6f87689d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/fields_form.tsx +++ b/x-pack/plugins/cases/public/components/connectors/fields_form.tsx @@ -10,7 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import { CaseActionConnector, ConnectorFieldsProps } from './types'; import { getCaseConnectors } from '.'; -import { ConnectorTypeFields } from '../../../../../cases/common/api/connectors'; +import { ConnectorTypeFields } from '../../../common'; interface Props extends Omit, 'connector'> { connector: CaseActionConnector | null; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/index.ts b/x-pack/plugins/cases/public/components/connectors/index.ts similarity index 93% rename from x-pack/plugins/security_solution/public/cases/components/connectors/index.ts rename to x-pack/plugins/cases/public/components/connectors/index.ts index dad7070aad705..71ba161eb63c9 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/index.ts +++ b/x-pack/plugins/cases/public/components/connectors/index.ts @@ -15,9 +15,9 @@ import { ServiceNowITSMFieldsType, ServiceNowSIRFieldsType, ResilientFieldsType, -} from '../../../../../cases/common/api/connectors'; +} from '../../../common'; -export { getActionType as getCaseConnectorUI } from './case'; +export { getActionType as getCaseConnectorUi } from './case'; export * from './config'; export * from './types'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/__mocks__/api.ts b/x-pack/plugins/cases/public/components/connectors/jira/__mocks__/api.ts similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/__mocks__/api.ts rename to x-pack/plugins/cases/public/components/connectors/jira/__mocks__/api.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/api.test.ts b/x-pack/plugins/cases/public/components/connectors/jira/api.test.ts similarity index 98% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/api.test.ts rename to x-pack/plugins/cases/public/components/connectors/jira/api.test.ts index 7190a44f3ab1f..bbab8a14b5ed9 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/api.test.ts +++ b/x-pack/plugins/cases/public/components/connectors/jira/api.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { httpServiceMock } from '../../../../../../../../src/core/public/mocks'; +import { httpServiceMock } from '../../../../../../../src/core/public/mocks'; import { getIssueTypes, getFieldsByIssueType, getIssues, getIssue } from './api'; const issueTypesResponse = { diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/api.ts b/x-pack/plugins/cases/public/components/connectors/jira/api.ts similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/api.ts rename to x-pack/plugins/cases/public/components/connectors/jira/api.ts index 4ebb06192e62d..dff3e3a5b41ab 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/api.ts +++ b/x-pack/plugins/cases/public/components/connectors/jira/api.ts @@ -6,7 +6,7 @@ */ import { HttpSetup } from 'kibana/public'; -import { ActionTypeExecutorResult } from '../../../../../../actions/common'; +import { ActionTypeExecutorResult } from '../../../../../actions/common'; import { IssueTypes, Fields, Issues, Issue } from './types'; export const BASE_ACTION_API_PATH = '/api/actions'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/case_fields.test.tsx b/x-pack/plugins/cases/public/components/connectors/jira/case_fields.test.tsx similarity index 99% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/case_fields.test.tsx rename to x-pack/plugins/cases/public/components/connectors/jira/case_fields.test.tsx index b151d41c4cdd8..38a1e30616200 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/case_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/case_fields.test.tsx @@ -18,12 +18,11 @@ import { useGetSingleIssue } from './use_get_single_issue'; import { useGetIssues } from './use_get_issues'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -jest.mock('../../../../common/lib/kibana'); jest.mock('./use_get_issue_types'); jest.mock('./use_get_fields_by_issue_type'); jest.mock('./use_get_single_issue'); jest.mock('./use_get_issues'); - +jest.mock('../../../common/lib/kibana'); const useGetIssueTypesMock = useGetIssueTypes as jest.Mock; const useGetFieldsByIssueTypeMock = useGetFieldsByIssueType as jest.Mock; const useGetSingleIssueMock = useGetSingleIssue as jest.Mock; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/case_fields.tsx b/x-pack/plugins/cases/public/components/connectors/jira/case_fields.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/case_fields.tsx rename to x-pack/plugins/cases/public/components/connectors/jira/case_fields.tsx index 22e80d43f34e1..6aff81f380015 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/case_fields.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/case_fields.tsx @@ -10,8 +10,8 @@ import { map } from 'lodash/fp'; import { EuiFormRow, EuiSelect, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import * as i18n from './translations'; -import { ConnectorTypes, JiraFieldsType } from '../../../../../../cases/common/api/connectors'; -import { useKibana } from '../../../../common/lib/kibana'; +import { ConnectorTypes, JiraFieldsType } from '../../../../common'; +import { useKibana } from '../../../common/lib/kibana'; import { ConnectorFieldsProps } from '../types'; import { useGetIssueTypes } from './use_get_issue_types'; import { useGetFieldsByIssueType } from './use_get_fields_by_issue_type'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/index.ts b/x-pack/plugins/cases/public/components/connectors/jira/index.ts similarity index 89% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/index.ts rename to x-pack/plugins/cases/public/components/connectors/jira/index.ts index 40e59a081a449..ea408a1bd6664 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/index.ts +++ b/x-pack/plugins/cases/public/components/connectors/jira/index.ts @@ -8,7 +8,7 @@ import { lazy } from 'react'; import { CaseConnector } from '../types'; -import { JiraFieldsType } from '../../../../../../cases/common/api/connectors'; +import { JiraFieldsType } from '../../../../common'; import * as i18n from './translations'; export * from './types'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/search_issues.tsx b/x-pack/plugins/cases/public/components/connectors/jira/search_issues.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/search_issues.tsx rename to x-pack/plugins/cases/public/components/connectors/jira/search_issues.tsx index 3fdc17b7157d6..79ac42e034c6a 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/search_issues.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/search_issues.tsx @@ -8,8 +8,8 @@ import React, { useMemo, useEffect, useCallback, useState, memo } from 'react'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -import { useKibana } from '../../../../common/lib/kibana'; -import { ActionConnector } from '../../../containers/types'; +import { useKibana } from '../../../common/lib/kibana'; +import { ActionConnector } from '../../../../common'; import { useGetIssues } from './use_get_issues'; import { useGetSingleIssue } from './use_get_single_issue'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/translations.ts b/x-pack/plugins/cases/public/components/connectors/jira/translations.ts similarity index 50% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/translations.ts rename to x-pack/plugins/cases/public/components/connectors/jira/translations.ts index a4948d61f952c..88dd7d0c7c27b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/translations.ts +++ b/x-pack/plugins/cases/public/components/connectors/jira/translations.ts @@ -8,70 +8,61 @@ import { i18n } from '@kbn/i18n'; export const ISSUE_TYPES_API_ERROR = i18n.translate( - 'xpack.securitySolution.components.connectors.jira.unableToGetIssueTypesMessage', + 'xpack.cases.connectors.jira.unableToGetIssueTypesMessage', { defaultMessage: 'Unable to get issue types', } ); export const FIELDS_API_ERROR = i18n.translate( - 'xpack.securitySolution.components.connectors.jira.unableToGetFieldsMessage', + 'xpack.cases.connectors.jira.unableToGetFieldsMessage', { defaultMessage: 'Unable to get connectors', } ); export const ISSUES_API_ERROR = i18n.translate( - 'xpack.securitySolution.components.connectors.jira.unableToGetIssuesMessage', + 'xpack.cases.connectors.jira.unableToGetIssuesMessage', { defaultMessage: 'Unable to get issues', } ); export const GET_ISSUE_API_ERROR = (id: string) => - i18n.translate('xpack.securitySolution.components.connectors.jira.unableToGetIssueMessage', { + i18n.translate('xpack.cases.connectors.jira.unableToGetIssueMessage', { defaultMessage: 'Unable to get issue with id {id}', values: { id }, }); export const SEARCH_ISSUES_COMBO_BOX_ARIA_LABEL = i18n.translate( - 'xpack.securitySolution.components.connectors.jira.searchIssuesComboBoxAriaLabel', + 'xpack.cases.connectors.jira.searchIssuesComboBoxAriaLabel', { defaultMessage: 'Type to search', } ); export const SEARCH_ISSUES_PLACEHOLDER = i18n.translate( - 'xpack.securitySolution.components.connectors.jira.searchIssuesComboBoxPlaceholder', + 'xpack.cases.connectors.jira.searchIssuesComboBoxPlaceholder', { defaultMessage: 'Type to search', } ); export const SEARCH_ISSUES_LOADING = i18n.translate( - 'xpack.securitySolution.components.connectors.jira.searchIssuesLoading', + 'xpack.cases.connectors.jira.searchIssuesLoading', { defaultMessage: 'Loading...', } ); -export const PRIORITY = i18n.translate( - 'xpack.securitySolution.cases.connectors.jira.prioritySelectFieldLabel', - { - defaultMessage: 'Priority', - } -); +export const PRIORITY = i18n.translate('xpack.cases.connectors.jira.prioritySelectFieldLabel', { + defaultMessage: 'Priority', +}); -export const ISSUE_TYPE = i18n.translate( - 'xpack.securitySolution.cases.connectors.jira.issueTypesSelectFieldLabel', - { - defaultMessage: 'Issue type', - } -); +export const ISSUE_TYPE = i18n.translate('xpack.cases.connectors.jira.issueTypesSelectFieldLabel', { + defaultMessage: 'Issue type', +}); -export const PARENT_ISSUE = i18n.translate( - 'xpack.securitySolution.cases.connectors.jira.parentIssueSearchLabel', - { - defaultMessage: 'Parent issue', - } -); +export const PARENT_ISSUE = i18n.translate('xpack.cases.connectors.jira.parentIssueSearchLabel', { + defaultMessage: 'Parent issue', +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/types.ts b/x-pack/plugins/cases/public/components/connectors/jira/types.ts similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/types.ts rename to x-pack/plugins/cases/public/components/connectors/jira/types.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_fields_by_issue_type.test.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.test.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_fields_by_issue_type.test.tsx rename to x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.test.tsx index 4ef5f14da2238..b4c2c848d79ed 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_fields_by_issue_type.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.test.tsx @@ -7,12 +7,12 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import { useKibana } from '../../../../common/lib/kibana'; +import { useKibana } from '../../../common/lib/kibana'; import { connector } from '../mock'; import { useGetFieldsByIssueType, UseGetFieldsByIssueType } from './use_get_fields_by_issue_type'; import * as api from './api'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../common/lib/kibana'); jest.mock('./api'); const useKibanaMock = useKibana as jest.Mocked; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_fields_by_issue_type.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_fields_by_issue_type.tsx rename to x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.tsx index 03000e8916617..a4958d91c88aa 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_fields_by_issue_type.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, ToastsApi } from 'kibana/public'; -import { ActionConnector } from '../../../containers/types'; +import { ActionConnector } from '../../../../common'; import { getFieldsByIssueType } from './api'; import { Fields } from './types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_issue_types.test.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.test.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_issue_types.test.tsx rename to x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.test.tsx index ee32d93c655be..6c1a9b5fcab08 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_issue_types.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.test.tsx @@ -7,12 +7,12 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import { useKibana } from '../../../../common/lib/kibana'; +import { useKibana } from '../../../common/lib/kibana'; import { connector } from '../mock'; import { useGetIssueTypes, UseGetIssueTypes } from './use_get_issue_types'; import * as api from './api'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../common/lib/kibana'); jest.mock('./api'); const useKibanaMock = useKibana as jest.Mocked; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_issue_types.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_issue_types.tsx rename to x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.tsx index 3c35d315a2bcd..447491d2a2fff 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_issue_types.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, ToastsApi } from 'kibana/public'; -import { ActionConnector } from '../../../containers/types'; +import { ActionConnector } from '../../../../common'; import { getIssueTypes } from './api'; import { IssueTypes } from './types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_issues.test.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.test.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_issues.test.tsx rename to x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.test.tsx index ee1d4ffd3d8ae..2308fe604e710 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_issues.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.test.tsx @@ -7,12 +7,12 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import { useKibana } from '../../../../common/lib/kibana'; +import { useKibana } from '../../../common/lib/kibana'; import { connector as actionConnector, issues } from '../mock'; import { useGetIssues, UseGetIssues } from './use_get_issues'; import * as api from './api'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../common/lib/kibana'); jest.mock('./api'); const useKibanaMock = useKibana as jest.Mocked; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_issues.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_issues.tsx rename to x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.tsx index b44b0558f1536..e4b6f5e4dea01 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_issues.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.tsx @@ -8,7 +8,7 @@ import { isEmpty, debounce } from 'lodash/fp'; import { useState, useEffect, useRef } from 'react'; import { HttpSetup, ToastsApi } from 'kibana/public'; -import { ActionConnector } from '../../../containers/types'; +import { ActionConnector } from '../../../../common'; import { getIssues } from './api'; import { Issues } from './types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_single_issue.test.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_single_issue.test.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_single_issue.test.tsx rename to x-pack/plugins/cases/public/components/connectors/jira/use_get_single_issue.test.tsx index ba9752ca71811..28949b456ecdd 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_single_issue.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_single_issue.test.tsx @@ -7,12 +7,12 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import { useKibana } from '../../../../common/lib/kibana'; +import { useKibana } from '../../../common/lib/kibana'; import { connector as actionConnector, issues } from '../mock'; import { useGetSingleIssue, UseGetSingleIssue } from './use_get_single_issue'; import * as api from './api'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../common/lib/kibana'); jest.mock('./api'); const useKibanaMock = useKibana as jest.Mocked; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_single_issue.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_single_issue.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_single_issue.tsx rename to x-pack/plugins/cases/public/components/connectors/jira/use_get_single_issue.tsx index 6c70286426168..e26940a40d39f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/jira/use_get_single_issue.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_single_issue.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, ToastsApi } from 'kibana/public'; -import { ActionConnector } from '../../../containers/types'; +import { ActionConnector } from '../../../../common'; import { getIssue } from './api'; import { Issue } from './types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/mock.ts b/x-pack/plugins/cases/public/components/connectors/mock.ts similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/connectors/mock.ts rename to x-pack/plugins/cases/public/components/connectors/mock.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/__mocks__/api.ts b/x-pack/plugins/cases/public/components/connectors/resilient/__mocks__/api.ts similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/connectors/resilient/__mocks__/api.ts rename to x-pack/plugins/cases/public/components/connectors/resilient/__mocks__/api.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/api.ts b/x-pack/plugins/cases/public/components/connectors/resilient/api.ts similarity index 93% rename from x-pack/plugins/security_solution/public/cases/components/connectors/resilient/api.ts rename to x-pack/plugins/cases/public/components/connectors/resilient/api.ts index 6d57f38fa961c..5fec83f303950 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/api.ts +++ b/x-pack/plugins/cases/public/components/connectors/resilient/api.ts @@ -6,7 +6,7 @@ */ import { HttpSetup } from 'kibana/public'; -import { ActionTypeExecutorResult } from '../../../../../../actions/common'; +import { ActionTypeExecutorResult } from '../../../../../actions/common'; import { ResilientIncidentTypes, ResilientSeverity } from './types'; export const BASE_ACTION_API_PATH = '/api/actions'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/case_fields.test.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.test.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/components/connectors/resilient/case_fields.test.tsx rename to x-pack/plugins/cases/public/components/connectors/resilient/case_fields.test.tsx index dd13083288020..dda6ba5de95cc 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/case_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.test.tsx @@ -15,7 +15,7 @@ import { useGetIncidentTypes } from './use_get_incident_types'; import { useGetSeverity } from './use_get_severity'; import Fields from './case_fields'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../common/lib/kibana'); jest.mock('./use_get_incident_types'); jest.mock('./use_get_severity'); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/case_fields.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/components/connectors/resilient/case_fields.tsx rename to x-pack/plugins/cases/public/components/connectors/resilient/case_fields.tsx index b1fbfb1169d08..e1eeb13bf684c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/case_fields.tsx +++ b/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.tsx @@ -15,13 +15,13 @@ import { EuiSpacer, } from '@elastic/eui'; -import { useKibana } from '../../../../common/lib/kibana'; +import { useKibana } from '../../../common/lib/kibana'; import { ConnectorFieldsProps } from '../types'; import { useGetIncidentTypes } from './use_get_incident_types'; import { useGetSeverity } from './use_get_severity'; import * as i18n from './translations'; -import { ConnectorTypes, ResilientFieldsType } from '../../../../../../cases/common/api/connectors'; +import { ConnectorTypes, ResilientFieldsType } from '../../../../common'; import { ConnectorCard } from '../card'; const ResilientFieldsComponent: React.FunctionComponent< diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/index.ts b/x-pack/plugins/cases/public/components/connectors/resilient/index.ts similarity index 88% rename from x-pack/plugins/security_solution/public/cases/components/connectors/resilient/index.ts rename to x-pack/plugins/cases/public/components/connectors/resilient/index.ts index 8a2603f39e102..c8e7ad9a063cb 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/index.ts +++ b/x-pack/plugins/cases/public/components/connectors/resilient/index.ts @@ -8,7 +8,7 @@ import { lazy } from 'react'; import { CaseConnector } from '../types'; -import { ResilientFieldsType } from '../../../../../../cases/common/api/connectors'; +import { ResilientFieldsType } from '../../../../common'; import * as i18n from './translations'; export * from './types'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/translations.ts b/x-pack/plugins/cases/public/components/connectors/resilient/translations.ts similarity index 60% rename from x-pack/plugins/security_solution/public/cases/components/connectors/resilient/translations.ts rename to x-pack/plugins/cases/public/components/connectors/resilient/translations.ts index 4f8061f48aa68..1b63a5098e92a 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/translations.ts +++ b/x-pack/plugins/cases/public/components/connectors/resilient/translations.ts @@ -8,36 +8,33 @@ import { i18n } from '@kbn/i18n'; export const INCIDENT_TYPES_API_ERROR = i18n.translate( - 'xpack.securitySolution.cases.connectors.resilient.unableToGetIncidentTypesMessage', + 'xpack.cases.connectors.resilient.unableToGetIncidentTypesMessage', { defaultMessage: 'Unable to get incident types', } ); export const SEVERITY_API_ERROR = i18n.translate( - 'xpack.securitySolution.cases.connectors.resilient.unableToGetSeverityMessage', + 'xpack.cases.connectors.resilient.unableToGetSeverityMessage', { defaultMessage: 'Unable to get severity', } ); export const INCIDENT_TYPES_PLACEHOLDER = i18n.translate( - 'xpack.securitySolution.cases.connectors.resilient.incidentTypesPlaceholder', + 'xpack.cases.connectors.resilient.incidentTypesPlaceholder', { defaultMessage: 'Choose types', } ); export const INCIDENT_TYPES_LABEL = i18n.translate( - 'xpack.securitySolution.cases.connectors.resilient.incidentTypesLabel', + 'xpack.cases.connectors.resilient.incidentTypesLabel', { defaultMessage: 'Incident Types', } ); -export const SEVERITY_LABEL = i18n.translate( - 'xpack.securitySolution.cases.connectors.resilient.severityLabel', - { - defaultMessage: 'Severity', - } -); +export const SEVERITY_LABEL = i18n.translate('xpack.cases.connectors.resilient.severityLabel', { + defaultMessage: 'Severity', +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/types.ts b/x-pack/plugins/cases/public/components/connectors/resilient/types.ts similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/connectors/resilient/types.ts rename to x-pack/plugins/cases/public/components/connectors/resilient/types.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/use_get_incident_types.test.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.test.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/cases/components/connectors/resilient/use_get_incident_types.test.tsx rename to x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.test.tsx index 19ce6d653f9fd..59c1f8e9b40d0 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/use_get_incident_types.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.test.tsx @@ -7,12 +7,12 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import { useKibana } from '../../../../common/lib/kibana'; +import { useKibana } from '../../../common/lib/kibana'; import { connector } from '../mock'; import { useGetIncidentTypes, UseGetIncidentTypes } from './use_get_incident_types'; import * as api from './api'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../common/lib/kibana'); jest.mock('./api'); const useKibanaMock = useKibana as jest.Mocked; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/use_get_incident_types.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/connectors/resilient/use_get_incident_types.tsx rename to x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.tsx index 34cbb0a69b0f4..530b56de8796d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/use_get_incident_types.tsx +++ b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, ToastsApi } from 'kibana/public'; -import { ActionConnector } from '../../../containers/types'; +import { ActionConnector } from '../../../../common'; import { getIncidentTypes } from './api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/use_get_severity.test.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.test.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/cases/components/connectors/resilient/use_get_severity.test.tsx rename to x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.test.tsx index 614ba3c236f06..f646dd7e8f7c2 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/use_get_severity.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.test.tsx @@ -7,12 +7,12 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import { useKibana } from '../../../../common/lib/kibana'; +import { useKibana } from '../../../common/lib/kibana'; import { connector } from '../mock'; import { useGetSeverity, UseGetSeverity } from './use_get_severity'; import * as api from './api'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../common/lib/kibana'); jest.mock('./api'); const useKibanaMock = useKibana as jest.Mocked; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/use_get_severity.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/connectors/resilient/use_get_severity.tsx rename to x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.tsx index 5b44c6b4a32b2..8753e3926ffe5 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/resilient/use_get_severity.tsx +++ b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, ToastsApi } from 'kibana/public'; -import { ActionConnector } from '../../../containers/types'; +import { ActionConnector } from '../../../../common'; import { getSeverity } from './api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/__mocks__/api.ts b/x-pack/plugins/cases/public/components/connectors/servicenow/__mocks__/api.ts similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/__mocks__/api.ts rename to x-pack/plugins/cases/public/components/connectors/servicenow/__mocks__/api.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/api.test.ts b/x-pack/plugins/cases/public/components/connectors/servicenow/api.test.ts similarity index 93% rename from x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/api.test.ts rename to x-pack/plugins/cases/public/components/connectors/servicenow/api.test.ts index 6a6bb7e947997..461823036ed21 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/api.test.ts +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/api.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { httpServiceMock } from '../../../../../../../../src/core/public/mocks'; +import { httpServiceMock } from '../../../../../../../src/core/public/mocks'; import { getChoices } from './api'; import { choices } from '../mock'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/api.ts b/x-pack/plugins/cases/public/components/connectors/servicenow/api.ts similarity index 91% rename from x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/api.ts rename to x-pack/plugins/cases/public/components/connectors/servicenow/api.ts index d91ad9f8762bd..e68eb18860ae3 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/api.ts +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/api.ts @@ -6,7 +6,7 @@ */ import { HttpSetup } from 'kibana/public'; -import { ActionTypeExecutorResult } from '../../../../../../actions/common'; +import { ActionTypeExecutorResult } from '../../../../../actions/common'; import { Choice } from './types'; export const BASE_ACTION_API_PATH = '/api/actions'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/helpers.ts b/x-pack/plugins/cases/public/components/connectors/servicenow/helpers.ts similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/helpers.ts rename to x-pack/plugins/cases/public/components/connectors/servicenow/helpers.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/index.ts b/x-pack/plugins/cases/public/components/connectors/servicenow/index.ts similarity index 88% rename from x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/index.ts rename to x-pack/plugins/cases/public/components/connectors/servicenow/index.ts index b342095c39ff0..a6f0795fe4d8f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/index.ts +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/index.ts @@ -8,10 +8,7 @@ import { lazy } from 'react'; import { CaseConnector } from '../types'; -import { - ServiceNowITSMFieldsType, - ServiceNowSIRFieldsType, -} from '../../../../../../cases/common/api/connectors'; +import { ServiceNowITSMFieldsType, ServiceNowSIRFieldsType } from '../../../../common'; import * as i18n from './translations'; export const getServiceNowITSMCaseConnector = (): CaseConnector => { diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_itsm_case_fields.test.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.test.tsx similarity index 99% rename from x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_itsm_case_fields.test.tsx rename to x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.test.tsx index 6e2bdec360fdf..9688ca191d672 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_itsm_case_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.test.tsx @@ -16,7 +16,7 @@ import Fields from './servicenow_itsm_case_fields'; let onChoicesSuccess = (c: Choice[]) => {}; -jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../common/lib/kibana'); jest.mock('./use_get_choices', () => ({ useGetChoices: (args: { onSuccess: () => void }) => { onChoicesSuccess = args.onSuccess; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_itsm_case_fields.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_itsm_case_fields.tsx rename to x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.tsx index accb8450802d4..710e230958354 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_itsm_case_fields.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.tsx @@ -10,11 +10,8 @@ import { EuiFormRow, EuiSelect, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@el import * as i18n from './translations'; import { ConnectorFieldsProps } from '../types'; -import { - ConnectorTypes, - ServiceNowITSMFieldsType, -} from '../../../../../../cases/common/api/connectors'; -import { useKibana } from '../../../../common/lib/kibana'; +import { ConnectorTypes, ServiceNowITSMFieldsType } from '../../../../common'; +import { useKibana } from '../../../common/lib/kibana'; import { ConnectorCard } from '../card'; import { useGetChoices } from './use_get_choices'; import { Fields, Choice } from './types'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_sir_case_fields.test.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.test.tsx similarity index 99% rename from x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_sir_case_fields.test.tsx rename to x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.test.tsx index 7cd32a0cbfbf3..4a5b34cd3c3cb 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_sir_case_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.test.tsx @@ -16,7 +16,7 @@ import Fields from './servicenow_sir_case_fields'; let onChoicesSuccess = (c: Choice[]) => {}; -jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../common/lib/kibana'); jest.mock('./use_get_choices', () => ({ useGetChoices: (args: { onSuccess: () => void }) => { onChoicesSuccess = args.onSuccess; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_sir_case_fields.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_sir_case_fields.tsx rename to x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.tsx index 63502e3454fcf..1f9a7cf7acd64 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/servicenow_sir_case_fields.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.tsx @@ -8,11 +8,8 @@ import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; import { EuiFormRow, EuiSelect, EuiFlexGroup, EuiFlexItem, EuiCheckbox } from '@elastic/eui'; -import { - ConnectorTypes, - ServiceNowSIRFieldsType, -} from '../../../../../../cases/common/api/connectors'; -import { useKibana } from '../../../../common/lib/kibana'; +import { ConnectorTypes, ServiceNowSIRFieldsType } from '../../../../common'; +import { useKibana } from '../../../common/lib/kibana'; import { ConnectorFieldsProps } from '../types'; import { ConnectorCard } from '../card'; import { useGetChoices } from './use_get_choices'; diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/translations.ts b/x-pack/plugins/cases/public/components/connectors/servicenow/translations.ts new file mode 100644 index 0000000000000..fc48ecf17f2c6 --- /dev/null +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/translations.ts @@ -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 { i18n } from '@kbn/i18n'; + +export const URGENCY = i18n.translate('xpack.cases.connectors.serviceNow.urgencySelectFieldLabel', { + defaultMessage: 'Urgency', +}); + +export const SEVERITY = i18n.translate( + 'xpack.cases.connectors.serviceNow.severitySelectFieldLabel', + { + defaultMessage: 'Severity', + } +); + +export const IMPACT = i18n.translate('xpack.cases.connectors.serviceNow.impactSelectFieldLabel', { + defaultMessage: 'Impact', +}); + +export const CHOICES_API_ERROR = i18n.translate( + 'xpack.cases.connectors.serviceNow.unableToGetChoicesMessage', + { + defaultMessage: 'Unable to get choices', + } +); + +export const MALWARE_URL = i18n.translate('xpack.cases.connectors.serviceNow.malwareURLTitle', { + defaultMessage: 'Malware URL', +}); + +export const MALWARE_HASH = i18n.translate('xpack.cases.connectors.serviceNow.malwareHashTitle', { + defaultMessage: 'Malware Hash', +}); + +export const CATEGORY = i18n.translate('xpack.cases.connectors.serviceNow.categoryTitle', { + defaultMessage: 'Category', +}); + +export const SUBCATEGORY = i18n.translate('xpack.cases.connectors.serviceNow.subcategoryTitle', { + defaultMessage: 'Subcategory', +}); + +export const SOURCE_IP = i18n.translate('xpack.cases.connectors.serviceNow.sourceIPTitle', { + defaultMessage: 'Source IP', +}); + +export const DEST_IP = i18n.translate('xpack.cases.connectors.serviceNow.destinationIPTitle', { + defaultMessage: 'Destination IP', +}); + +export const PRIORITY = i18n.translate( + 'xpack.cases.connectors.serviceNow.prioritySelectFieldTitle', + { + defaultMessage: 'Priority', + } +); + +export const ALERT_FIELDS_LABEL = i18n.translate( + 'xpack.cases.connectors.serviceNow.alertFieldsTitle', + { + defaultMessage: 'Select Observables to push', + } +); + +export const ALERT_FIELD_ENABLED_TEXT = i18n.translate( + 'xpack.cases.connectors.serviceNow.alertFieldEnabledText', + { + defaultMessage: 'Yes', + } +); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/types.ts b/x-pack/plugins/cases/public/components/connectors/servicenow/types.ts similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/types.ts rename to x-pack/plugins/cases/public/components/connectors/servicenow/types.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/use_get_choices.test.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/use_get_choices.test.tsx rename to x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx index 2492fbaaf5a83..9f88da9f35eb5 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/use_get_choices.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx @@ -7,14 +7,14 @@ import { renderHook } from '@testing-library/react-hooks'; -import { useKibana } from '../../../../common/lib/kibana'; -import { ActionConnector } from '../../../containers/types'; +import { useKibana } from '../../../common/lib/kibana'; +import { ActionConnector } from '../../../../common'; import { choices } from '../mock'; import { useGetChoices, UseGetChoices, UseGetChoicesProps } from './use_get_choices'; import * as api from './api'; jest.mock('./api'); -jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../common/lib/kibana'); const useKibanaMock = useKibana as jest.Mocked; const onSuccess = jest.fn(); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/use_get_choices.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/use_get_choices.tsx rename to x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.tsx index a979f96d84ab2..4edf740a60011 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/use_get_choices.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, ToastsApi } from 'kibana/public'; -import { ActionConnector } from '../../../containers/types'; +import { ActionConnector } from '../../../../common'; import { getChoices } from './api'; import { Choice } from './types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/types.ts b/x-pack/plugins/cases/public/components/connectors/types.ts similarity index 95% rename from x-pack/plugins/security_solution/public/cases/components/connectors/types.ts rename to x-pack/plugins/cases/public/components/connectors/types.ts index 11452b966670b..fc2f66d331700 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/types.ts +++ b/x-pack/plugins/cases/public/components/connectors/types.ts @@ -12,9 +12,9 @@ import { CaseField, ActionConnector, ConnectorTypeFields, -} from '../../../../../cases/common/api'; +} from '../../../common'; -export { ThirdPartyField as AllThirdPartyFields } from '../../../../../cases/common/api'; +export { ThirdPartyField as AllThirdPartyFields } from '../../../common'; export type CaseActionConnector = ActionConnector; export interface ThirdPartyField { diff --git a/x-pack/plugins/security_solution/public/cases/components/create/connector.test.tsx b/x-pack/plugins/cases/public/components/create/connector.test.tsx similarity index 76% rename from x-pack/plugins/security_solution/public/cases/components/create/connector.test.tsx rename to x-pack/plugins/cases/public/components/create/connector.test.tsx index 9c5a4a0784af1..9eb475f54221d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/connector.test.tsx +++ b/x-pack/plugins/cases/public/components/create/connector.test.tsx @@ -10,17 +10,16 @@ import { mount } from 'enzyme'; import { act, waitFor } from '@testing-library/react'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -import { useForm, Form, FormHook } from '../../../shared_imports'; +import { useForm, Form, FormHook } from '../../common/shared_imports'; import { connectorsMock } from '../../containers/mock'; import { Connector } from './connector'; -import { useConnectors } from '../../containers/configure/use_connectors'; 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 { schema, FormProps } from './schema'; -jest.mock('../../../common/lib/kibana', () => { +jest.mock('../../common/lib/kibana', () => { return { useKibana: () => ({ services: { @@ -30,12 +29,11 @@ jest.mock('../../../common/lib/kibana', () => { }), }; }); -jest.mock('../../containers/configure/use_connectors'); + jest.mock('../connectors/resilient/use_get_incident_types'); jest.mock('../connectors/resilient/use_get_severity'); jest.mock('../connectors/servicenow/use_get_choices'); -const useConnectorsMock = useConnectors as jest.Mock; const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock; const useGetSeverityMock = useGetSeverity as jest.Mock; const useGetChoicesMock = useGetChoices as jest.Mock; @@ -55,6 +53,12 @@ const useGetChoicesResponse = { choices, }; +const defaultProps = { + connectors: connectorsMock, + isLoading: false, + isLoadingConnectors: false, +}; + describe('Connector', () => { let globalForm: FormHook; @@ -74,7 +78,6 @@ describe('Connector', () => { beforeEach(() => { jest.resetAllMocks(); - useConnectorsMock.mockReturnValue({ loading: false, connectors: connectorsMock }); useGetIncidentTypesMock.mockReturnValue(useGetIncidentTypesResponse); useGetSeverityMock.mockReturnValue(useGetSeverityResponse); useGetChoicesMock.mockReturnValue(useGetChoicesResponse); @@ -83,7 +86,7 @@ describe('Connector', () => { it('it renders', async () => { const wrapper = mount( - + ); @@ -102,36 +105,26 @@ describe('Connector', () => { }); }); - it('it is loading when fetching connectors', async () => { - useConnectorsMock.mockReturnValue({ loading: true, connectors: connectorsMock }); + it('it is disabled and loading when isLoadingConnectors=true', async () => { const wrapper = mount( - + ); expect( wrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('isLoading') ).toEqual(true); - }); - - it('it is disabled when fetching connectors', async () => { - useConnectorsMock.mockReturnValue({ loading: true, connectors: connectorsMock }); - const wrapper = mount( - - - - ); expect(wrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('disabled')).toEqual( true ); }); - it('it is disabled and loading when passing loading as true', async () => { + it('it is disabled and loading when isLoading=true', async () => { const wrapper = mount( - + ); @@ -146,16 +139,13 @@ describe('Connector', () => { it(`it should change connector`, async () => { const wrapper = mount( - + ); - await waitFor(() => { - 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'); - wrapper.update(); - }); + 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(); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx b/x-pack/plugins/cases/public/components/create/connector.tsx similarity index 81% rename from x-pack/plugins/security_solution/public/cases/components/create/connector.tsx rename to x-pack/plugins/cases/public/components/create/connector.tsx index 7912d97528cd2..9591933806946 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx +++ b/x-pack/plugins/cases/public/components/create/connector.tsx @@ -8,17 +8,18 @@ import React, { memo, useCallback } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { ConnectorTypes } from '../../../../../cases/common/api'; -import { UseField, useFormData, FieldHook, useFormContext } from '../../../shared_imports'; -import { useConnectors } from '../../containers/configure/use_connectors'; +import { ConnectorTypes } from '../../../common'; +import { UseField, useFormData, FieldHook, useFormContext } from '../../common/shared_imports'; import { ConnectorSelector } from '../connector_selector/form'; import { ConnectorFieldsForm } from '../connectors/fields_form'; -import { ActionConnector } from '../../containers/types'; +import { ActionConnector } from '../../../common'; import { getConnectorById } from '../configure_cases/utils'; import { FormProps } from './schema'; interface Props { + connectors: ActionConnector[]; isLoading: boolean; + isLoadingConnectors: boolean; hideConnectorServiceNowSir?: boolean; } @@ -55,16 +56,17 @@ const ConnectorFields = ({ ); }; -const ConnectorComponent: React.FC = ({ hideConnectorServiceNowSir = false, isLoading }) => { +const ConnectorComponent: React.FC = ({ + connectors, + hideConnectorServiceNowSir = false, + isLoading, + isLoadingConnectors, +}) => { const { getFields } = useFormContext(); - const { loading: isLoadingConnectors, connectors } = useConnectors(); - const handleConnectorChange = useCallback( - (newConnector) => { - const { fields } = getFields(); - fields.setValue(null); - }, - [getFields] - ); + const handleConnectorChange = useCallback(() => { + const { fields } = getFields(); + fields.setValue(null); + }, [getFields]); return ( diff --git a/x-pack/plugins/security_solution/public/cases/components/create/description.test.tsx b/x-pack/plugins/cases/public/components/create/description.test.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/cases/components/create/description.test.tsx rename to x-pack/plugins/cases/public/components/create/description.test.tsx index 7d7b5278bf8a7..fcd1f82d64a53 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/description.test.tsx +++ b/x-pack/plugins/cases/public/components/create/description.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { act } from '@testing-library/react'; -import { useForm, Form, FormHook } from '../../../shared_imports'; +import { useForm, Form, FormHook } from '../../common/shared_imports'; import { Description } from './description'; import { schema, FormProps } from './schema'; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/description.tsx b/x-pack/plugins/cases/public/components/create/description.tsx similarity index 84% rename from x-pack/plugins/security_solution/public/cases/components/create/description.tsx rename to x-pack/plugins/cases/public/components/create/description.tsx index 0191dfdb929e5..0a7102cff1ad5 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/description.tsx +++ b/x-pack/plugins/cases/public/components/create/description.tsx @@ -6,9 +6,8 @@ */ import React, { memo } from 'react'; -import { MarkdownEditorForm } from '../../../common/components/markdown_editor/eui_form'; -import { UseField } from '../../../shared_imports'; - +import { MarkdownEditorForm } from '../markdown_editor'; +import { UseField } from '../../common/shared_imports'; interface Props { isLoading: boolean; } diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.test.tsx b/x-pack/plugins/cases/public/components/create/flyout.test.tsx similarity index 68% rename from x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.test.tsx rename to x-pack/plugins/cases/public/components/create/flyout.test.tsx index 08fca0cc6e009..5187029ab60c7 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.test.tsx +++ b/x-pack/plugins/cases/public/components/create/flyout.test.tsx @@ -5,13 +5,11 @@ * 2.0. */ -/* eslint-disable react/display-name */ import React, { ReactNode } from 'react'; import { mount } from 'enzyme'; -import '../../../common/mock/match_media'; -import { CreateCaseModal } from './create_case_modal'; -import { TestProviders } from '../../../common/mock'; +import { CreateCaseFlyout } from './flyout'; +import { TestProviders } from '../../common/mock'; jest.mock('../create/form_context', () => { return { @@ -56,15 +54,14 @@ jest.mock('../create/submit_button', () => { }; }); -const onCloseCaseModal = jest.fn(); +const onCloseFlyout = jest.fn(); const onSuccess = jest.fn(); const defaultProps = { - isModalOpen: true, - onCloseCaseModal, + onCloseFlyout, onSuccess, }; -describe('CreateCaseModal', () => { +describe('CreateCaseFlyout', () => { beforeEach(() => { jest.resetAllMocks(); }); @@ -72,38 +69,28 @@ describe('CreateCaseModal', () => { it('renders', () => { const wrapper = mount( - + ); - expect(wrapper.find(`[data-test-subj='all-cases-modal']`).exists()).toBeTruthy(); - }); - - it('it does not render the modal isModalOpen=false ', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find(`[data-test-subj='all-cases-modal']`).exists()).toBeFalsy(); + expect(wrapper.find(`[data-test-subj='create-case-flyout']`).exists()).toBeTruthy(); }); it('Closing modal calls onCloseCaseModal', () => { const wrapper = mount( - + ); - wrapper.find('.euiModal__closeIcon').first().simulate('click'); - expect(onCloseCaseModal).toBeCalled(); + wrapper.find('.euiFlyout__closeButton').first().simulate('click'); + expect(onCloseFlyout).toBeCalled(); }); it('pass the correct props to FormContext component', () => { const wrapper = mount( - + ); @@ -118,7 +105,7 @@ describe('CreateCaseModal', () => { it('onSuccess called when creating a case', () => { const wrapper = mount( - + ); diff --git a/x-pack/plugins/cases/public/components/create/flyout.tsx b/x-pack/plugins/cases/public/components/create/flyout.tsx new file mode 100644 index 0000000000000..8ed09865e9eab --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/flyout.tsx @@ -0,0 +1,71 @@ +/* + * 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 styled from 'styled-components'; +import { EuiFlyout, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui'; + +import { FormContext } from '../create/form_context'; +import { CreateCaseForm } from '../create/form'; +import { SubmitCaseButton } from '../create/submit_button'; +import { Case } from '../../containers/types'; +import * as i18n from '../../common/translations'; + +export interface CreateCaseModalProps { + onCloseFlyout: () => void; + onSuccess: (theCase: Case) => Promise; + afterCaseCreated?: (theCase: Case) => Promise; +} + +const Container = styled.div` + ${({ theme }) => ` + margin-top: ${theme.eui.euiSize}; + text-align: right; + `} +`; + +const StyledFlyout = styled(EuiFlyout)` + ${({ theme }) => ` + z-index: ${theme.eui.euiZModal}; + `} +`; + +// Adding bottom padding because timeline's +// bottom bar gonna hide the submit button. +const FormWrapper = styled.div` + padding-bottom: 50px; +`; + +const CreateCaseFlyoutComponent: React.FC = ({ + onSuccess, + afterCaseCreated, + onCloseFlyout, +}) => { + return ( + + + +

    {i18n.CREATE_TITLE}

    +
    +
    + + + + + + + + + + +
    + ); +}; + +export const CreateCaseFlyout = memo(CreateCaseFlyoutComponent); + +CreateCaseFlyout.displayName = 'CreateCaseFlyout'; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form.test.tsx b/x-pack/plugins/cases/public/components/create/form.test.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/create/form.test.tsx rename to x-pack/plugins/cases/public/components/create/form.test.tsx index 029965444929b..9e59924bdf483 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/form.test.tsx +++ b/x-pack/plugins/cases/public/components/create/form.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { act, waitFor } from '@testing-library/react'; -import { useForm, Form, FormHook } from '../../../shared_imports'; +import { useForm, Form, FormHook } from '../../common/shared_imports'; import { useGetTags } from '../../containers/use_get_tags'; import { useConnectors } from '../../containers/configure/use_connectors'; import { connectorsMock } from '../../containers/mock'; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form.tsx b/x-pack/plugins/cases/public/components/create/form.tsx similarity index 84% rename from x-pack/plugins/security_solution/public/cases/components/create/form.tsx rename to x-pack/plugins/cases/public/components/create/form.tsx index 09518c6f6adc1..83f759947ba65 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/form.tsx +++ b/x-pack/plugins/cases/public/components/create/form.tsx @@ -9,7 +9,7 @@ import React, { useMemo } from 'react'; import { EuiLoadingSpinner, EuiSteps } from '@elastic/eui'; import styled, { css } from 'styled-components'; -import { useFormContext } from '../../../shared_imports'; +import { useFormContext } from '../../common/shared_imports'; import { Title } from './title'; import { Description } from './description'; @@ -17,6 +17,7 @@ import { Tags } from './tags'; import { Connector } from './connector'; import * as i18n from './translations'; import { SyncAlertsToggle } from './sync_alerts_toggle'; +import { ActionConnector } from '../../../common'; interface ContainerProps { big?: boolean; @@ -36,12 +37,19 @@ const MySpinner = styled(EuiLoadingSpinner)` `; interface Props { + connectors?: ActionConnector[]; hideConnectorServiceNowSir?: boolean; + isLoadingConnectors?: boolean; withSteps?: boolean; } - +const empty: ActionConnector[] = []; export const CreateCaseForm: React.FC = React.memo( - ({ hideConnectorServiceNowSir = false, withSteps = true }) => { + ({ + connectors = empty, + isLoadingConnectors = false, + hideConnectorServiceNowSir = false, + withSteps = true, + }) => { const { isSubmitting } = useFormContext(); const firstStep = useMemo( @@ -80,13 +88,15 @@ export const CreateCaseForm: React.FC = React.memo( children: ( ), }), - [hideConnectorServiceNowSir, isSubmitting] + [connectors, hideConnectorServiceNowSir, isLoadingConnectors, isSubmitting] ); const allSteps = useMemo(() => [firstStep, secondStep, thirdStep], [ diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form_context.test.tsx b/x-pack/plugins/cases/public/components/create/form_context.test.tsx similarity index 94% rename from x-pack/plugins/security_solution/public/cases/components/create/form_context.test.tsx rename to x-pack/plugins/cases/public/components/create/form_context.test.tsx index 99626c4cfb797..9a8671c7fc571 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/form_context.test.tsx +++ b/x-pack/plugins/cases/public/components/create/form_context.test.tsx @@ -10,9 +10,10 @@ import { mount, ReactWrapper } from 'enzyme'; import { act, waitFor } from '@testing-library/react'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -import { ConnectorTypes } from '../../../../../cases/common/api'; -import { TestProviders } from '../../../common/mock'; +import { ConnectorTypes } from '../../../common'; +import { TestProviders } from '../../common/mock'; import { usePostCase } from '../../containers/use_post_case'; +import { usePostComment } from '../../containers/use_post_comment'; import { useGetTags } from '../../containers/use_get_tags'; import { useConnectors } from '../../containers/configure/use_connectors'; import { useCaseConfigure } from '../../containers/configure/use_configure'; @@ -41,6 +42,7 @@ import { usePostPushToService } from '../../containers/use_post_push_to_service' const sampleId = 'case-id'; jest.mock('../../containers/use_post_case'); +jest.mock('../../containers/use_post_comment'); jest.mock('../../containers/use_post_push_to_service'); jest.mock('../../containers/use_get_tags'); jest.mock('../../containers/configure/use_connectors'); @@ -56,6 +58,7 @@ jest.mock('../connectors/servicenow/use_get_choices'); const useConnectorsMock = useConnectors as jest.Mock; const useCaseConfigureMock = useCaseConfigure as jest.Mock; const usePostCaseMock = usePostCase as jest.Mock; +const usePostCommentMock = usePostComment as jest.Mock; const usePostPushToServiceMock = usePostPushToService as jest.Mock; const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock; const useGetSeverityMock = useGetSeverity as jest.Mock; @@ -71,6 +74,11 @@ const defaultPostCase = { postCase, }; +const defaultCreateCaseForm = { + isLoadingConnectors: false, + connectors: [], +}; + const defaultPostPushToService = { isLoading: false, isError: false, @@ -99,14 +107,15 @@ describe('Create case', () => { const fetchTags = jest.fn(); const onFormSubmitSuccess = jest.fn(); const afterCaseCreated = jest.fn(); + const postComment = jest.fn(); - beforeEach(() => { - jest.resetAllMocks(); + beforeAll(() => { postCase.mockResolvedValue({ id: sampleId, ...sampleData, }); usePostCaseMock.mockImplementation(() => defaultPostCase); + usePostCommentMock.mockImplementation(() => ({ postComment })); usePostPushToServiceMock.mockImplementation(() => defaultPostPushToService); useConnectorsMock.mockReturnValue(sampleConnectorData); useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse); @@ -121,13 +130,16 @@ describe('Create case', () => { fetchTags, })); }); + beforeEach(() => { + jest.clearAllMocks(); + }); describe('Step 1 - Case Fields', () => { it('it renders', async () => { const wrapper = mount( - + @@ -151,7 +163,7 @@ describe('Create case', () => { const wrapper = mount( - + @@ -171,7 +183,7 @@ describe('Create case', () => { const wrapper = mount( - + @@ -206,7 +218,7 @@ describe('Create case', () => { const wrapper = mount( - + @@ -256,7 +268,7 @@ describe('Create case', () => { const wrapper = mount( - + @@ -281,7 +293,7 @@ describe('Create case', () => { const wrapper = mount( - + @@ -348,7 +360,7 @@ describe('Create case', () => { const wrapper = mount( - + @@ -416,7 +428,7 @@ describe('Create case', () => { const wrapper = mount( - + @@ -506,7 +518,7 @@ describe('Create case', () => { const wrapper = mount( - + @@ -604,7 +616,7 @@ describe('Create case', () => { const wrapper = mount( - + @@ -622,10 +634,13 @@ describe('Create case', () => { wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); await waitFor(() => { - expect(afterCaseCreated).toHaveBeenCalledWith({ - id: sampleId, - ...sampleData, - }); + expect(afterCaseCreated).toHaveBeenCalledWith( + { + id: sampleId, + ...sampleData, + }, + postComment + ); }); }); @@ -638,7 +653,7 @@ describe('Create case', () => { const wrapper = mount( - + diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx b/x-pack/plugins/cases/public/components/create/form_context.tsx similarity index 75% rename from x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx rename to x-pack/plugins/cases/public/components/create/form_context.tsx index b575dfe42f074..7ca3fe4b88c8d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx +++ b/x-pack/plugins/cases/public/components/create/form_context.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useMemo } from 'react'; import { schema, FormProps } from './schema'; -import { Form, useForm } from '../../../shared_imports'; +import { Form, useForm } from '../../common/shared_imports'; import { getConnectorById, getNoneConnector, @@ -19,7 +19,8 @@ import { usePostPushToService } from '../../containers/use_post_push_to_service' import { useConnectors } from '../../containers/configure/use_connectors'; import { useCaseConfigure } from '../../containers/configure/use_configure'; import { Case } from '../../containers/types'; -import { CaseType, ConnectorTypes } from '../../../../../cases/common/api'; +import { CaseType, ConnectorTypes } from '../../../common'; +import { UsePostComment, usePostComment } from '../../containers/use_post_comment'; const initialCaseValue: FormProps = { description: '', @@ -31,8 +32,9 @@ const initialCaseValue: FormProps = { }; interface Props { - afterCaseCreated?: (theCase: Case) => Promise; + afterCaseCreated?: (theCase: Case, postComment: UsePostComment['postComment']) => Promise; caseType?: CaseType; + children?: JSX.Element | JSX.Element[]; hideConnectorServiceNowSir?: boolean; onSuccess?: (theCase: Case) => Promise; } @@ -44,9 +46,10 @@ export const FormContext: React.FC = ({ hideConnectorServiceNowSir, onSuccess, }) => { - const { connectors } = useConnectors(); + const { connectors, loading: isLoadingConnectors } = useConnectors(); const { connector: configurationConnector } = useCaseConfigure(); const { postCase } = usePostCase(); + const { postComment } = usePostComment(); const { pushCaseToExternalService } = usePostPushToService(); const connectorId = useMemo(() => { @@ -86,7 +89,7 @@ export const FormContext: React.FC = ({ }); if (afterCaseCreated && updatedCase) { - await afterCaseCreated(updatedCase); + await afterCaseCreated(updatedCase, postComment); } if (updatedCase?.id && dataConnectorId !== 'none') { @@ -101,7 +104,15 @@ export const FormContext: React.FC = ({ } } }, - [caseType, connectors, postCase, onSuccess, pushCaseToExternalService, afterCaseCreated] + [ + caseType, + connectors, + postCase, + postComment, + onSuccess, + pushCaseToExternalService, + afterCaseCreated, + ] ); const { form } = useForm({ @@ -114,7 +125,16 @@ export const FormContext: React.FC = ({ // Set the selected connector to the configuration connector useEffect(() => setFieldValue('connectorId', connectorId), [connectorId, setFieldValue]); - return
    {children}
    ; + const childrenWithExtraProp = useMemo( + () => + children != null + ? React.Children.map(children, (child: React.ReactElement) => + React.cloneElement(child, { connectors, isLoadingConnectors }) + ) + : null, + [children, connectors, isLoadingConnectors] + ); + return
    {childrenWithExtraProp}
    ; }; FormContext.displayName = 'FormContext'; diff --git a/x-pack/plugins/cases/public/components/create/index.test.tsx b/x-pack/plugins/cases/public/components/create/index.test.tsx new file mode 100644 index 0000000000000..e82af8edc6337 --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/index.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 { mount, ReactWrapper } from 'enzyme'; +import { act, waitFor } from '@testing-library/react'; +import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; + +import { TestProviders } from '../../common/mock'; +import { useGetTags } from '../../containers/use_get_tags'; +import { useConnectors } from '../../containers/configure/use_connectors'; +import { useCaseConfigure } from '../../containers/configure/use_configure'; +import { useGetIncidentTypes } from '../connectors/resilient/use_get_incident_types'; +import { useGetSeverity } from '../connectors/resilient/use_get_severity'; +import { useGetIssueTypes } from '../connectors/jira/use_get_issue_types'; +import { useGetFieldsByIssueType } from '../connectors/jira/use_get_fields_by_issue_type'; +import { useCaseConfigureResponse } from '../configure_cases/__mock__'; +import { + sampleConnectorData, + sampleData, + sampleTags, + useGetIncidentTypesResponse, + useGetSeverityResponse, + useGetIssueTypesResponse, + useGetFieldsByIssueTypeResponse, +} from './mock'; +import { CreateCase } from '.'; + +jest.mock('../../containers/api'); +jest.mock('../../containers/use_get_tags'); +jest.mock('../../containers/configure/use_connectors'); +jest.mock('../../containers/configure/use_configure'); +jest.mock('../connectors/resilient/use_get_incident_types'); +jest.mock('../connectors/resilient/use_get_severity'); +jest.mock('../connectors/jira/use_get_issue_types'); +jest.mock('../connectors/jira/use_get_fields_by_issue_type'); +jest.mock('../connectors/jira/use_get_single_issue'); +jest.mock('../connectors/jira/use_get_issues'); + +const useConnectorsMock = useConnectors as jest.Mock; +const useCaseConfigureMock = useCaseConfigure as jest.Mock; +const useGetTagsMock = useGetTags as jest.Mock; +const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock; +const useGetSeverityMock = useGetSeverity as jest.Mock; +const useGetIssueTypesMock = useGetIssueTypes as jest.Mock; +const useGetFieldsByIssueTypeMock = useGetFieldsByIssueType as jest.Mock; +const fetchTags = jest.fn(); + +const fillForm = (wrapper: ReactWrapper) => { + wrapper + .find(`[data-test-subj="caseTitle"] input`) + .first() + .simulate('change', { target: { value: sampleData.title } }); + + wrapper + .find(`[data-test-subj="caseDescription"] textarea`) + .first() + .simulate('change', { target: { value: sampleData.description } }); + + act(() => { + ((wrapper.find(EuiComboBox).props() as unknown) as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + }).onChange(sampleTags.map((tag) => ({ label: tag }))); + }); +}; + +const defaultProps = { + onCancel: jest.fn(), + onSuccess: jest.fn(), +}; + +describe('CreateCase case', () => { + beforeEach(() => { + jest.resetAllMocks(); + useConnectorsMock.mockReturnValue(sampleConnectorData); + useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse); + useGetIncidentTypesMock.mockReturnValue(useGetIncidentTypesResponse); + useGetSeverityMock.mockReturnValue(useGetSeverityResponse); + useGetIssueTypesMock.mockReturnValue(useGetIssueTypesResponse); + useGetFieldsByIssueTypeMock.mockReturnValue(useGetFieldsByIssueTypeResponse); + useGetTagsMock.mockImplementation(() => ({ + tags: sampleTags, + fetchTags, + })); + }); + + it('it renders', async () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="create-case-submit"]`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="create-case-cancel"]`).exists()).toBeTruthy(); + }); + + it('should call cancel on cancel click', async () => { + const wrapper = mount( + + + + ); + + wrapper.find(`[data-test-subj="create-case-cancel"]`).first().simulate('click'); + expect(defaultProps.onCancel).toHaveBeenCalled(); + }); + + it('should redirect to new case when posting the case', async () => { + const wrapper = mount( + + + + ); + + fillForm(wrapper); + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + await waitFor(() => { + expect(defaultProps.onSuccess).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/create/index.tsx b/x-pack/plugins/cases/public/components/create/index.tsx new file mode 100644 index 0000000000000..a1de4d9730b9f --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/index.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 { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import styled from 'styled-components'; + +import { Field, getUseField } from '../../common/shared_imports'; +import * as i18n from './translations'; +import { CreateCaseForm } from './form'; +import { FormContext } from './form_context'; +import { SubmitCaseButton } from './submit_button'; +import { Case } from '../../containers/types'; +import { CaseType } from '../../../common/api/cases'; +import { CasesTimelineIntegration, CasesTimelineIntegrationProvider } from '../timeline_context'; +import { fieldName as descriptionFieldName } from './description'; +import { InsertTimeline } from '../insert_timeline'; +import { UsePostComment } from '../../containers/use_post_comment'; + +export const CommonUseField = getUseField({ component: Field }); + +const Container = styled.div` + ${({ theme }) => ` + margin-top: ${theme.eui.euiSize}; + `} +`; + +export interface CreateCaseProps { + afterCaseCreated?: (theCase: Case, postComment: UsePostComment['postComment']) => Promise; + caseType?: CaseType; + hideConnectorServiceNowSir?: boolean; + onCancel: () => void; + onSuccess: (theCase: Case) => Promise; + timelineIntegration?: CasesTimelineIntegration; + withSteps?: boolean; +} + +export const CreateCase = ({ + afterCaseCreated, + caseType, + hideConnectorServiceNowSir, + onCancel, + onSuccess, + timelineIntegration, + withSteps, +}: CreateCaseProps) => ( + + + + + + + + {i18n.CANCEL} + + + + + + + + + + +); + +// eslint-disable-next-line import/no-default-export +export { CreateCase as default }; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/mock.ts b/x-pack/plugins/cases/public/components/create/mock.ts similarity index 92% rename from x-pack/plugins/security_solution/public/cases/components/create/mock.ts rename to x-pack/plugins/cases/public/components/create/mock.ts index 6e17be8d53e5a..eb40fa097d3cc 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/mock.ts +++ b/x-pack/plugins/cases/public/components/create/mock.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { CasePostRequest, CaseType } from '../../../../../cases/common/api'; -import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; +import { CasePostRequest, CaseType, ConnectorTypes } from '../../../common'; import { choices } from '../connectors/mock'; export const sampleTags = ['coke', 'pepsi']; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.test.tsx b/x-pack/plugins/cases/public/components/create/optional_field_label/index.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.test.tsx rename to x-pack/plugins/cases/public/components/create/optional_field_label/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.tsx b/x-pack/plugins/cases/public/components/create/optional_field_label/index.tsx similarity index 89% rename from x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.tsx rename to x-pack/plugins/cases/public/components/create/optional_field_label/index.tsx index f67090a1cd41c..ea994b2219961 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.tsx +++ b/x-pack/plugins/cases/public/components/create/optional_field_label/index.tsx @@ -8,7 +8,7 @@ import { EuiText } from '@elastic/eui'; import React from 'react'; -import * as i18n from '../../../translations'; +import * as i18n from '../../../common/translations'; export const OptionalFieldLabel = ( diff --git a/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx b/x-pack/plugins/cases/public/components/create/schema.tsx similarity index 92% rename from x-pack/plugins/security_solution/public/cases/components/create/schema.tsx rename to x-pack/plugins/cases/public/components/create/schema.tsx index b069a484d314c..7ca1e2e061545 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx +++ b/x-pack/plugins/cases/public/components/create/schema.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { CasePostRequest, ConnectorTypeFields } from '../../../../../cases/common/api'; -import { FIELD_TYPES, fieldValidators, FormSchema } from '../../../shared_imports'; +import { CasePostRequest, ConnectorTypeFields } from '../../../common'; +import { FIELD_TYPES, fieldValidators, FormSchema } from '../../common/shared_imports'; import * as i18n from './translations'; import { OptionalFieldLabel } from './optional_field_label'; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/submit_button.test.tsx b/x-pack/plugins/cases/public/components/create/submit_button.test.tsx similarity index 77% rename from x-pack/plugins/security_solution/public/cases/components/create/submit_button.test.tsx rename to x-pack/plugins/cases/public/components/create/submit_button.test.tsx index ab98e75b6058e..62279500616ee 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/submit_button.test.tsx +++ b/x-pack/plugins/cases/public/components/create/submit_button.test.tsx @@ -7,9 +7,9 @@ import React from 'react'; import { mount } from 'enzyme'; -import { act, waitFor } from '@testing-library/react'; +import { waitFor } from '@testing-library/react'; -import { useForm, Form } from '../../../shared_imports'; +import { useForm, Form } from '../../common/shared_imports'; import { SubmitCaseButton } from './submit_button'; import { schema, FormProps } from './schema'; @@ -29,7 +29,7 @@ describe('SubmitCaseButton', () => { }; beforeEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); }); it('it renders', async () => { @@ -48,11 +48,7 @@ describe('SubmitCaseButton', () => { ); - - await act(async () => { - wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); - }); - + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); await waitFor(() => expect(onSubmit).toBeCalled()); }); @@ -63,12 +59,12 @@ describe('SubmitCaseButton', () => { ); - await waitFor(() => { - wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + await waitFor(() => expect( wrapper.find(`[data-test-subj="create-case-submit"]`).first().prop('isDisabled') - ).toBeTruthy(); - }); + ).toBeTruthy() + ); }); it('it is loading when submitting', async () => { @@ -78,11 +74,11 @@ describe('SubmitCaseButton', () => { ); - await waitFor(() => { - wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + await waitFor(() => expect( wrapper.find(`[data-test-subj="create-case-submit"]`).first().prop('isLoading') - ).toBeTruthy(); - }); + ).toBeTruthy() + ); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/submit_button.tsx b/x-pack/plugins/cases/public/components/create/submit_button.tsx similarity index 92% rename from x-pack/plugins/security_solution/public/cases/components/create/submit_button.tsx rename to x-pack/plugins/cases/public/components/create/submit_button.tsx index de2b2d410e60e..b5e58517e6ec1 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/submit_button.tsx +++ b/x-pack/plugins/cases/public/components/create/submit_button.tsx @@ -8,7 +8,7 @@ import React, { memo } from 'react'; import { EuiButton } from '@elastic/eui'; -import { useFormContext } from '../../../shared_imports'; +import { useFormContext } from '../../common/shared_imports'; import * as i18n from './translations'; const SubmitCaseButtonComponent: React.FC = () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/create/sync_alerts_toggle.test.tsx b/x-pack/plugins/cases/public/components/create/sync_alerts_toggle.test.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/create/sync_alerts_toggle.test.tsx rename to x-pack/plugins/cases/public/components/create/sync_alerts_toggle.test.tsx index eadec1525ed90..b4a37f0abb518 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/sync_alerts_toggle.test.tsx +++ b/x-pack/plugins/cases/public/components/create/sync_alerts_toggle.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { waitFor } from '@testing-library/react'; -import { useForm, Form, FormHook } from '../../../shared_imports'; +import { useForm, Form, FormHook } from '../../common/shared_imports'; import { SyncAlertsToggle } from './sync_alerts_toggle'; import { schema, FormProps } from './schema'; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/sync_alerts_toggle.tsx b/x-pack/plugins/cases/public/components/create/sync_alerts_toggle.tsx similarity index 93% rename from x-pack/plugins/security_solution/public/cases/components/create/sync_alerts_toggle.tsx rename to x-pack/plugins/cases/public/components/create/sync_alerts_toggle.tsx index 2ab5b8f5375cd..bed8e6d18f5e3 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/sync_alerts_toggle.tsx +++ b/x-pack/plugins/cases/public/components/create/sync_alerts_toggle.tsx @@ -6,7 +6,7 @@ */ import React, { memo } from 'react'; -import { Field, getUseField, useFormData } from '../../../shared_imports'; +import { Field, getUseField, useFormData } from '../../common/shared_imports'; import * as i18n from './translations'; const CommonUseField = getUseField({ component: Field }); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/tags.test.tsx b/x-pack/plugins/cases/public/components/create/tags.test.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/create/tags.test.tsx rename to x-pack/plugins/cases/public/components/create/tags.test.tsx index c723d456afe73..2eddb83dcac29 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/tags.test.tsx +++ b/x-pack/plugins/cases/public/components/create/tags.test.tsx @@ -10,7 +10,7 @@ import { mount } from 'enzyme'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; import { waitFor } from '@testing-library/react'; -import { useForm, Form, FormHook } from '../../../shared_imports'; +import { useForm, Form, FormHook } from '../../common/shared_imports'; import { useGetTags } from '../../containers/use_get_tags'; import { Tags } from './tags'; import { schema, FormProps } from './schema'; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/tags.tsx b/x-pack/plugins/cases/public/components/create/tags.tsx similarity index 94% rename from x-pack/plugins/security_solution/public/cases/components/create/tags.tsx rename to x-pack/plugins/cases/public/components/create/tags.tsx index fd0372e2f8125..ac0b67529e15a 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/tags.tsx +++ b/x-pack/plugins/cases/public/components/create/tags.tsx @@ -7,7 +7,7 @@ import React, { memo, useMemo } from 'react'; -import { Field, getUseField } from '../../../shared_imports'; +import { Field, getUseField } from '../../common/shared_imports'; import { useGetTags } from '../../containers/use_get_tags'; const CommonUseField = getUseField({ component: Field }); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/title.test.tsx b/x-pack/plugins/cases/public/components/create/title.test.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/create/title.test.tsx rename to x-pack/plugins/cases/public/components/create/title.test.tsx index 2ac14ccd1b254..a41d5afbb4038 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/title.test.tsx +++ b/x-pack/plugins/cases/public/components/create/title.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { act } from '@testing-library/react'; -import { useForm, Form, FormHook } from '../../../shared_imports'; +import { useForm, Form, FormHook } from '../../common/shared_imports'; import { Title } from './title'; import { schema, FormProps } from './schema'; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/title.tsx b/x-pack/plugins/cases/public/components/create/title.tsx similarity index 92% rename from x-pack/plugins/security_solution/public/cases/components/create/title.tsx rename to x-pack/plugins/cases/public/components/create/title.tsx index 95f705791e704..cc51a805b5c38 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/title.tsx +++ b/x-pack/plugins/cases/public/components/create/title.tsx @@ -6,7 +6,7 @@ */ import React, { memo } from 'react'; -import { Field, getUseField } from '../../../shared_imports'; +import { Field, getUseField } from '../../common/shared_imports'; const CommonUseField = getUseField({ component: Field }); diff --git a/x-pack/plugins/cases/public/components/create/translations.ts b/x-pack/plugins/cases/public/components/create/translations.ts new file mode 100644 index 0000000000000..7e0f7e5a6b9d5 --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/translations.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 { i18n } from '@kbn/i18n'; + +export * from '../../common/translations'; + +export const STEP_ONE_TITLE = i18n.translate('xpack.cases.create.stepOneTitle', { + defaultMessage: 'Case fields', +}); + +export const STEP_TWO_TITLE = i18n.translate('xpack.cases.create.stepTwoTitle', { + defaultMessage: 'Case settings', +}); + +export const STEP_THREE_TITLE = i18n.translate('xpack.cases.create.stepThreeTitle', { + defaultMessage: 'External Connector Fields', +}); + +export const SYNC_ALERTS_LABEL = i18n.translate('xpack.cases.create.syncAlertsLabel', { + defaultMessage: 'Sync alert status with case status', +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/edit_connector/helpers.ts b/x-pack/plugins/cases/public/components/edit_connector/helpers.ts similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/edit_connector/helpers.ts rename to x-pack/plugins/cases/public/components/edit_connector/helpers.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.test.tsx b/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/edit_connector/index.test.tsx rename to x-pack/plugins/cases/public/components/edit_connector/index.test.tsx index 113c5da5d0c0f..3b6d4bd3f33f2 100644 --- a/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.test.tsx +++ b/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx @@ -10,14 +10,12 @@ import { mount } from 'enzyme'; import { EditConnector } from './index'; import { getFormMock, useFormMock } from '../__mock__/form'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; import { connectorsMock } from '../../containers/configure/mock'; import { waitFor } from '@testing-library/react'; import { caseUserActions } from '../../containers/mock'; -jest.mock( - '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' -); +jest.mock('../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'); const onSubmit = jest.fn(); const defaultProps = { diff --git a/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.tsx b/x-pack/plugins/cases/public/components/edit_connector/index.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/edit_connector/index.tsx rename to x-pack/plugins/cases/public/components/edit_connector/index.tsx index f76adfd2a840f..56f1a77fc407e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/edit_connector/index.tsx +++ b/x-pack/plugins/cases/public/components/edit_connector/index.tsx @@ -20,10 +20,9 @@ import { import styled from 'styled-components'; import { noop } from 'lodash/fp'; -import { Form, UseField, useForm } from '../../../shared_imports'; -import { ConnectorTypeFields } from '../../../../../cases/common/api/connectors'; +import { Form, UseField, useForm } from '../../common/shared_imports'; +import { ActionConnector, ConnectorTypeFields } from '../../../common'; import { ConnectorSelector } from '../connector_selector/form'; -import { ActionConnector } from '../../../../../cases/common/api'; import { ConnectorFieldsForm } from '../connectors/fields_form'; import { getConnectorById } from '../configure_cases/utils'; import { CaseUserActions } from '../../containers/types'; diff --git a/x-pack/plugins/security_solution/public/cases/components/edit_connector/schema.tsx b/x-pack/plugins/cases/public/components/edit_connector/schema.tsx similarity index 85% rename from x-pack/plugins/security_solution/public/cases/components/edit_connector/schema.tsx rename to x-pack/plugins/cases/public/components/edit_connector/schema.tsx index f757c2b6a86c4..a12511f704be2 100644 --- a/x-pack/plugins/security_solution/public/cases/components/edit_connector/schema.tsx +++ b/x-pack/plugins/cases/public/components/edit_connector/schema.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { FormSchema, FIELD_TYPES } from '../../../shared_imports'; +import { FormSchema, FIELD_TYPES } from '../../common/shared_imports'; export interface FormProps { connectorId: string; diff --git a/x-pack/plugins/security_solution/public/cases/components/edit_connector/translations.ts b/x-pack/plugins/cases/public/components/edit_connector/translations.ts similarity index 79% rename from x-pack/plugins/security_solution/public/cases/components/edit_connector/translations.ts rename to x-pack/plugins/cases/public/components/edit_connector/translations.ts index 12fa0d1855062..ab69c94321703 100644 --- a/x-pack/plugins/security_solution/public/cases/components/edit_connector/translations.ts +++ b/x-pack/plugins/cases/public/components/edit_connector/translations.ts @@ -7,10 +7,10 @@ import { i18n } from '@kbn/i18n'; -export * from '../../translations'; +export * from '../../common/translations'; export const EDIT_CONNECTOR_ARIA = i18n.translate( - 'xpack.securitySolution.cases.editConnector.editConnectorLinkAria', + 'xpack.cases.editConnector.editConnectorLinkAria', { defaultMessage: 'click to edit connector', } diff --git a/x-pack/plugins/cases/public/components/empty_value/__snapshots__/empty_value.test.tsx.snap b/x-pack/plugins/cases/public/components/empty_value/__snapshots__/empty_value.test.tsx.snap new file mode 100644 index 0000000000000..142ed7a0d7175 --- /dev/null +++ b/x-pack/plugins/cases/public/components/empty_value/__snapshots__/empty_value.test.tsx.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EmptyValue it renders against snapshot 1`] = ` +

    + (Empty String) +

    +`; diff --git a/x-pack/plugins/cases/public/components/empty_value/empty_value.test.tsx b/x-pack/plugins/cases/public/components/empty_value/empty_value.test.tsx new file mode 100644 index 0000000000000..e1dfc71867f6e --- /dev/null +++ b/x-pack/plugins/cases/public/components/empty_value/empty_value.test.tsx @@ -0,0 +1,166 @@ +/* + * 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 { mount, shallow } from 'enzyme'; +import React from 'react'; +import { ThemeProvider } from 'styled-components'; +import { mountWithIntl } from '@kbn/test/jest'; + +import { + defaultToEmptyTag, + getEmptyString, + getEmptyStringTag, + getEmptyTagValue, + getEmptyValue, + getOrEmptyTag, +} from '.'; +import { getMockTheme } from '../../common/lib/kibana/kibana_react.mock'; + +describe('EmptyValue', () => { + const mockTheme = getMockTheme({ eui: { euiColorMediumShade: '#ece' } }); + + test('it renders against snapshot', () => { + const wrapper = shallow(

    {getEmptyString()}

    ); + expect(wrapper).toMatchSnapshot(); + }); + + describe('#getEmptyValue', () => { + test('should return an empty value', () => expect(getEmptyValue()).toBe('—')); + }); + + describe('#getEmptyString', () => { + test('should turn into an empty string place holder', () => { + const wrapper = mountWithIntl( + +

    {getEmptyString()}

    +
    + ); + expect(wrapper.text()).toBe('(Empty String)'); + }); + }); + + describe('#getEmptyTagValue', () => { + const wrapper = mount( + +

    {getEmptyTagValue()}

    +
    + ); + test('should return an empty tag value', () => expect(wrapper.text()).toBe('—')); + }); + + describe('#getEmptyStringTag', () => { + test('should turn into an span that has length of 1', () => { + const wrapper = mountWithIntl( + +

    {getEmptyStringTag()}

    +
    + ); + expect(wrapper.find('span')).toHaveLength(1); + }); + + test('should turn into an empty string tag place holder', () => { + const wrapper = mountWithIntl( + +

    {getEmptyStringTag()}

    +
    + ); + expect(wrapper.text()).toBe(getEmptyString()); + }); + }); + + describe('#defaultToEmptyTag', () => { + test('should default to an empty value when a value is null', () => { + const wrapper = mount( + +

    {defaultToEmptyTag(null)}

    +
    + ); + expect(wrapper.text()).toBe(getEmptyValue()); + }); + + test('should default to an empty value when a value is undefined', () => { + const wrapper = mount( + +

    {defaultToEmptyTag(undefined)}

    +
    + ); + expect(wrapper.text()).toBe(getEmptyValue()); + }); + + test('should return a deep path value', () => { + const test = { + a: { + b: { + c: 1, + }, + }, + }; + const wrapper = mount(

    {defaultToEmptyTag(test.a.b.c)}

    ); + expect(wrapper.text()).toBe('1'); + }); + }); + + describe('#getOrEmptyTag', () => { + test('should default empty value when a deep rooted value is null', () => { + const test = { + a: { + b: { + c: null, + }, + }, + }; + const wrapper = mount( + +

    {getOrEmptyTag('a.b.c', test)}

    +
    + ); + expect(wrapper.text()).toBe(getEmptyValue()); + }); + + test('should default empty value when a deep rooted value is undefined', () => { + const test = { + a: { + b: { + c: undefined, + }, + }, + }; + const wrapper = mount( + +

    {getOrEmptyTag('a.b.c', test)}

    +
    + ); + expect(wrapper.text()).toBe(getEmptyValue()); + }); + + test('should default empty value when a deep rooted value is missing', () => { + const test = { + a: { + b: {}, + }, + }; + const wrapper = mount( + +

    {getOrEmptyTag('a.b.c', test)}

    +
    + ); + expect(wrapper.text()).toBe(getEmptyValue()); + }); + + test('should return a deep path value', () => { + const test = { + a: { + b: { + c: 1, + }, + }, + }; + const wrapper = mount(

    {getOrEmptyTag('a.b.c', test)}

    ); + expect(wrapper.text()).toBe('1'); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/empty_value/index.tsx b/x-pack/plugins/cases/public/components/empty_value/index.tsx new file mode 100644 index 0000000000000..86efb4a78277a --- /dev/null +++ b/x-pack/plugins/cases/public/components/empty_value/index.tsx @@ -0,0 +1,49 @@ +/* + * 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 { get, isString } from 'lodash/fp'; +import React from 'react'; +import styled from 'styled-components'; + +import * as i18n from './translations'; + +const EmptyWrapper = styled.span` + color: ${(props) => props.theme.eui.euiColorMediumShade}; +`; + +EmptyWrapper.displayName = 'EmptyWrapper'; + +export const getEmptyValue = () => '—'; +export const getEmptyString = () => `(${i18n.EMPTY_STRING})`; + +export const getEmptyTagValue = () => {getEmptyValue()}; +export const getEmptyStringTag = () => {getEmptyString()}; + +export const defaultToEmptyTag = (item: T): JSX.Element => { + if (item == null) { + return getEmptyTagValue(); + } else if (isString(item) && item === '') { + return getEmptyStringTag(); + } else { + return <>{item}; + } +}; + +export const getOrEmptyTag = (path: string, item: unknown): JSX.Element => { + const text = get(path, item); + return getOrEmptyTagFromValue(text); +}; + +export const getOrEmptyTagFromValue = (value: string | number | null | undefined): JSX.Element => { + if (value == null) { + return getEmptyTagValue(); + } else if (value === '') { + return getEmptyStringTag(); + } else { + return <>{value}; + } +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/translations.ts b/x-pack/plugins/cases/public/components/empty_value/translations.ts similarity index 69% rename from x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/translations.ts rename to x-pack/plugins/cases/public/components/empty_value/translations.ts index 36db3c631100f..af04a6d404553 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/translations.ts +++ b/x-pack/plugins/cases/public/components/empty_value/translations.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -export const SELECT_CASE_TITLE = i18n.translate('xpack.securitySolution.cases.caseModal.title', { - defaultMessage: 'Select case', + +export const EMPTY_STRING = i18n.translate('xpack.cases.emptyString.emptyStringDescription', { + defaultMessage: 'Empty String', }); diff --git a/x-pack/plugins/security_solution/public/cases/components/filter_popover/index.tsx b/x-pack/plugins/cases/public/components/filter_popover/index.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/filter_popover/index.tsx rename to x-pack/plugins/cases/public/components/filter_popover/index.tsx diff --git a/x-pack/plugins/cases/public/components/formatted_date/__snapshots__/index.test.tsx.snap b/x-pack/plugins/cases/public/components/formatted_date/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..9e851ddcd7d0f --- /dev/null +++ b/x-pack/plugins/cases/public/components/formatted_date/__snapshots__/index.test.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`formatted_date PreferenceFormattedDate renders correctly against snapshot 1`] = ` + + 2019-02-25T22:27:05Z + +`; diff --git a/x-pack/plugins/cases/public/components/formatted_date/index.test.tsx b/x-pack/plugins/cases/public/components/formatted_date/index.test.tsx new file mode 100644 index 0000000000000..d54430b9f27da --- /dev/null +++ b/x-pack/plugins/cases/public/components/formatted_date/index.test.tsx @@ -0,0 +1,170 @@ +/* + * 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 { mount, shallow } from 'enzyme'; +import React from 'react'; + +import { useDateFormat, useTimeZone } from '../../common/lib/kibana'; + +import { TestProviders } from '../../common/mock'; +import { getEmptyString, getEmptyValue } from '../empty_value'; +import { PreferenceFormattedDate, FormattedDate, FormattedRelativePreferenceDate } from '.'; + +jest.mock('../../common/lib/kibana'); +const mockUseDateFormat = useDateFormat as jest.Mock; +const mockUseTimeZone = useTimeZone as jest.Mock; + +const isoDateString = '2019-02-25T22:27:05.000Z'; + +describe('formatted_date', () => { + let isoDate: Date; + + beforeEach(() => { + isoDate = new Date(isoDateString); + mockUseDateFormat.mockImplementation(() => 'MMM D, YYYY @ HH:mm:ss.SSS'); + mockUseTimeZone.mockImplementation(() => 'UTC'); + }); + + describe('PreferenceFormattedDate', () => { + test('renders correctly against snapshot', () => { + mockUseDateFormat.mockImplementation(() => ''); + const wrapper = mount(); + + expect(wrapper).toMatchSnapshot(); + }); + + test('it renders the date with the default configuration', () => { + const wrapper = mount(); + + expect(wrapper.text()).toEqual('Feb 25, 2019 @ 22:27:05.000'); + }); + + test('it renders a UTC ISO8601 date string supplied when no date format configuration exists', () => { + mockUseDateFormat.mockImplementation(() => ''); + const wrapper = mount(); + + expect(wrapper.text()).toEqual('2019-02-25T22:27:05Z'); + }); + + test('it renders the correct timezone when a non-UTC configuration exists', () => { + mockUseTimeZone.mockImplementation(() => 'America/Denver'); + const wrapper = mount(); + + expect(wrapper.text()).toEqual('Feb 25, 2019 @ 15:27:05.000'); + }); + + test('it renders the date with a user-defined format', () => { + mockUseDateFormat.mockImplementation(() => 'MMM-DD-YYYY'); + const wrapper = mount(); + + expect(wrapper.text()).toEqual('Feb-25-2019'); + }); + }); + + describe('FormattedDate', () => { + test('it renders against a numeric epoch', () => { + const wrapper = mount(); + expect(wrapper.text()).toEqual('May 28, 2019 @ 21:35:39.000'); + }); + + test('it renders against a string epoch', () => { + const wrapper = mount(); + expect(wrapper.text()).toEqual('May 28, 2019 @ 21:35:39.000'); + }); + + test('it renders against a ISO string', () => { + const wrapper = mount( + + ); + expect(wrapper.text()).toEqual('May 28, 2019 @ 22:04:49.957'); + }); + + test('it renders against an empty string as an empty string placeholder', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(getEmptyString()); + }); + + test('it renders against an null as a EMPTY_VALUE', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(getEmptyValue()); + }); + + test('it renders against an undefined as a EMPTY_VALUE', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(getEmptyValue()); + }); + + test('it renders against an invalid date time as just the string its self', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual('Rebecca Evan Braden'); + }); + }); + + describe('FormattedRelativePreferenceDate', () => { + test('renders time over an hour correctly against snapshot', () => { + const wrapper = shallow(); + expect(wrapper.find('[data-test-subj="preference-time"]').exists()).toBe(true); + }); + + test('renders time under an hour correctly against snapshot', () => { + const timeTwelveMinutesAgo = new Date(new Date().getTime() - 12 * 60 * 1000).toISOString(); + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="relative-time"]').exists()).toBe(true); + }); + + test('renders empty string value correctly', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toBe(getEmptyString()); + }); + + test('renders undefined value correctly', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toBe(getEmptyValue()); + }); + + test('renders null value correctly', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toBe(getEmptyValue()); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/formatted_date/index.tsx b/x-pack/plugins/cases/public/components/formatted_date/index.tsx new file mode 100644 index 0000000000000..5bb90bfbff797 --- /dev/null +++ b/x-pack/plugins/cases/public/components/formatted_date/index.tsx @@ -0,0 +1,173 @@ +/* + * 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 moment from 'moment-timezone'; +import React from 'react'; +import { FormattedRelative } from '@kbn/i18n/react'; + +import { useDateFormat, useTimeZone, useUiSetting$ } from '../../common/lib/kibana'; +import { getOrEmptyTagFromValue } from '../empty_value'; +import { LocalizedDateTooltip } from '../localized_date_tooltip'; +import { getMaybeDate } from './maybe_date'; + +export const PreferenceFormattedDate = React.memo<{ dateFormat?: string; value: Date }>( + /* eslint-disable-next-line react-hooks/rules-of-hooks */ + ({ value, dateFormat = useDateFormat() }) => ( + <>{moment.tz(value, useTimeZone()).format(dateFormat)} + ) +); + +PreferenceFormattedDate.displayName = 'PreferenceFormattedDate'; + +export const PreferenceFormattedDateFromPrimitive = ({ + value, +}: { + value?: string | number | null; +}) => { + if (value == null) { + return getOrEmptyTagFromValue(value); + } + const maybeDate = getMaybeDate(value); + if (!maybeDate.isValid()) { + return getOrEmptyTagFromValue(value); + } + const date = maybeDate.toDate(); + return ; +}; + +PreferenceFormattedDateFromPrimitive.displayName = 'PreferenceFormattedDateFromPrimitive'; + +/** + * This function may be passed to `Array.find()` to locate the `P1DT` + * configuration (sub) setting, a string array that contains two entries + * like the following example: `['P1DT', 'YYYY-MM-DD']`. + */ +export const isP1DTFormatterSetting = (formatNameFormatterPair?: string[]) => + Array.isArray(formatNameFormatterPair) && + formatNameFormatterPair[0] === 'P1DT' && + formatNameFormatterPair.length === 2; + +/** + * Renders a date in `P1DT` format, e.g. `YYYY-MM-DD`, as specified by + * the `P1DT1` entry in the `dateFormat:scaled` Kibana Advanced setting. + * + * If the `P1DT` format is not specified in the `dateFormat:scaled` setting, + * the fallback format `YYYY-MM-DD` will be applied + */ +export const PreferenceFormattedP1DTDate = React.memo<{ value: Date }>(({ value }) => { + /** + * A fallback "format name / formatter" 2-tuple for the `P1DT` formatter, which is + * one of many such pairs expected to be contained in the `dateFormat:scaled` + * Kibana advanced setting. + */ + const FALLBACK_DATE_FORMAT_SCALED_P1DT = ['P1DT', 'YYYY-MM-DD']; + + // Read the 'dateFormat:scaled' Kibana Advanced setting, which contains 2-tuple sub-settings: + const [scaledDateFormatPreference] = useUiSetting$('dateFormat:scaled'); + + // attempt to find the nested `['P1DT', 'formatString']` setting + const maybeP1DTFormatter = Array.isArray(scaledDateFormatPreference) + ? scaledDateFormatPreference.find(isP1DTFormatterSetting) + : null; + + const p1dtFormat = + Array.isArray(maybeP1DTFormatter) && maybeP1DTFormatter.length === 2 + ? maybeP1DTFormatter[1] + : FALLBACK_DATE_FORMAT_SCALED_P1DT[1]; + + return ; +}); + +PreferenceFormattedP1DTDate.displayName = 'PreferenceFormattedP1DTDate'; + +/** + * Renders the specified date value in a format determined by the user's preferences, + * with a tooltip that renders: + * - the name of the field + * - a humanized relative date (e.g. 16 minutes ago) + * - a long representation of the date that includes the day of the week (e.g. Thursday, March 21, 2019 6:47pm) + * - the raw date value (e.g. 2019-03-22T00:47:46Z) + */ +export const FormattedDate = React.memo<{ + fieldName: string; + value?: string | number | null; + className?: string; +}>( + ({ value, fieldName, className = '' }): JSX.Element => { + if (value == null) { + return getOrEmptyTagFromValue(value); + } + const maybeDate = getMaybeDate(value); + return maybeDate.isValid() ? ( + + + + ) : ( + getOrEmptyTagFromValue(value) + ); + } +); + +FormattedDate.displayName = 'FormattedDate'; + +/** + * Renders the specified date value according to under/over one hour + * Under an hour = relative format + * Over an hour = in a format determined by the user's preferences, + * with a tooltip that renders: + * - the name of the field + * - a humanized relative date (e.g. 16 minutes ago) + * - a long representation of the date that includes the day of the week (e.g. Thursday, March 21, 2019 6:47pm) + * - the raw date value (e.g. 2019-03-22T00:47:46Z) + */ + +export const FormattedRelativePreferenceDate = ({ value }: { value?: string | number | null }) => { + if (value == null) { + return getOrEmptyTagFromValue(value); + } + const maybeDate = getMaybeDate(value); + if (!maybeDate.isValid()) { + return getOrEmptyTagFromValue(value); + } + const date = maybeDate.toDate(); + return ( + + {moment(date).add(1, 'hours').isBefore(new Date()) ? ( + + ) : ( + + )} + + ); +}; + +/** + * Renders a preceding label according to under/over one hour + */ + +export const FormattedRelativePreferenceLabel = ({ + value, + preferenceLabel, + relativeLabel, +}: { + value?: string | number | null; + preferenceLabel?: string | null; + relativeLabel?: string | null; +}) => { + if (value == null) { + return null; + } + const maybeDate = getMaybeDate(value); + if (!maybeDate.isValid()) { + return null; + } + return moment(maybeDate.toDate()).add(1, 'hours').isBefore(new Date()) ? ( + <>{preferenceLabel} + ) : ( + <>{relativeLabel} + ); +}; diff --git a/x-pack/plugins/cases/public/components/formatted_date/maybe_date.test.ts b/x-pack/plugins/cases/public/components/formatted_date/maybe_date.test.ts new file mode 100644 index 0000000000000..402d811da7bd9 --- /dev/null +++ b/x-pack/plugins/cases/public/components/formatted_date/maybe_date.test.ts @@ -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 { getMaybeDate } from './maybe_date'; + +describe('#getMaybeDate', () => { + test('returns empty string as invalid date', () => { + expect(getMaybeDate('').isValid()).toBe(false); + }); + + test('returns string with empty spaces as invalid date', () => { + expect(getMaybeDate(' ').isValid()).toBe(false); + }); + + test('returns string date time as valid date', () => { + expect(getMaybeDate('2019-05-28T23:05:28.405Z').isValid()).toBe(true); + }); + + test('returns string date time as the date we expect', () => { + expect(getMaybeDate('2019-05-28T23:05:28.405Z').toISOString()).toBe('2019-05-28T23:05:28.405Z'); + }); + + test('returns plain string number as epoch as valid date', () => { + expect(getMaybeDate('1559084770612').isValid()).toBe(true); + }); + + test('returns plain string number as the date we expect', () => { + expect(getMaybeDate('1559084770612').toDate().toISOString()).toBe('2019-05-28T23:06:10.612Z'); + }); + + test('returns plain number as epoch as valid date', () => { + expect(getMaybeDate(1559084770612).isValid()).toBe(true); + }); + + test('returns plain number as epoch as the date we expect', () => { + expect(getMaybeDate(1559084770612).toDate().toISOString()).toBe('2019-05-28T23:06:10.612Z'); + }); + + test('returns a short date time string as an epoch (sadly) so this is ambiguous', () => { + expect(getMaybeDate('20190101').toDate().toISOString()).toBe('1970-01-01T05:36:30.101Z'); + }); +}); diff --git a/x-pack/plugins/cases/public/components/formatted_date/maybe_date.ts b/x-pack/plugins/cases/public/components/formatted_date/maybe_date.ts new file mode 100644 index 0000000000000..cc7add4f0f1f2 --- /dev/null +++ b/x-pack/plugins/cases/public/components/formatted_date/maybe_date.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 { isString } from 'lodash/fp'; +import moment from 'moment'; + +export const getMaybeDate = (value: string | number): moment.Moment => { + if (isString(value) && value.trim() !== '') { + const maybeDate = moment(new Date(value)); + if (maybeDate.isValid() || isNaN(+value)) { + return maybeDate; + } else { + return moment(new Date(+value)); + } + } else { + return moment(new Date(value)); + } +}; diff --git a/x-pack/plugins/cases/public/components/header_page/__snapshots__/editable_title.test.tsx.snap b/x-pack/plugins/cases/public/components/header_page/__snapshots__/editable_title.test.tsx.snap new file mode 100644 index 0000000000000..c8d4b6ec3b4c8 --- /dev/null +++ b/x-pack/plugins/cases/public/components/header_page/__snapshots__/editable_title.test.tsx.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EditableTitle it renders 1`] = ` + + + + + + + + +`; diff --git a/x-pack/plugins/cases/public/components/header_page/__snapshots__/index.test.tsx.snap b/x-pack/plugins/cases/public/components/header_page/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..a100f5e4f93b4 --- /dev/null +++ b/x-pack/plugins/cases/public/components/header_page/__snapshots__/index.test.tsx.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`HeaderPage it renders 1`] = ` +
    + + + + + + + +

    + Test supplement +

    +
    +
    +
    +`; diff --git a/x-pack/plugins/cases/public/components/header_page/__snapshots__/title.test.tsx.snap b/x-pack/plugins/cases/public/components/header_page/__snapshots__/title.test.tsx.snap new file mode 100644 index 0000000000000..05af2fee2c2a2 --- /dev/null +++ b/x-pack/plugins/cases/public/components/header_page/__snapshots__/title.test.tsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Title it renders 1`] = ` + +

    + Test title + + +

    +
    +`; diff --git a/x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx b/x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx new file mode 100644 index 0000000000000..90a10a388d717 --- /dev/null +++ b/x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx @@ -0,0 +1,172 @@ +/* + * 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 { shallow } from 'enzyme'; +import React from 'react'; + +import '../../common/mock/match_media'; +import { TestProviders } from '../../common/mock'; +import { EditableTitle } from './editable_title'; +import { useMountAppended } from '../../utils/use_mount_appended'; + +describe('EditableTitle', () => { + const mount = useMountAppended(); + const submitTitle = jest.fn(); + + test('it renders', () => { + const wrapper = shallow( + + ); + + expect(wrapper).toMatchSnapshot(); + }); + + test('it shows the edit title input field', () => { + const wrapper = mount( + + + + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find('[data-test-subj="editable-title-input-field"]').first().exists()).toBe( + true + ); + }); + + test('it shows the submit button', () => { + const wrapper = mount( + + + + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find('[data-test-subj="editable-title-submit-btn"]').first().exists()).toBe( + true + ); + }); + + test('it shows the cancel button', () => { + const wrapper = mount( + + + + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find('[data-test-subj="editable-title-cancel-btn"]').first().exists()).toBe( + true + ); + }); + + test('it DOES NOT shows the edit icon when in edit mode', () => { + const wrapper = mount( + + + + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find('[data-test-subj="editable-title-edit-icon"]').first().exists()).toBe( + false + ); + }); + + test('it switch to non edit mode when canceled', () => { + const wrapper = mount( + + + + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="editable-title-cancel-btn"]').simulate('click'); + + expect(wrapper.find('[data-test-subj="editable-title-edit-icon"]').first().exists()).toBe(true); + }); + + test('it should change the title', () => { + const newTitle = 'new test title'; + + const wrapper = mount( + + + + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + + wrapper + .find('input[data-test-subj="editable-title-input-field"]') + .simulate('change', { target: { value: newTitle } }); + + wrapper.update(); + + expect( + wrapper.find('input[data-test-subj="editable-title-input-field"]').prop('value') + ).toEqual(newTitle); + }); + + test('it should NOT change the title when cancel', () => { + const title = 'Test title'; + const newTitle = 'new test title'; + + const wrapper = mount( + + + + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + + wrapper + .find('input[data-test-subj="editable-title-input-field"]') + .simulate('change', { target: { value: newTitle } }); + wrapper.update(); + + wrapper.find('button[data-test-subj="editable-title-cancel-btn"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find('h1[data-test-subj="header-page-title"]').text()).toEqual(title); + }); + + test('it submits the title', () => { + const newTitle = 'new test title'; + + const wrapper = mount( + + + + ); + + wrapper.find('button[data-test-subj="editable-title-edit-icon"]').simulate('click'); + wrapper.update(); + + wrapper + .find('input[data-test-subj="editable-title-input-field"]') + .simulate('change', { target: { value: newTitle } }); + + wrapper.find('button[data-test-subj="editable-title-submit-btn"]').simulate('click'); + wrapper.update(); + + expect(submitTitle).toHaveBeenCalled(); + expect(submitTitle.mock.calls[0][0]).toEqual(newTitle); + expect(wrapper.find('[data-test-subj="editable-title-edit-icon"]').first().exists()).toBe(true); + }); +}); diff --git a/x-pack/plugins/cases/public/components/header_page/editable_title.tsx b/x-pack/plugins/cases/public/components/header_page/editable_title.tsx new file mode 100644 index 0000000000000..b53560db6745b --- /dev/null +++ b/x-pack/plugins/cases/public/components/header_page/editable_title.tsx @@ -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 React, { useState, useCallback, ChangeEvent } from 'react'; +import styled, { css } from 'styled-components'; + +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFieldText, + EuiButtonIcon, + EuiLoadingSpinner, +} from '@elastic/eui'; + +import * as i18n from './translations'; + +import { Title } from './title'; + +const MyEuiButtonIcon = styled(EuiButtonIcon)` + ${({ theme }) => css` + margin-left: ${theme.eui.euiSize}; + `} +`; + +const MySpinner = styled(EuiLoadingSpinner)` + ${({ theme }) => css` + margin-left: ${theme.eui.euiSize}; + `} +`; + +interface Props { + disabled?: boolean; + isLoading: boolean; + title: string | React.ReactNode; + onSubmit: (title: string) => void; +} + +const EditableTitleComponent: React.FC = ({ + disabled = false, + onSubmit, + isLoading, + title, +}) => { + const [editMode, setEditMode] = useState(false); + const [changedTitle, onTitleChange] = useState(typeof title === 'string' ? title : ''); + + const onCancel = useCallback(() => setEditMode(false), []); + const onClickEditIcon = useCallback(() => setEditMode(true), []); + + const onClickSubmit = useCallback((): void => { + if (changedTitle !== title) { + onSubmit(changedTitle); + } + setEditMode(false); + }, [changedTitle, onSubmit, title]); + + const handleOnChange = useCallback( + (e: ChangeEvent) => onTitleChange(e.target.value), + [] + ); + return editMode ? ( + + + + + + + + {i18n.SAVE} + + + + + {i18n.CANCEL} + + + + + + ) : ( + + + + </EuiFlexItem> + <EuiFlexItem grow={false}> + {isLoading && <MySpinner data-test-subj="editable-title-loading" />} + {!isLoading && ( + <MyEuiButtonIcon + isDisabled={disabled} + aria-label={i18n.EDIT_TITLE_ARIA(title as string)} + iconType="pencil" + onClick={onClickEditIcon} + data-test-subj="editable-title-edit-icon" + /> + )} + </EuiFlexItem> + </EuiFlexGroup> + ); +}; + +export const EditableTitle = React.memo(EditableTitleComponent); diff --git a/x-pack/plugins/cases/public/components/header_page/index.test.tsx b/x-pack/plugins/cases/public/components/header_page/index.test.tsx new file mode 100644 index 0000000000000..d84a6d9272def --- /dev/null +++ b/x-pack/plugins/cases/public/components/header_page/index.test.tsx @@ -0,0 +1,157 @@ +/* + * 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 euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { shallow } from 'enzyme'; +import React from 'react'; + +import '../../common/mock/match_media'; +import { TestProviders } from '../../common/mock'; +import { HeaderPage } from './index'; +import { useMountAppended } from '../../utils/use_mount_appended'; + +jest.mock('react-router-dom', () => { + const original = jest.requireActual('react-router-dom'); + + return { + ...original, + useHistory: () => ({ + useHistory: jest.fn(), + }), + }; +}); + +describe('HeaderPage', () => { + const mount = useMountAppended(); + + test('it renders', () => { + const wrapper = shallow( + <HeaderPage + badgeOptions={{ beta: true, text: 'Beta', tooltip: 'Test tooltip' }} + border + subtitle="Test subtitle" + subtitle2="Test subtitle 2" + title="Test title" + > + <p>{'Test supplement'}</p> + </HeaderPage> + ); + + expect(wrapper).toMatchSnapshot(); + }); + + test('it renders the back link when provided', () => { + const wrapper = mount( + <TestProviders> + <HeaderPage + backOptions={{ href: '#', text: 'Test link', onClick: jest.fn() }} + title="Test title" + /> + </TestProviders> + ); + + expect(wrapper.find('.casesHeaderPage__linkBack').first().exists()).toBe(true); + }); + + test('it DOES NOT render the back link when not provided', () => { + const wrapper = mount( + <TestProviders> + <HeaderPage title="Test title" /> + </TestProviders> + ); + + expect(wrapper.find('.casesHeaderPage__linkBack').first().exists()).toBe(false); + }); + + test('it renders the first subtitle when provided', () => { + const wrapper = mount( + <TestProviders> + <HeaderPage subtitle="Test subtitle" title="Test title" /> + </TestProviders> + ); + + expect(wrapper.find('[data-test-subj="header-page-subtitle"]').first().exists()).toBe(true); + }); + + test('it DOES NOT render the first subtitle when not provided', () => { + const wrapper = mount( + <TestProviders> + <HeaderPage title="Test title" /> + </TestProviders> + ); + + expect(wrapper.find('[data-test-subj="header-section-subtitle"]').first().exists()).toBe(false); + }); + + test('it renders the second subtitle when provided', () => { + const wrapper = mount( + <TestProviders> + <HeaderPage subtitle2="Test subtitle 2" title="Test title" /> + </TestProviders> + ); + + expect(wrapper.find('[data-test-subj="header-page-subtitle-2"]').first().exists()).toBe(true); + }); + + test('it DOES NOT render the second subtitle when not provided', () => { + const wrapper = mount( + <TestProviders> + <HeaderPage title="Test title" /> + </TestProviders> + ); + + expect(wrapper.find('[data-test-subj="header-section-subtitle-2"]').first().exists()).toBe( + false + ); + }); + + test('it renders supplements when children provided', () => { + const wrapper = mount( + <TestProviders> + <HeaderPage title="Test title"> + <p>{'Test supplement'}</p> + </HeaderPage> + </TestProviders> + ); + + expect(wrapper.find('[data-test-subj="header-page-supplements"]').first().exists()).toBe(true); + }); + + test('it DOES NOT render supplements when children not provided', () => { + const wrapper = mount( + <TestProviders> + <HeaderPage title="Test title" /> + </TestProviders> + ); + + expect(wrapper.find('[data-test-subj="header-page-supplements"]').first().exists()).toBe(false); + }); + + test('it applies border styles when border is true', () => { + const wrapper = mount( + <TestProviders> + <HeaderPage border title="Test title" /> + </TestProviders> + ); + const casesHeaderPage = wrapper.find('.casesHeaderPage').first(); + + expect(casesHeaderPage).toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); + expect(casesHeaderPage).toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.l); + }); + + test('it DOES NOT apply border styles when border is false', () => { + const wrapper = mount( + <TestProviders> + <HeaderPage title="Test title" /> + </TestProviders> + ); + const casesHeaderPage = wrapper.find('.casesHeaderPage').first(); + + expect(casesHeaderPage).not.toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); + expect(casesHeaderPage).not.toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.l); + }); +}); diff --git a/x-pack/plugins/cases/public/components/header_page/index.tsx b/x-pack/plugins/cases/public/components/header_page/index.tsx new file mode 100644 index 0000000000000..dc9f73e37b027 --- /dev/null +++ b/x-pack/plugins/cases/public/components/header_page/index.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 { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiProgress } from '@elastic/eui'; +import React from 'react'; +import styled, { css } from 'styled-components'; + +import { LinkIcon, LinkIconProps } from '../link_icon'; +import { Subtitle, SubtitleProps } from '../subtitle'; +import { Title } from './title'; +import { BadgeOptions, TitleProp } from './types'; +interface HeaderProps { + border?: boolean; + isLoading?: boolean; +} + +const Header = styled.header.attrs({ + className: 'casesHeaderPage', +})<HeaderProps>` + ${({ border, theme }) => css` + margin-bottom: ${theme.eui.euiSizeL}; + + ${border && + css` + border-bottom: ${theme.eui.euiBorderThin}; + padding-bottom: ${theme.eui.paddingSizes.l}; + .euiProgress { + top: ${theme.eui.paddingSizes.l}; + } + `} + `} +`; +Header.displayName = 'Header'; + +const FlexItem = styled(EuiFlexItem)` + display: block; +`; +FlexItem.displayName = 'FlexItem'; + +const LinkBack = styled.div.attrs({ + className: 'casesHeaderPage__linkBack', +})` + ${({ theme }) => css` + font-size: ${theme.eui.euiFontSizeXS}; + line-height: ${theme.eui.euiLineHeight}; + margin-bottom: ${theme.eui.euiSizeS}; + `} +`; +LinkBack.displayName = 'LinkBack'; + +const Badge = (styled(EuiBadge)` + letter-spacing: 0; +` as unknown) as typeof EuiBadge; +Badge.displayName = 'Badge'; + +interface BackOptions { + href: LinkIconProps['href']; + onClick?: (ev: MouseEvent) => void; + text: LinkIconProps['children']; + dataTestSubj?: string; +} + +export interface HeaderPageProps extends HeaderProps { + backOptions?: BackOptions; + /** A component to be displayed as the back button. Used only if `backOption` is not defined */ + backComponent?: React.ReactNode; + badgeOptions?: BadgeOptions; + children?: React.ReactNode; + subtitle?: SubtitleProps['items']; + subtitle2?: SubtitleProps['items']; + title: TitleProp; + titleNode?: React.ReactElement; +} + +const HeaderPageComponent: React.FC<HeaderPageProps> = ({ + backOptions, + backComponent, + badgeOptions, + border, + children, + isLoading, + subtitle, + subtitle2, + title, + titleNode, + ...rest +}) => { + return ( + <Header border={border} {...rest}> + <EuiFlexGroup alignItems="center"> + <FlexItem> + {backOptions && ( + <LinkBack> + <LinkIcon + dataTestSubj={backOptions.dataTestSubj} + onClick={backOptions.onClick} + href={backOptions.href} + iconType="arrowLeft" + > + {backOptions.text} + </LinkIcon> + </LinkBack> + )} + + {!backOptions && backComponent && <>{backComponent}</>} + + {titleNode || <Title title={title} badgeOptions={badgeOptions} />} + + {subtitle && <Subtitle data-test-subj="header-page-subtitle" items={subtitle} />} + {subtitle2 && <Subtitle data-test-subj="header-page-subtitle-2" items={subtitle2} />} + {border && isLoading && <EuiProgress size="xs" color="accent" />} + </FlexItem> + + {children && ( + <FlexItem data-test-subj="header-page-supplements" grow={false}> + {children} + </FlexItem> + )} + </EuiFlexGroup> + </Header> + ); +}; + +export const HeaderPage = React.memo(HeaderPageComponent); diff --git a/x-pack/plugins/cases/public/components/header_page/title.test.tsx b/x-pack/plugins/cases/public/components/header_page/title.test.tsx new file mode 100644 index 0000000000000..2423104eb8819 --- /dev/null +++ b/x-pack/plugins/cases/public/components/header_page/title.test.tsx @@ -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 { shallow } from 'enzyme'; +import React from 'react'; + +import '../../common/mock/match_media'; +import { TestProviders } from '../../common/mock'; +import { Title } from './title'; +import { useMountAppended } from '../../utils/use_mount_appended'; + +describe('Title', () => { + const mount = useMountAppended(); + + test('it renders', () => { + const wrapper = shallow( + <Title + badgeOptions={{ beta: true, text: 'Beta', tooltip: 'Test tooltip' }} + title="Test title" + /> + ); + + expect(wrapper).toMatchSnapshot(); + }); + + test('it renders the title', () => { + const wrapper = mount( + <TestProviders> + <Title title="Test title" /> + </TestProviders> + ); + + expect(wrapper.find('[data-test-subj="header-page-title"]').first().exists()).toBe(true); + }); +}); diff --git a/x-pack/plugins/cases/public/components/header_page/title.tsx b/x-pack/plugins/cases/public/components/header_page/title.tsx new file mode 100644 index 0000000000000..3a0390a436e1c --- /dev/null +++ b/x-pack/plugins/cases/public/components/header_page/title.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 React from 'react'; +import { EuiBetaBadge, EuiBadge, EuiTitle } from '@elastic/eui'; +import styled from 'styled-components'; + +import { BadgeOptions, TitleProp } from './types'; + +const StyledEuiBetaBadge = styled(EuiBetaBadge)` + vertical-align: middle; +`; + +StyledEuiBetaBadge.displayName = 'StyledEuiBetaBadge'; + +const Badge = (styled(EuiBadge)` + letter-spacing: 0; +` as unknown) as typeof EuiBadge; +Badge.displayName = 'Badge'; + +interface Props { + badgeOptions?: BadgeOptions; + title: TitleProp; +} + +const TitleComponent: React.FC<Props> = ({ title, badgeOptions }) => ( + <EuiTitle size="l"> + <h1 data-test-subj="header-page-title"> + {title} + {badgeOptions && ( + <> + {' '} + {badgeOptions.beta ? ( + <StyledEuiBetaBadge + label={badgeOptions.text} + tooltipContent={badgeOptions.tooltip} + tooltipPosition="bottom" + /> + ) : ( + <Badge color="hollow" title=""> + {badgeOptions.text} + </Badge> + )} + </> + )} + </h1> + </EuiTitle> +); + +export const Title = React.memo(TitleComponent); diff --git a/x-pack/plugins/cases/public/components/header_page/translations.ts b/x-pack/plugins/cases/public/components/header_page/translations.ts new file mode 100644 index 0000000000000..b24c347857a6c --- /dev/null +++ b/x-pack/plugins/cases/public/components/header_page/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 SAVE = i18n.translate('xpack.cases.header.editableTitle.save', { + defaultMessage: 'Save', +}); + +export const CANCEL = i18n.translate('xpack.cases.header.editableTitle.cancel', { + defaultMessage: 'Cancel', +}); + +export const EDIT_TITLE_ARIA = (title: string) => + i18n.translate('xpack.cases.header.editableTitle.editButtonAria', { + values: { title }, + defaultMessage: 'You can edit {title} by clicking', + }); diff --git a/x-pack/plugins/cases/public/components/header_page/types.ts b/x-pack/plugins/cases/public/components/header_page/types.ts new file mode 100644 index 0000000000000..e95d0c8e1e69c --- /dev/null +++ b/x-pack/plugins/cases/public/components/header_page/types.ts @@ -0,0 +1,20 @@ +/* + * 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 React from 'react'; +export type TitleProp = string | React.ReactNode; + +export interface DraggableArguments { + field: string; + value: string; +} + +export interface BadgeOptions { + beta?: boolean; + text: string; + tooltip?: string; +} diff --git a/x-pack/plugins/cases/public/components/insert_timeline/index.test.tsx b/x-pack/plugins/cases/public/components/insert_timeline/index.test.tsx new file mode 100644 index 0000000000000..84a19578c80de --- /dev/null +++ b/x-pack/plugins/cases/public/components/insert_timeline/index.test.tsx @@ -0,0 +1,73 @@ +/* + * 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 { mount } from 'enzyme'; +import { waitFor } from '@testing-library/react'; + +import { TestProviders } from '../../common/mock'; +import { Form, useForm, FormHook } from '../../common/shared_imports'; +import { CasesTimelineIntegrationProvider } from '../timeline_context'; +import { timelineIntegrationMock } from '../__mock__/timeline'; +import { getFormMock } from '../__mock__/form'; +import { InsertTimeline } from '.'; +import { useTimelineContext } from '../timeline_context/use_timeline_context'; + +jest.mock('../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'); +jest.mock('../timeline_context/use_timeline_context'); + +const useFormMock = useForm as jest.Mock; +const useTimelineContextMock = useTimelineContext as jest.Mock; + +describe('InsertTimeline ', () => { + const formHookMock = getFormMock({ comment: 'someValue' }); + const mockTimelineIntegration = { ...timelineIntegrationMock }; + const useInsertTimelineMock = jest.fn(); + let attachTimeline = jest.fn(); + beforeEach(() => { + jest.resetAllMocks(); + useFormMock.mockImplementation(() => ({ form: formHookMock })); + }); + + it('it should not call useInsertTimeline without timeline context', async () => { + mount( + <TestProviders> + <CasesTimelineIntegrationProvider> + <Form form={(formHookMock as unknown) as FormHook}> + <InsertTimeline fieldName="comment" /> + </Form> + </CasesTimelineIntegrationProvider> + </TestProviders> + ); + + await waitFor(() => { + expect(attachTimeline).not.toHaveBeenCalled(); + }); + }); + + it('should call useInsertTimeline with correct arguments', async () => { + useInsertTimelineMock.mockImplementation((comment, onTimelineAttached) => { + attachTimeline = onTimelineAttached; + }); + mockTimelineIntegration.hooks.useInsertTimeline = useInsertTimelineMock; + useTimelineContextMock.mockImplementation(() => ({ ...mockTimelineIntegration })); + + mount( + <TestProviders> + <CasesTimelineIntegrationProvider timelineIntegration={mockTimelineIntegration}> + <Form form={(formHookMock as unknown) as FormHook}> + <InsertTimeline fieldName="comment" /> + </Form> + </CasesTimelineIntegrationProvider> + </TestProviders> + ); + + await waitFor(() => { + expect(useInsertTimelineMock).toHaveBeenCalledWith('someValue', attachTimeline); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/insert_timeline/index.tsx b/x-pack/plugins/cases/public/components/insert_timeline/index.tsx new file mode 100644 index 0000000000000..473bf5485782f --- /dev/null +++ b/x-pack/plugins/cases/public/components/insert_timeline/index.tsx @@ -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 { useCallback } from 'react'; +import { useFormContext } from '../../common/shared_imports'; +import { useTimelineContext } from '../timeline_context/use_timeline_context'; + +type InsertFields = 'comment' | 'description'; + +export const InsertTimeline = ({ fieldName }: { fieldName: InsertFields }) => { + const { setFieldValue, getFormData } = useFormContext(); + const timelineHooks = useTimelineContext()?.hooks; + const formData = getFormData(); + const onTimelineAttached = useCallback((newValue: string) => setFieldValue(fieldName, newValue), [ + fieldName, + setFieldValue, + ]); + timelineHooks?.useInsertTimeline(formData[fieldName] ?? '', onTimelineAttached); + return null; +}; diff --git a/x-pack/plugins/cases/public/components/link_icon/__snapshots__/index.test.tsx.snap b/x-pack/plugins/cases/public/components/link_icon/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..7044c055e4b78 --- /dev/null +++ b/x-pack/plugins/cases/public/components/link_icon/__snapshots__/index.test.tsx.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LinkIcon it renders 1`] = ` +<Link + aria-label="Test link" + className="casesLinkIcon" + href="#" + iconSide="right" +> + <EuiIcon + size="xxl" + type="alert" + /> + <span + className="casesLinkIcon__label" + > + Test link + </span> +</Link> +`; diff --git a/x-pack/plugins/cases/public/components/link_icon/index.test.tsx b/x-pack/plugins/cases/public/components/link_icon/index.test.tsx new file mode 100644 index 0000000000000..4600f0dc4adc4 --- /dev/null +++ b/x-pack/plugins/cases/public/components/link_icon/index.test.tsx @@ -0,0 +1,95 @@ +/* + * 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 { mount, shallow } from 'enzyme'; +import React from 'react'; + +import { TestProviders } from '../../common/mock'; +import { LinkIcon } from './index'; + +describe('LinkIcon', () => { + test('it renders', () => { + const wrapper = shallow( + <LinkIcon href="#" iconSide="right" iconSize="xxl" iconType="alert"> + {'Test link'} + </LinkIcon> + ); + + expect(wrapper).toMatchSnapshot(); + }); + + test('it renders an action button when onClick is provided', () => { + const wrapper = mount( + <TestProviders> + <LinkIcon iconType="alert" onClick={() => alert('Test alert')}> + {'Test link'} + </LinkIcon> + </TestProviders> + ); + + expect(wrapper.find('button').first().exists()).toBe(true); + }); + + test('it renders an action link when href is provided', () => { + const wrapper = mount( + <TestProviders> + <LinkIcon href="#" iconType="alert"> + {'Test link'} + </LinkIcon> + </TestProviders> + ); + + expect(wrapper.find('a').first().exists()).toBe(true); + }); + + test('it renders an icon', () => { + const wrapper = mount( + <TestProviders> + <LinkIcon iconType="alert">{'Test link'}</LinkIcon> + </TestProviders> + ); + + expect(wrapper.find('[data-euiicon-type]').first().exists()).toBe(true); + }); + + test('it positions the icon to the right when iconSide is right', () => { + const wrapper = mount( + <TestProviders> + <LinkIcon iconSide="right" iconType="alert"> + {'Test link'} + </LinkIcon> + </TestProviders> + ); + + expect(wrapper.find('.casesLinkIcon').at(1)).toHaveStyleRule('flex-direction', 'row-reverse'); + }); + + test('it positions the icon to the left when iconSide is left (or not provided)', () => { + const wrapper = mount( + <TestProviders> + <LinkIcon iconSide="left" iconType="alert"> + {'Test link'} + </LinkIcon> + </TestProviders> + ); + + expect(wrapper.find('.casesLinkIcon').at(1)).not.toHaveStyleRule( + 'flex-direction', + 'row-reverse' + ); + }); + + test('it renders a label', () => { + const wrapper = mount( + <TestProviders> + <LinkIcon iconType="alert">{'Test link'}</LinkIcon> + </TestProviders> + ); + + expect(wrapper.find('.casesLinkIcon__label').first().exists()).toBe(true); + }); +}); diff --git a/x-pack/plugins/cases/public/components/link_icon/index.tsx b/x-pack/plugins/cases/public/components/link_icon/index.tsx new file mode 100644 index 0000000000000..b33529399db90 --- /dev/null +++ b/x-pack/plugins/cases/public/components/link_icon/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 { EuiIcon, EuiLink, IconSize, IconType } from '@elastic/eui'; +import { LinkAnchorProps } from '@elastic/eui/src/components/link/link'; +import React, { ReactNode, useCallback, useMemo } from 'react'; +import styled, { css } from 'styled-components'; + +interface LinkProps { + ariaLabel?: string; + color?: LinkAnchorProps['color']; + disabled?: boolean; + href?: string; + iconSide?: 'left' | 'right'; + onClick?: Function; +} + +export const Link = styled(({ iconSide, children, ...rest }) => ( + <EuiLink {...rest}>{children}</EuiLink> +))<LinkProps>` + ${({ iconSide, theme }) => css` + align-items: center; + display: inline-flex; + vertical-align: top; + white-space: nowrap; + + ${iconSide === 'left' && + css` + .euiIcon { + margin-right: ${theme.eui.euiSizeXS}; + } + `} + + ${iconSide === 'right' && + css` + flex-direction: row-reverse; + + .euiIcon { + margin-left: ${theme.eui.euiSizeXS}; + } + `} + `} +`; +Link.displayName = 'Link'; + +export interface LinkIconProps extends LinkProps { + children: string | ReactNode; + iconSize?: IconSize; + iconType: IconType; + dataTestSubj?: string; +} + +export const LinkIcon = React.memo<LinkIconProps>( + ({ + ariaLabel, + children, + color, + dataTestSubj, + disabled, + href, + iconSide = 'left', + iconSize = 's', + iconType, + onClick, + }) => { + const getChildrenString = useCallback((theChild: string | ReactNode): string => { + if ( + typeof theChild === 'object' && + theChild != null && + 'props' in theChild && + theChild.props && + theChild.props.children + ) { + return getChildrenString(theChild.props.children); + } + return theChild != null && Object.keys(theChild).length > 0 ? (theChild as string) : ''; + }, []); + const aria = useMemo(() => { + if (ariaLabel) { + return ariaLabel; + } + return getChildrenString(children); + }, [ariaLabel, children, getChildrenString]); + + return ( + <Link + className="casesLinkIcon" + color={color} + data-test-subj={dataTestSubj} + disabled={disabled} + href={href} + iconSide={iconSide} + onClick={onClick} + aria-label={aria} + > + <EuiIcon size={iconSize} type={iconType} /> + <span className="casesLinkIcon__label">{children}</span> + </Link> + ); + } +); +LinkIcon.displayName = 'LinkIcon'; diff --git a/x-pack/plugins/cases/public/components/links/index.tsx b/x-pack/plugins/cases/public/components/links/index.tsx new file mode 100644 index 0000000000000..310d700aa2a25 --- /dev/null +++ b/x-pack/plugins/cases/public/components/links/index.tsx @@ -0,0 +1,70 @@ +/* + * 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, + EuiButtonProps, + EuiLink, + EuiLinkProps, + PropsForAnchor, + PropsForButton, +} from '@elastic/eui'; +import React, { useCallback } from 'react'; +import * as i18n from './translations'; + +export interface CasesNavigation<T = React.MouseEvent | MouseEvent, K = null> { + href: K extends 'configurable' ? (arg: T) => string : string; + onClick: (arg: T) => void; +} + +export const LinkButton: React.FC< + PropsForButton<EuiButtonProps> | PropsForAnchor<EuiButtonProps> +> = ({ children, ...props }) => <EuiButton {...props}>{children}</EuiButton>; + +export const LinkAnchor: React.FC<EuiLinkProps> = ({ children, ...props }) => ( + <EuiLink {...props}>{children}</EuiLink> +); + +export interface CaseDetailsHrefSchema { + detailName: string; + search?: string; + subCaseId?: string; +} + +const CaseDetailsLinkComponent: React.FC<{ + children?: React.ReactNode; + detailName: string; + caseDetailsNavigation: CasesNavigation<CaseDetailsHrefSchema, 'configurable'>; + subCaseId?: string; + title?: string; +}> = ({ caseDetailsNavigation, children, detailName, subCaseId, title }) => { + const { href: getHref, onClick } = caseDetailsNavigation; + const goToCaseDetails = useCallback( + (ev) => { + if (onClick) { + ev.preventDefault(); + onClick({ detailName, subCaseId }); + } + }, + [detailName, onClick, subCaseId] + ); + + const href = getHref({ detailName, subCaseId }); + + return ( + <LinkAnchor + onClick={goToCaseDetails} + href={href} + data-test-subj="case-details-link" + aria-label={i18n.CASE_DETAILS_LINK_ARIA(title ?? detailName)} + > + {children ? children : detailName} + </LinkAnchor> + ); +}; +export const CaseDetailsLink = React.memo(CaseDetailsLinkComponent); +CaseDetailsLink.displayName = 'CaseDetailsLink'; diff --git a/x-pack/plugins/cases/public/components/links/translations.ts b/x-pack/plugins/cases/public/components/links/translations.ts new file mode 100644 index 0000000000000..248750961d348 --- /dev/null +++ b/x-pack/plugins/cases/public/components/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 CASE_DETAILS_LINK_ARIA = (detailName: string) => + i18n.translate('xpack.cases.caseTable.caseDetailsLinkAria', { + values: { detailName }, + defaultMessage: 'click to visit case with title {detailName}', + }); diff --git a/x-pack/plugins/cases/public/components/localized_date_tooltip/index.test.tsx b/x-pack/plugins/cases/public/components/localized_date_tooltip/index.test.tsx new file mode 100644 index 0000000000000..83fba7a041ca5 --- /dev/null +++ b/x-pack/plugins/cases/public/components/localized_date_tooltip/index.test.tsx @@ -0,0 +1,49 @@ +/* + * 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 { mount } from 'enzyme'; +import moment from 'moment-timezone'; +import React from 'react'; + +import { LocalizedDateTooltip } from '.'; + +describe('LocalizedDateTooltip', () => { + beforeEach(() => { + moment.tz.setDefault('UTC'); + }); + afterEach(() => { + moment.tz.setDefault('Browser'); + }); + + moment.locale('en'); + const date = moment('2019-02-19 04:21:00'); + + const sampleContentText = + 'this content is typically the string representation of the date prop, but can be any valid react child'; + + const SampleContent = () => <span data-test-subj="sample-content">{sampleContentText}</span>; + + test('it renders the child content', () => { + const wrapper = mount( + <LocalizedDateTooltip date={date.toDate()}> + <SampleContent /> + </LocalizedDateTooltip> + ); + + expect(wrapper.find('[data-test-subj="sample-content"]').exists()).toEqual(true); + }); + + test('it renders', () => { + const wrapper = mount( + <LocalizedDateTooltip date={date.toDate()}> + <SampleContent /> + </LocalizedDateTooltip> + ); + + expect(wrapper.find('[data-test-subj="localized-date-tool-tip"]').exists()).toEqual(true); + }); +}); diff --git a/x-pack/plugins/cases/public/components/localized_date_tooltip/index.tsx b/x-pack/plugins/cases/public/components/localized_date_tooltip/index.tsx new file mode 100644 index 0000000000000..3b140caeeda30 --- /dev/null +++ b/x-pack/plugins/cases/public/components/localized_date_tooltip/index.tsx @@ -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 { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import { FormattedRelative } from '@kbn/i18n/react'; +import moment from 'moment'; +import React from 'react'; + +export const LocalizedDateTooltip = React.memo<{ + children: React.ReactNode; + date: Date; + fieldName?: string; + className?: string; +}>(({ children, date, fieldName, className = '' }) => ( + <EuiToolTip + data-test-subj="localized-date-tool-tip" + anchorClassName={className} + content={ + <EuiFlexGroup data-test-subj="dates-container" direction="column" gutterSize="none"> + {fieldName != null ? ( + <EuiFlexItem grow={false}> + <span data-test-subj="field-name">{fieldName}</span> + </EuiFlexItem> + ) : null} + <EuiFlexItem grow={false}> + <FormattedRelative + data-test-subj="humanized-relative-date" + value={moment.utc(date).toDate()} + /> + </EuiFlexItem> + <EuiFlexItem data-test-subj="with-day-of-week" grow={false}> + {moment.utc(date).local().format('llll')} + </EuiFlexItem> + <EuiFlexItem data-test-subj="with-time-zone-offset-in-hours" grow={false}> + {moment(date).format()} + </EuiFlexItem> + </EuiFlexGroup> + } + > + <>{children}</> + </EuiToolTip> +)); + +LocalizedDateTooltip.displayName = 'LocalizedDateTooltip'; diff --git a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx new file mode 100644 index 0000000000000..f80e66a8c3e9f --- /dev/null +++ b/x-pack/plugins/cases/public/components/markdown_editor/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, { memo, useEffect, useState, useCallback } from 'react'; +import { PluggableList } from 'unified'; +import { EuiMarkdownEditor } from '@elastic/eui'; +import { EuiMarkdownEditorUiPlugin } from '@elastic/eui'; +import { usePlugins } from './use_plugins'; + +interface MarkdownEditorProps { + ariaLabel: string; + dataTestSubj?: string; + editorId?: string; + height?: number; + onChange: (content: string) => void; + parsingPlugins?: PluggableList; + processingPlugins?: PluggableList; + uiPlugins?: EuiMarkdownEditorUiPlugin[] | undefined; + value: string; +} + +const MarkdownEditorComponent: React.FC<MarkdownEditorProps> = ({ + ariaLabel, + dataTestSubj, + editorId, + height, + onChange, + value, +}) => { + const [markdownErrorMessages, setMarkdownErrorMessages] = useState([]); + const onParse = useCallback((err, { messages }) => { + setMarkdownErrorMessages(err ? [err] : messages); + }, []); + const { parsingPlugins, processingPlugins, uiPlugins } = usePlugins(); + + useEffect( + () => document.querySelector<HTMLElement>('textarea.euiMarkdownEditorTextArea')?.focus(), + [] + ); + + return ( + <EuiMarkdownEditor + aria-label={ariaLabel} + editorId={editorId} + onChange={onChange} + value={value} + uiPlugins={uiPlugins} + parsingPluginList={parsingPlugins} + processingPluginList={processingPlugins} + onParse={onParse} + errors={markdownErrorMessages} + data-test-subj={dataTestSubj} + height={height} + /> + ); +}; + +export const MarkdownEditor = memo(MarkdownEditorComponent); 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 new file mode 100644 index 0000000000000..5b0634302dfb6 --- /dev/null +++ b/x-pack/plugins/cases/public/components/markdown_editor/eui_form.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import styled from 'styled-components'; +import { EuiMarkdownEditorProps, EuiFormRow, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import { FieldHook, getFieldValidityAndErrorMessage } from '../../common/shared_imports'; +import { MarkdownEditor } from './editor'; + +type MarkdownEditorFormProps = EuiMarkdownEditorProps & { + id: string; + field: FieldHook; + dataTestSubj: string; + idAria: string; + isDisabled?: boolean; + bottomRightContent?: React.ReactNode; +}; + +const BottomContentWrapper = styled(EuiFlexGroup)` + ${({ theme }) => ` + padding: ${theme.eui.ruleMargins.marginSmall} 0; + `} +`; + +export const MarkdownEditorForm: React.FC<MarkdownEditorFormProps> = ({ + id, + field, + dataTestSubj, + idAria, + bottomRightContent, +}) => { + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + + return ( + <EuiFormRow + data-test-subj={dataTestSubj} + describedByIds={idAria ? [idAria] : undefined} + error={errorMessage} + fullWidth + helpText={field.helpText} + isInvalid={isInvalid} + label={field.label} + labelAppend={field.labelAppend} + > + <> + <MarkdownEditor + ariaLabel={idAria} + editorId={id} + onChange={field.setValue} + value={field.value as string} + data-test-subj={`${dataTestSubj}-markdown-editor`} + /> + {bottomRightContent && ( + <BottomContentWrapper justifyContent={'flexEnd'}> + <EuiFlexItem grow={false}>{bottomRightContent}</EuiFlexItem> + </BottomContentWrapper> + )} + </> + </EuiFormRow> + ); +}; diff --git a/x-pack/plugins/cases/public/components/markdown_editor/index.tsx b/x-pack/plugins/cases/public/components/markdown_editor/index.tsx new file mode 100644 index 0000000000000..e77a36d48f7d9 --- /dev/null +++ b/x-pack/plugins/cases/public/components/markdown_editor/index.tsx @@ -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. + */ + +export * from './types'; +export * from './renderer'; +export * from './editor'; +export * from './eui_form'; diff --git a/x-pack/plugins/cases/public/components/markdown_editor/markdown_link.tsx b/x-pack/plugins/cases/public/components/markdown_editor/markdown_link.tsx new file mode 100644 index 0000000000000..7cc8a07c8c04e --- /dev/null +++ b/x-pack/plugins/cases/public/components/markdown_editor/markdown_link.tsx @@ -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 React, { memo } from 'react'; +import { EuiLink, EuiLinkAnchorProps, EuiToolTip } from '@elastic/eui'; + +type MarkdownLinkProps = { disableLinks?: boolean } & EuiLinkAnchorProps; + +/** prevents search engine manipulation by noting the linked document is not trusted or endorsed by us */ +const REL_NOFOLLOW = 'nofollow'; + +const MarkdownLinkComponent: React.FC<MarkdownLinkProps> = ({ + disableLinks, + href, + target, + children, + ...props +}) => ( + <EuiToolTip content={href}> + <EuiLink + href={disableLinks ? undefined : href} + data-test-subj="markdown-link" + rel={`${REL_NOFOLLOW}`} + target="_blank" + > + {children} + </EuiLink> + </EuiToolTip> +); + +export const MarkdownLink = memo(MarkdownLinkComponent); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/renderer.test.tsx b/x-pack/plugins/cases/public/components/markdown_editor/renderer.test.tsx new file mode 100644 index 0000000000000..5d299529561ba --- /dev/null +++ b/x-pack/plugins/cases/public/components/markdown_editor/renderer.test.tsx @@ -0,0 +1,63 @@ +/* + * 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 { mount } from 'enzyme'; + +import { removeExternalLinkText } from '../../common/test_utils'; +import { MarkdownRenderer } from './renderer'; + +describe('Markdown', () => { + describe('markdown links', () => { + const markdownWithLink = 'A link to an external site [External Site](https://google.com)'; + + test('it renders the expected link text', () => { + const wrapper = mount(<MarkdownRenderer>{markdownWithLink}</MarkdownRenderer>); + + expect( + removeExternalLinkText(wrapper.find('[data-test-subj="markdown-link"]').first().text()) + ).toEqual('External Site'); + }); + + test('it renders the expected href', () => { + const wrapper = mount(<MarkdownRenderer>{markdownWithLink}</MarkdownRenderer>); + + expect(wrapper.find('[data-test-subj="markdown-link"]').first().getDOMNode()).toHaveProperty( + 'href', + 'https://google.com/' + ); + }); + + test('it does NOT render the href if links are disabled', () => { + const wrapper = mount( + <MarkdownRenderer disableLinks={true}>{markdownWithLink}</MarkdownRenderer> + ); + + expect( + wrapper.find('[data-test-subj="markdown-link"]').first().getDOMNode() + ).not.toHaveProperty('href'); + }); + + test('it opens links in a new tab via target="_blank"', () => { + const wrapper = mount(<MarkdownRenderer>{markdownWithLink}</MarkdownRenderer>); + + expect(wrapper.find('[data-test-subj="markdown-link"]').first().getDOMNode()).toHaveProperty( + 'target', + '_blank' + ); + }); + + test('it sets the link `rel` attribute to `noopener` to prevent the new page from accessing `window.opener`, `nofollow` to note the link is not endorsed by us, and noreferrer to prevent the browser from sending the current address', () => { + const wrapper = mount(<MarkdownRenderer>{markdownWithLink}</MarkdownRenderer>); + + expect(wrapper.find('[data-test-subj="markdown-link"]').first().getDOMNode()).toHaveProperty( + 'rel', + 'nofollow noopener noreferrer' + ); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/renderer.tsx b/x-pack/plugins/cases/public/components/markdown_editor/renderer.tsx new file mode 100644 index 0000000000000..6a91dda97a892 --- /dev/null +++ b/x-pack/plugins/cases/public/components/markdown_editor/renderer.tsx @@ -0,0 +1,40 @@ +/* + * 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 { cloneDeep } from 'lodash/fp'; +import { EuiMarkdownFormat, EuiLinkAnchorProps } from '@elastic/eui'; +import { MarkdownLink } from './markdown_link'; +import { usePlugins } from './use_plugins'; + +interface Props { + children: string; + disableLinks?: boolean; +} + +const MarkdownRendererComponent: React.FC<Props> = ({ children, disableLinks }) => { + const { processingPlugins, parsingPlugins } = usePlugins(); + const MarkdownLinkProcessingComponent: React.FC<EuiLinkAnchorProps> = useMemo( + () => (props) => <MarkdownLink {...props} disableLinks={disableLinks} />, + [disableLinks] + ); + // Deep clone of the processing plugins to prevent affecting the markdown editor. + const processingPluginList = cloneDeep(processingPlugins); + // This line of code is TS-compatible and it will break if [1][1] change in the future. + processingPluginList[1][1].components.a = MarkdownLinkProcessingComponent; + + return ( + <EuiMarkdownFormat + parsingPluginList={parsingPlugins} + processingPluginList={processingPluginList} + > + {children} + </EuiMarkdownFormat> + ); +}; + +export const MarkdownRenderer = memo(MarkdownRendererComponent); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/translations.ts b/x-pack/plugins/cases/public/components/markdown_editor/translations.ts new file mode 100644 index 0000000000000..365738f53ef8a --- /dev/null +++ b/x-pack/plugins/cases/public/components/markdown_editor/translations.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const MARKDOWN_SYNTAX_HELP = i18n.translate('xpack.cases.markdownEditor.markdownInputHelp', { + defaultMessage: 'Markdown syntax help', +}); + +export const MARKDOWN = i18n.translate('xpack.cases.markdownEditor.markdown', { + defaultMessage: 'Markdown', +}); +export const PREVIEW = i18n.translate('xpack.cases.markdownEditor.preview', { + defaultMessage: 'Preview', +}); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/types.ts b/x-pack/plugins/cases/public/components/markdown_editor/types.ts new file mode 100644 index 0000000000000..bb932f2fcfe22 --- /dev/null +++ b/x-pack/plugins/cases/public/components/markdown_editor/types.ts @@ -0,0 +1,30 @@ +/* + * 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 { FunctionComponent } from 'react'; +import { Plugin, PluggableList } from 'unified'; +// Remove after this issue is resolved: https://github.com/elastic/eui/issues/4688 +// eslint-disable-next-line import/no-extraneous-dependencies +import { Options as Remark2RehypeOptions } from 'mdast-util-to-hast'; +// eslint-disable-next-line import/no-extraneous-dependencies +import rehype2react from 'rehype-react'; +import { EuiLinkAnchorProps } from '@elastic/eui'; +export interface CursorPosition { + start: number; + end: number; +} + +export type TemporaryProcessingPluginsType = [ + [Plugin, Remark2RehypeOptions], + [ + typeof rehype2react, + Parameters<typeof rehype2react>[0] & { + components: { a: FunctionComponent<EuiLinkAnchorProps>; timeline: unknown }; + } + ], + ...PluggableList +]; diff --git a/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts b/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts new file mode 100644 index 0000000000000..e98af8bca8bce --- /dev/null +++ b/x-pack/plugins/cases/public/components/markdown_editor/use_plugins.ts @@ -0,0 +1,40 @@ +/* + * 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 { + getDefaultEuiMarkdownParsingPlugins, + getDefaultEuiMarkdownProcessingPlugins, + getDefaultEuiMarkdownUiPlugins, +} from '@elastic/eui'; +import { useMemo } from 'react'; +import { useTimelineContext } from '../timeline_context/use_timeline_context'; +import { TemporaryProcessingPluginsType } from './types'; + +export const usePlugins = () => { + const timelinePlugins = useTimelineContext()?.editor_plugins; + + return useMemo(() => { + const uiPlugins = getDefaultEuiMarkdownUiPlugins(); + const parsingPlugins = getDefaultEuiMarkdownParsingPlugins(); + const processingPlugins = getDefaultEuiMarkdownProcessingPlugins() as TemporaryProcessingPluginsType; + + if (timelinePlugins) { + uiPlugins.push(timelinePlugins.uiPlugin); + + parsingPlugins.push(timelinePlugins.parsingPlugin); + + // This line of code is TS-compatible and it will break if [1][1] change in the future. + processingPlugins[1][1].components.timeline = timelinePlugins.processingPluginRenderer; + } + + return { + uiPlugins, + parsingPlugins, + processingPlugins, + }; + }, [timelinePlugins]); +}; diff --git a/x-pack/plugins/cases/public/components/panel/index.test.tsx b/x-pack/plugins/cases/public/components/panel/index.test.tsx new file mode 100644 index 0000000000000..81c80158ae577 --- /dev/null +++ b/x-pack/plugins/cases/public/components/panel/index.test.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mount } from 'enzyme'; +import { Panel } from '.'; +import React from 'react'; + +describe('Panel', () => { + test('it does not have the boolean loading as a Eui Property', () => { + const wrapper = mount(<Panel loading={true} />); + expect(Object.keys(wrapper.find('EuiPanel').props())).not.toContain('loading'); + }); +}); diff --git a/x-pack/plugins/cases/public/components/panel/index.tsx b/x-pack/plugins/cases/public/components/panel/index.tsx new file mode 100644 index 0000000000000..652d22409cb0c --- /dev/null +++ b/x-pack/plugins/cases/public/components/panel/index.tsx @@ -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 styled from 'styled-components'; +import React from 'react'; +import { EuiPanel } from '@elastic/eui'; + +/** + * The reason for the type of syntax below of: + * `styled(({ loading, ...props })` + * is filter out the "loading" attribute from being put on the DOM + * and getting one of the stack traces from + * ``` + * ReactJS about non-standard HTML such as this one: + * Warning: Received `true` for a non-boolean attribute `loading`. + * If you want to write it to the DOM, pass a string instead: loading="true" or loading={value.toString()}. + * ``` + * + * Ref: https://github.com/styled-components/styled-components/issues/1198#issuecomment-425650423 + * Ref: https://github.com/elastic/kibana/pull/41596#issuecomment-514418978 + * Ref: https://www.styled-components.com/docs/faqs#why-am-i-getting-html-attribute-warnings + * Ref: https://reactjs.org/blog/2017/09/08/dom-attributes-in-react-16.html + */ +export const Panel = styled(({ loading, ...props }) => <EuiPanel {...props} />)` + position: relative; + ${({ loading }) => + loading && + ` + overflow: hidden; + `} +`; + +Panel.displayName = 'Panel'; diff --git a/x-pack/plugins/security_solution/public/cases/components/property_actions/index.tsx b/x-pack/plugins/cases/public/components/property_actions/index.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/property_actions/index.tsx rename to x-pack/plugins/cases/public/components/property_actions/index.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/property_actions/translations.ts b/x-pack/plugins/cases/public/components/property_actions/translations.ts similarity index 63% rename from x-pack/plugins/security_solution/public/cases/components/property_actions/translations.ts rename to x-pack/plugins/cases/public/components/property_actions/translations.ts index c5c11e0637d7b..4066254878657 100644 --- a/x-pack/plugins/security_solution/public/cases/components/property_actions/translations.ts +++ b/x-pack/plugins/cases/public/components/property_actions/translations.ts @@ -7,9 +7,6 @@ import { i18n } from '@kbn/i18n'; -export const ACTIONS_ARIA = i18n.translate( - 'xpack.securitySolution.cases.caseView.editActionsLinkAria', - { - defaultMessage: 'click to see all actions', - } -); +export const ACTIONS_ARIA = i18n.translate('xpack.cases.caseView.editActionsLinkAria', { + defaultMessage: 'click to see all actions', +}); diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/filters/index.tsx b/x-pack/plugins/cases/public/components/recent_cases/filters/index.tsx similarity index 93% rename from x-pack/plugins/security_solution/public/overview/components/recent_cases/filters/index.tsx rename to x-pack/plugins/cases/public/components/recent_cases/filters/index.tsx index 5b6c59e31e202..cc37a826e18b9 100644 --- a/x-pack/plugins/security_solution/public/overview/components/recent_cases/filters/index.tsx +++ b/x-pack/plugins/cases/public/components/recent_cases/filters/index.tsx @@ -27,7 +27,7 @@ const toggleButtonIcons: EuiButtonGroupOptionProps[] = [ }, ]; -export const Filters = React.memo<{ +export const RecentCasesFilters = React.memo<{ filterBy: FilterMode; setFilterBy: (filterBy: FilterMode) => void; showMyRecentlyReported: boolean; @@ -57,4 +57,4 @@ export const Filters = React.memo<{ ); }); -Filters.displayName = 'Filters'; +RecentCasesFilters.displayName = 'RecentCasesFilters'; diff --git a/x-pack/plugins/cases/public/components/recent_cases/icon_with_count.tsx b/x-pack/plugins/cases/public/components/recent_cases/icon_with_count.tsx new file mode 100644 index 0000000000000..f46eb631ca2d6 --- /dev/null +++ b/x-pack/plugins/cases/public/components/recent_cases/icon_with_count.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 { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiToolTip } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; + +const Icon = styled(EuiIcon)` + margin-right: 8px; +`; + +const FlexGroup = styled(EuiFlexGroup)` + margin-right: 16px; +`; +const OuterContainer = styled.span` + width: fit-content; +`; +export const IconWithCount = React.memo<{ count: number; icon: string; tooltip: string }>( + ({ count, icon, tooltip }) => ( + <OuterContainer> + <EuiToolTip content={tooltip}> + <FlexGroup alignItems="center" gutterSize="none"> + <EuiFlexItem grow={false}> + <Icon color="subdued" size="s" type={icon} /> + </EuiFlexItem> + + <EuiFlexItem grow={false}> + <EuiText color="subdued" size="xs"> + {count} + </EuiText> + </EuiFlexItem> + </FlexGroup> + </EuiToolTip> + </OuterContainer> + ) +); + +IconWithCount.displayName = 'IconWithCount'; diff --git a/x-pack/plugins/cases/public/components/recent_cases/index.test.tsx b/x-pack/plugins/cases/public/components/recent_cases/index.test.tsx new file mode 100644 index 0000000000000..933ea51bffac4 --- /dev/null +++ b/x-pack/plugins/cases/public/components/recent_cases/index.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 { configure, render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import RecentCases from '.'; +import { TestProviders } from '../../common/mock'; +import { useGetCases } from '../../containers/use_get_cases'; +import { useGetCasesMockState } from '../../containers/mock'; +jest.mock('../../containers/use_get_cases'); +configure({ testIdAttribute: 'data-test-subj' }); +const defaultProps = { + allCasesNavigation: { + href: 'all-cases-href', + onClick: jest.fn(), + }, + caseDetailsNavigation: { + href: () => 'case-details-href', + onClick: jest.fn(), + }, + createCaseNavigation: { + href: 'create-details-href', + onClick: jest.fn(), + }, + maxCasesToShow: 10, +}; +const setFilters = jest.fn(); +const mockData = { + ...useGetCasesMockState, + setFilters, +}; +const useGetCasesMock = useGetCases as jest.Mock; +describe('RecentCases', () => { + beforeEach(() => { + jest.clearAllMocks(); + useGetCasesMock.mockImplementation(() => mockData); + }); + it('is good at loading', () => { + useGetCasesMock.mockImplementation(() => ({ + ...mockData, + loading: 'cases', + })); + const { getAllByTestId } = render( + <TestProviders> + <RecentCases {...defaultProps} /> + </TestProviders> + ); + expect(getAllByTestId('loadingPlaceholders')).toHaveLength(3); + }); + it('is good at rendering cases', () => { + const { getAllByTestId } = render( + <TestProviders> + <RecentCases {...defaultProps} /> + </TestProviders> + ); + expect(getAllByTestId('case-details-link')).toHaveLength(5); + }); + it('is good at rendering max cases', () => { + render( + <TestProviders> + <RecentCases {...{ ...defaultProps, maxCasesToShow: 2 }} /> + </TestProviders> + ); + expect(useGetCasesMock).toBeCalledWith({ perPage: 2 }); + }); + it('updates filters', () => { + const { getByTestId } = render( + <TestProviders> + <RecentCases {...defaultProps} /> + </TestProviders> + ); + const yo = getByTestId('myRecentlyReported'); + userEvent.click(yo); + expect(setFilters).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/recent_cases/index.tsx b/x-pack/plugins/cases/public/components/recent_cases/index.tsx new file mode 100644 index 0000000000000..05aff25d0dbd8 --- /dev/null +++ b/x-pack/plugins/cases/public/components/recent_cases/index.tsx @@ -0,0 +1,91 @@ +/* + * 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, EuiHorizontalRule, EuiText, EuiTitle } from '@elastic/eui'; +import React, { useMemo, useState } from 'react'; + +import * as i18n from './translations'; +import { CaseDetailsHrefSchema, CasesNavigation, LinkAnchor } from '../links'; +import { RecentCasesFilters } from './filters'; +import { RecentCasesComp } from './recent_cases'; +import { FilterMode as RecentCasesFilterMode } from './types'; +import { useCurrentUser } from '../../common/lib/kibana'; + +export interface RecentCasesProps { + allCasesNavigation: CasesNavigation; + caseDetailsNavigation: CasesNavigation<CaseDetailsHrefSchema, 'configurable'>; + createCaseNavigation: CasesNavigation; + maxCasesToShow: number; +} + +const RecentCases = ({ + allCasesNavigation, + caseDetailsNavigation, + createCaseNavigation, + maxCasesToShow, +}: RecentCasesProps) => { + const currentUser = useCurrentUser(); + const [recentCasesFilterBy, setRecentCasesFilterBy] = useState<RecentCasesFilterMode>( + 'recentlyCreated' + ); + + const recentCasesFilterOptions = useMemo( + () => + recentCasesFilterBy === 'myRecentlyReported' && currentUser != null + ? { + reporters: [ + { + email: currentUser.email, + full_name: currentUser.fullName, + username: currentUser.username, + }, + ], + } + : {}, + [currentUser, recentCasesFilterBy] + ); + return ( + <> + <> + <EuiFlexGroup alignItems="center" gutterSize="none" justifyContent="spaceBetween"> + <EuiFlexItem grow={false}> + <EuiTitle size="xs"> + <h2>{i18n.RECENT_CASES}</h2> + </EuiTitle> + </EuiFlexItem> + + <EuiFlexItem grow={false}> + <RecentCasesFilters + filterBy={recentCasesFilterBy} + setFilterBy={setRecentCasesFilterBy} + showMyRecentlyReported={currentUser != null} + /> + </EuiFlexItem> + </EuiFlexGroup> + <EuiHorizontalRule margin="s" /> + </> + <EuiText color="subdued" size="s"> + <RecentCasesComp + caseDetailsNavigation={caseDetailsNavigation} + createCaseNavigation={createCaseNavigation} + filterOptions={recentCasesFilterOptions} + maxCasesToShow={maxCasesToShow} + /> + <EuiHorizontalRule margin="s" /> + <EuiText size="xs"> + <LinkAnchor onClick={allCasesNavigation.onClick} href={allCasesNavigation.href}> + {' '} + {i18n.VIEW_ALL_CASES} + </LinkAnchor> + </EuiText> + </EuiText> + </> + ); +}; + +// eslint-disable-next-line import/no-default-export +export { RecentCases as default }; diff --git a/x-pack/plugins/cases/public/components/recent_cases/loading_placeholders.tsx b/x-pack/plugins/cases/public/components/recent_cases/loading_placeholders.tsx new file mode 100644 index 0000000000000..6e839e00a511d --- /dev/null +++ b/x-pack/plugins/cases/public/components/recent_cases/loading_placeholders.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiLoadingContent, EuiSpacer } from '@elastic/eui'; +import React from 'react'; + +const LoadingPlaceholdersComponent: React.FC<{ + lines: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; + placeholders: number; +}> = ({ lines, placeholders }) => ( + <> + {[...Array(placeholders).keys()].map((_, i) => ( + <React.Fragment key={i}> + <EuiLoadingContent lines={lines} data-test-subj={'loadingPlaceholders'} /> + {i !== placeholders - 1 && <EuiSpacer size="l" />} + </React.Fragment> + ))} + </> +); + +LoadingPlaceholdersComponent.displayName = 'LoadingPlaceholdersComponent'; + +export const LoadingPlaceholders = React.memo(LoadingPlaceholdersComponent); diff --git a/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.test.tsx b/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.test.tsx new file mode 100644 index 0000000000000..0295632cc137a --- /dev/null +++ b/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.test.tsx @@ -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 React from 'react'; +import { mount } from 'enzyme'; + +import { TestProviders } from '../../../common/mock'; +import { NoCases } from '.'; + +describe('RecentCases', () => { + it('if no cases, a link to create cases will exist', () => { + const createCaseHref = '/create'; + const wrapper = mount( + <TestProviders> + <NoCases createCaseHref={createCaseHref} /> + </TestProviders> + ); + expect(wrapper.find(`[data-test-subj="no-cases-create-case"]`).first().prop('href')).toEqual( + createCaseHref + ); + }); +}); diff --git a/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.tsx b/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.tsx new file mode 100644 index 0000000000000..df0efcec4552c --- /dev/null +++ b/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.tsx @@ -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 React from 'react'; + +import { EuiLink } from '@elastic/eui'; +import * as i18n from '../translations'; + +const NoCasesComponent = ({ createCaseHref }: { createCaseHref: string }) => ( + <> + <span>{i18n.NO_CASES}</span> + <EuiLink + data-test-subj="no-cases-create-case" + href={createCaseHref} + >{` ${i18n.START_A_NEW_CASE}`}</EuiLink> + {'!'} + </> +); + +NoCasesComponent.displayName = 'NoCasesComponent'; + +export const NoCases = React.memo(NoCasesComponent); diff --git a/x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx b/x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx new file mode 100644 index 0000000000000..12935e75c064f --- /dev/null +++ b/x-pack/plugins/cases/public/components/recent_cases/recent_cases.tsx @@ -0,0 +1,96 @@ +/* + * 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, EuiSpacer, EuiText } from '@elastic/eui'; +import React, { useEffect, useMemo, useRef } from 'react'; +import { isEqual } from 'lodash/fp'; +import styled from 'styled-components'; + +import { IconWithCount } from './icon_with_count'; +import * as i18n from './translations'; +import { useGetCases } from '../../containers/use_get_cases'; +import { CaseDetailsHrefSchema, CaseDetailsLink, CasesNavigation } from '../links'; +import { LoadingPlaceholders } from './loading_placeholders'; +import { NoCases } from './no_cases'; +import { isSubCase } from '../all_cases/helpers'; +import { MarkdownRenderer } from '../markdown_editor'; +import { FilterOptions } from '../../containers/types'; + +const MarkdownContainer = styled.div` + max-height: 150px; + overflow-y: auto; + width: 300px; +`; + +export interface RecentCasesProps { + filterOptions: Partial<FilterOptions>; + caseDetailsNavigation: CasesNavigation<CaseDetailsHrefSchema, 'configurable'>; + createCaseNavigation: CasesNavigation; + maxCasesToShow: number; +} +const usePrevious = (value: Partial<FilterOptions>) => { + const ref = useRef(); + useEffect(() => { + (ref.current as unknown) = value; + }); + return ref.current; +}; +export const RecentCasesComp = ({ + caseDetailsNavigation, + createCaseNavigation, + filterOptions, + maxCasesToShow, +}: RecentCasesProps) => { + const previousFilterOptions = usePrevious(filterOptions); + const { data, loading, setFilters } = useGetCases({ perPage: maxCasesToShow }); + + useEffect(() => { + if (previousFilterOptions !== undefined && !isEqual(previousFilterOptions, filterOptions)) { + setFilters(filterOptions); + } + }, [previousFilterOptions, filterOptions, setFilters]); + + const isLoadingCases = useMemo( + () => loading.indexOf('cases') > -1 || loading.indexOf('caseUpdate') > -1, + [loading] + ); + + return isLoadingCases ? ( + <LoadingPlaceholders lines={2} placeholders={3} /> + ) : !isLoadingCases && data.cases.length === 0 ? ( + <NoCases createCaseHref={createCaseNavigation.href} /> + ) : ( + <> + {data.cases.map((c, i) => ( + <EuiFlexGroup key={c.id} gutterSize="none" justifyContent="spaceBetween"> + <EuiFlexItem grow={false}> + <EuiText size="s"> + <CaseDetailsLink + caseDetailsNavigation={caseDetailsNavigation} + detailName={isSubCase(c) ? c.caseParentId : c.id} + title={c.title} + subCaseId={isSubCase(c) ? c.id : undefined} + > + {c.title} + </CaseDetailsLink> + </EuiText> + + <IconWithCount count={c.totalComment} icon={'editorComment'} tooltip={i18n.COMMENTS} /> + {c.description && c.description.length && ( + <MarkdownContainer> + <EuiText color="subdued" size="xs"> + <MarkdownRenderer disableLinks={true}>{c.description}</MarkdownRenderer> + </EuiText> + </MarkdownContainer> + )} + {i !== data.cases.length - 1 && <EuiSpacer size="l" />} + </EuiFlexItem> + </EuiFlexGroup> + ))} + </> + ); +}; diff --git a/x-pack/plugins/cases/public/components/recent_cases/translations.ts b/x-pack/plugins/cases/public/components/recent_cases/translations.ts new file mode 100644 index 0000000000000..c8f6c349d8f72 --- /dev/null +++ b/x-pack/plugins/cases/public/components/recent_cases/translations.ts @@ -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 { i18n } from '@kbn/i18n'; + +export const COMMENTS = i18n.translate('xpack.cases.recentCases.commentsTooltip', { + defaultMessage: 'Comments', +}); + +export const MY_RECENTLY_REPORTED_CASES = i18n.translate( + 'xpack.cases.recentCases.myRecentlyReportedCasesButtonLabel', + { + defaultMessage: 'My recently reported cases', + } +); + +export const NO_CASES = i18n.translate('xpack.cases.recentCases.noCasesMessage', { + defaultMessage: 'No cases have been created yet. Put your detective hat on and', +}); + +export const RECENT_CASES = i18n.translate('xpack.cases.recentCases.recentCasesSidebarTitle', { + defaultMessage: 'Recent cases', +}); + +export const RECENTLY_CREATED_CASES = i18n.translate( + 'xpack.cases.recentCases.recentlyCreatedCasesButtonLabel', + { + defaultMessage: 'Recently created cases', + } +); + +export const START_A_NEW_CASE = i18n.translate('xpack.cases.recentCases.startNewCaseLink', { + defaultMessage: 'start a new case', +}); + +export const VIEW_ALL_CASES = i18n.translate('xpack.cases.recentCases.viewAllCasesLink', { + defaultMessage: 'View all cases', +}); + +export const CASES_FILTER_CONTROL = i18n.translate('xpack.cases.recentCases.controlLegend', { + defaultMessage: 'Cases filter', +}); diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/types.ts b/x-pack/plugins/cases/public/components/recent_cases/types.ts similarity index 100% rename from x-pack/plugins/security_solution/public/overview/components/recent_cases/types.ts rename to x-pack/plugins/cases/public/components/recent_cases/types.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/status/button.test.tsx b/x-pack/plugins/cases/public/components/status/button.test.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/status/button.test.tsx rename to x-pack/plugins/cases/public/components/status/button.test.tsx index 6bf4eb95bc049..a4d4a53ff4a62 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/button.test.tsx +++ b/x-pack/plugins/cases/public/components/status/button.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../../../cases/common/api'; +import { CaseStatuses } from '../../../common'; import { StatusActionButton } from './button'; describe('StatusActionButton', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/status/button.tsx b/x-pack/plugins/cases/public/components/status/button.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/cases/components/status/button.tsx rename to x-pack/plugins/cases/public/components/status/button.tsx index 5a0d98fc8a11a..623afeb43c596 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/button.tsx +++ b/x-pack/plugins/cases/public/components/status/button.tsx @@ -8,7 +8,7 @@ import React, { memo, useCallback, useMemo } from 'react'; import { EuiButton } from '@elastic/eui'; -import { CaseStatuses, caseStatuses } from '../../../../../cases/common/api'; +import { CaseStatuses, caseStatuses } from '../../../common'; import { statuses } from './config'; interface Props { diff --git a/x-pack/plugins/security_solution/public/cases/components/status/config.ts b/x-pack/plugins/cases/public/components/status/config.ts similarity index 93% rename from x-pack/plugins/security_solution/public/cases/components/status/config.ts rename to x-pack/plugins/cases/public/components/status/config.ts index 47a74549f03cc..0202507aa3721 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/config.ts +++ b/x-pack/plugins/cases/public/components/status/config.ts @@ -4,9 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { CaseStatuses } from '../../../../../cases/common/api'; +import { CaseStatuses, StatusAll } from '../../../common'; import * as i18n from './translations'; -import { AllCaseStatus, Statuses, StatusAll } from './types'; +import { AllCaseStatus, Statuses } from './types'; export const allCaseStatus: AllCaseStatus = { [StatusAll]: { color: 'hollow', label: i18n.ALL }, diff --git a/x-pack/plugins/security_solution/public/cases/components/status/index.ts b/x-pack/plugins/cases/public/components/status/index.ts similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/status/index.ts rename to x-pack/plugins/cases/public/components/status/index.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/status/stats.test.tsx b/x-pack/plugins/cases/public/components/status/stats.test.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/status/stats.test.tsx rename to x-pack/plugins/cases/public/components/status/stats.test.tsx index 266ceb04e4335..b2da828da77b0 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/stats.test.tsx +++ b/x-pack/plugins/cases/public/components/status/stats.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../../../cases/common/api'; +import { CaseStatuses } from '../../../common'; import { Stats } from './stats'; describe('Stats', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/status/stats.tsx b/x-pack/plugins/cases/public/components/status/stats.tsx similarity index 94% rename from x-pack/plugins/security_solution/public/cases/components/status/stats.tsx rename to x-pack/plugins/cases/public/components/status/stats.tsx index 43001c2cf5947..071ea43746fdc 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/stats.tsx +++ b/x-pack/plugins/cases/public/components/status/stats.tsx @@ -7,7 +7,7 @@ import React, { memo, useMemo } from 'react'; import { EuiDescriptionList, EuiLoadingSpinner } from '@elastic/eui'; -import { CaseStatuses } from '../../../../../cases/common/api'; +import { CaseStatuses } from '../../../common'; import { statuses } from './config'; export interface Props { diff --git a/x-pack/plugins/security_solution/public/cases/components/status/status.test.tsx b/x-pack/plugins/cases/public/components/status/status.test.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/status/status.test.tsx rename to x-pack/plugins/cases/public/components/status/status.test.tsx index eff9d73c2adf9..7cddbf5ca4a1d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/status.test.tsx +++ b/x-pack/plugins/cases/public/components/status/status.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../../../cases/common/api'; +import { CaseStatuses } from '../../../common'; import { Status } from './status'; describe('Stats', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/status/status.tsx b/x-pack/plugins/cases/public/components/status/status.tsx similarity index 94% rename from x-pack/plugins/security_solution/public/cases/components/status/status.tsx rename to x-pack/plugins/cases/public/components/status/status.tsx index de4c979daf4c1..03dca8642aed7 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/status.tsx +++ b/x-pack/plugins/cases/public/components/status/status.tsx @@ -10,8 +10,8 @@ import { noop } from 'lodash/fp'; import { EuiBadge } from '@elastic/eui'; import { allCaseStatus, statuses } from './config'; -import { CaseStatusWithAllStatus, StatusAll } from './types'; import * as i18n from './translations'; +import { CaseStatusWithAllStatus, StatusAll } from '../../../common'; interface Props { type: CaseStatusWithAllStatus; diff --git a/x-pack/plugins/cases/public/components/status/translations.ts b/x-pack/plugins/cases/public/components/status/translations.ts new file mode 100644 index 0000000000000..b3eadfd681ba5 --- /dev/null +++ b/x-pack/plugins/cases/public/components/status/translations.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. + */ + +import { i18n } from '@kbn/i18n'; +export * from '../../common/translations'; + +export const ALL = i18n.translate('xpack.cases.status.all', { + defaultMessage: 'All', +}); + +export const OPEN = i18n.translate('xpack.cases.status.open', { + defaultMessage: 'Open', +}); + +export const IN_PROGRESS = i18n.translate('xpack.cases.status.inProgress', { + defaultMessage: 'In progress', +}); + +export const CLOSED = i18n.translate('xpack.cases.status.closed', { + defaultMessage: 'Closed', +}); + +export const STATUS_ICON_ARIA = i18n.translate('xpack.cases.status.iconAria', { + defaultMessage: 'Change status', +}); + +export const CASE_OPENED = i18n.translate('xpack.cases.caseView.caseOpened', { + defaultMessage: 'Case opened', +}); + +export const CASE_IN_PROGRESS = i18n.translate('xpack.cases.caseView.caseInProgress', { + defaultMessage: 'Case in progress', +}); + +export const CASE_CLOSED = i18n.translate('xpack.cases.caseView.caseClosed', { + defaultMessage: 'Case closed', +}); + +export const BULK_ACTION_CLOSE_SELECTED = i18n.translate( + 'xpack.cases.caseTable.bulkActions.closeSelectedTitle', + { + defaultMessage: 'Close selected', + } +); + +export const BULK_ACTION_OPEN_SELECTED = i18n.translate( + 'xpack.cases.caseTable.bulkActions.openSelectedTitle', + { + defaultMessage: 'Open selected', + } +); + +export const BULK_ACTION_DELETE_SELECTED = i18n.translate( + 'xpack.cases.caseTable.bulkActions.deleteSelectedTitle', + { + defaultMessage: 'Delete selected', + } +); + +export const BULK_ACTION_MARK_IN_PROGRESS = i18n.translate( + 'xpack.cases.caseTable.bulkActions.markInProgressTitle', + { + defaultMessage: 'Mark in progress', + } +); diff --git a/x-pack/plugins/security_solution/public/cases/components/status/types.ts b/x-pack/plugins/cases/public/components/status/types.ts similarity index 78% rename from x-pack/plugins/security_solution/public/cases/components/status/types.ts rename to x-pack/plugins/cases/public/components/status/types.ts index 5618e7802579d..f8115b8d692b3 100644 --- a/x-pack/plugins/security_solution/public/cases/components/status/types.ts +++ b/x-pack/plugins/cases/public/components/status/types.ts @@ -6,12 +6,7 @@ */ import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; -import { CaseStatuses } from '../../../../../cases/common/api'; - -export const StatusAll = 'all' as const; -type StatusAllType = typeof StatusAll; - -export type CaseStatusWithAllStatus = CaseStatuses | StatusAllType; +import { CaseStatuses, StatusAllType } from '../../../common'; export type AllCaseStatus = Record<StatusAllType, { color: string; label: string }>; diff --git a/x-pack/plugins/cases/public/components/subtitle/__snapshots__/index.test.tsx.snap b/x-pack/plugins/cases/public/components/subtitle/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..7ffd043d16aed --- /dev/null +++ b/x-pack/plugins/cases/public/components/subtitle/__snapshots__/index.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Subtitle it renders 1`] = ` +<Wrapper + className="casesSubtitle" +> + <SubtitleItem> + Test subtitle + </SubtitleItem> +</Wrapper> +`; diff --git a/x-pack/plugins/cases/public/components/subtitle/index.test.tsx b/x-pack/plugins/cases/public/components/subtitle/index.test.tsx new file mode 100644 index 0000000000000..20120edc91937 --- /dev/null +++ b/x-pack/plugins/cases/public/components/subtitle/index.test.tsx @@ -0,0 +1,70 @@ +/* + * 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 { mount, shallow } from 'enzyme'; +import React from 'react'; + +import { TestProviders } from '../../common/mock'; +import { Subtitle } from './index'; + +describe('Subtitle', () => { + test('it renders', () => { + const wrapper = shallow(<Subtitle items="Test subtitle" />); + + expect(wrapper).toMatchSnapshot(); + }); + + test('it renders one subtitle string item', () => { + const wrapper = mount( + <TestProviders> + <Subtitle items="Test subtitle" /> + </TestProviders> + ); + + expect(wrapper.find('.casesSubtitle__item--text').length).toEqual(1); + }); + + test('it renders multiple subtitle string items', () => { + const wrapper = mount( + <TestProviders> + <Subtitle items={['Test subtitle 1', 'Test subtitle 2']} /> + </TestProviders> + ); + + expect(wrapper.find('.casesSubtitle__item--text').length).toEqual(2); + }); + + test('it renders one subtitle React.ReactNode item', () => { + const wrapper = mount( + <TestProviders> + <Subtitle items={<span>{'Test subtitle'}</span>} /> + </TestProviders> + ); + + expect(wrapper.find('.casesSubtitle__item--node').length).toEqual(1); + }); + + test('it renders multiple subtitle React.ReactNode items', () => { + const wrapper = mount( + <TestProviders> + <Subtitle items={[<span>{'Test subtitle 1'}</span>, <span>{'Test subtitle 2'}</span>]} /> + </TestProviders> + ); + + expect(wrapper.find('.casesSubtitle__item--node').length).toEqual(2); + }); + + test('it renders multiple subtitle items of mixed type', () => { + const wrapper = mount( + <TestProviders> + <Subtitle items={['Test subtitle 1', <span>{'Test subtitle 2'}</span>]} /> + </TestProviders> + ); + + expect(wrapper.find('.casesSubtitle__item').length).toEqual(2); + }); +}); diff --git a/x-pack/plugins/cases/public/components/subtitle/index.tsx b/x-pack/plugins/cases/public/components/subtitle/index.tsx new file mode 100644 index 0000000000000..267c564fc498d --- /dev/null +++ b/x-pack/plugins/cases/public/components/subtitle/index.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 React from 'react'; +import styled, { css } from 'styled-components'; + +const Wrapper = styled.div` + ${({ theme }) => css` + margin-top: ${theme.eui.euiSizeS}; + + .casesSubtitle__item { + color: ${theme.eui.euiTextSubduedColor}; + font-size: ${theme.eui.euiFontSizeXS}; + line-height: ${theme.eui.euiLineHeight}; + + @media only screen and (min-width: ${theme.eui.euiBreakpoints.s}) { + display: inline-block; + margin-right: ${theme.eui.euiSize}; + + &:last-child { + margin-right: 0; + } + } + } + `} +`; +Wrapper.displayName = 'Wrapper'; + +interface SubtitleItemProps { + children: string | React.ReactNode; + dataTestSubj?: string; +} + +const SubtitleItem = React.memo<SubtitleItemProps>( + ({ children, dataTestSubj = 'header-panel-subtitle' }) => { + if (typeof children === 'string') { + return ( + <p className="casesSubtitle__item casesSubtitle__item--text" data-test-subj={dataTestSubj}> + {children} + </p> + ); + } else { + return ( + <div + className="casesSubtitle__item casesSubtitle__item--node" + data-test-subj={dataTestSubj} + > + {children} + </div> + ); + } + } +); +SubtitleItem.displayName = 'SubtitleItem'; + +export interface SubtitleProps { + items: string | React.ReactNode | Array<string | React.ReactNode>; +} + +export const Subtitle = React.memo<SubtitleProps>(({ items }) => { + return ( + <Wrapper className="casesSubtitle"> + {Array.isArray(items) ? ( + items.map((item, i) => <SubtitleItem key={i}>{item}</SubtitleItem>) + ) : ( + <SubtitleItem>{items}</SubtitleItem> + )} + </Wrapper> + ); +}); +Subtitle.displayName = 'Subtitle'; diff --git a/x-pack/plugins/security_solution/public/cases/components/tag_list/index.test.tsx b/x-pack/plugins/cases/public/components/tag_list/index.test.tsx similarity index 89% rename from x-pack/plugins/security_solution/public/cases/components/tag_list/index.test.tsx rename to x-pack/plugins/cases/public/components/tag_list/index.test.tsx index eb9cef2d9d1ef..296c4ba0e893b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/tag_list/index.test.tsx +++ b/x-pack/plugins/cases/public/components/tag_list/index.test.tsx @@ -10,17 +10,15 @@ import { mount } from 'enzyme'; import { TagList } from '.'; import { getFormMock } from '../__mock__/form'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; import { waitFor } from '@testing-library/react'; -import { useForm } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'; +import { useForm } from '../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'; import { useGetTags } from '../../containers/use_get_tags'; -jest.mock( - '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' -); +jest.mock('../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'); jest.mock('../../containers/use_get_tags'); jest.mock( - '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider', + '../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider', () => ({ FormDataProvider: ({ children }: { children: ({ tags }: { tags: string[] }) => void }) => children({ tags: ['rad', 'dude'] }), @@ -30,7 +28,6 @@ jest.mock('@elastic/eui', () => { const original = jest.requireActual('@elastic/eui'); return { ...original, - // eslint-disable-next-line react/display-name EuiFieldText: () => <input />, }; }); diff --git a/x-pack/plugins/security_solution/public/cases/components/tag_list/index.tsx b/x-pack/plugins/cases/public/components/tag_list/index.tsx similarity index 99% rename from x-pack/plugins/security_solution/public/cases/components/tag_list/index.tsx rename to x-pack/plugins/cases/public/components/tag_list/index.tsx index 8e47437b37c0e..137d58932b6ef 100644 --- a/x-pack/plugins/security_solution/public/cases/components/tag_list/index.tsx +++ b/x-pack/plugins/cases/public/components/tag_list/index.tsx @@ -19,7 +19,7 @@ import { import styled, { css } from 'styled-components'; import { isEqual } from 'lodash/fp'; import * as i18n from './translations'; -import { Form, FormDataProvider, useForm, getUseField, Field } from '../../../shared_imports'; +import { Form, FormDataProvider, useForm, getUseField, Field } from '../../common/shared_imports'; import { schema } from './schema'; import { useGetTags } from '../../containers/use_get_tags'; diff --git a/x-pack/plugins/security_solution/public/cases/components/tag_list/schema.tsx b/x-pack/plugins/cases/public/components/tag_list/schema.tsx similarity index 86% rename from x-pack/plugins/security_solution/public/cases/components/tag_list/schema.tsx rename to x-pack/plugins/cases/public/components/tag_list/schema.tsx index 281198d51ea7d..d7db17bd97cbd 100644 --- a/x-pack/plugins/security_solution/public/cases/components/tag_list/schema.tsx +++ b/x-pack/plugins/cases/public/components/tag_list/schema.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { FormSchema } from '../../../shared_imports'; +import { FormSchema } from '../../common/shared_imports'; import { schemaTags } from '../create/schema'; export const schema: FormSchema = { diff --git a/x-pack/plugins/security_solution/public/cases/components/tag_list/tags.tsx b/x-pack/plugins/cases/public/components/tag_list/tags.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/tag_list/tags.tsx rename to x-pack/plugins/cases/public/components/tag_list/tags.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/tag_list/translations.ts b/x-pack/plugins/cases/public/components/tag_list/translations.ts similarity index 59% rename from x-pack/plugins/security_solution/public/cases/components/tag_list/translations.ts rename to x-pack/plugins/cases/public/components/tag_list/translations.ts index 4bddfbdbc1a85..54e9cd05039fc 100644 --- a/x-pack/plugins/security_solution/public/cases/components/tag_list/translations.ts +++ b/x-pack/plugins/cases/public/components/tag_list/translations.ts @@ -7,11 +7,8 @@ import { i18n } from '@kbn/i18n'; -export * from '../../translations'; +export * from '../../common/translations'; -export const EDIT_TAGS_ARIA = i18n.translate( - 'xpack.securitySolution.cases.caseView.editTagsLinkAria', - { - defaultMessage: 'click to edit tags', - } -); +export const EDIT_TAGS_ARIA = i18n.translate('xpack.cases.caseView.editTagsLinkAria', { + defaultMessage: 'click to edit tags', +}); diff --git a/x-pack/plugins/cases/public/components/timeline_context/index.tsx b/x-pack/plugins/cases/public/components/timeline_context/index.tsx new file mode 100644 index 0000000000000..727e4b64628d1 --- /dev/null +++ b/x-pack/plugins/cases/public/components/timeline_context/index.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { EuiMarkdownEditorUiPlugin, EuiMarkdownAstNodePosition } from '@elastic/eui'; +import { Plugin } from 'unified'; +/** + * @description - manage the plugins, hooks, and ui components needed to enable timeline functionality within the cases plugin + * @TODO - To better encapsulate the timeline logic needed by cases, we are managing it in this top level context. + * This helps us avoid any prop drilling and makes it much easier later on to remove this logic when timeline becomes it's own plugin. + */ + +// TODO: copied from 'use_insert_timeline' in security_solution till timeline moved into it's own plugin. +interface UseInsertTimelineReturn { + handleOnTimelineChange: (title: string, id: string | null, graphEventId?: string) => void; +} + +interface TimelineProcessingPluginRendererProps { + id: string | null; + title: string; + graphEventId?: string; + type: 'timeline'; + [key: string]: string | null | undefined; +} + +export interface CasesTimelineIntegration { + editor_plugins: { + parsingPlugin: Plugin; + processingPluginRenderer: React.FC< + TimelineProcessingPluginRendererProps & { position: EuiMarkdownAstNodePosition } + >; + uiPlugin: EuiMarkdownEditorUiPlugin; + }; + hooks: { + useInsertTimeline: ( + value: string, + onChange: (newValue: string) => void + ) => UseInsertTimelineReturn; + }; + ui?: { + renderInvestigateInTimelineActionComponent?: (alertIds: string[]) => JSX.Element; + renderTimelineDetailsPanel?: () => JSX.Element; + }; +} + +// This context is available to all children of the stateful_event component where the provider is currently set +export const CasesTimelineIntegrationContext = React.createContext<CasesTimelineIntegration | null>( + null +); + +export const CasesTimelineIntegrationProvider: React.FC<{ + timelineIntegration?: CasesTimelineIntegration; +}> = ({ children, timelineIntegration }) => { + const [activeTimelineIntegration] = useState(timelineIntegration ?? null); + + return ( + <CasesTimelineIntegrationContext.Provider value={activeTimelineIntegration}> + {children} + </CasesTimelineIntegrationContext.Provider> + ); +}; diff --git a/x-pack/plugins/cases/public/components/timeline_context/use_timeline_context.ts b/x-pack/plugins/cases/public/components/timeline_context/use_timeline_context.ts new file mode 100644 index 0000000000000..d0f9417c20ab1 --- /dev/null +++ b/x-pack/plugins/cases/public/components/timeline_context/use_timeline_context.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useContext } from 'react'; +import { CasesTimelineIntegrationContext } from '.'; + +export const useTimelineContext = () => { + return useContext(CasesTimelineIntegrationContext); +}; diff --git a/x-pack/plugins/cases/public/components/use_create_case_modal/create_case_modal.test.tsx b/x-pack/plugins/cases/public/components/use_create_case_modal/create_case_modal.test.tsx new file mode 100644 index 0000000000000..661a0eedfeae4 --- /dev/null +++ b/x-pack/plugins/cases/public/components/use_create_case_modal/create_case_modal.test.tsx @@ -0,0 +1,76 @@ +/* + * 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 { mount } from 'enzyme'; + +import { CreateCaseModal } from './create_case_modal'; +import { TestProviders } from '../../common/mock'; +import { getCreateCaseLazy as getCreateCase } from '../../methods'; + +jest.mock('../../methods'); +const getCreateCaseMock = getCreateCase as jest.Mock; +const onCloseCaseModal = jest.fn(); +const onSuccess = jest.fn(); +const defaultProps = { + isModalOpen: true, + onCloseCaseModal, + onSuccess, +}; + +describe('CreateCaseModal', () => { + beforeEach(() => { + jest.resetAllMocks(); + getCreateCaseMock.mockReturnValue(<></>); + }); + + it('renders', () => { + const wrapper = mount( + <TestProviders> + <CreateCaseModal {...defaultProps} /> + </TestProviders> + ); + + expect(wrapper.find(`[data-test-subj='create-case-modal']`).exists()).toBeTruthy(); + }); + + it('it does not render the modal isModalOpen=false ', () => { + const wrapper = mount( + <TestProviders> + <CreateCaseModal {...defaultProps} isModalOpen={false} /> + </TestProviders> + ); + + expect(wrapper.find(`[data-test-subj='create-case-modal']`).exists()).toBeFalsy(); + }); + + it('Closing modal calls onCloseCaseModal', () => { + const wrapper = mount( + <TestProviders> + <CreateCaseModal {...defaultProps} /> + </TestProviders> + ); + + wrapper.find('.euiModal__closeIcon').first().simulate('click'); + expect(onCloseCaseModal).toBeCalled(); + }); + + it('pass the correct props to getCreateCase method', () => { + mount( + <TestProviders> + <CreateCaseModal {...defaultProps} /> + </TestProviders> + ); + + expect(getCreateCaseMock.mock.calls[0][0]).toEqual( + expect.objectContaining({ + onSuccess, + onCancel: onCloseCaseModal, + }) + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx b/x-pack/plugins/cases/public/components/use_create_case_modal/create_case_modal.tsx similarity index 54% rename from x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx rename to x-pack/plugins/cases/public/components/use_create_case_modal/create_case_modal.tsx index 4b5eb00d95a80..e78b432b3a27c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx +++ b/x-pack/plugins/cases/public/components/use_create_case_modal/create_case_modal.tsx @@ -6,57 +6,41 @@ */ import React, { memo } from 'react'; -import styled from 'styled-components'; import { EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui'; -import { FormContext } from '../create/form_context'; -import { CreateCaseForm } from '../create/form'; -import { SubmitCaseButton } from '../create/submit_button'; import { Case } from '../../containers/types'; -import * as i18n from '../../translations'; -import { CaseType } from '../../../../../cases/common/api'; +import * as i18n from '../../common/translations'; +import { CaseType } from '../../../common'; +import { getCreateCaseLazy as getCreateCase } from '../../methods'; export interface CreateCaseModalProps { + caseType?: CaseType; + hideConnectorServiceNowSir?: boolean; isModalOpen: boolean; onCloseCaseModal: () => void; onSuccess: (theCase: Case) => Promise<void>; - caseType?: CaseType; - hideConnectorServiceNowSir?: boolean; } -const Container = styled.div` - ${({ theme }) => ` - margin-top: ${theme.eui.euiSize}; - text-align: right; - `} -`; - const CreateModalComponent: React.FC<CreateCaseModalProps> = ({ + caseType = CaseType.individual, + hideConnectorServiceNowSir, isModalOpen, onCloseCaseModal, onSuccess, - caseType = CaseType.individual, - hideConnectorServiceNowSir = false, }) => { return isModalOpen ? ( - <EuiModal onClose={onCloseCaseModal} data-test-subj="all-cases-modal"> + <EuiModal onClose={onCloseCaseModal} data-test-subj="create-case-modal"> <EuiModalHeader> <EuiModalHeaderTitle>{i18n.CREATE_TITLE}</EuiModalHeaderTitle> </EuiModalHeader> <EuiModalBody> - <FormContext - hideConnectorServiceNowSir={hideConnectorServiceNowSir} - caseType={caseType} - onSuccess={onSuccess} - > - <CreateCaseForm - withSteps={false} - hideConnectorServiceNowSir={hideConnectorServiceNowSir} - /> - <Container> - <SubmitCaseButton /> - </Container> - </FormContext> + {getCreateCase({ + caseType, + hideConnectorServiceNowSir, + onCancel: onCloseCaseModal, + onSuccess, + withSteps: false, + })} </EuiModalBody> </EuiModal> ) : null; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.test.tsx b/x-pack/plugins/cases/public/components/use_create_case_modal/index.test.tsx similarity index 66% rename from x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.test.tsx rename to x-pack/plugins/cases/public/components/use_create_case_modal/index.test.tsx index 5174c03e56e0b..b227dd4b898b2 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.test.tsx +++ b/x-pack/plugins/cases/public/components/use_create_case_modal/index.test.tsx @@ -5,63 +5,15 @@ * 2.0. */ -/* eslint-disable react/display-name */ -import React, { ReactNode } from 'react'; +import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { render } from '@testing-library/react'; -import { useKibana } from '../../../common/lib/kibana'; -import '../../../common/mock/match_media'; +import { useKibana } from '../../common/lib/kibana'; import { useCreateCaseModal, UseCreateCaseModalProps, UseCreateCaseModalReturnedValues } from '.'; -import { mockTimelineModel, TestProviders } from '../../../common/mock'; -import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; - -jest.mock('../../../common/lib/kibana'); -jest.mock('../create/form_context', () => { - return { - FormContext: ({ - children, - onSuccess, - }: { - children: ReactNode; - onSuccess: ({ id }: { id: string }) => Promise<void>; - }) => { - return ( - <> - <button - type="button" - data-test-subj="form-context-on-success" - onClick={async () => { - await onSuccess({ id: 'case-id' }); - }} - > - {'Form submit'} - </button> - {children} - </> - ); - }, - }; -}); - -jest.mock('../create/form', () => { - return { - CreateCaseForm: () => { - return <>{'form'}</>; - }, - }; -}); - -jest.mock('../create/submit_button', () => { - return { - SubmitCaseButton: () => { - return <>{'Submit'}</>; - }, - }; -}); +import { TestProviders } from '../../common/mock'; -jest.mock('../../../common/hooks/use_selector'); +jest.mock('../../common/lib/kibana'); const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>; const onCaseCreated = jest.fn(); @@ -72,7 +24,6 @@ describe('useCreateCaseModal', () => { beforeEach(() => { navigateToApp = jest.fn(); useKibanaMock().services.application.navigateToApp = navigateToApp; - (useDeepEqualSelector as jest.Mock).mockReturnValue(mockTimelineModel); }); it('init', async () => { @@ -148,7 +99,7 @@ describe('useCreateCaseModal', () => { render(<TestProviders>{modal}</TestProviders>); act(() => { - userEvent.click(screen.getByText('Form submit')); + result.current.modal.props.onSuccess({ id: 'case-id' }); }); expect(result.current.isModalOpen).toBe(false); diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx b/x-pack/plugins/cases/public/components/use_create_case_modal/index.tsx similarity index 91% rename from x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx rename to x-pack/plugins/cases/public/components/use_create_case_modal/index.tsx index 5d2f54bd1f142..7ad85773a7917 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx +++ b/x-pack/plugins/cases/public/components/use_create_case_modal/index.tsx @@ -6,8 +6,7 @@ */ import React, { useState, useCallback, useMemo } from 'react'; -import { CaseType } from '../../../../../cases/common/api'; -import { Case } from '../../containers/types'; +import { Case, CaseType } from '../../../common'; import { CreateCaseModal } from './create_case_modal'; export interface UseCreateCaseModalProps { @@ -38,7 +37,7 @@ export const useCreateCaseModal = ({ [onCaseCreated, closeModal] ); - const state = useMemo( + return useMemo( () => ({ modal: ( <CreateCaseModal @@ -55,6 +54,4 @@ export const useCreateCaseModal = ({ }), [caseType, closeModal, hideConnectorServiceNowSir, isModalOpen, onSuccess, openModal] ); - - return state; }; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/helpers.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/helpers.tsx similarity index 92% rename from x-pack/plugins/security_solution/public/cases/components/use_push_to_service/helpers.tsx rename to x-pack/plugins/cases/public/components/use_push_to_service/helpers.tsx index 30d2cb720c031..302e45f5e7e70 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/helpers.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/helpers.tsx @@ -19,7 +19,7 @@ export const getLicenseError = () => ({ description: ( <FormattedMessage defaultMessage="Opening cases in external systems is available when you have the {appropriateLicense}, are using a {cloud}, or are testing out a Free Trial." - id="xpack.securitySolution.cases.caseView.pushToServiceDisableByLicenseDescription" + id="xpack.cases.caseView.pushToServiceDisableByLicenseDescription" values={{ appropriateLicense: ( <EuiLink href="https://www.elastic.co/subscriptions" target="_blank"> @@ -42,7 +42,7 @@ export const getKibanaConfigError = () => ({ description: ( <FormattedMessage defaultMessage="The kibana.yml file is configured to only allow specific connectors. To enable opening a case in external systems, add .[actionTypeId] (ex: .servicenow | .jira) to the xpack.actions.enabledActiontypes setting. For more information, see {link}." - id="xpack.securitySolution.cases.caseView.pushToServiceDisableByConfigDescription" + id="xpack.cases.caseView.pushToServiceDisableByConfigDescription" values={{ link: ( <EuiLink href="#" target="_blank"> diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx rename to x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx index c058473bbfe3f..d808234bcad36 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.test.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx @@ -5,20 +5,18 @@ * 2.0. */ -/* eslint-disable react/display-name */ import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; -import '../../../common/mock/match_media'; +import '../../common/mock/match_media'; import { usePushToService, ReturnUsePushToService, UsePushToService } from '.'; -import { TestProviders } from '../../../common/mock'; - -import { CaseStatuses } from '../../../../../cases/common/api'; +import { TestProviders } from '../../common/mock'; +import { CaseStatuses } from '../../../common'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; import { basicPush, actionLicenses } from '../../containers/mock'; import { useGetActionLicense } from '../../containers/use_get_action_license'; import { connectorsMock } from '../../containers/configure/mock'; -import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; +import { ConnectorTypes } from '../../../common/api/connectors'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -31,7 +29,6 @@ jest.mock('react-router-dom', () => { }; }); -jest.mock('../../../common/components/link_to'); jest.mock('../../containers/use_get_action_license'); jest.mock('../../containers/use_post_push_to_service'); jest.mock('../../containers/configure/api'); @@ -67,10 +64,14 @@ describe('usePushToService', () => { caseId, caseServices, caseStatus: CaseStatuses.open, + configureCasesNavigation: { + href: 'href', + onClick: jest.fn(), + }, connectors: connectorsMock, + isValidConnector: true, updateCase, userCanCrud: true, - isValidConnector: true, }; beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx similarity index 83% rename from x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.tsx rename to x-pack/plugins/cases/public/components/use_push_to_service/index.tsx index d83ddb08b51d2..a4ce8e3d92522 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/index.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx @@ -8,24 +8,22 @@ import { EuiButton, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useCallback, useMemo } from 'react'; -import { useHistory } from 'react-router-dom'; import { Case } from '../../containers/types'; import { useGetActionLicense } from '../../containers/use_get_action_license'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; -import { getConfigureCasesUrl, useFormatUrl } from '../../../common/components/link_to'; import { CaseCallOut } from '../callout'; import { getLicenseError, getKibanaConfigError } from './helpers'; import * as i18n from './translations'; -import { CaseConnector, ActionConnector, CaseStatuses } from '../../../../../cases/common/api'; +import { CaseConnector, ActionConnector, CaseStatuses } from '../../../common'; import { CaseServices } from '../../containers/use_get_case_user_actions'; -import { LinkAnchor } from '../../../common/components/links'; -import { SecurityPageName } from '../../../app/types'; +import { CasesNavigation, LinkAnchor } from '../links'; import { ErrorMessage } from '../callout/types'; export interface UsePushToService { caseId: string; caseStatus: string; + configureCasesNavigation: CasesNavigation; connector: CaseConnector; caseServices: CaseServices; connectors: ActionConnector[]; @@ -40,6 +38,7 @@ export interface ReturnUsePushToService { } export const usePushToService = ({ + configureCasesNavigation: { onClick, href }, connector, caseId, caseServices, @@ -49,8 +48,6 @@ export const usePushToService = ({ userCanCrud, isValidConnector, }: UsePushToService): ReturnUsePushToService => { - const history = useHistory(); - const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.case); const { isLoading, pushCaseToExternalService } = usePostPushToService(); const { isLoading: loadingLicense, actionLicense } = useGetActionLicense(); @@ -68,14 +65,6 @@ export const usePushToService = ({ } }, [caseId, connector, pushCaseToExternalService, updateCase]); - const goToConfigureCases = useCallback( - (ev) => { - ev.preventDefault(); - history.push(getConfigureCasesUrl(urlSearch)); - }, - [history, urlSearch] - ); - const errorsMsg = useMemo(() => { let errors: ErrorMessage[] = []; if (actionLicense != null && !actionLicense.enabledInLicense) { @@ -90,14 +79,10 @@ export const usePushToService = ({ description: ( <FormattedMessage defaultMessage="To open and update cases in external systems, you must configure a {link}." - id="xpack.securitySolution.cases.caseView.pushToServiceDisableByNoConnectors" + id="xpack.cases.caseView.pushToServiceDisableByNoConnectors" values={{ link: ( - <LinkAnchor - onClick={goToConfigureCases} - href={formatUrl(getConfigureCasesUrl())} - target="_blank" - > + <LinkAnchor onClick={onClick} href={href} target="_blank"> {i18n.LINK_CONNECTOR_CONFIGURE} </LinkAnchor> ), @@ -115,7 +100,7 @@ export const usePushToService = ({ description: ( <FormattedMessage defaultMessage="To open and update cases in external systems, you must select an external incident management system for this case." - id="xpack.securitySolution.cases.caseView.pushToServiceDisableByNoCaseConfigDescription" + id="xpack.cases.caseView.pushToServiceDisableByNoCaseConfigDescription" /> ), }, @@ -129,7 +114,7 @@ export const usePushToService = ({ description: ( <FormattedMessage defaultMessage="The connector used to send updates to external service has been deleted. To update cases in external systems, select a different connector or create a new one." - id="xpack.securitySolution.cases.caseView.pushToServiceDisableByInvalidConnector" + id="xpack.cases.caseView.pushToServiceDisableByInvalidConnector" /> ), errorType: 'danger', @@ -145,7 +130,7 @@ export const usePushToService = ({ description: ( <FormattedMessage defaultMessage="Closed cases cannot be sent to external systems. Reopen the case if you want to open or update it in an external system." - id="xpack.securitySolution.cases.caseView.pushToServiceDisableBecauseCaseClosedDescription" + id="xpack.cases.caseView.pushToServiceDisableBecauseCaseClosedDescription" /> ), }, @@ -156,7 +141,7 @@ export const usePushToService = ({ } return errors; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [actionLicense, caseStatus, connectors.length, connector, loadingLicense, urlSearch]); + }, [actionLicense, caseStatus, connectors.length, connector, loadingLicense]); const pushToServiceButton = useMemo(() => { return ( diff --git a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/translations.ts b/x-pack/plugins/cases/public/components/use_push_to_service/translations.ts similarity index 57% rename from x-pack/plugins/security_solution/public/cases/components/use_push_to_service/translations.ts rename to x-pack/plugins/cases/public/components/use_push_to_service/translations.ts index 28a7312328b78..fd6faa634e053 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_push_to_service/translations.ts +++ b/x-pack/plugins/cases/public/components/use_push_to_service/translations.ts @@ -8,19 +8,19 @@ import { i18n } from '@kbn/i18n'; export const ERROR_PUSH_SERVICE_CALLOUT_TITLE = i18n.translate( - 'xpack.securitySolution.cases.caseView.errorsPushServiceCallOutTitle', + 'xpack.cases.caseView.errorsPushServiceCallOutTitle', { defaultMessage: 'To send cases to external systems, you need to:', } ); export const PUSH_THIRD = (thirdParty: string) => { if (thirdParty === 'none') { - return i18n.translate('xpack.securitySolution.cases.caseView.pushThirdPartyIncident', { + return i18n.translate('xpack.cases.caseView.pushThirdPartyIncident', { defaultMessage: 'Push as external incident', }); } - return i18n.translate('xpack.securitySolution.cases.caseView.pushNamedIncident', { + return i18n.translate('xpack.cases.caseView.pushNamedIncident', { values: { thirdParty }, defaultMessage: 'Push as { thirdParty } incident', }); @@ -28,68 +28,62 @@ export const PUSH_THIRD = (thirdParty: string) => { export const UPDATE_THIRD = (thirdParty: string) => { if (thirdParty === 'none') { - return i18n.translate('xpack.securitySolution.cases.caseView.updateThirdPartyIncident', { + return i18n.translate('xpack.cases.caseView.updateThirdPartyIncident', { defaultMessage: 'Update external incident', }); } - return i18n.translate('xpack.securitySolution.cases.caseView.updateNamedIncident', { + return i18n.translate('xpack.cases.caseView.updateNamedIncident', { values: { thirdParty }, defaultMessage: 'Update { thirdParty } incident', }); }; export const PUSH_DISABLE_BY_NO_CONFIG_TITLE = i18n.translate( - 'xpack.securitySolution.cases.caseView.pushToServiceDisableByNoConfigTitle', + 'xpack.cases.caseView.pushToServiceDisableByNoConfigTitle', { defaultMessage: 'Configure external connector', } ); export const PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE = i18n.translate( - 'xpack.securitySolution.cases.caseView.pushToServiceDisableByNoCaseConfigTitle', + 'xpack.cases.caseView.pushToServiceDisableByNoCaseConfigTitle', { defaultMessage: 'Select external connector', } ); export const PUSH_DISABLE_BECAUSE_CASE_CLOSED_TITLE = i18n.translate( - 'xpack.securitySolution.cases.caseView.pushToServiceDisableBecauseCaseClosedTitle', + 'xpack.cases.caseView.pushToServiceDisableBecauseCaseClosedTitle', { defaultMessage: 'Reopen the case', } ); export const PUSH_DISABLE_BY_KIBANA_CONFIG_TITLE = i18n.translate( - 'xpack.securitySolution.cases.caseView.pushToServiceDisableByConfigTitle', + 'xpack.cases.caseView.pushToServiceDisableByConfigTitle', { defaultMessage: 'Enable external service in Kibana configuration file', } ); export const PUSH_DISABLE_BY_LICENSE_TITLE = i18n.translate( - 'xpack.securitySolution.cases.caseView.pushToServiceDisableByLicenseTitle', + 'xpack.cases.caseView.pushToServiceDisableByLicenseTitle', { defaultMessage: 'Upgrade to an appropriate license', } ); -export const LINK_CLOUD_DEPLOYMENT = i18n.translate( - 'xpack.securitySolution.cases.caseView.cloudDeploymentLink', - { - defaultMessage: 'cloud deployment', - } -); +export const LINK_CLOUD_DEPLOYMENT = i18n.translate('xpack.cases.caseView.cloudDeploymentLink', { + defaultMessage: 'cloud deployment', +}); -export const LINK_APPROPRIATE_LICENSE = i18n.translate( - 'xpack.securitySolution.cases.caseView.appropiateLicense', - { - defaultMessage: 'appropriate license', - } -); +export const LINK_APPROPRIATE_LICENSE = i18n.translate('xpack.cases.caseView.appropiateLicense', { + defaultMessage: 'appropriate license', +}); export const LINK_CONNECTOR_CONFIGURE = i18n.translate( - 'xpack.securitySolution.cases.caseView.connectorConfigureLink', + 'xpack.cases.caseView.connectorConfigureLink', { defaultMessage: 'connector', } diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/helpers.test.tsx similarity index 82% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.test.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/helpers.test.tsx index a62c6c0ef682d..b49a010cff38f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.test.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/helpers.test.tsx @@ -8,9 +8,14 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../../../cases/common/api'; +import { CaseStatuses } from '../../../common'; import { basicPush, getUserAction } from '../../containers/mock'; -import { getLabelTitle, getPushedServiceLabelTitle, getConnectorLabelTitle } from './helpers'; +import { + getLabelTitle, + getPushedServiceLabelTitle, + getConnectorLabelTitle, + toStringArray, +} from './helpers'; import { connectorsMock } from '../../containers/configure/mock'; import * as i18n from './translations'; @@ -182,4 +187,38 @@ describe('User action tree helpers', () => { expect(result).toEqual('changed connector field'); }); + + describe('toStringArray', () => { + const circularReference = { otherData: 123, circularReference: undefined }; + // @ts-ignore testing catch on circular reference + circularReference.circularReference = circularReference; + it('handles all data types in an array', () => { + const value = [1, true, { a: 1 }, circularReference, 'yeah', 100n, null]; + const res = toStringArray(value); + expect(res).toEqual(['1', 'true', '{"a":1}', 'Invalid Object', 'yeah', '100']); + }); + it('handles null', () => { + const value = null; + const res = toStringArray(value); + expect(res).toEqual([]); + }); + + it('handles object', () => { + const value = { a: true }; + const res = toStringArray(value); + expect(res).toEqual([JSON.stringify(value)]); + }); + + it('handles Invalid Object', () => { + const value = circularReference; + const res = toStringArray(value); + expect(res).toEqual(['Invalid Object']); + }); + + it('handles unexpected value', () => { + const value = 100n; + const res = toStringArray(value); + expect(res).toEqual(['100']); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.tsx b/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx similarity index 72% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx index cc8d560f91b1f..024fa4d494908 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/helpers.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx @@ -6,16 +6,14 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiCommentProps } from '@elastic/eui'; -import { isObject, get, isString, isNumber, isEmpty } from 'lodash'; -import React, { useMemo } from 'react'; +import React from 'react'; -import { SearchResponse } from 'elasticsearch'; import { CaseFullExternalService, ActionConnector, CaseStatuses, CommentType, -} from '../../../../../cases/common/api'; +} from '../../../common'; import { CaseUserActions } from '../../containers/types'; import { CaseServices } from '../../containers/use_get_case_user_actions'; import { parseString } from '../../containers/utils'; @@ -28,15 +26,6 @@ import { Status, statuses } from '../status'; import { UserActionShowAlert } from './user_action_show_alert'; import * as i18n from './translations'; import { AlertCommentEvent } from './user_action_alert_comment_event'; -import { InvestigateInTimelineAction } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_timeline_action'; -import { Ecs } from '../../../../common/ecs'; -import { TimelineNonEcsData } from '../../../../common/search_strategy'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; -import { SourcererScopeName } from '../../../common/store/sourcerer/model'; -import { buildAlertsQuery } from '../case_view/helpers'; -import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query'; -import { KibanaServices } from '../../../common/lib/kibana'; -import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../common/constants'; interface LabelTitle { action: CaseUserActions; @@ -173,10 +162,12 @@ const getUpdateActionIcon = (actionField: string): string => { export const getUpdateAction = ({ action, + getCaseDetailHrefWithCommentId, label, handleOutlineComment, }: { action: CaseUserActions; + getCaseDetailHrefWithCommentId: (commentId: string) => string; label: string | JSX.Element; handleOutlineComment: (id: string) => void; }): EuiCommentProps => ({ @@ -194,7 +185,10 @@ export const getUpdateAction = ({ actions: ( <EuiFlexGroup> <EuiFlexItem> - <UserActionCopyLink id={action.actionId} /> + <UserActionCopyLink + getCaseDetailHrefWithCommentId={getCaseDetailHrefWithCommentId} + id={action.actionId} + /> </EuiFlexItem> {action.action === 'update' && action.commentId != null && ( <EuiFlexItem> @@ -208,6 +202,9 @@ export const getUpdateAction = ({ export const getAlertAttachment = ({ action, alertId, + getCaseDetailHrefWithCommentId, + getRuleDetailsHref, + onRuleDetailsClick, index, loadingAlertData, ruleId, @@ -215,6 +212,9 @@ export const getAlertAttachment = ({ onShowAlertDetails, }: { action: CaseUserActions; + getCaseDetailHrefWithCommentId: (commentId: string) => string; + getRuleDetailsHref: (ruleId: string | null | undefined) => string; + onRuleDetailsClick?: (ruleId: string | null | undefined) => void; onShowAlertDetails: (alertId: string, index: string) => void; alertId: string; index: string; @@ -234,7 +234,9 @@ export const getAlertAttachment = ({ event: ( <AlertCommentEvent alertId={alertId} + getRuleDetailsHref={getRuleDetailsHref} loadingAlertData={loadingAlertData} + onRuleDetailsClick={onRuleDetailsClick} ruleId={ruleId} ruleName={ruleName} commentType={CommentType.alert} @@ -246,7 +248,10 @@ export const getAlertAttachment = ({ actions: ( <EuiFlexGroup> <EuiFlexItem> - <UserActionCopyLink id={action.actionId} /> + <UserActionCopyLink + id={action.actionId} + getCaseDetailHrefWithCommentId={getCaseDetailHrefWithCommentId} + /> </EuiFlexItem> <EuiFlexItem> <UserActionShowAlert @@ -285,7 +290,7 @@ export const toStringArray = (value: unknown): string[] => { }, []); } else if (value == null) { return []; - } else if (!Array.isArray(value) && typeof value === 'object') { + } else if (typeof value === 'object') { try { return [JSON.stringify(value)]; } catch { @@ -296,54 +301,25 @@ export const toStringArray = (value: unknown): string[] => { } }; -export const formatAlertToEcsSignal = (alert: {}): Ecs => - Object.keys(alert).reduce<Ecs>((accumulator, key) => { - const item = get(alert, key); - if (item != null && isObject(item)) { - return { ...accumulator, [key]: formatAlertToEcsSignal(item) }; - } else if (Array.isArray(item) || isString(item) || isNumber(item)) { - return { ...accumulator, [key]: toStringArray(item) }; - } - return accumulator; - }, {} as Ecs); - -const EMPTY_ARRAY: TimelineNonEcsData[] = []; export const getGeneratedAlertsAttachment = ({ action, alertIds, + getCaseDetailHrefWithCommentId, + getRuleDetailsHref, + onRuleDetailsClick, + renderInvestigateInTimelineActionComponent, ruleId, ruleName, }: { action: CaseUserActions; alertIds: string[]; + getCaseDetailHrefWithCommentId: (commentId: string) => string; + getRuleDetailsHref: (ruleId: string | null | undefined) => string; + onRuleDetailsClick?: (ruleId: string | null | undefined) => void; + renderInvestigateInTimelineActionComponent?: (alertIds: string[]) => JSX.Element; ruleId: string; ruleName: string; }): EuiCommentProps => { - const fetchEcsAlertsData = async (fetchAlertIds?: string[]): Promise<Ecs[]> => { - if (isEmpty(fetchAlertIds)) { - return []; - } - const alertResponse = await KibanaServices.get().http.fetch< - SearchResponse<{ '@timestamp': string; [key: string]: unknown }> - >(DETECTION_ENGINE_QUERY_SIGNALS_URL, { - method: 'POST', - body: JSON.stringify(buildAlertsQuery(fetchAlertIds ?? [])), - }); - return ( - alertResponse?.hits.hits.reduce<Ecs[]>( - (acc, { _id, _index, _source }) => [ - ...acc, - { - ...formatAlertToEcsSignal(_source as {}), - _id, - _index, - timestamp: _source['@timestamp'], - }, - ], - [] - ) ?? [] - ); - }; return { username: <EuiIcon type="logoSecurity" size="m" />, className: 'comment-alert', @@ -351,6 +327,8 @@ export const getGeneratedAlertsAttachment = ({ event: ( <AlertCommentEvent alertId={alertIds[0]} + getRuleDetailsHref={getRuleDetailsHref} + onRuleDetailsClick={onRuleDetailsClick} ruleId={ruleId} ruleName={ruleName} alertsCount={alertIds.length} @@ -363,18 +341,14 @@ export const getGeneratedAlertsAttachment = ({ actions: ( <EuiFlexGroup> <EuiFlexItem> - <UserActionCopyLink id={action.actionId} /> - </EuiFlexItem> - <EuiFlexItem> - <InvestigateInTimelineAction - ariaLabel={i18n.SEND_ALERT_TO_TIMELINE} - alertIds={alertIds} - key="investigate-in-timeline" - ecsRowData={null} - fetchEcsAlertsData={fetchEcsAlertsData} - nonEcsRowData={EMPTY_ARRAY} + <UserActionCopyLink + getCaseDetailHrefWithCommentId={getCaseDetailHrefWithCommentId} + id={action.actionId} /> </EuiFlexItem> + {renderInvestigateInTimelineActionComponent ? ( + <EuiFlexItem>{renderInvestigateInTimelineActionComponent(alertIds)}</EuiFlexItem> + ) : null} </EuiFlexGroup> ), }; @@ -389,15 +363,6 @@ interface Signal { }; } -interface SignalHit { - _id: string; - _index: string; - _source: { - '@timestamp': string; - signal: Signal; - }; -} - export interface Alert { _id: string; _index: string; @@ -405,32 +370,3 @@ export interface Alert { signal: Signal; [key: string]: unknown; } - -export const useFetchAlertData = (alertIds: string[]): [boolean, Record<string, Ecs>] => { - const { selectedPatterns } = useSourcererScope(SourcererScopeName.detections); - const alertsQuery = useMemo(() => buildAlertsQuery(alertIds), [alertIds]); - - const { loading: isLoadingAlerts, data: alertsData } = useQueryAlerts<SignalHit, unknown>( - alertsQuery, - selectedPatterns[0] - ); - - const alerts = useMemo( - () => - alertsData?.hits.hits.reduce<Record<string, Ecs>>( - (acc, { _id, _index, _source }) => ({ - ...acc, - [_id]: { - ...formatAlertToEcsSignal(_source), - _id, - _index, - timestamp: _source['@timestamp'], - }, - }), - {} - ) ?? {}, - [alertsData?.hits.hits] - ); - - return [isLoadingAlerts, alerts]; -}; diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx similarity index 77% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.test.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx index a5c6b2d50f4a2..b30726bf23b25 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.test.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx @@ -14,7 +14,8 @@ import { getFormMock, useFormMock, useFormDataMock } from '../__mock__/form'; import { useUpdateComment } from '../../containers/use_update_comment'; import { basicCase, basicPush, getUserAction } from '../../containers/mock'; import { UserActionTree } from '.'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; +import { Ecs } from '../../../common'; const fetchUserActions = jest.fn(); const onUpdateField = jest.fn(); @@ -25,13 +26,21 @@ const defaultProps = { caseServices: {}, caseUserActions: [], connectors: [], + getCaseDetailHrefWithCommentId: jest.fn(), + getRuleDetailsHref: jest.fn(), + onRuleDetailsClick: jest.fn(), data: basicCase, fetchUserActions, isLoadingDescription: false, isLoadingUserActions: false, onUpdateField, + selectedAlertPatterns: ['some-test-pattern'], updateCase, userCanCrud: true, + useFetchAlertData: (): [boolean, Record<string, Ecs>] => [ + false, + { 'some-id': { _id: 'some-id' } }, + ], alerts: {}, onShowAlertDetails, }; @@ -40,14 +49,13 @@ jest.mock('../../containers/use_update_comment'); jest.mock('./user_action_timestamp'); const patchComment = jest.fn(); -// FLAKY: https://github.com/elastic/kibana/issues/96362 -describe.skip('UserActionTree ', () => { + +describe(`UserActionTree`, () => { const sampleData = { content: 'what a great comment update', }; beforeEach(() => { jest.clearAllMocks(); - jest.resetAllMocks(); useUpdateCommentMock.mockImplementation(() => ({ isLoadingIds: [], patchComment, @@ -69,7 +77,7 @@ describe.skip('UserActionTree ', () => { </Router> </TestProviders> ); - expect(wrapper.find(`[data-test-subj="user-actions-loading"]`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="user-actions-loading"]`).exists()).toEqual(true); expect(wrapper.find(`[data-test-subj="user-action-avatar"]`).first().prop('name')).toEqual( defaultProps.data.createdBy.fullName @@ -106,10 +114,8 @@ describe.skip('UserActionTree ', () => { </Router> </TestProviders> ); - await waitFor(() => { - expect(wrapper.find(`[data-test-subj="top-footer"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="bottom-footer"]`).exists()).toBeTruthy(); - }); + expect(wrapper.find(`[data-test-subj="top-footer"]`).exists()).toEqual(true); + expect(wrapper.find(`[data-test-subj="bottom-footer"]`).exists()).toEqual(true); }); it('Renders service now update line with top only when push is up to date', async () => { @@ -135,12 +141,9 @@ describe.skip('UserActionTree ', () => { </Router> </TestProviders> ); - await waitFor(() => { - expect(wrapper.find(`[data-test-subj="top-footer"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="bottom-footer"]`).exists()).toBeFalsy(); - }); + expect(wrapper.find(`[data-test-subj="top-footer"]`).exists()).toEqual(true); + expect(wrapper.find(`[data-test-subj="bottom-footer"]`).exists()).toEqual(false); }); - it('Outlines comment when update move to link is clicked', async () => { const ourActions = [getUserAction(['comment'], 'create'), getUserAction(['comment'], 'update')]; const props = { @@ -155,32 +158,29 @@ describe.skip('UserActionTree ', () => { </Router> </TestProviders> ); - - await waitFor(() => { - expect( - wrapper - .find(`[data-test-subj="comment-create-action-${props.data.comments[0].id}"]`) - .first() - .hasClass('outlined') - ).toBeFalsy(); - + expect( wrapper - .find( - `[data-test-subj="comment-update-action-${ourActions[1].actionId}"] [data-test-subj="move-to-link-${props.data.comments[0].id}"]` - ) + .find(`[data-test-subj="comment-create-action-${props.data.comments[0].id}"]`) .first() - .simulate('click'); + .hasClass('outlined') + ).toEqual(false); - wrapper.update(); + wrapper + .find( + `[data-test-subj="comment-update-action-${ourActions[1].actionId}"] [data-test-subj="move-to-link-${props.data.comments[0].id}"]` + ) + .first() + .simulate('click'); + + await waitFor(() => { expect( wrapper .find(`[data-test-subj="comment-create-action-${props.data.comments[0].id}"]`) .first() .hasClass('outlined') - ).toBeTruthy(); + ).toEqual(true); }); }); - it('Switches to markdown when edit is clicked and back to panel when canceled', async () => { const ourActions = [getUserAction(['comment'], 'create')]; const props = { @@ -196,46 +196,27 @@ describe.skip('UserActionTree ', () => { </TestProviders> ); - await waitFor(() => { - expect( - wrapper - .find( - `[data-test-subj="comment-create-action-${props.data.comments[0].id}"] [data-test-subj="user-action-markdown-form"]` - ) - .exists() - ).toEqual(false); - - wrapper - .find( - `[data-test-subj="comment-create-action-${props.data.comments[0].id}"] [data-test-subj="property-actions-ellipses"]` - ) - .first() - .simulate('click'); - - wrapper.update(); - - wrapper - .find( - `[data-test-subj="comment-create-action-${props.data.comments[0].id}"] [data-test-subj="property-actions-pencil"]` - ) - .first() - .simulate('click'); - - expect( - wrapper - .find( - `[data-test-subj="comment-create-action-${props.data.comments[0].id}"] [data-test-subj="user-action-markdown-form"]` - ) - .exists() - ).toEqual(true); + wrapper + .find( + `[data-test-subj="comment-create-action-${props.data.comments[0].id}"] [data-test-subj="property-actions-ellipses"]` + ) + .first() + .simulate('click'); + wrapper + .find( + `[data-test-subj="comment-create-action-${props.data.comments[0].id}"] [data-test-subj="property-actions-pencil"]` + ) + .first() + .simulate('click'); - wrapper - .find( - `[data-test-subj="comment-create-action-${props.data.comments[0].id}"] [data-test-subj="user-action-cancel-markdown"]` - ) - .first() - .simulate('click'); + wrapper + .find( + `[data-test-subj="comment-create-action-${props.data.comments[0].id}"] [data-test-subj="user-action-cancel-markdown"]` + ) + .first() + .simulate('click'); + await waitFor(() => { expect( wrapper .find( @@ -304,11 +285,10 @@ describe.skip('UserActionTree ', () => { }); it('calls update description when description markdown is saved', async () => { - const props = defaultProps; const wrapper = mount( <TestProviders> <Router history={mockHistory}> - <UserActionTree {...props} /> + <UserActionTree {...defaultProps} /> </Router> </TestProviders> ); @@ -327,9 +307,9 @@ describe.skip('UserActionTree ', () => { .find(`[data-test-subj="description-action"] [data-test-subj="user-action-save-markdown"]`) .first() .simulate('click'); + await waitFor(() => { wrapper.update(); - expect( wrapper .find( @@ -337,7 +317,6 @@ describe.skip('UserActionTree ', () => { ) .exists() ).toEqual(false); - expect(onUpdateField).toBeCalledWith({ key: 'description', value: sampleData.content }); }); }); @@ -365,16 +344,13 @@ describe.skip('UserActionTree ', () => { .first() .simulate('click'); + wrapper + .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-quote"]`) + .first() + .simulate('click'); await waitFor(() => { - wrapper.update(); - - wrapper - .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-quote"]`) - .first() - .simulate('click'); + expect(setFieldValue).toBeCalledWith('comment', `> ${props.data.description} \n`); }); - - expect(setFieldValue).toBeCalledWith('comment', `> ${props.data.description} \n`); }); it('Outlines comment when url param is provided', async () => { @@ -395,14 +371,11 @@ describe.skip('UserActionTree ', () => { </TestProviders> ); - await waitFor(() => { - wrapper.update(); - expect( - wrapper - .find(`[data-test-subj="comment-create-action-${commentId}"]`) - .first() - .hasClass('outlined') - ).toBeTruthy(); - }); + expect( + wrapper + .find(`[data-test-subj="comment-create-action-${commentId}"]`) + .first() + .hasClass('outlined') + ).toEqual(true); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx similarity index 89% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/index.tsx index f8d6872a4b740..09b024fb2ca3d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -23,14 +23,14 @@ import * as i18n from './translations'; import { Case, CaseUserActions } from '../../containers/types'; import { useUpdateComment } from '../../containers/use_update_comment'; -import { useCurrentUser } from '../../../common/lib/kibana'; +import { useCurrentUser } from '../../common/lib/kibana'; import { AddComment, AddCommentRefObject } from '../add_comment'; import { ActionConnector, AlertCommentRequestRt, CommentType, ContextTypeUserRt, -} from '../../../../../cases/common/api'; +} from '../../../common'; import { CaseServices } from '../../containers/use_get_case_user_actions'; import { parseString } from '../../containers/utils'; import { OnUpdateFields } from '../case_view'; @@ -42,7 +42,6 @@ import { getUpdateAction, getAlertAttachment, getGeneratedAlertsAttachment, - useFetchAlertData, } from './helpers'; import { UserActionAvatar } from './user_action_avatar'; import { UserActionMarkdown } from './user_action_markdown'; @@ -50,17 +49,22 @@ import { UserActionTimestamp } from './user_action_timestamp'; import { UserActionUsername } from './user_action_username'; import { UserActionContentToolbar } from './user_action_content_toolbar'; import { getManualAlertIdsWithNoRuleId } from '../case_view/helpers'; - +import { Ecs } from '../../../common'; export interface UserActionTreeProps { + getCaseDetailHrefWithCommentId: (commentId: string) => string; caseServices: CaseServices; caseUserActions: CaseUserActions[]; connectors: ActionConnector[]; data: Case; + getRuleDetailsHref: (ruleId: string | null | undefined) => string; fetchUserActions: () => void; isLoadingDescription: boolean; isLoadingUserActions: boolean; + onRuleDetailsClick?: (ruleId: string | null | undefined) => void; onUpdateField: ({ key, value, onSuccess, onError }: OnUpdateFields) => void; + renderInvestigateInTimelineActionComponent?: (alertIds: string[]) => JSX.Element; updateCase: (newCase: Case) => void; + useFetchAlertData: (alertIds: string[]) => [boolean, Record<string, Ecs>]; userCanCrud: boolean; onShowAlertDetails: (alertId: string, index: string) => void; } @@ -111,14 +115,19 @@ const NEW_ID = 'newComment'; export const UserActionTree = React.memo( ({ data: caseData, + getCaseDetailHrefWithCommentId, caseServices, caseUserActions, connectors, + getRuleDetailsHref, fetchUserActions, isLoadingDescription, isLoadingUserActions, + onRuleDetailsClick, onUpdateField, + renderInvestigateInTimelineActionComponent, updateCase, + useFetchAlertData, userCanCrud, onShowAlertDetails, }: UserActionTreeProps) => { @@ -272,6 +281,7 @@ export const UserActionTree = React.memo( }), actions: ( <UserActionContentToolbar + getCaseDetailHrefWithCommentId={getCaseDetailHrefWithCommentId} id={DESCRIPTION_ID} editLabel={i18n.EDIT_DESCRIPTION} quoteLabel={i18n.QUOTE} @@ -285,6 +295,7 @@ export const UserActionTree = React.memo( [ MarkdownDescription, caseData, + getCaseDetailHrefWithCommentId, handleManageMarkdownEditId, handleManageQuote, isLoadingDescription, @@ -296,7 +307,6 @@ export const UserActionTree = React.memo( const userActions: EuiCommentProps[] = useMemo( () => caseUserActions.reduce<EuiCommentProps[]>( - // eslint-disable-next-line complexity (comments, action, index) => { // Comment creation if (action.commentId != null && action.action === 'create') { @@ -346,6 +356,7 @@ export const UserActionTree = React.memo( ), actions: ( <UserActionContentToolbar + getCaseDetailHrefWithCommentId={getCaseDetailHrefWithCommentId} id={comment.id} editLabel={i18n.EDIT_COMMENT} quoteLabel={i18n.QUOTE} @@ -389,8 +400,11 @@ export const UserActionTree = React.memo( getAlertAttachment({ action, alertId, + getCaseDetailHrefWithCommentId, + getRuleDetailsHref, index: alertIndex, loadingAlertData, + onRuleDetailsClick, ruleId, ruleName, onShowAlertDetails, @@ -411,6 +425,10 @@ export const UserActionTree = React.memo( getGeneratedAlertsAttachment({ action, alertIds, + getCaseDetailHrefWithCommentId, + getRuleDetailsHref, + onRuleDetailsClick, + renderInvestigateInTimelineActionComponent, ruleId: comment.rule?.id ?? '', ruleName: comment.rule?.name ?? i18n.UNKNOWN_RULE, }), @@ -421,7 +439,15 @@ export const UserActionTree = React.memo( // Connectors if (action.actionField.length === 1 && action.actionField[0] === 'connector') { const label = getConnectorLabelTitle({ action, connectors }); - return [...comments, getUpdateAction({ action, label, handleOutlineComment })]; + return [ + ...comments, + getUpdateAction({ + action, + label, + getCaseDetailHrefWithCommentId, + handleOutlineComment, + }), + ]; } // Pushed information @@ -474,7 +500,12 @@ export const UserActionTree = React.memo( return [ ...comments, - getUpdateAction({ action, label, handleOutlineComment }), + getUpdateAction({ + action, + label, + getCaseDetailHrefWithCommentId, + handleOutlineComment, + }), ...footers, ]; } @@ -490,7 +521,15 @@ export const UserActionTree = React.memo( field: myField, }); - return [...comments, getUpdateAction({ action, label, handleOutlineComment })]; + return [ + ...comments, + getUpdateAction({ + action, + label, + getCaseDetailHrefWithCommentId, + handleOutlineComment, + }), + ]; } return comments; @@ -498,22 +537,26 @@ export const UserActionTree = React.memo( [descriptionCommentListObj] ), [ - caseData, - caseServices, caseUserActions, - connectors, - handleOutlineComment, descriptionCommentListObj, + caseData.comments, + selectedOutlineCommentId, + manageMarkdownEditIds, handleManageMarkdownEditId, - handleManageQuote, handleSaveComment, + getCaseDetailHrefWithCommentId, + userCanCrud, isLoadingIds, - loadingAlertData, + handleManageQuote, manualAlertsData, - manageMarkdownEditIds, - selectedOutlineCommentId, - userCanCrud, + getRuleDetailsHref, + loadingAlertData, + onRuleDetailsClick, onShowAlertDetails, + renderInvestigateInTimelineActionComponent, + connectors, + handleOutlineComment, + caseServices, ] ); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/schema.ts b/x-pack/plugins/cases/public/components/user_action_tree/schema.ts similarity index 88% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/schema.ts rename to x-pack/plugins/cases/public/components/user_action_tree/schema.ts index c96041219a3e7..8c455818bf910 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/schema.ts +++ b/x-pack/plugins/cases/public/components/user_action_tree/schema.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { FIELD_TYPES, fieldValidators, FormSchema } from '../../../shared_imports'; -import * as i18n from '../../translations'; +import { FIELD_TYPES, fieldValidators, FormSchema } from '../../common/shared_imports'; +import * as i18n from '../../common/translations'; const { emptyField } = fieldValidators; export interface Content { diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts b/x-pack/plugins/cases/public/components/user_action_tree/translations.ts similarity index 52% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts rename to x-pack/plugins/cases/public/components/user_action_tree/translations.ts index 8218712fb359f..256e7ad66eeb6 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/translations.ts +++ b/x-pack/plugins/cases/public/components/user_action_tree/translations.ts @@ -10,75 +10,63 @@ import { i18n } from '@kbn/i18n'; export * from '../case_view/translations'; export const ALREADY_PUSHED_TO_SERVICE = (externalService: string) => - i18n.translate('xpack.securitySolution.cases.caseView.alreadyPushedToExternalService', { + i18n.translate('xpack.cases.caseView.alreadyPushedToExternalService', { values: { externalService }, defaultMessage: 'Already pushed to { externalService } incident', }); export const REQUIRED_UPDATE_TO_SERVICE = (externalService: string) => - i18n.translate('xpack.securitySolution.cases.caseView.requiredUpdateToExternalService', { + i18n.translate('xpack.cases.caseView.requiredUpdateToExternalService', { values: { externalService }, defaultMessage: 'Requires update to { externalService } incident', }); -export const COPY_REFERENCE_LINK = i18n.translate( - 'xpack.securitySolution.cases.caseView.copyCommentLinkAria', - { - defaultMessage: 'Copy reference link', - } -); +export const COPY_REFERENCE_LINK = i18n.translate('xpack.cases.caseView.copyCommentLinkAria', { + defaultMessage: 'Copy reference link', +}); -export const MOVE_TO_ORIGINAL_COMMENT = i18n.translate( - 'xpack.securitySolution.cases.caseView.moveToCommentAria', - { - defaultMessage: 'Highlight the referenced comment', - } -); +export const MOVE_TO_ORIGINAL_COMMENT = i18n.translate('xpack.cases.caseView.moveToCommentAria', { + defaultMessage: 'Highlight the referenced comment', +}); export const ALERT_COMMENT_LABEL_TITLE = i18n.translate( - 'xpack.securitySolution.cases.caseView.alertCommentLabelTitle', + 'xpack.cases.caseView.alertCommentLabelTitle', { defaultMessage: 'added an alert from', } ); export const GENERATED_ALERT_COMMENT_LABEL_TITLE = i18n.translate( - 'xpack.securitySolution.cases.caseView.generatedAlertCommentLabelTitle', + 'xpack.cases.caseView.generatedAlertCommentLabelTitle', { defaultMessage: 'were added from', } ); export const GENERATED_ALERT_COUNT_COMMENT_LABEL_TITLE = (totalCount: number) => - i18n.translate('xpack.securitySolution.cases.caseView.generatedAlertCountCommentLabelTitle', { + i18n.translate('xpack.cases.caseView.generatedAlertCountCommentLabelTitle', { values: { totalCount }, defaultMessage: `{totalCount} {totalCount, plural, =1 {alert} other {alerts}}`, }); export const ALERT_RULE_DELETED_COMMENT_LABEL = i18n.translate( - 'xpack.securitySolution.cases.caseView.alertRuleDeletedLabelTitle', + 'xpack.cases.caseView.alertRuleDeletedLabelTitle', { defaultMessage: 'added an alert', } ); -export const SHOW_ALERT_TOOLTIP = i18n.translate( - 'xpack.securitySolution.cases.caseView.showAlertTooltip', - { - defaultMessage: 'Show alert details', - } -); +export const SHOW_ALERT_TOOLTIP = i18n.translate('xpack.cases.caseView.showAlertTooltip', { + defaultMessage: 'Show alert details', +}); export const SEND_ALERT_TO_TIMELINE = i18n.translate( - 'xpack.securitySolution.cases.caseView.sendAlertToTimelineTooltip', + 'xpack.cases.caseView.sendAlertToTimelineTooltip', { defaultMessage: 'Investigate in timeline', } ); -export const UNKNOWN_RULE = i18n.translate( - 'xpack.securitySolution.cases.caseView.unknownRule.label', - { - defaultMessage: 'Unknown rule', - } -); +export const UNKNOWN_RULE = i18n.translate('xpack.cases.caseView.unknownRule.label', { + defaultMessage: 'Unknown rule', +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.test.tsx similarity index 76% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.test.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.test.tsx index 3bfdf2d2c5e62..a049deb264d4c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.test.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.test.tsx @@ -8,20 +8,23 @@ import React from 'react'; import { mount } from 'enzyme'; -import { TestProviders } from '../../../common/mock'; -import { useKibana } from '../../../common/lib/kibana'; +import { TestProviders } from '../../common/mock'; +import { useKibana } from '../../common/lib/kibana'; import { AlertCommentEvent } from './user_action_alert_comment_event'; -import { CommentType } from '../../../../../cases/common/api'; +import { CommentType } from '../../../common'; const props = { alertId: 'alert-id-1', + getCaseDetailHrefWithCommentId: jest.fn().mockReturnValue('someCaseDetail-withcomment'), + getRuleDetailsHref: jest.fn().mockReturnValue('some-detection-rule-link'), + onRuleDetailsClick: jest.fn(), ruleId: 'rule-id-1', ruleName: 'Awesome rule', alertsCount: 1, commentType: CommentType.alert, }; -jest.mock('../../../common/lib/kibana'); +jest.mock('../../common/lib/kibana'); const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>; describe('UserActionAvatar ', () => { @@ -61,15 +64,15 @@ describe('UserActionAvatar ', () => { }); it('navigate to app on link click', async () => { + const onRuleDetailsClick = jest.fn(); + const wrapper = mount( <TestProviders> - <AlertCommentEvent {...props} /> + <AlertCommentEvent {...props} onRuleDetailsClick={onRuleDetailsClick} /> </TestProviders> ); wrapper.find(`[data-test-subj="alert-rule-link-alert-id-1"]`).first().simulate('click'); - expect(navigateToApp).toHaveBeenCalledWith('securitySolution:detections', { - path: '/rules/id/rule-id-1', - }); + expect(onRuleDetailsClick).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.tsx similarity index 70% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.tsx index a72bebbaf0999..ee962f1407d74 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.tsx @@ -9,18 +9,15 @@ import React, { memo, useCallback } from 'react'; import { isEmpty } from 'lodash'; import { EuiText, EuiLoadingSpinner } from '@elastic/eui'; -import { APP_ID } from '../../../../common/constants'; -import { useKibana } from '../../../common/lib/kibana'; -import { getRuleDetailsUrl, useFormatUrl } from '../../../common/components/link_to'; -import { SecurityPageName } from '../../../app/types'; - import * as i18n from './translations'; -import { CommentType } from '../../../../../cases/common/api'; -import { LinkAnchor } from '../../../common/components/links'; +import { CommentType } from '../../../common'; +import { LinkAnchor } from '../links'; interface Props { alertId: string; commentType: CommentType; + getRuleDetailsHref: (ruleId: string | null | undefined) => string; + onRuleDetailsClick?: (ruleId: string | null | undefined) => void; ruleId?: string | null; ruleName?: string | null; alertsCount?: number; @@ -29,24 +26,22 @@ interface Props { const AlertCommentEventComponent: React.FC<Props> = ({ alertId, + getRuleDetailsHref, loadingAlertData = false, + onRuleDetailsClick, ruleId, ruleName, alertsCount, commentType, }) => { - const { navigateToApp } = useKibana().services.application; - const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.detections); - const onLinkClick = useCallback( (ev: { preventDefault: () => void }) => { ev.preventDefault(); - navigateToApp(`${APP_ID}:${SecurityPageName.detections}`, { - path: getRuleDetailsUrl(ruleId ?? ''), - }); + if (onRuleDetailsClick) onRuleDetailsClick(ruleId); }, - [ruleId, navigateToApp] + [ruleId, onRuleDetailsClick] ); + const detectionsRuleDetailsHref = getRuleDetailsHref(ruleId); return commentType !== CommentType.generatedAlert ? ( <> @@ -55,7 +50,7 @@ const AlertCommentEventComponent: React.FC<Props> = ({ {!loadingAlertData && !isEmpty(ruleId) && ( <LinkAnchor onClick={onLinkClick} - href={formatUrl(getRuleDetailsUrl(ruleId ?? '', urlSearch))} + href={detectionsRuleDetailsHref} data-test-subj={`alert-rule-link-${alertId ?? 'deleted'}`} > {ruleName ?? i18n.UNKNOWN_RULE} @@ -71,7 +66,7 @@ const AlertCommentEventComponent: React.FC<Props> = ({ {!loadingAlertData && ruleId !== '' && ( <LinkAnchor onClick={onLinkClick} - href={formatUrl(getRuleDetailsUrl(ruleId ?? '', urlSearch))} + href={detectionsRuleDetailsHref} data-test-subj={`alert-rule-link-${alertId ?? 'deleted'}`} > {ruleName} diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_avatar.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_avatar.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_avatar.test.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_avatar.test.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_avatar.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_avatar.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_avatar.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_avatar.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_content_toolbar.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_content_toolbar.test.tsx similarity index 90% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_content_toolbar.test.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_content_toolbar.test.tsx index 051a5c7fe975c..dc14011087a86 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_content_toolbar.test.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_content_toolbar.test.tsx @@ -18,9 +18,7 @@ jest.mock('react-router-dom', () => { }; }); -jest.mock('../../../common/components/navigation/use_get_url_search'); - -jest.mock('../../../common/lib/kibana', () => { +jest.mock('../../common/lib/kibana', () => { return { useKibana: () => ({ services: { @@ -33,6 +31,7 @@ jest.mock('../../../common/lib/kibana', () => { }); const props = { + getCaseDetailHrefWithCommentId: jest.fn().mockReturnValue('case-detail-url-with-comment-id-1'), id: '1', editLabel: 'edit', quoteLabel: 'quote', diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_content_toolbar.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_content_toolbar.tsx similarity index 85% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_content_toolbar.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_content_toolbar.tsx index fd679ced5dd6d..f1f0a0148b9c6 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_content_toolbar.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_content_toolbar.tsx @@ -13,6 +13,7 @@ import { UserActionPropertyActions } from './user_action_property_actions'; interface UserActionContentToolbarProps { id: string; + getCaseDetailHrefWithCommentId: (commentId: string) => string; editLabel: string; quoteLabel: string; disabled: boolean; @@ -23,6 +24,7 @@ interface UserActionContentToolbarProps { const UserActionContentToolbarComponent = ({ id, + getCaseDetailHrefWithCommentId, editLabel, quoteLabel, disabled, @@ -33,7 +35,10 @@ const UserActionContentToolbarComponent = ({ return ( <EuiFlexGroup> <EuiFlexItem> - <UserActionCopyLink id={id} /> + <UserActionCopyLink + id={id} + getCaseDetailHrefWithCommentId={getCaseDetailHrefWithCommentId} + /> </EuiFlexItem> <EuiFlexItem> <UserActionPropertyActions diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_copy_link.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_copy_link.test.tsx similarity index 65% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_copy_link.test.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_copy_link.test.tsx index c1d4894854bd9..51381bee98978 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_copy_link.test.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_copy_link.test.tsx @@ -5,17 +5,15 @@ * 2.0. */ +// TODO: removed dependencies on UrlGetSearch + import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; import { useParams } from 'react-router-dom'; import copy from 'copy-to-clipboard'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; import { UserActionCopyLink } from './user_action_copy_link'; -import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; - -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)))'; jest.mock('react-router-dom', () => { const originalModule = jest.requireActual('react-router-dom'); @@ -30,14 +28,12 @@ jest.mock('copy-to-clipboard', () => { return jest.fn(); }); -jest.mock('../../../common/components/navigation/use_get_url_search'); - const mockGetUrlForApp = jest.fn( (appId: string, options?: { path?: string; absolute?: boolean }) => `${appId}${options?.path ?? ''}` ); -jest.mock('../../../common/lib/kibana', () => { +jest.mock('../../common/lib/kibana', () => { return { useKibana: () => ({ services: { @@ -51,6 +47,7 @@ jest.mock('../../../common/lib/kibana', () => { const props = { id: 'comment-id', + getCaseDetailHrefWithCommentId: jest.fn().mockReturnValue('random-url'), }; describe('UserActionCopyLink ', () => { @@ -58,7 +55,6 @@ describe('UserActionCopyLink ', () => { beforeAll(() => { (useParams as jest.Mock).mockReturnValue({ detailName: 'case-1' }); - (useGetUrlSearch as jest.Mock).mockReturnValue(searchURL); wrapper = mount(<UserActionCopyLink {...props} />, { wrappingComponent: TestProviders }); }); @@ -68,8 +64,6 @@ describe('UserActionCopyLink ', () => { it('calls copy clipboard correctly', async () => { wrapper.find(`[data-test-subj="copy-link-${props.id}"]`).first().simulate('click'); - expect(copy).toHaveBeenCalledWith( - 'securitySolution:case/case-1/comment-id?timerange=(global:(linkTo:!(),timerange:(from:1585487656371,fromStr:now-24h,kind:relative,to:1585574056371,toStr:now)),timeline:(linkTo:!(),timerange:(from:1585227005527,kind:absolute,to:1585313405527)))' - ); + expect(copy).toHaveBeenCalledWith('random-url'); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_copy_link.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_copy_link.tsx similarity index 59% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_copy_link.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_copy_link.tsx index ff4e151197464..0cc837fcb60b5 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_copy_link.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_copy_link.tsx @@ -7,28 +7,22 @@ import React, { memo, useCallback } from 'react'; import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; -import { useParams } from 'react-router-dom'; import copy from 'copy-to-clipboard'; -import { useFormatUrl, getCaseDetailsUrlWithCommentId } from '../../../common/components/link_to'; -import { SecurityPageName } from '../../../app/types'; import * as i18n from './translations'; interface UserActionCopyLinkProps { id: string; + getCaseDetailHrefWithCommentId: (commentId: string) => string; } -const UserActionCopyLinkComponent = ({ id: commentId }: UserActionCopyLinkProps) => { - const { detailName: caseId, subCaseId } = useParams<{ detailName: string; subCaseId?: string }>(); - const { formatUrl } = useFormatUrl(SecurityPageName.case); - +const UserActionCopyLinkComponent = ({ + id: commentId, + getCaseDetailHrefWithCommentId, +}: UserActionCopyLinkProps) => { const handleAnchorLink = useCallback(() => { - copy( - formatUrl(getCaseDetailsUrlWithCommentId({ id: caseId, commentId, subCaseId }), { - absolute: true, - }) - ); - }, [caseId, commentId, formatUrl, subCaseId]); + copy(getCaseDetailHrefWithCommentId(commentId)); + }, [getCaseDetailHrefWithCommentId, commentId]); return ( <EuiToolTip position="top" content={<p>{i18n.COPY_REFERENCE_LINK}</p>}> diff --git a/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.test.tsx new file mode 100644 index 0000000000000..6fff3c8f9abe2 --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.test.tsx @@ -0,0 +1,73 @@ +/* + * 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 { mount } from 'enzyme'; +import { Router, mockHistory } from '../__mock__/router'; +import { UserActionMarkdown } from './user_action_markdown'; +import { TestProviders } from '../../common/mock'; +import { waitFor } from '@testing-library/react'; +const onChangeEditable = jest.fn(); +const onSaveContent = jest.fn(); + +const hyperlink = `[hyperlink](http://elastic.co)`; +const defaultProps = { + content: `A link to a timeline ${hyperlink}`, + id: 'markdown-id', + isEditable: true, + onChangeEditable, + onSaveContent, +}; + +describe('UserActionMarkdown ', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('Renders markdown correctly when not in edit mode', async () => { + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <UserActionMarkdown {...{ ...defaultProps, isEditable: false }} /> + </Router> + </TestProviders> + ); + + expect(wrapper.find(`[data-test-subj="markdown-link"]`).first().text()).toContain('hyperlink'); + }); + + it('Save button click calls onSaveContent and onChangeEditable', async () => { + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <UserActionMarkdown {...defaultProps} /> + </Router> + </TestProviders> + ); + wrapper.find(`[data-test-subj="user-action-save-markdown"]`).first().simulate('click'); + + await waitFor(() => { + expect(onSaveContent).toHaveBeenCalledWith(defaultProps.content); + expect(onChangeEditable).toHaveBeenCalledWith(defaultProps.id); + }); + }); + it('Cancel button click calls only onChangeEditable', async () => { + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <UserActionMarkdown {...defaultProps} /> + </Router> + </TestProviders> + ); + wrapper.find(`[data-test-subj="user-action-cancel-markdown"]`).first().simulate('click'); + + await waitFor(() => { + expect(onSaveContent).not.toHaveBeenCalled(); + expect(onChangeEditable).toHaveBeenCalledWith(defaultProps.id); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx similarity index 94% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx index c5707b0293d0e..19cc804786af1 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_markdown.tsx @@ -10,9 +10,9 @@ import React, { useCallback } from 'react'; import styled from 'styled-components'; import * as i18n from '../case_view/translations'; -import { Form, useForm, UseField } from '../../../shared_imports'; +import { Form, useForm, UseField } from '../../common/shared_imports'; import { schema, Content } from './schema'; -import { MarkdownRenderer, MarkdownEditorForm } from '../../../common/components/markdown_editor'; +import { MarkdownRenderer, MarkdownEditorForm } from '../markdown_editor'; const ContentWrapper = styled.div` padding: ${({ theme }) => `${theme.eui.euiSizeM} ${theme.eui.euiSizeL}`}; diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_move_to_reference.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_move_to_reference.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_move_to_reference.test.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_move_to_reference.test.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_move_to_reference.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_move_to_reference.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_move_to_reference.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_move_to_reference.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_property_actions.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_property_actions.test.tsx similarity index 69% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_property_actions.test.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_property_actions.test.tsx index 0e8a30befd000..57958d3d8e5af 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_property_actions.test.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_property_actions.test.tsx @@ -8,15 +8,16 @@ import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; import { UserActionPropertyActions } from './user_action_property_actions'; - +const onEdit = jest.fn(); +const onQuote = jest.fn(); const props = { id: 'property-actions-id', editLabel: 'edit', quoteLabel: 'quote', disabled: false, isLoading: false, - onEdit: jest.fn(), - onQuote: jest.fn(), + onEdit, + onQuote, }; describe('UserActionPropertyActions ', () => { @@ -26,6 +27,10 @@ describe('UserActionPropertyActions ', () => { wrapper = mount(<UserActionPropertyActions {...props} />); }); + beforeEach(() => { + jest.clearAllMocks(); + }); + it('it renders', async () => { expect( wrapper.find('[data-test-subj="user-action-title-loading"]').first().exists() @@ -40,6 +45,18 @@ describe('UserActionPropertyActions ', () => { wrapper.find('[data-test-subj="property-actions-quote"]').exists(); }); + it('quote click calls onQuote', async () => { + wrapper.find('[data-test-subj="property-actions-ellipses"]').first().simulate('click'); + wrapper.find('[data-test-subj="property-actions-quote"]').first().simulate('click'); + expect(onQuote).toHaveBeenCalledWith(props.id); + }); + + it('pencil click calls onEdit', async () => { + wrapper.find('[data-test-subj="property-actions-ellipses"]').first().simulate('click'); + wrapper.find('[data-test-subj="property-actions-pencil"]').first().simulate('click'); + expect(onEdit).toHaveBeenCalledWith(props.id); + }); + it('it shows the spinner when loading', async () => { wrapper = mount(<UserActionPropertyActions {...props} isLoading={true} />); expect( diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_property_actions.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_property_actions.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_property_actions.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_property_actions.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_show_alert.test.tsx similarity index 77% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.test.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_show_alert.test.tsx index 789a6eb68e0fc..d6005a8bd521e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.test.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_show_alert.test.tsx @@ -8,23 +8,11 @@ import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; import { UserActionShowAlert } from './user_action_show_alert'; -import { RuleEcs } from '../../../../common/ecs/rule'; const props = { id: 'action-id', alertId: 'alert-id', index: 'alert-index', - alert: { - _id: 'alert-id', - _index: 'alert-index', - timestamp: '2021-01-07T13:58:31.487Z', - rule: { - id: ['rule-id'], - name: ['Awesome Rule'], - from: ['2021-01-07T13:58:31.487Z'], - to: ['2021-01-07T14:58:31.487Z'], - } as RuleEcs, - }, onShowAlertDetails: jest.fn(), }; diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_show_alert.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_show_alert.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_show_alert.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_timestamp.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_timestamp.test.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_timestamp.test.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_timestamp.test.tsx index 6aa6710cb6ea1..de2dc90ac43e9 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_timestamp.test.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_timestamp.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; -import { TestProviders } from '../../../common/mock'; +import { TestProviders } from '../../common/mock'; import { UserActionTimestamp } from './user_action_timestamp'; jest.mock('@kbn/i18n/react', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_timestamp.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_timestamp.tsx similarity index 94% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_timestamp.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_timestamp.tsx index e51bc261ff800..2e3973458c249 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_timestamp.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_timestamp.tsx @@ -9,7 +9,7 @@ import React, { memo } from 'react'; import { EuiTextColor } from '@elastic/eui'; import { FormattedRelative } from '@kbn/i18n/react'; -import { LocalizedDateTooltip } from '../../../common/components/localized_date_tooltip'; +import { LocalizedDateTooltip } from '../../components/localized_date_tooltip'; import * as i18n from './translations'; interface UserActionAvatarProps { diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_username.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_username.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_username.test.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_username.test.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_username.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_username.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_username.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_username.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_username_with_avatar.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_username_with_avatar.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_username_with_avatar.test.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_username_with_avatar.test.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_username_with_avatar.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_username_with_avatar.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_username_with_avatar.tsx rename to x-pack/plugins/cases/public/components/user_action_tree/user_action_username_with_avatar.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/user_list/index.test.tsx b/x-pack/plugins/cases/public/components/user_list/index.test.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/cases/components/user_list/index.test.tsx rename to x-pack/plugins/cases/public/components/user_list/index.test.tsx index 9c6509eeabc15..70f9e7d2fbdfc 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_list/index.test.tsx +++ b/x-pack/plugins/cases/public/components/user_list/index.test.tsx @@ -15,11 +15,9 @@ describe('UserList ', () => { const caseLink = 'http://reddit.com'; const user = { username: 'username', fullName: 'Full Name', email: 'testemail@elastic.co' }; const open = jest.fn(); - beforeAll(() => { - window.open = open; - }); beforeEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); + window.open = open; }); it('triggers mailto when email icon clicked', () => { const wrapper = shallow( diff --git a/x-pack/plugins/security_solution/public/cases/components/user_list/index.tsx b/x-pack/plugins/cases/public/components/user_list/index.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/cases/components/user_list/index.tsx rename to x-pack/plugins/cases/public/components/user_list/index.tsx diff --git a/x-pack/plugins/security_solution/public/cases/components/user_list/translations.ts b/x-pack/plugins/cases/public/components/user_list/translations.ts similarity index 84% rename from x-pack/plugins/security_solution/public/cases/components/user_list/translations.ts rename to x-pack/plugins/cases/public/components/user_list/translations.ts index 81d2c7d50e5d7..73610e5959345 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_list/translations.ts +++ b/x-pack/plugins/cases/public/components/user_list/translations.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; export const SEND_EMAIL_ARIA = (user: string) => - i18n.translate('xpack.securitySolution.cases.caseView.sendEmalLinkAria', { + i18n.translate('xpack.cases.caseView.sendEmalLinkAria', { values: { user }, defaultMessage: 'click to send an email to {user}', }); diff --git a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar.test.tsx.snap b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar.test.tsx.snap new file mode 100644 index 0000000000000..f082dc4023e7a --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar.test.tsx.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UtilityBar it renders 1`] = ` +<UtilityBar> + <UtilityBarSection> + <UtilityBarGroup> + <UtilityBarText> + Test text + </UtilityBarText> + </UtilityBarGroup> + <UtilityBarGroup> + <UtilityBarAction + iconType="" + popoverContent={[Function]} + > + Test action + </UtilityBarAction> + </UtilityBarGroup> + </UtilityBarSection> + <UtilityBarSection> + <UtilityBarGroup> + <UtilityBarAction + iconType="cross" + > + Test action + </UtilityBarAction> + </UtilityBarGroup> + </UtilityBarSection> +</UtilityBar> +`; diff --git a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_action.test.tsx.snap b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_action.test.tsx.snap new file mode 100644 index 0000000000000..eb20ac217b300 --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_action.test.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UtilityBarAction it renders 1`] = ` +<UtilityBarAction + iconType="alert" +> + Test action +</UtilityBarAction> +`; diff --git a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap new file mode 100644 index 0000000000000..8ef7ee1cfe842 --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UtilityBarGroup it renders 1`] = ` +<UtilityBarGroup> + <UtilityBarText> + Test text + </UtilityBarText> +</UtilityBarGroup> +`; diff --git a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap new file mode 100644 index 0000000000000..2fe3b8ac5c7aa --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UtilityBarSection it renders 1`] = ` +<UtilityBarSection> + <UtilityBarGroup> + <UtilityBarText> + Test text + </UtilityBarText> + </UtilityBarGroup> +</UtilityBarSection> +`; diff --git a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap new file mode 100644 index 0000000000000..cf635ffa49c4c --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UtilityBarText it renders 1`] = ` +<UtilityBarText> + Test text +</UtilityBarText> +`; diff --git a/x-pack/plugins/cases/public/components/utility_bar/index.ts b/x-pack/plugins/cases/public/components/utility_bar/index.ts new file mode 100644 index 0000000000000..830f3cb043ba9 --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { UtilityBar } from './utility_bar'; +export { UtilityBarAction } from './utility_bar_action'; +export { UtilityBarGroup } from './utility_bar_group'; +export { UtilityBarSection } from './utility_bar_section'; +export { UtilityBarSpacer } from './utility_bar_spacer'; +export { UtilityBarText } from './utility_bar_text'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/styles.tsx b/x-pack/plugins/cases/public/components/utility_bar/styles.tsx new file mode 100644 index 0000000000000..158f0c5ebea15 --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/styles.tsx @@ -0,0 +1,144 @@ +/* + * 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 styled, { css } from 'styled-components'; + +/** + * UTILITY BAR + */ + +export interface BarProps { + border?: boolean; +} + +export interface BarSectionProps { + grow?: boolean; +} + +export interface BarGroupProps { + grow?: boolean; +} + +export const Bar = styled.aside.attrs({ + className: 'casesUtilityBar', +})<BarProps>` + ${({ border, theme }) => css` + ${border && + css` + border-bottom: ${theme.eui.euiBorderThin}; + padding-bottom: ${theme.eui.paddingSizes.s}; + `} + + @media only screen and (min-width: ${theme.eui.euiBreakpoints.l}) { + display: flex; + justify-content: space-between; + } + `} +`; +Bar.displayName = 'Bar'; + +export const BarSection = styled.div.attrs({ + className: 'casesUtilityBar__section', +})<BarSectionProps>` + ${({ grow, theme }) => css` + & + & { + margin-top: ${theme.eui.euiSizeS}; + } + + @media only screen and (min-width: ${theme.eui.euiBreakpoints.m}) { + display: flex; + flex-wrap: wrap; + } + + @media only screen and (min-width: ${theme.eui.euiBreakpoints.l}) { + & + & { + margin-top: 0; + margin-left: ${theme.eui.euiSize}; + } + } + ${grow && + css` + flex: 1; + `} + `} +`; +BarSection.displayName = 'BarSection'; + +export const BarGroup = styled.div.attrs({ + className: 'casesUtilityBar__group', +})<BarGroupProps>` + ${({ grow, theme }) => css` + align-items: flex-start; + display: flex; + flex-wrap: wrap; + + & + & { + margin-top: ${theme.eui.euiSizeS}; + } + + @media only screen and (min-width: ${theme.eui.euiBreakpoints.m}) { + border-right: ${theme.eui.euiBorderThin}; + flex-wrap: nowrap; + margin-right: ${theme.eui.paddingSizes.m}; + padding-right: ${theme.eui.paddingSizes.m}; + + & + & { + margin-top: 0; + } + + &:last-child { + border-right: none; + margin-right: 0; + padding-right: 0; + } + } + + & > * { + margin-right: ${theme.eui.euiSize}; + + &:last-child { + margin-right: 0; + } + } + ${grow && + css` + flex: 1; + `} + `} +`; +BarGroup.displayName = 'BarGroup'; + +export const BarText = styled.p.attrs({ + className: 'casesUtilityBar__text', +})` + ${({ theme }) => css` + color: ${theme.eui.euiTextSubduedColor}; + font-size: ${theme.eui.euiFontSizeXS}; + line-height: ${theme.eui.euiLineHeight}; + white-space: nowrap; + `} +`; +BarText.displayName = 'BarText'; + +export const BarAction = styled.div.attrs({ + className: 'casesUtilityBar__action', +})` + ${({ theme }) => css` + font-size: ${theme.eui.euiFontSizeXS}; + line-height: ${theme.eui.euiLineHeight}; + `} +`; +BarAction.displayName = 'BarAction'; + +export const BarSpacer = styled.div.attrs({ + className: 'casesUtilityBar__spacer', +})` + ${() => css` + flex: 1; + `} +`; +BarSpacer.displayName = 'BarSpacer'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.tsx new file mode 100644 index 0000000000000..98af25a9af466 --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.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 euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { mount, shallow } from 'enzyme'; +import React from 'react'; +import { TestProviders } from '../../common/mock'; + +import { + UtilityBar, + UtilityBarAction, + UtilityBarGroup, + UtilityBarSection, + UtilityBarText, +} from './index'; + +describe('UtilityBar', () => { + test('it renders', () => { + const wrapper = shallow( + <TestProviders> + <UtilityBar> + <UtilityBarSection> + <UtilityBarGroup> + <UtilityBarText>{'Test text'}</UtilityBarText> + </UtilityBarGroup> + + <UtilityBarGroup> + <UtilityBarAction iconType="" popoverContent={() => <p>{'Test popover'}</p>}> + {'Test action'} + </UtilityBarAction> + </UtilityBarGroup> + </UtilityBarSection> + + <UtilityBarSection> + <UtilityBarGroup> + <UtilityBarAction iconType="cross">{'Test action'}</UtilityBarAction> + </UtilityBarGroup> + </UtilityBarSection> + </UtilityBar> + </TestProviders> + ); + + expect(wrapper.find('UtilityBar')).toMatchSnapshot(); + }); + + test('it applies border styles when border is true', () => { + const wrapper = mount( + <TestProviders> + <UtilityBar border> + <UtilityBarSection> + <UtilityBarGroup> + <UtilityBarText>{'Test text'}</UtilityBarText> + </UtilityBarGroup> + + <UtilityBarGroup> + <UtilityBarAction iconType="" popoverContent={() => <p>{'Test popover'}</p>}> + {'Test action'} + </UtilityBarAction> + </UtilityBarGroup> + </UtilityBarSection> + + <UtilityBarSection> + <UtilityBarGroup> + <UtilityBarAction iconType="cross">{'Test action'}</UtilityBarAction> + </UtilityBarGroup> + </UtilityBarSection> + </UtilityBar> + </TestProviders> + ); + const casesUtilityBar = wrapper.find('.casesUtilityBar').first(); + + expect(casesUtilityBar).toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); + expect(casesUtilityBar).toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.s); + }); + + test('it DOES NOT apply border styles when border is false', () => { + const wrapper = mount( + <TestProviders> + <UtilityBar> + <UtilityBarSection> + <UtilityBarGroup> + <UtilityBarText>{'Test text'}</UtilityBarText> + </UtilityBarGroup> + + <UtilityBarGroup> + <UtilityBarAction iconType="" popoverContent={() => <p>{'Test popover'}</p>}> + {'Test action'} + </UtilityBarAction> + </UtilityBarGroup> + </UtilityBarSection> + + <UtilityBarSection> + <UtilityBarGroup> + <UtilityBarAction iconType="cross">{'Test action'}</UtilityBarAction> + </UtilityBarGroup> + </UtilityBarSection> + </UtilityBar> + </TestProviders> + ); + const casesUtilityBar = wrapper.find('.casesUtilityBar').first(); + + expect(casesUtilityBar).not.toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); + expect(casesUtilityBar).not.toHaveStyleRule('padding-bottom', euiDarkVars.paddingSizes.s); + }); +}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar.tsx new file mode 100644 index 0000000000000..ff47459d437be --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/utility_bar.tsx @@ -0,0 +1,20 @@ +/* + * 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 { Bar, BarProps } from './styles'; + +interface UtilityBarProps extends BarProps { + children: React.ReactNode; +} + +export const UtilityBar = React.memo<UtilityBarProps>(({ border, children }) => ( + <Bar border={border}>{children}</Bar> +)); + +UtilityBar.displayName = 'UtilityBar'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.test.tsx new file mode 100644 index 0000000000000..8fc67cefc0f61 --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.test.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 { mount, shallow } from 'enzyme'; +import React from 'react'; + +import { TestProviders } from '../../common/mock'; +import { UtilityBarAction } from './index'; + +describe('UtilityBarAction', () => { + test('it renders', () => { + const wrapper = shallow( + <TestProviders> + <UtilityBarAction iconType="alert">{'Test action'}</UtilityBarAction> + </TestProviders> + ); + + expect(wrapper.find('UtilityBarAction')).toMatchSnapshot(); + }); + + test('it renders a popover', () => { + const wrapper = mount( + <TestProviders> + <UtilityBarAction iconType="alert" popoverContent={() => <p>{'Test popover'}</p>}> + {'Test action'} + </UtilityBarAction> + </TestProviders> + ); + + expect(wrapper.find('.euiPopover').first().exists()).toBe(true); + }); +}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.tsx new file mode 100644 index 0000000000000..19cb8ef4f613b --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.tsx @@ -0,0 +1,97 @@ +/* + * 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 { EuiPopover } from '@elastic/eui'; +import React, { useCallback, useState } from 'react'; + +import { LinkIcon, LinkIconProps } from '../link_icon'; +import { BarAction } from './styles'; + +const Popover = React.memo<UtilityBarActionProps>( + ({ children, color, iconSide, iconSize, iconType, popoverContent, disabled, ownFocus }) => { + const [popoverState, setPopoverState] = useState(false); + + const closePopover = useCallback(() => setPopoverState(false), [setPopoverState]); + + return ( + <EuiPopover + ownFocus={ownFocus} + button={ + <LinkIcon + color={color} + iconSide={iconSide} + iconSize={iconSize} + iconType={iconType} + onClick={() => setPopoverState(!popoverState)} + disabled={disabled} + > + {children} + </LinkIcon> + } + closePopover={() => setPopoverState(false)} + isOpen={popoverState} + repositionOnScroll + > + {popoverContent?.(closePopover)} + </EuiPopover> + ); + } +); + +Popover.displayName = 'Popover'; + +export interface UtilityBarActionProps extends LinkIconProps { + popoverContent?: (closePopover: () => void) => React.ReactNode; + dataTestSubj?: string; + ownFocus?: boolean; +} + +export const UtilityBarAction = React.memo<UtilityBarActionProps>( + ({ + children, + color, + dataTestSubj, + disabled, + href, + iconSide, + iconSize, + iconType, + ownFocus, + onClick, + popoverContent, + }) => ( + <BarAction data-test-subj={dataTestSubj}> + {popoverContent ? ( + <Popover + disabled={disabled} + color={color} + iconSide={iconSide} + iconSize={iconSize} + iconType={iconType} + ownFocus={ownFocus} + popoverContent={popoverContent} + > + {children} + </Popover> + ) : ( + <LinkIcon + color={color} + disabled={disabled} + href={href} + iconSide={iconSide} + iconSize={iconSize} + iconType={iconType} + onClick={onClick} + > + {children} + </LinkIcon> + )} + </BarAction> + ) +); + +UtilityBarAction.displayName = 'UtilityBarAction'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.test.tsx new file mode 100644 index 0000000000000..546dcf48bba9a --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.test.tsx @@ -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 { shallow } from 'enzyme'; +import React from 'react'; + +import { TestProviders } from '../../common/mock'; +import { UtilityBarGroup, UtilityBarText } from './index'; + +describe('UtilityBarGroup', () => { + test('it renders', () => { + const wrapper = shallow( + <TestProviders> + <UtilityBarGroup> + <UtilityBarText>{'Test text'}</UtilityBarText> + </UtilityBarGroup> + </TestProviders> + ); + + expect(wrapper.find('UtilityBarGroup')).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.tsx new file mode 100644 index 0000000000000..ef83d6effc8a3 --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.tsx @@ -0,0 +1,20 @@ +/* + * 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 { BarGroup, BarGroupProps } from './styles'; + +export interface UtilityBarGroupProps extends BarGroupProps { + children: React.ReactNode; +} + +export const UtilityBarGroup = React.memo<UtilityBarGroupProps>(({ grow, children }) => ( + <BarGroup grow={grow}>{children}</BarGroup> +)); + +UtilityBarGroup.displayName = 'UtilityBarGroup'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.test.tsx new file mode 100644 index 0000000000000..f06ff651b5419 --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.test.tsx @@ -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 { shallow } from 'enzyme'; +import React from 'react'; + +import { TestProviders } from '../../common/mock'; +import { UtilityBarGroup, UtilityBarSection, UtilityBarText } from './index'; + +describe('UtilityBarSection', () => { + test('it renders', () => { + const wrapper = shallow( + <TestProviders> + <UtilityBarSection> + <UtilityBarGroup> + <UtilityBarText>{'Test text'}</UtilityBarText> + </UtilityBarGroup> + </UtilityBarSection> + </TestProviders> + ); + + expect(wrapper.find('UtilityBarSection')).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.tsx new file mode 100644 index 0000000000000..c84219cc63488 --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.tsx @@ -0,0 +1,20 @@ +/* + * 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 { BarSection, BarSectionProps } from './styles'; + +export interface UtilityBarSectionProps extends BarSectionProps { + children: React.ReactNode; +} + +export const UtilityBarSection = React.memo<UtilityBarSectionProps>(({ grow, children }) => ( + <BarSection grow={grow}>{children}</BarSection> +)); + +UtilityBarSection.displayName = 'UtilityBarSection'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_spacer.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_spacer.tsx new file mode 100644 index 0000000000000..11b3be8d656e4 --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_spacer.tsx @@ -0,0 +1,20 @@ +/* + * 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 { BarSpacer } from './styles'; + +export interface UtilityBarSpacerProps { + dataTestSubj?: string; +} + +export const UtilityBarSpacer = React.memo<UtilityBarSpacerProps>(({ dataTestSubj }) => ( + <BarSpacer data-test-subj={dataTestSubj} /> +)); + +UtilityBarSpacer.displayName = 'UtilityBarSpacer'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.test.tsx new file mode 100644 index 0000000000000..456a1f4bed3be --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.test.tsx @@ -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 { shallow } from 'enzyme'; +import React from 'react'; + +import { TestProviders } from '../../common/mock'; +import { UtilityBarText } from './index'; + +describe('UtilityBarText', () => { + test('it renders', () => { + const wrapper = shallow( + <TestProviders> + <UtilityBarText>{'Test text'}</UtilityBarText> + </TestProviders> + ); + + expect(wrapper.find('UtilityBarText')).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.tsx new file mode 100644 index 0000000000000..c0be3cbfbe202 --- /dev/null +++ b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.tsx @@ -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 React from 'react'; + +import { BarText } from './styles'; + +export interface UtilityBarTextProps { + children: string | JSX.Element; + dataTestSubj?: string; +} + +export const UtilityBarText = React.memo<UtilityBarTextProps>(({ children, dataTestSubj }) => ( + <BarText data-test-subj={dataTestSubj}>{children}</BarText> +)); + +UtilityBarText.displayName = 'UtilityBarText'; diff --git a/x-pack/plugins/cases/public/components/wrappers/index.tsx b/x-pack/plugins/cases/public/components/wrappers/index.tsx new file mode 100644 index 0000000000000..3b33e9304da83 --- /dev/null +++ b/x-pack/plugins/cases/public/components/wrappers/index.tsx @@ -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 styled from 'styled-components'; + +export const WhitePageWrapper = styled.div` + background-color: ${({ theme }) => theme.eui.euiColorEmptyShade}; + border-top: ${({ theme }) => theme.eui.euiBorderThin}; + flex: 1 1 auto; +`; + +export const SectionWrapper = styled.div` + box-sizing: content-box; + margin: 0 auto; + max-width: 1175px; + width: 100%; +`; + +export const HeaderWrapper = styled.div` + padding: ${({ theme }) => + `${theme.eui.paddingSizes.l} ${theme.eui.paddingSizes.l} 0 ${theme.eui.paddingSizes.l}`}; +`; diff --git a/x-pack/plugins/security_solution/public/cases/containers/__mocks__/api.ts b/x-pack/plugins/cases/public/containers/__mocks__/api.ts similarity index 98% rename from x-pack/plugins/security_solution/public/cases/containers/__mocks__/api.ts rename to x-pack/plugins/cases/public/containers/__mocks__/api.ts index 11ae4fd6bf178..4dbb10da95b2d 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/__mocks__/api.ts +++ b/x-pack/plugins/cases/public/containers/__mocks__/api.ts @@ -33,7 +33,7 @@ import { CommentRequest, User, CaseStatuses, -} from '../../../../../cases/common/api'; +} from '../../../common'; export const getCase = async ( caseId: string, diff --git a/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx b/x-pack/plugins/cases/public/containers/api.test.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/containers/api.test.tsx rename to x-pack/plugins/cases/public/containers/api.test.tsx index e6ecf45097a1a..3e71a05df7cc1 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx +++ b/x-pack/plugins/cases/public/containers/api.test.tsx @@ -5,10 +5,10 @@ * 2.0. */ -import { KibanaServices } from '../../common/lib/kibana'; +import { KibanaServices } from '../common/lib/kibana'; -import { ConnectorTypes, CommentType, CaseStatuses } from '../../../../cases/common/api'; -import { CASES_URL } from '../../../../cases/common/constants'; +import { ConnectorTypes, CommentType, CaseStatuses } from '../../common'; +import { CASES_URL } from '../../common'; import { deleteCases, @@ -50,7 +50,7 @@ import { DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases'; const abortCtrl = new AbortController(); const mockKibanaServices = KibanaServices.get as jest.Mock; -jest.mock('../../common/lib/kibana'); +jest.mock('../common/lib/kibana'); const fetchMock = jest.fn(); mockKibanaServices.mockReturnValue({ http: { fetch: fetchMock } }); diff --git a/x-pack/plugins/security_solution/public/cases/containers/api.ts b/x-pack/plugins/cases/public/containers/api.ts similarity index 97% rename from x-pack/plugins/security_solution/public/cases/containers/api.ts rename to x-pack/plugins/cases/public/containers/api.ts index 644c7dbf716bf..75263d4d38978 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/api.ts +++ b/x-pack/plugins/cases/public/containers/api.ts @@ -18,11 +18,12 @@ import { CaseUserActionsResponse, CommentRequest, CommentType, + StatusAll, SubCasePatchRequest, SubCaseResponse, SubCasesResponse, User, -} from '../../../../cases/common/api'; +} from '../../common'; import { ACTION_TYPES_URL, @@ -32,7 +33,7 @@ import { CASES_URL, SUB_CASE_DETAILS_URL, SUB_CASES_PATCH_DEL_URL, -} from '../../../../cases/common/constants'; +} from '../../common'; import { getCaseCommentsUrl, @@ -41,10 +42,9 @@ import { getCaseUserActionUrl, getSubCaseDetailsUrl, getSubCaseUserActionUrl, -} from '../../../../cases/common/api/helpers'; +} from '../../common'; -import { KibanaServices } from '../../common/lib/kibana'; -import { StatusAll } from '../components/status'; +import { KibanaServices } from '../common/lib/kibana'; import { ActionLicense, diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/__mocks__/api.ts b/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts similarity index 96% rename from x-pack/plugins/security_solution/public/cases/containers/configure/__mocks__/api.ts rename to x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts index d9cd81f143816..ea4b92706b4d1 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/__mocks__/api.ts +++ b/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts @@ -10,7 +10,7 @@ import { CasesConfigureRequest, ActionConnector, ActionTypeConnector, -} from '../../../../../../cases/common/api'; +} from '../../../../common'; import { ApiProps } from '../../types'; import { CaseConfigure } from '../types'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/api.test.ts b/x-pack/plugins/cases/public/containers/configure/api.test.ts similarity index 96% rename from x-pack/plugins/security_solution/public/cases/containers/configure/api.test.ts rename to x-pack/plugins/cases/public/containers/configure/api.test.ts index 0c7ae422be861..ae749b4391776 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/api.test.ts +++ b/x-pack/plugins/cases/public/containers/configure/api.test.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { KibanaServices } from '../../../common/lib/kibana'; import { fetchConnectors, getCaseConfigure, @@ -20,11 +19,12 @@ import { caseConfigurationResposeMock, caseConfigurationCamelCaseResponseMock, } from './mock'; -import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; +import { ConnectorTypes } from '../../../common'; +import { KibanaServices } from '../../common/lib/kibana'; const abortCtrl = new AbortController(); const mockKibanaServices = KibanaServices.get as jest.Mock; -jest.mock('../../../common/lib/kibana'); +jest.mock('../../common/lib/kibana'); const fetchMock = jest.fn(); mockKibanaServices.mockReturnValue({ http: { fetch: fetchMock } }); diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/api.ts b/x-pack/plugins/cases/public/containers/configure/api.ts similarity index 94% rename from x-pack/plugins/security_solution/public/cases/containers/configure/api.ts rename to x-pack/plugins/cases/public/containers/configure/api.ts index 943724ef08398..ca8b7e3a05734 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/api.ts +++ b/x-pack/plugins/cases/public/containers/configure/api.ts @@ -7,19 +7,16 @@ import { isEmpty } from 'lodash/fp'; import { + ACTION_TYPES_URL, ActionConnector, ActionTypeConnector, - CasesConfigurePatch, - CasesConfigureResponse, - CasesConfigureRequest, -} from '../../../../../cases/common/api'; -import { KibanaServices } from '../../../common/lib/kibana'; - -import { CASE_CONFIGURE_CONNECTORS_URL, CASE_CONFIGURE_URL, - ACTION_TYPES_URL, -} from '../../../../../cases/common/constants'; + CasesConfigurePatch, + CasesConfigureRequest, + CasesConfigureResponse, +} from '../../../common'; +import { KibanaServices } from '../../common/lib/kibana'; import { ApiProps } from '../types'; import { convertToCamelCase, decodeCaseConfigureResponse } from '../utils'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/mock.ts b/x-pack/plugins/cases/public/containers/configure/mock.ts similarity index 98% rename from x-pack/plugins/security_solution/public/cases/containers/configure/mock.ts rename to x-pack/plugins/cases/public/containers/configure/mock.ts index 4e71c9a990ece..766452e3e58e7 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/mock.ts +++ b/x-pack/plugins/cases/public/containers/configure/mock.ts @@ -11,7 +11,7 @@ import { CasesConfigureResponse, CasesConfigureRequest, ConnectorTypes, -} from '../../../../../cases/common/api'; +} from '../../../common'; import { CaseConfigure, CaseConnectorMapping } from './types'; export const mappings: CaseConnectorMapping[] = [ diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/translations.ts b/x-pack/plugins/cases/public/containers/configure/translations.ts similarity index 64% rename from x-pack/plugins/security_solution/public/cases/containers/configure/translations.ts rename to x-pack/plugins/cases/public/containers/configure/translations.ts index 455293b217679..e77b9f57c8f4c 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/translations.ts +++ b/x-pack/plugins/cases/public/containers/configure/translations.ts @@ -9,9 +9,6 @@ import { i18n } from '@kbn/i18n'; export * from '../translations'; -export const SUCCESS_CONFIGURE = i18n.translate( - 'xpack.securitySolution.cases.configure.successSaveToast', - { - defaultMessage: 'Saved external connection settings', - } -); +export const SUCCESS_CONFIGURE = i18n.translate('xpack.cases.configure.successSaveToast', { + defaultMessage: 'Saved external connection settings', +}); diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/types.ts b/x-pack/plugins/cases/public/containers/configure/types.ts similarity index 95% rename from x-pack/plugins/security_solution/public/cases/containers/configure/types.ts rename to x-pack/plugins/cases/public/containers/configure/types.ts index aa86d1bfdb0b1..b021ae2163fa2 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/types.ts +++ b/x-pack/plugins/cases/public/containers/configure/types.ts @@ -15,7 +15,7 @@ import { CasesConfigure, ClosureType, ThirdPartyField, -} from '../../../../../cases/common/api'; +} from '../../../common'; export { ActionConnector, diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/use_action_types.test.tsx b/x-pack/plugins/cases/public/containers/configure/use_action_types.test.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/containers/configure/use_action_types.test.tsx rename to x-pack/plugins/cases/public/containers/configure/use_action_types.test.tsx index 25017f7931db8..fad84617ee140 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/use_action_types.test.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_action_types.test.tsx @@ -11,6 +11,7 @@ import { actionTypesMock } from './mock'; import * as api from './api'; jest.mock('./api'); +jest.mock('../../common/lib/kibana'); describe('useActionTypes', () => { beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/use_action_types.tsx b/x-pack/plugins/cases/public/containers/configure/use_action_types.tsx similarity index 85% rename from x-pack/plugins/security_solution/public/cases/containers/configure/use_action_types.tsx rename to x-pack/plugins/cases/public/containers/configure/use_action_types.tsx index 3590fffdef5b2..eaaadd65d29d1 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/use_action_types.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_action_types.tsx @@ -7,10 +7,10 @@ import { useState, useEffect, useCallback, useRef } from 'react'; -import { useStateToaster, errorToToaster } from '../../../common/components/toasters'; import * as i18n from '../translations'; import { fetchActionTypes } from './api'; import { ActionTypeConnector } from './types'; +import { useToasts } from '../../common/lib/kibana'; export interface UseActionTypesResponse { loading: boolean; @@ -19,7 +19,7 @@ export interface UseActionTypesResponse { } export const useActionTypes = (): UseActionTypesResponse => { - const [, dispatchToaster] = useStateToaster(); + const toasts = useToasts(); const [loading, setLoading] = useState(true); const [actionTypes, setActionTypes] = useState<ActionTypeConnector[]>([]); const isCancelledRef = useRef(false); @@ -43,14 +43,12 @@ export const useActionTypes = (): UseActionTypesResponse => { if (!isCancelledRef.current) { setLoading(false); setActionTypes([]); - errorToToaster({ + toasts.addError(error.body && error.body.message ? new Error(error.body.message) : error, { title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, }); } } - }, [dispatchToaster]); + }, [toasts]); useEffect(() => { if (queryFirstTime.current) { diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.test.tsx b/x-pack/plugins/cases/public/containers/configure/use_configure.test.tsx similarity index 94% rename from x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.test.tsx rename to x-pack/plugins/cases/public/containers/configure/use_configure.test.tsx index 44a503cd089ef..968afcc6ecfb3 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.test.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_configure.test.tsx @@ -14,15 +14,21 @@ import { } from './use_configure'; import { mappings, caseConfigurationCamelCaseResponseMock } from './mock'; import * as api from './api'; -import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; +import { ConnectorTypes } from '../../../common'; +const mockErrorToast = jest.fn(); +const mockSuccessToast = jest.fn(); jest.mock('./api'); -const mockErrorToToaster = jest.fn(); -jest.mock('../../../common/components/toasters', () => { - const original = jest.requireActual('../../../common/components/toasters'); +jest.mock('../../common/lib/kibana', () => { + const originalModule = jest.requireActual('../../common/lib/kibana'); return { - ...original, - errorToToaster: () => mockErrorToToaster(), + ...originalModule, + useToasts: () => { + return { + addError: mockErrorToast, + addSuccess: mockSuccessToast, + }; + }, }; }); const configuration: ConnectorConfiguration = { @@ -164,7 +170,7 @@ describe('useConfigure', () => { ); await waitForNextUpdate(); await waitForNextUpdate(); - expect(mockErrorToToaster).not.toHaveBeenCalled(); + expect(mockErrorToast).not.toHaveBeenCalled(); result.current.persistCaseConfigure(configuration); @@ -190,7 +196,7 @@ describe('useConfigure', () => { ); await waitForNextUpdate(); await waitForNextUpdate(); - expect(mockErrorToToaster).toHaveBeenCalled(); + expect(mockErrorToast).toHaveBeenCalled(); }); }); @@ -219,12 +225,12 @@ describe('useConfigure', () => { ); await waitForNextUpdate(); await waitForNextUpdate(); - expect(mockErrorToToaster).not.toHaveBeenCalled(); + expect(mockErrorToast).not.toHaveBeenCalled(); result.current.persistCaseConfigure(configuration); - expect(mockErrorToToaster).not.toHaveBeenCalled(); + expect(mockErrorToast).not.toHaveBeenCalled(); await waitForNextUpdate(); - expect(mockErrorToToaster).toHaveBeenCalled(); + expect(mockErrorToast).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.tsx b/x-pack/plugins/cases/public/containers/configure/use_configure.tsx similarity index 89% rename from x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.tsx rename to x-pack/plugins/cases/public/containers/configure/use_configure.tsx index 2ec2a73363bfe..c4b3db5956cd7 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/use_configure.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_configure.tsx @@ -8,14 +8,10 @@ import { useEffect, useCallback, useReducer, useRef } from 'react'; import { getCaseConfigure, patchCaseConfigure, postCaseConfigure } from './api'; -import { - useStateToaster, - errorToToaster, - displaySuccessToast, -} from '../../../common/components/toasters'; import * as i18n from './translations'; import { ClosureType, CaseConfigure, CaseConnector, CaseConnectorMapping } from './types'; -import { ConnectorTypes } from '../../../../../cases/common/api/connectors'; +import { ConnectorTypes } from '../../../common'; +import { useToasts } from '../../common/lib/kibana'; export type ConnectorConfiguration = { connector: CaseConnector } & { closureType: CaseConfigure['closureType']; @@ -149,7 +145,7 @@ export const initialState: State = { export const useCaseConfigure = (): ReturnUseCaseConfigure => { const [state, dispatch] = useReducer(configureCasesReducer, initialState); - + const toasts = useToasts(); const setCurrentConfiguration = useCallback((configuration: ConnectorConfiguration) => { dispatch({ currentConfiguration: configuration, @@ -206,7 +202,6 @@ export const useCaseConfigure = (): ReturnUseCaseConfigure => { }); }, []); - const [, dispatchToaster] = useStateToaster(); const isCancelledRefetchRef = useRef(false); const abortCtrlRefetchRef = useRef(new AbortController()); @@ -243,9 +238,7 @@ export const useCaseConfigure = (): ReturnUseCaseConfigure => { } } if (res.error != null) { - errorToToaster({ - dispatchToaster, - error: new Error(res.error), + toasts.addError(new Error(res.error), { title: i18n.ERROR_TITLE, }); } @@ -255,11 +248,10 @@ export const useCaseConfigure = (): ReturnUseCaseConfigure => { } catch (error) { if (!isCancelledRefetchRef.current) { if (error.name !== 'AbortError') { - errorToToaster({ - dispatchToaster, - error: error.body && error.body.message ? new Error(error.body.message) : error, - title: i18n.ERROR_TITLE, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_TITLE } + ); } setLoading(false); } @@ -290,7 +282,6 @@ export const useCaseConfigure = (): ReturnUseCaseConfigure => { }, abortCtrlPersistRef.current.signal ); - if (!isCancelledPersistRef.current) { setConnector(res.connector); if (setClosureType) { @@ -307,23 +298,22 @@ export const useCaseConfigure = (): ReturnUseCaseConfigure => { }); } if (res.error != null) { - errorToToaster({ - dispatchToaster, - error: new Error(res.error), + toasts.addError(new Error(res.error), { title: i18n.ERROR_TITLE, }); } - displaySuccessToast(i18n.SUCCESS_CONFIGURE, dispatchToaster); + toasts.addSuccess(i18n.SUCCESS_CONFIGURE); setPersistLoading(false); } } catch (error) { if (!isCancelledPersistRef.current) { if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { + title: i18n.ERROR_TITLE, + } + ); } setConnector(state.currentConfiguration.connector); setPersistLoading(false); @@ -331,14 +321,15 @@ export const useCaseConfigure = (): ReturnUseCaseConfigure => { } }, [ - dispatchToaster, setClosureType, setConnector, setCurrentConfiguration, setMappings, setPersistLoading, setVersion, - state, + state.currentConfiguration.connector, + state.version, + toasts, ] ); diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/use_connectors.test.tsx b/x-pack/plugins/cases/public/containers/configure/use_connectors.test.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/containers/configure/use_connectors.test.tsx rename to x-pack/plugins/cases/public/containers/configure/use_connectors.test.tsx index ed1dfcbc40c87..e3d2650fee025 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/use_connectors.test.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_connectors.test.tsx @@ -11,6 +11,7 @@ import { connectorsMock } from './mock'; import * as api from './api'; jest.mock('./api'); +jest.mock('../../common/lib/kibana'); describe('useConnectors', () => { beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/cases/containers/configure/use_connectors.tsx b/x-pack/plugins/cases/public/containers/configure/use_connectors.tsx similarity index 68% rename from x-pack/plugins/security_solution/public/cases/containers/configure/use_connectors.tsx rename to x-pack/plugins/cases/public/containers/configure/use_connectors.tsx index 338d04f702c63..3b91c77d0235a 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/configure/use_connectors.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_connectors.tsx @@ -7,10 +7,10 @@ import { useState, useEffect, useCallback, useRef } from 'react'; -import { useStateToaster, errorToToaster } from '../../../common/components/toasters'; import * as i18n from '../translations'; import { fetchConnectors } from './api'; import { ActionConnector } from './types'; +import { useToasts } from '../../common/lib/kibana'; export interface UseConnectorsResponse { loading: boolean; @@ -19,9 +19,14 @@ export interface UseConnectorsResponse { } export const useConnectors = (): UseConnectorsResponse => { - const [, dispatchToaster] = useStateToaster(); - const [loading, setLoading] = useState(true); - const [connectors, setConnectors] = useState<ActionConnector[]>([]); + const toasts = useToasts(); + const [state, setState] = useState<{ + loading: boolean; + connectors: ActionConnector[]; + }>({ + loading: true, + connectors: [], + }); const isCancelledRef = useRef(false); const abortCtrlRef = useRef(new AbortController()); @@ -30,26 +35,30 @@ export const useConnectors = (): UseConnectorsResponse => { isCancelledRef.current = false; abortCtrlRef.current.abort(); abortCtrlRef.current = new AbortController(); - - setLoading(true); + setState({ + ...state, + loading: true, + }); const res = await fetchConnectors({ signal: abortCtrlRef.current.signal }); if (!isCancelledRef.current) { - setLoading(false); - setConnectors(res); + setState({ + loading: false, + connectors: res, + }); } } catch (error) { if (!isCancelledRef.current) { if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_TITLE } + ); } - - setLoading(false); - setConnectors([]); + setState({ + loading: false, + connectors: [], + }); } } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -65,8 +74,8 @@ export const useConnectors = (): UseConnectorsResponse => { }, []); return { - loading, - connectors, + loading: state.loading, + connectors: state.connectors, refetchConnectors, }; }; diff --git a/x-pack/plugins/security_solution/public/cases/containers/constants.ts b/x-pack/plugins/cases/public/containers/constants.ts similarity index 100% rename from x-pack/plugins/security_solution/public/cases/containers/constants.ts rename to x-pack/plugins/cases/public/containers/constants.ts diff --git a/x-pack/plugins/security_solution/public/cases/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts similarity index 98% rename from x-pack/plugins/security_solution/public/cases/containers/mock.ts rename to x-pack/plugins/cases/public/containers/mock.ts index 6e937fe7760cd..1e7cec29de56b 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/mock.ts +++ b/x-pack/plugins/cases/public/containers/mock.ts @@ -8,21 +8,21 @@ import { ActionLicense, AllCases, Case, CasesStatus, CaseUserActions, Comment } from './types'; import { - CommentResponse, - CaseStatuses, - UserAction, - UserActionField, + AssociationType, CaseResponse, + CasesFindResponse, + CasesResponse, CasesStatusResponse, + CaseStatuses, + CaseType, CaseUserActionsResponse, - CasesResponse, - CasesFindResponse, + CommentResponse, CommentType, - AssociationType, - CaseType, -} from '../../../../cases/common/api'; + ConnectorTypes, + UserAction, + UserActionField, +} from '../../common'; import { UseGetCasesState, DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases'; -import { ConnectorTypes } from '../../../../cases/common/api/connectors'; export { connectorsMock } from './configure/mock'; export const basicCaseId = 'basic-case-id'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/translations.ts b/x-pack/plugins/cases/public/containers/translations.ts similarity index 64% rename from x-pack/plugins/security_solution/public/cases/containers/translations.ts rename to x-pack/plugins/cases/public/containers/translations.ts index 4c7afc9224445..966a5e158923f 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/translations.ts +++ b/x-pack/plugins/cases/public/containers/translations.ts @@ -7,27 +7,24 @@ import { i18n } from '@kbn/i18n'; -export * from '../translations'; +export * from '../common/translations'; -export const ERROR_TITLE = i18n.translate('xpack.securitySolution.containers.cases.errorTitle', { +export const ERROR_TITLE = i18n.translate('xpack.cases.containers.errorTitle', { defaultMessage: 'Error fetching data', }); -export const ERROR_DELETING = i18n.translate( - 'xpack.securitySolution.containers.cases.errorDeletingTitle', - { - defaultMessage: 'Error deleting data', - } -); +export const ERROR_DELETING = i18n.translate('xpack.cases.containers.errorDeletingTitle', { + defaultMessage: 'Error deleting data', +}); export const UPDATED_CASE = (caseTitle: string) => - i18n.translate('xpack.securitySolution.containers.cases.updatedCase', { + i18n.translate('xpack.cases.containers.updatedCase', { values: { caseTitle }, defaultMessage: 'Updated "{caseTitle}"', }); export const DELETED_CASES = (totalCases: number, caseTitle?: string) => - i18n.translate('xpack.securitySolution.containers.cases.deletedCases', { + i18n.translate('xpack.cases.containers.deletedCases', { values: { caseTitle, totalCases }, defaultMessage: 'Deleted {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', }); @@ -39,7 +36,7 @@ export const CLOSED_CASES = ({ totalCases: number; caseTitle?: string; }) => - i18n.translate('xpack.securitySolution.containers.cases.closedCases', { + i18n.translate('xpack.cases.containers.closedCases', { values: { caseTitle, totalCases }, defaultMessage: 'Closed {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', }); @@ -51,7 +48,7 @@ export const REOPENED_CASES = ({ totalCases: number; caseTitle?: string; }) => - i18n.translate('xpack.securitySolution.containers.cases.reopenedCases', { + i18n.translate('xpack.cases.containers.reopenedCases', { values: { caseTitle, totalCases }, defaultMessage: 'Opened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', }); @@ -63,33 +60,30 @@ export const MARK_IN_PROGRESS_CASES = ({ totalCases: number; caseTitle?: string; }) => - i18n.translate('xpack.securitySolution.containers.cases.markInProgressCases', { + i18n.translate('xpack.cases.containers.markInProgressCases', { values: { caseTitle, totalCases }, defaultMessage: 'Marked {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}} as in progress', }); export const SUCCESS_SEND_TO_EXTERNAL_SERVICE = (serviceName: string) => - i18n.translate('xpack.securitySolution.containers.cases.pushToExternalService', { + i18n.translate('xpack.cases.containers.pushToExternalService', { values: { serviceName }, defaultMessage: 'Successfully sent to { serviceName }', }); -export const ERROR_GET_FIELDS = i18n.translate( - 'xpack.securitySolution.cases.configure.errorGetFields', - { - defaultMessage: 'Error getting fields from service', - } -); +export const ERROR_GET_FIELDS = i18n.translate('xpack.cases.configure.errorGetFields', { + defaultMessage: 'Error getting fields from service', +}); export const SYNC_CASE = (caseTitle: string) => - i18n.translate('xpack.securitySolution.containers.cases.syncCase', { + i18n.translate('xpack.cases.containers.syncCase', { values: { caseTitle }, defaultMessage: 'Alerts in "{caseTitle}" have been synced', }); export const STATUS_CHANGED_TOASTER_TEXT = i18n.translate( - 'xpack.securitySolution.cases.containers.statusChangeToasterText', + 'xpack.cases.containers.statusChangeToasterText', { defaultMessage: 'Alerts in this case have been also had their status updated', } diff --git a/x-pack/plugins/cases/public/containers/types.ts b/x-pack/plugins/cases/public/containers/types.ts new file mode 100644 index 0000000000000..62a5f9299498e --- /dev/null +++ b/x-pack/plugins/cases/public/containers/types.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 * from '../../common/ui'; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.test.tsx b/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.test.tsx rename to x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx index d5562afec1d26..67f202e6adbad 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx @@ -6,12 +6,13 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; -import { CaseStatuses } from '../../../../cases/common/api'; +import { CaseStatuses } from '../../common'; import { useUpdateCases, UseUpdateCases } from './use_bulk_update_case'; import { basicCase } from './mock'; import * as api from './api'; jest.mock('./api'); +jest.mock('../common/lib/kibana'); describe('useUpdateCases', () => { const abortCtrl = new AbortController(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx b/x-pack/plugins/cases/public/containers/use_bulk_update_case.tsx similarity index 89% rename from x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx rename to x-pack/plugins/cases/public/containers/use_bulk_update_case.tsx index d39da93a06a48..ae2d09deafb04 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_bulk_update_case.tsx +++ b/x-pack/plugins/cases/public/containers/use_bulk_update_case.tsx @@ -6,15 +6,11 @@ */ import { useCallback, useReducer, useRef, useEffect } from 'react'; -import { CaseStatuses } from '../../../../cases/common/api'; -import { - displaySuccessToast, - errorToToaster, - useStateToaster, -} from '../../common/components/toasters'; +import { CaseStatuses } from '../../common'; import * as i18n from './translations'; import { patchCasesStatus } from './api'; import { BulkUpdateStatus, Case } from './types'; +import { useToasts } from '../common/lib/kibana'; interface UpdateState { isUpdated: boolean; @@ -86,7 +82,7 @@ export const useUpdateCases = (): UseUpdateCases => { isError: false, isUpdated: false, }); - const [, dispatchToaster] = useStateToaster(); + const toasts = useToasts(); const isCancelledRef = useRef(false); const abortCtrlRef = useRef(new AbortController()); @@ -112,16 +108,15 @@ export const useUpdateCases = (): UseUpdateCases => { const message = action === 'status' ? getStatusToasterMessage(patchResponse[0].status, messageArgs) : ''; - displaySuccessToast(message, dispatchToaster); + toasts.addSuccess(message); } } catch (error) { if (!isCancelledRef.current) { if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_TITLE } + ); } dispatch({ type: 'FETCH_FAILURE' }); } diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.test.tsx b/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.test.tsx rename to x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx index b4fa816412c68..e86ed0c036974 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx @@ -7,11 +7,12 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import { CaseType } from '../../../../cases/common/api'; +import { CaseType } from '../../common'; import { useDeleteCases, UseDeleteCase } from './use_delete_cases'; import * as api from './api'; jest.mock('./api'); +jest.mock('../common/lib/kibana'); describe('useDeleteCases', () => { const abortCtrl = new AbortController(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.tsx b/x-pack/plugins/cases/public/containers/use_delete_cases.tsx similarity index 90% rename from x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.tsx rename to x-pack/plugins/cases/public/containers/use_delete_cases.tsx index f3d59a2883f2a..81a44004b2441 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_delete_cases.tsx +++ b/x-pack/plugins/cases/public/containers/use_delete_cases.tsx @@ -6,14 +6,10 @@ */ import { useCallback, useReducer, useRef, useEffect } from 'react'; -import { - displaySuccessToast, - errorToToaster, - useStateToaster, -} from '../../common/components/toasters'; import * as i18n from './translations'; import { deleteCases, deleteSubCases } from './api'; import { DeleteCase } from './types'; +import { useToasts } from '../common/lib/kibana'; interface DeleteState { isDisplayConfirmDeleteModal: boolean; @@ -77,7 +73,7 @@ export const useDeleteCases = (): UseDeleteCase => { isError: false, isDeleted: false, }); - const [, dispatchToaster] = useStateToaster(); + const toasts = useToasts(); const isCancelledRef = useRef(false); const abortCtrlRef = useRef(new AbortController()); @@ -98,19 +94,17 @@ export const useDeleteCases = (): UseDeleteCase => { if (!isCancelledRef.current) { dispatch({ type: 'FETCH_SUCCESS', payload: true }); - displaySuccessToast( - i18n.DELETED_CASES(cases.length, cases.length === 1 ? cases[0].title : ''), - dispatchToaster + toasts.addSuccess( + i18n.DELETED_CASES(cases.length, cases.length === 1 ? cases[0].title : '') ); } } catch (error) { if (!isCancelledRef.current) { if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_DELETING, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_DELETING } + ); } dispatch({ type: 'FETCH_FAILURE' }); } diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_action_license.test.tsx b/x-pack/plugins/cases/public/containers/use_get_action_license.test.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/containers/use_get_action_license.test.tsx rename to x-pack/plugins/cases/public/containers/use_get_action_license.test.tsx index 4c6cbae0c8981..ae6a884514161 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_action_license.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_action_license.test.tsx @@ -11,6 +11,7 @@ import { actionLicenses } from './mock'; import * as api from './api'; jest.mock('./api'); +jest.mock('../common/lib/kibana'); describe('useGetActionLicense', () => { const abortCtrl = new AbortController(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_action_license.tsx b/x-pack/plugins/cases/public/containers/use_get_action_license.tsx similarity index 86% rename from x-pack/plugins/security_solution/public/cases/containers/use_get_action_license.tsx rename to x-pack/plugins/cases/public/containers/use_get_action_license.tsx index 9b10247794c8d..4f28d88c14b25 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_action_license.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_action_license.tsx @@ -7,7 +7,7 @@ import { useCallback, useEffect, useState, useRef } from 'react'; -import { errorToToaster, useStateToaster } from '../../common/components/toasters'; +import { useToasts } from '../common/lib/kibana'; import { getActionLicense } from './api'; import * as i18n from './translations'; import { ActionLicense } from './types'; @@ -28,7 +28,7 @@ const MINIMUM_LICENSE_REQUIRED_CONNECTOR = '.jira'; export const useGetActionLicense = (): ActionLicenseState => { const [actionLicenseState, setActionLicensesState] = useState<ActionLicenseState>(initialData); - const [, dispatchToaster] = useStateToaster(); + const toasts = useToasts(); const isCancelledRef = useRef(false); const abortCtrlRef = useRef(new AbortController()); @@ -54,11 +54,10 @@ export const useGetActionLicense = (): ActionLicenseState => { } catch (error) { if (!isCancelledRef.current) { if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_TITLE } + ); } setActionLicensesState({ diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_case.test.tsx b/x-pack/plugins/cases/public/containers/use_get_case.test.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/containers/use_get_case.test.tsx rename to x-pack/plugins/cases/public/containers/use_get_case.test.tsx index a3d64a17727e5..75d9ac74a8ccf 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_case.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case.test.tsx @@ -11,6 +11,7 @@ import { basicCase } from './mock'; import * as api from './api'; jest.mock('./api'); +jest.mock('../common/lib/kibana'); describe('useGetCase', () => { const abortCtrl = new AbortController(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx b/x-pack/plugins/cases/public/containers/use_get_case.tsx similarity index 89% rename from x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx rename to x-pack/plugins/cases/public/containers/use_get_case.tsx index 70e202b5d6bdf..7b59f8e06b7af 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case.tsx @@ -9,7 +9,7 @@ import { useEffect, useReducer, useCallback, useRef } from 'react'; import { Case } from './types'; import * as i18n from './translations'; -import { errorToToaster, useStateToaster } from '../../common/components/toasters'; +import { useToasts } from '../common/lib/kibana'; import { getCase, getSubCase } from './api'; interface CaseState { @@ -66,7 +66,7 @@ export const useGetCase = (caseId: string, subCaseId?: string): UseGetCase => { isError: false, data: null, }); - const [, dispatchToaster] = useStateToaster(); + const toasts = useToasts(); const isCancelledRef = useRef(false); const abortCtrlRef = useRef(new AbortController()); @@ -91,11 +91,10 @@ export const useGetCase = (caseId: string, subCaseId?: string): UseGetCase => { } catch (error) { if (!isCancelledRef.current) { if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_TITLE } + ); } dispatch({ type: 'FETCH_FAILURE' }); } diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.test.tsx b/x-pack/plugins/cases/public/containers/use_get_case_user_actions.test.tsx similarity index 99% rename from x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.test.tsx rename to x-pack/plugins/cases/public/containers/use_get_case_user_actions.test.tsx index 1c8096198007e..62b4cf92434cd 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case_user_actions.test.tsx @@ -23,6 +23,7 @@ import { import * as api from './api'; jest.mock('./api'); +jest.mock('../common/lib/kibana'); describe('useGetCaseUserActions', () => { const abortCtrl = new AbortController(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx b/x-pack/plugins/cases/public/containers/use_get_case_user_actions.tsx similarity index 94% rename from x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx rename to x-pack/plugins/cases/public/containers/use_get_case_user_actions.tsx index 3b28c20d9a4df..66aa93154b318 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_case_user_actions.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case_user_actions.tsx @@ -9,12 +9,17 @@ import { isEmpty, uniqBy } from 'lodash/fp'; import { useCallback, useEffect, useState, useRef } from 'react'; import deepEqual from 'fast-deep-equal'; -import { errorToToaster, useStateToaster } from '../../common/components/toasters'; -import { CaseFullExternalService } from '../../../../cases/common/api/cases'; +import { + CaseFullExternalService, + CaseConnector, + CaseExternalService, + CaseUserActions, + ElasticUser, +} from '../../common'; import { getCaseUserActions, getSubCaseUserActions } from './api'; import * as i18n from './translations'; -import { CaseConnector, CaseExternalService, CaseUserActions, ElasticUser } from './types'; import { convertToCamelCase, parseString } from './utils'; +import { useToasts } from '../common/lib/kibana'; export interface CaseService extends CaseExternalService { firstPushIndex: number; @@ -246,7 +251,7 @@ export const useGetCaseUserActions = ( ); const abortCtrlRef = useRef(new AbortController()); const isCancelledRef = useRef(false); - const [, dispatchToaster] = useStateToaster(); + const toasts = useToasts(); const fetchCaseUserActions = useCallback( async (thisCaseId: string, thisCaseConnectorId: string, thisSubCaseId?: string) => { @@ -288,11 +293,10 @@ export const useGetCaseUserActions = ( } catch (error) { if (!isCancelledRef.current) { if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_TITLE } + ); } setCaseUserActionsState({ diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_cases.test.tsx b/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/containers/use_get_cases.test.tsx rename to x-pack/plugins/cases/public/containers/use_get_cases.test.tsx index 3a62ae70b82de..b07fec4984eb1 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_cases.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx @@ -6,7 +6,7 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; -import { CaseStatuses } from '../../../../cases/common/api'; +import { CaseStatuses } from '../../common'; import { DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS, @@ -19,6 +19,7 @@ import { allCases, basicCase } from './mock'; import * as api from './api'; jest.mock('./api'); +jest.mock('../common/lib/kibana'); describe('useGetCases', () => { const abortCtrl = new AbortController(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_cases.tsx b/x-pack/plugins/cases/public/containers/use_get_cases.tsx similarity index 79% rename from x-pack/plugins/security_solution/public/cases/containers/use_get_cases.tsx rename to x-pack/plugins/cases/public/containers/use_get_cases.tsx index d27bb5ab1b462..ec1abd6214926 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_cases.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_cases.tsx @@ -7,11 +7,18 @@ import { useCallback, useEffect, useReducer, useRef } from 'react'; import { DEFAULT_TABLE_ACTIVE_PAGE, DEFAULT_TABLE_LIMIT } from './constants'; -import { AllCases, SortFieldCase, FilterOptions, QueryParams, Case, UpdateByKey } from './types'; -import { errorToToaster, useStateToaster } from '../../common/components/toasters'; +import { + AllCases, + Case, + FilterOptions, + QueryParams, + SortFieldCase, + StatusAll, + UpdateByKey, +} from './types'; +import { useToasts } from '../common/lib/kibana'; import * as i18n from './translations'; import { getCases, patchCase } from './api'; -import { StatusAll } from '../components/status'; export interface UseGetCasesState { data: AllCases; @@ -130,19 +137,20 @@ export interface UseGetCases extends UseGetCasesState { setSelectedCases: (mySelectedCases: Case[]) => void; } +const empty = {}; export const useGetCases = ( - initialQueryParams?: QueryParams, - initialFilterOptions?: FilterOptions + initialQueryParams: Partial<QueryParams> = empty, + initialFilterOptions: Partial<FilterOptions> = empty ): UseGetCases => { const [state, dispatch] = useReducer(dataFetchReducer, { data: initialData, - filterOptions: initialFilterOptions ?? DEFAULT_FILTER_OPTIONS, + filterOptions: { ...DEFAULT_FILTER_OPTIONS, ...initialFilterOptions }, isError: false, loading: [], - queryParams: initialQueryParams ?? DEFAULT_QUERY_PARAMS, + queryParams: { ...DEFAULT_QUERY_PARAMS, ...initialQueryParams }, selectedCases: [], }); - const [, dispatchToaster] = useStateToaster(); + const toasts = useToasts(); const didCancelFetchCases = useRef(false); const didCancelUpdateCases = useRef(false); const abortCtrlFetchCases = useRef(new AbortController()); @@ -160,39 +168,40 @@ export const useGetCases = ( dispatch({ type: 'UPDATE_FILTER_OPTIONS', payload: newFilters }); }, []); - const fetchCases = useCallback(async (filterOptions: FilterOptions, queryParams: QueryParams) => { - try { - didCancelFetchCases.current = false; - abortCtrlFetchCases.current.abort(); - abortCtrlFetchCases.current = new AbortController(); - dispatch({ type: 'FETCH_INIT', payload: 'cases' }); - - const response = await getCases({ - filterOptions, - queryParams, - signal: abortCtrlFetchCases.current.signal, - }); - - if (!didCancelFetchCases.current) { - dispatch({ - type: 'FETCH_CASES_SUCCESS', - payload: response, + const fetchCases = useCallback( + async (filterOptions: FilterOptions, queryParams: QueryParams) => { + try { + didCancelFetchCases.current = false; + abortCtrlFetchCases.current.abort(); + abortCtrlFetchCases.current = new AbortController(); + dispatch({ type: 'FETCH_INIT', payload: 'cases' }); + + const response = await getCases({ + filterOptions, + queryParams, + signal: abortCtrlFetchCases.current.signal, }); - } - } catch (error) { - if (!didCancelFetchCases.current) { - if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, + + if (!didCancelFetchCases.current) { + dispatch({ + type: 'FETCH_CASES_SUCCESS', + payload: response, }); } - dispatch({ type: 'FETCH_FAILURE', payload: 'cases' }); + } catch (error) { + if (!didCancelFetchCases.current) { + if (error.name !== 'AbortError') { + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_TITLE } + ); + } + dispatch({ type: 'FETCH_FAILURE', payload: 'cases' }); + } } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, + [toasts] + ); const dispatchUpdateCaseProperty = useCallback( async ({ updateKey, updateValue, caseId, refetchCasesStatus, version }: UpdateCase) => { @@ -218,7 +227,7 @@ export const useGetCases = ( } catch (error) { if (!didCancelUpdateCases.current) { if (error.name !== 'AbortError') { - errorToToaster({ title: i18n.ERROR_TITLE, error, dispatchToaster }); + toasts.addError(error, { title: i18n.ERROR_TITLE }); } dispatch({ type: 'FETCH_FAILURE', payload: 'caseUpdate' }); } diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_cases_status.test.tsx b/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/containers/use_get_cases_status.test.tsx rename to x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx index 30714a2d8d938..f795d5cc60e71 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_cases_status.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx @@ -11,6 +11,7 @@ import { casesStatus } from './mock'; import * as api from './api'; jest.mock('./api'); +jest.mock('../common/lib/kibana'); describe('useGetCasesStatus', () => { const abortCtrl = new AbortController(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_cases_status.tsx b/x-pack/plugins/cases/public/containers/use_get_cases_status.tsx similarity index 86% rename from x-pack/plugins/security_solution/public/cases/containers/use_get_cases_status.tsx rename to x-pack/plugins/cases/public/containers/use_get_cases_status.tsx index 087f7ef455cba..c3244bb38f151 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_cases_status.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_cases_status.tsx @@ -7,10 +7,10 @@ import { useCallback, useEffect, useState, useRef } from 'react'; -import { errorToToaster, useStateToaster } from '../../common/components/toasters'; import { getCasesStatus } from './api'; import * as i18n from './translations'; import { CasesStatus } from './types'; +import { useToasts } from '../common/lib/kibana'; interface CasesStatusState extends CasesStatus { isLoading: boolean; @@ -31,7 +31,7 @@ export interface UseGetCasesStatus extends CasesStatusState { export const useGetCasesStatus = (): UseGetCasesStatus => { const [casesStatusState, setCasesStatusState] = useState<CasesStatusState>(initialData); - const [, dispatchToaster] = useStateToaster(); + const toasts = useToasts(); const isCancelledRef = useRef(false); const abortCtrlRef = useRef(new AbortController()); @@ -57,11 +57,10 @@ export const useGetCasesStatus = (): UseGetCasesStatus => { } catch (error) { if (!isCancelledRef.current) { if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_TITLE } + ); } setCasesStatusState({ countClosedCases: 0, diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_reporters.test.tsx b/x-pack/plugins/cases/public/containers/use_get_reporters.test.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/containers/use_get_reporters.test.tsx rename to x-pack/plugins/cases/public/containers/use_get_reporters.test.tsx index ff1c5a3eb4de7..8345ddf107872 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_reporters.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_reporters.test.tsx @@ -11,6 +11,7 @@ import { reporters, respReporters } from './mock'; import * as api from './api'; jest.mock('./api'); +jest.mock('../common/lib/kibana'); describe('useGetReporters', () => { const abortCtrl = new AbortController(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_reporters.tsx b/x-pack/plugins/cases/public/containers/use_get_reporters.tsx similarity index 82% rename from x-pack/plugins/security_solution/public/cases/containers/use_get_reporters.tsx rename to x-pack/plugins/cases/public/containers/use_get_reporters.tsx index 10c2d26d6b33d..a9d28de33cb41 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_reporters.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_reporters.tsx @@ -8,10 +8,10 @@ import { useCallback, useEffect, useState, useRef } from 'react'; import { isEmpty } from 'lodash/fp'; -import { User } from '../../../../cases/common/api'; -import { errorToToaster, useStateToaster } from '../../common/components/toasters'; +import { User } from '../../common'; import { getReporters } from './api'; import * as i18n from './translations'; +import { useToasts } from '../common/lib/kibana'; interface ReportersState { reporters: string[]; @@ -34,7 +34,7 @@ export interface UseGetReporters extends ReportersState { export const useGetReporters = (): UseGetReporters => { const [reportersState, setReporterState] = useState<ReportersState>(initialData); - const [, dispatchToaster] = useStateToaster(); + const toasts = useToasts(); const isCancelledRef = useRef(false); const abortCtrlRef = useRef(new AbortController()); @@ -64,11 +64,10 @@ export const useGetReporters = (): UseGetReporters => { } catch (error) { if (!isCancelledRef.current) { if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_TITLE } + ); } setReporterState({ @@ -79,8 +78,7 @@ export const useGetReporters = (): UseGetReporters => { }); } } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [reportersState]); + }, [reportersState, toasts]); useEffect(() => { fetchReporters(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_tags.test.tsx b/x-pack/plugins/cases/public/containers/use_get_tags.test.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/containers/use_get_tags.test.tsx rename to x-pack/plugins/cases/public/containers/use_get_tags.test.tsx index 8042e560df350..3fecfb51b958c 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_tags.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_tags.test.tsx @@ -11,6 +11,7 @@ import { tags } from './mock'; import * as api from './api'; jest.mock('./api'); +jest.mock('../common/lib/kibana'); describe('useGetTags', () => { const abortCtrl = new AbortController(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_tags.tsx b/x-pack/plugins/cases/public/containers/use_get_tags.tsx similarity index 87% rename from x-pack/plugins/security_solution/public/cases/containers/use_get_tags.tsx rename to x-pack/plugins/cases/public/containers/use_get_tags.tsx index 4a7a298e2cd86..4368b025baa38 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_tags.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_tags.tsx @@ -6,7 +6,7 @@ */ import { useEffect, useReducer, useRef, useCallback } from 'react'; -import { errorToToaster, useStateToaster } from '../../common/components/toasters'; +import { useToasts } from '../common/lib/kibana'; import { getTags } from './api'; import * as i18n from './translations'; @@ -57,7 +57,7 @@ export const useGetTags = (): UseGetTags => { isError: false, tags: initialData, }); - const [, dispatchToaster] = useStateToaster(); + const toasts = useToasts(); const isCancelledRef = useRef(false); const abortCtrlRef = useRef(new AbortController()); @@ -76,11 +76,10 @@ export const useGetTags = (): UseGetTags => { } catch (error) { if (!isCancelledRef.current) { if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_TITLE } + ); } dispatch({ type: 'FETCH_FAILURE' }); } diff --git a/x-pack/plugins/cases/public/containers/use_messages_storage.test.tsx b/x-pack/plugins/cases/public/containers/use_messages_storage.test.tsx new file mode 100644 index 0000000000000..73bfc49f077ae --- /dev/null +++ b/x-pack/plugins/cases/public/containers/use_messages_storage.test.tsx @@ -0,0 +1,97 @@ +/* + * 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, act } from '@testing-library/react-hooks'; +import { useMessagesStorage, UseMessagesStorage } from './use_messages_storage'; + +describe('useLocalStorage', () => { + beforeEach(() => { + localStorage.clear(); + }); + afterEach(() => { + localStorage.clear(); + }); + + it('should return an empty array when there is no messages', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook<string, UseMessagesStorage>(() => + useMessagesStorage() + ); + await waitForNextUpdate(); + const { getMessages } = result.current; + expect(getMessages('case')).toEqual([]); + }); + }); + + it('should add a message', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook<string, UseMessagesStorage>(() => + useMessagesStorage() + ); + await waitForNextUpdate(); + const { getMessages, addMessage } = result.current; + addMessage('case', 'id-1'); + expect(getMessages('case')).toEqual(['id-1']); + }); + }); + + it('should add multiple messages', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook<string, UseMessagesStorage>(() => + useMessagesStorage() + ); + await waitForNextUpdate(); + const { getMessages, addMessage } = result.current; + addMessage('case', 'id-1'); + addMessage('case', 'id-2'); + expect(getMessages('case')).toEqual(['id-1', 'id-2']); + }); + }); + + it('should remove a message', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook<string, UseMessagesStorage>(() => + useMessagesStorage() + ); + await waitForNextUpdate(); + const { getMessages, addMessage, removeMessage } = result.current; + addMessage('case', 'id-1'); + addMessage('case', 'id-2'); + removeMessage('case', 'id-2'); + expect(getMessages('case')).toEqual(['id-1']); + }); + }); + + it('should return presence of a message', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook<string, UseMessagesStorage>(() => + useMessagesStorage() + ); + await waitForNextUpdate(); + const { hasMessage, addMessage, removeMessage } = result.current; + addMessage('case', 'id-1'); + addMessage('case', 'id-2'); + removeMessage('case', 'id-2'); + expect(hasMessage('case', 'id-1')).toEqual(true); + expect(hasMessage('case', 'id-2')).toEqual(false); + }); + }); + + it('should clear all messages', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook<string, UseMessagesStorage>(() => + useMessagesStorage() + ); + await waitForNextUpdate(); + const { getMessages, addMessage, clearAllMessages } = result.current; + addMessage('case', 'id-1'); + addMessage('case', 'id-2'); + clearAllMessages('case'); + expect(getMessages('case')).toEqual([]); + }); + }); +}); diff --git a/x-pack/plugins/cases/public/containers/use_messages_storage.tsx b/x-pack/plugins/cases/public/containers/use_messages_storage.tsx new file mode 100644 index 0000000000000..c7eed3cbd881b --- /dev/null +++ b/x-pack/plugins/cases/public/containers/use_messages_storage.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 { useCallback, useMemo } from 'react'; +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; + +export interface UseMessagesStorage { + getMessages: (plugin: string) => string[]; + addMessage: (plugin: string, id: string) => void; + removeMessage: (plugin: string, id: string) => void; + clearAllMessages: (plugin: string) => void; + hasMessage: (plugin: string, id: string) => boolean; +} + +// TODO: Removed const { storage } = useKibana().services; in favor of using the util directly +export const useMessagesStorage = (): UseMessagesStorage => { + const storage = useMemo(() => new Storage(localStorage), []); + + const getMessages = useCallback( + (plugin: string): string[] => storage.get(`${plugin}-messages`) ?? [], + [storage] + ); + + const addMessage = useCallback( + (plugin: string, id: string) => { + const pluginStorage = storage.get(`${plugin}-messages`) ?? []; + storage.set(`${plugin}-messages`, [...pluginStorage, id]); + }, + [storage] + ); + + const hasMessage = useCallback( + (plugin: string, id: string): boolean => { + const pluginStorage = storage.get(`${plugin}-messages`) ?? []; + return pluginStorage.filter((val: string) => val === id).length > 0; + }, + [storage] + ); + + const removeMessage = useCallback( + (plugin: string, id: string) => { + const pluginStorage = storage.get(`${plugin}-messages`) ?? []; + storage.set(`${plugin}-messages`, [...pluginStorage.filter((val: string) => val !== id)]); + }, + [storage] + ); + + const clearAllMessages = useCallback( + (plugin: string): string[] => storage.remove(`${plugin}-messages`), + [storage] + ); + + return { + getMessages, + addMessage, + clearAllMessages, + removeMessage, + hasMessage, + }; +}; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_case.test.tsx b/x-pack/plugins/cases/public/containers/use_post_case.test.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/containers/use_post_case.test.tsx rename to x-pack/plugins/cases/public/containers/use_post_case.test.tsx index 3731af4d73db5..f7f7f1419c713 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_case.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_post_case.test.tsx @@ -8,10 +8,11 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { usePostCase, UsePostCase } from './use_post_case'; import * as api from './api'; -import { ConnectorTypes } from '../../../../cases/common/api/connectors'; +import { ConnectorTypes } from '../../common'; import { basicCasePost } from './mock'; jest.mock('./api'); +jest.mock('../common/lib/kibana'); describe('usePostCase', () => { const abortCtrl = new AbortController(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_case.tsx b/x-pack/plugins/cases/public/containers/use_post_case.tsx similarity index 85% rename from x-pack/plugins/security_solution/public/cases/containers/use_post_case.tsx rename to x-pack/plugins/cases/public/containers/use_post_case.tsx index 35c2b66156456..f3c92fc1ab336 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_case.tsx +++ b/x-pack/plugins/cases/public/containers/use_post_case.tsx @@ -6,11 +6,11 @@ */ import { useReducer, useCallback, useRef, useEffect } from 'react'; -import { CasePostRequest } from '../../../../cases/common/api'; -import { errorToToaster, useStateToaster } from '../../common/components/toasters'; +import { CasePostRequest } from '../../common'; import { postCase } from './api'; import * as i18n from './translations'; import { Case } from './types'; +import { useToasts } from '../common/lib/kibana'; interface NewCaseState { isLoading: boolean; isError: boolean; @@ -49,7 +49,7 @@ export const usePostCase = (): UsePostCase => { isLoading: false, isError: false, }); - const [, dispatchToaster] = useStateToaster(); + const toasts = useToasts(); const isCancelledRef = useRef(false); const abortCtrlRef = useRef(new AbortController()); @@ -69,11 +69,10 @@ export const usePostCase = (): UsePostCase => { } catch (error) { if (!isCancelledRef.current) { if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_TITLE } + ); } dispatch({ type: 'FETCH_FAILURE' }); } diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx b/x-pack/plugins/cases/public/containers/use_post_comment.test.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx rename to x-pack/plugins/cases/public/containers/use_post_comment.test.tsx index 4d4ac5d071fa5..5b927f55c9e91 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_post_comment.test.tsx @@ -7,12 +7,13 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import { CommentType } from '../../../../cases/common/api'; +import { CommentType } from '../../common'; import { usePostComment, UsePostComment } from './use_post_comment'; import { basicCaseId, basicSubCaseId } from './mock'; import * as api from './api'; jest.mock('./api'); +jest.mock('../common/lib/kibana'); describe('usePostComment', () => { const abortCtrl = new AbortController(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx b/x-pack/plugins/cases/public/containers/use_post_comment.tsx similarity index 85% rename from x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx rename to x-pack/plugins/cases/public/containers/use_post_comment.tsx index 252059514da8e..15cf398a2fdb2 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_comment.tsx +++ b/x-pack/plugins/cases/public/containers/use_post_comment.tsx @@ -6,12 +6,12 @@ */ import { useReducer, useCallback, useRef, useEffect } from 'react'; -import { CommentRequest } from '../../../../cases/common/api'; -import { errorToToaster, useStateToaster } from '../../common/components/toasters'; +import { CommentRequest } from '../../common'; import { postComment } from './api'; import * as i18n from './translations'; import { Case } from './types'; +import { useToasts } from '../common/lib/kibana'; interface NewCommentState { isLoading: boolean; @@ -56,7 +56,7 @@ export const usePostComment = (): UsePostComment => { isLoading: false, isError: false, }); - const [, dispatchToaster] = useStateToaster(); + const toasts = useToasts(); const isCancelledRef = useRef(false); const abortCtrlRef = useRef(new AbortController()); @@ -79,17 +79,16 @@ export const usePostComment = (): UsePostComment => { } catch (error) { if (!isCancelledRef.current) { if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_TITLE } + ); } dispatch({ type: 'FETCH_FAILURE' }); } } }, - [dispatchToaster] + [toasts] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.test.tsx b/x-pack/plugins/cases/public/containers/use_post_push_to_service.test.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.test.tsx rename to x-pack/plugins/cases/public/containers/use_post_push_to_service.test.tsx index e008927019987..18e3c4be493b8 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_post_push_to_service.test.tsx @@ -9,9 +9,10 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { usePostPushToService, UsePostPushToService } from './use_post_push_to_service'; import { pushedCase } from './mock'; import * as api from './api'; -import { CaseConnector, ConnectorTypes } from '../../../../cases/common/api'; +import { CaseConnector, ConnectorTypes } from '../../common'; jest.mock('./api'); +jest.mock('../common/lib/kibana'); describe('usePostPushToService', () => { const abortCtrl = new AbortController(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.tsx b/x-pack/plugins/cases/public/containers/use_post_push_to_service.tsx similarity index 81% rename from x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.tsx rename to x-pack/plugins/cases/public/containers/use_post_push_to_service.tsx index 9fd0fda5c9723..bee89e21b4283 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_push_to_service.tsx +++ b/x-pack/plugins/cases/public/containers/use_post_push_to_service.tsx @@ -6,16 +6,12 @@ */ import { useReducer, useCallback, useRef, useEffect } from 'react'; -import { CaseConnector } from '../../../../cases/common/api'; -import { - errorToToaster, - useStateToaster, - displaySuccessToast, -} from '../../common/components/toasters'; +import { CaseConnector } from '../../common'; import { pushCase } from './api'; import * as i18n from './translations'; import { Case } from './types'; +import { useToasts } from '../common/lib/kibana'; interface PushToServiceState { isLoading: boolean; @@ -65,7 +61,7 @@ export const usePostPushToService = (): UsePostPushToService => { isLoading: false, isError: false, }); - const [, dispatchToaster] = useStateToaster(); + const toasts = useToasts(); const cancel = useRef(false); const abortCtrlRef = useRef(new AbortController()); @@ -81,21 +77,17 @@ export const usePostPushToService = (): UsePostPushToService => { if (!cancel.current) { dispatch({ type: 'FETCH_SUCCESS' }); - displaySuccessToast( - i18n.SUCCESS_SEND_TO_EXTERNAL_SERVICE(connector.name), - dispatchToaster - ); + toasts.addSuccess(i18n.SUCCESS_SEND_TO_EXTERNAL_SERVICE(connector.name)); } return response; } catch (error) { if (!cancel.current) { if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_TITLE } + ); } dispatch({ type: 'FETCH_FAILURE' }); } diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_update_case.test.tsx b/x-pack/plugins/cases/public/containers/use_update_case.test.tsx similarity index 99% rename from x-pack/plugins/security_solution/public/cases/containers/use_update_case.test.tsx rename to x-pack/plugins/cases/public/containers/use_update_case.test.tsx index 65309d6d29e05..666e8df0c2413 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_update_case.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_update_case.test.tsx @@ -12,6 +12,7 @@ import * as api from './api'; import { UpdateKey } from './types'; jest.mock('./api'); +jest.mock('../common/lib/kibana'); describe('useUpdateCase', () => { const abortCtrl = new AbortController(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx b/x-pack/plugins/cases/public/containers/use_update_case.tsx similarity index 86% rename from x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx rename to x-pack/plugins/cases/public/containers/use_update_case.tsx index 9a79699d8f919..b6ea580cf542a 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx +++ b/x-pack/plugins/cases/public/containers/use_update_case.tsx @@ -7,9 +7,9 @@ import { useReducer, useCallback, useRef, useEffect } from 'react'; -import { errorToToaster, useStateToaster } from '../../common/components/toasters'; +import { useToasts } from '../common/lib/kibana'; import { patchCase, patchSubCase } from './api'; -import { UpdateKey, UpdateByKey, CaseStatuses } from './types'; +import { UpdateKey, UpdateByKey, CaseStatuses } from '../../common'; import * as i18n from './translations'; import { createUpdateSuccessToaster } from './utils'; @@ -68,7 +68,7 @@ export const useUpdateCase = ({ isError: false, updateKey: null, }); - const [, dispatchToaster] = useStateToaster(); + const toasts = useToasts(); const isCancelledRef = useRef(false); const abortCtrlRef = useRef(new AbortController()); @@ -111,10 +111,9 @@ export const useUpdateCase = ({ updateCase(response[0]); } dispatch({ type: 'FETCH_SUCCESS' }); - dispatchToaster({ - type: 'addToaster', - toast: createUpdateSuccessToaster(caseData, response[0], updateKey, updateValue), - }); + toasts.addSuccess( + createUpdateSuccessToaster(caseData, response[0], updateKey, updateValue) + ); if (onSuccess) { onSuccess(); @@ -123,11 +122,10 @@ export const useUpdateCase = ({ } catch (error) { if (!isCancelledRef.current) { if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_TITLE } + ); } dispatch({ type: 'FETCH_FAILURE' }); if (onError) { diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.test.tsx b/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx similarity index 99% rename from x-pack/plugins/security_solution/public/cases/containers/use_update_comment.test.tsx rename to x-pack/plugins/cases/public/containers/use_update_comment.test.tsx index 9ff266ad9c988..b936eb126f0d4 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx @@ -11,6 +11,7 @@ import { basicCase, basicCaseCommentPatch, basicSubCaseId } from './mock'; import * as api from './api'; jest.mock('./api'); +jest.mock('../common/lib/kibana'); describe('useUpdateComment', () => { const abortCtrl = new AbortController(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.tsx b/x-pack/plugins/cases/public/containers/use_update_comment.tsx similarity index 90% rename from x-pack/plugins/security_solution/public/cases/containers/use_update_comment.tsx rename to x-pack/plugins/cases/public/containers/use_update_comment.tsx index 81bce248852fe..512b5b50a22b9 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_update_comment.tsx +++ b/x-pack/plugins/cases/public/containers/use_update_comment.tsx @@ -6,7 +6,7 @@ */ import { useReducer, useCallback, useRef, useEffect } from 'react'; -import { errorToToaster, useStateToaster } from '../../common/components/toasters'; +import { useToasts } from '../common/lib/kibana'; import { patchComment } from './api'; import * as i18n from './translations'; import { Case } from './types'; @@ -69,7 +69,7 @@ export const useUpdateComment = (): UseUpdateComment => { isLoadingIds: [], isError: false, }); - const [, dispatchToaster] = useStateToaster(); + const toasts = useToasts(); const isCancelledRef = useRef(false); const abortCtrlRef = useRef(new AbortController()); @@ -106,11 +106,10 @@ export const useUpdateComment = (): UseUpdateComment => { } catch (error) { if (!isCancelledRef.current) { if (error.name !== 'AbortError') { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { title: i18n.ERROR_TITLE } + ); } dispatch({ type: 'FETCH_FAILURE', payload: commentId }); } diff --git a/x-pack/plugins/security_solution/public/cases/containers/utils.test.ts b/x-pack/plugins/cases/public/containers/utils.test.ts similarity index 77% rename from x-pack/plugins/security_solution/public/cases/containers/utils.test.ts rename to x-pack/plugins/cases/public/containers/utils.test.ts index 6c1fb60298938..3ee6182cb053d 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/utils.test.ts +++ b/x-pack/plugins/cases/public/containers/utils.test.ts @@ -50,25 +50,18 @@ describe('utils', () => { describe('createUpdateSuccessToaster', () => { it('creates the correct toast when sync alerts is turned on and case has alerts', () => { // We remove the id as is randomly generated - const { id, ...toast } = createUpdateSuccessToaster( - caseBeforeUpdate, - caseAfterUpdate, - 'settings', - { - syncAlerts: true, - } - ); + const toast = createUpdateSuccessToaster(caseBeforeUpdate, caseAfterUpdate, 'settings', { + syncAlerts: true, + }); expect(toast).toEqual({ - color: 'success', - iconType: 'check', title: 'Alerts in "My case" have been synced', }); }); it('creates the correct toast when sync alerts is turned on and case does NOT have alerts', () => { // We remove the id as is randomly generated - const { id, ...toast } = createUpdateSuccessToaster( + const toast = createUpdateSuccessToaster( { ...caseBeforeUpdate, comments: [] }, caseAfterUpdate, 'settings', @@ -78,33 +71,24 @@ describe('utils', () => { ); expect(toast).toEqual({ - color: 'success', - iconType: 'check', title: 'Updated "My case"', }); }); it('creates the correct toast when sync alerts is turned off and case has alerts', () => { // We remove the id as is randomly generated - const { id, ...toast } = createUpdateSuccessToaster( - caseBeforeUpdate, - caseAfterUpdate, - 'settings', - { - syncAlerts: false, - } - ); + const toast = createUpdateSuccessToaster(caseBeforeUpdate, caseAfterUpdate, 'settings', { + syncAlerts: false, + }); expect(toast).toEqual({ - color: 'success', - iconType: 'check', title: 'Updated "My case"', }); }); it('creates the correct toast when the status change, case has alerts, and sync alerts is on', () => { // We remove the id as is randomly generated - const { id, ...toast } = createUpdateSuccessToaster( + const toast = createUpdateSuccessToaster( caseBeforeUpdate, caseAfterUpdate, 'status', @@ -112,8 +96,6 @@ describe('utils', () => { ); expect(toast).toEqual({ - color: 'success', - iconType: 'check', title: 'Updated "My case"', text: 'Alerts in this case have been also had their status updated', }); @@ -121,7 +103,7 @@ describe('utils', () => { it('creates the correct toast when the status change, case has alerts, and sync alerts is off', () => { // We remove the id as is randomly generated - const { id, ...toast } = createUpdateSuccessToaster( + const toast = createUpdateSuccessToaster( { ...caseBeforeUpdate, settings: { syncAlerts: false } }, caseAfterUpdate, 'status', @@ -129,15 +111,13 @@ describe('utils', () => { ); expect(toast).toEqual({ - color: 'success', - iconType: 'check', title: 'Updated "My case"', }); }); it('creates the correct toast when the status change, case does NOT have alerts, and sync alerts is on', () => { // We remove the id as is randomly generated - const { id, ...toast } = createUpdateSuccessToaster( + const toast = createUpdateSuccessToaster( { ...caseBeforeUpdate, comments: [] }, caseAfterUpdate, 'status', @@ -145,15 +125,13 @@ describe('utils', () => { ); expect(toast).toEqual({ - color: 'success', - iconType: 'check', title: 'Updated "My case"', }); }); it('creates the correct toast if not a status or a setting', () => { // We remove the id as is randomly generated - const { id, ...toast } = createUpdateSuccessToaster( + const toast = createUpdateSuccessToaster( caseBeforeUpdate, caseAfterUpdate, 'title', @@ -161,8 +139,6 @@ describe('utils', () => { ); expect(toast).toEqual({ - color: 'success', - iconType: 'check', title: 'Updated "My case"', }); }); diff --git a/x-pack/plugins/security_solution/public/cases/containers/utils.ts b/x-pack/plugins/cases/public/containers/utils.ts similarity index 92% rename from x-pack/plugins/security_solution/public/cases/containers/utils.ts rename to x-pack/plugins/cases/public/containers/utils.ts index 7c33e4481b2aa..5ef30aa800f90 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/utils.ts +++ b/x-pack/plugins/cases/public/containers/utils.ts @@ -5,13 +5,13 @@ * 2.0. */ -import uuid from 'uuid'; import { set } from '@elastic/safer-lodash-set'; import { camelCase, isArray, isObject } from 'lodash'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; +import { ToastInputFields } from 'kibana/public'; import { CasesFindResponse, CasesFindResponseRt, @@ -28,8 +28,7 @@ import { CaseUserActionsResponseRt, CommentType, CasePatchRequest, -} from '../../../../cases/common/api'; -import { AppToast, ToasterError } from '../../common/components/toasters'; +} from '../../common'; import { AllCases, Case, UpdateByKey } from './types'; import * as i18n from './translations'; @@ -115,20 +114,26 @@ export const valueToUpdateIsStatus = ( value: UpdateByKey['updateValue'] ): value is CasePatchRequest['status'] => key === 'status'; +export class ToasterError extends Error { + public readonly messages: string[]; + + constructor(messages: string[]) { + super(messages[0]); + this.name = 'ToasterError'; + this.messages = messages; + } +} export const createUpdateSuccessToaster = ( caseBeforeUpdate: Case, caseAfterUpdate: Case, key: UpdateByKey['updateKey'], value: UpdateByKey['updateValue'] -): AppToast => { +): ToastInputFields => { const caseHasAlerts = caseBeforeUpdate.comments.some( (comment) => comment.type === CommentType.alert ); - const toast: AppToast = { - id: uuid.v4(), - color: 'success', - iconType: 'check', + const toast: ToastInputFields = { title: i18n.UPDATED_CASE(caseAfterUpdate.title), }; diff --git a/x-pack/plugins/cases/public/index.tsx b/x-pack/plugins/cases/public/index.tsx new file mode 100644 index 0000000000000..e8589152b7ca8 --- /dev/null +++ b/x-pack/plugins/cases/public/index.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PluginInitializerContext } from 'kibana/public'; +import { CasesUiPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new CasesUiPlugin(initializerContext); +} + +export { CasesUiPlugin }; +export * from './plugin'; +export * from './types'; diff --git a/x-pack/plugins/cases/public/methods/get_all_cases.tsx b/x-pack/plugins/cases/public/methods/get_all_cases.tsx new file mode 100644 index 0000000000000..d3e7a924788f3 --- /dev/null +++ b/x-pack/plugins/cases/public/methods/get_all_cases.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiLoadingSpinner } from '@elastic/eui'; +import React, { lazy, Suspense } from 'react'; +import { AllCasesProps } from '../components/all_cases'; + +const AllCasesLazy = lazy(() => import('../components/all_cases')); +export const getAllCasesLazy = (props: AllCasesProps) => ( + <Suspense fallback={<EuiLoadingSpinner />}> + <AllCasesLazy {...props} /> + </Suspense> +); diff --git a/x-pack/plugins/cases/public/methods/get_all_cases_selector_modal.tsx b/x-pack/plugins/cases/public/methods/get_all_cases_selector_modal.tsx new file mode 100644 index 0000000000000..b6caae39c284a --- /dev/null +++ b/x-pack/plugins/cases/public/methods/get_all_cases_selector_modal.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { lazy, Suspense } from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import { AllCasesSelectorModalProps } from '../components/all_cases/selector_modal'; + +const AllCasesSelectorModalLazy = lazy(() => import('../components/all_cases/selector_modal')); +export const getAllCasesSelectorModalLazy = (props: AllCasesSelectorModalProps) => ( + <Suspense fallback={<EuiLoadingSpinner />}> + <AllCasesSelectorModalLazy {...props} /> + </Suspense> +); diff --git a/x-pack/plugins/cases/public/methods/get_case_view.tsx b/x-pack/plugins/cases/public/methods/get_case_view.tsx new file mode 100644 index 0000000000000..00fe2438a1a7d --- /dev/null +++ b/x-pack/plugins/cases/public/methods/get_case_view.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { lazy, Suspense } from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import { CaseViewProps } from '../components/case_view'; + +const CaseViewLazy = lazy(() => import('../components/case_view')); +export const getCaseViewLazy = (props: CaseViewProps) => ( + <Suspense fallback={<EuiLoadingSpinner />}> + <CaseViewLazy {...props} /> + </Suspense> +); diff --git a/x-pack/plugins/cases/public/methods/get_configure_cases.tsx b/x-pack/plugins/cases/public/methods/get_configure_cases.tsx new file mode 100644 index 0000000000000..96a3dbd55d7de --- /dev/null +++ b/x-pack/plugins/cases/public/methods/get_configure_cases.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiLoadingSpinner } from '@elastic/eui'; +import React, { lazy, Suspense } from 'react'; +import { ConfigureCasesProps } from '../components/configure_cases'; + +const ConfigureCasesLazy = lazy(() => import('../components/configure_cases')); +export const getConfigureCasesLazy = (props: ConfigureCasesProps) => ( + <Suspense fallback={<EuiLoadingSpinner />}> + <ConfigureCasesLazy {...props} /> + </Suspense> +); diff --git a/x-pack/plugins/cases/public/methods/get_create_case.tsx b/x-pack/plugins/cases/public/methods/get_create_case.tsx new file mode 100644 index 0000000000000..b030ed669b663 --- /dev/null +++ b/x-pack/plugins/cases/public/methods/get_create_case.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { lazy, Suspense } from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import { CreateCaseProps } from '../components/create'; + +const CreateCaseLazy = lazy(() => import('../components/create')); +export const getCreateCaseLazy = (props: CreateCaseProps) => ( + <Suspense fallback={<EuiLoadingSpinner />}> + <CreateCaseLazy {...props} /> + </Suspense> +); diff --git a/x-pack/plugins/cases/public/methods/get_recent_cases.tsx b/x-pack/plugins/cases/public/methods/get_recent_cases.tsx new file mode 100644 index 0000000000000..e87db9320ca3d --- /dev/null +++ b/x-pack/plugins/cases/public/methods/get_recent_cases.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiLoadingSpinner } from '@elastic/eui'; +import React, { lazy, Suspense } from 'react'; +import { RecentCasesProps } from '../components/recent_cases'; + +const RecentCasesLazy = lazy(() => import('../components/recent_cases')); +export const getRecentCasesLazy = (props: RecentCasesProps) => ( + <Suspense fallback={<EuiLoadingSpinner />}> + <RecentCasesLazy {...props} /> + </Suspense> +); diff --git a/x-pack/plugins/cases/public/methods/index.ts b/x-pack/plugins/cases/public/methods/index.ts new file mode 100644 index 0000000000000..1d91e7c4df6d2 --- /dev/null +++ b/x-pack/plugins/cases/public/methods/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './get_all_cases'; +export * from './get_create_case'; +export * from './get_case_view'; +export * from './get_configure_cases'; +export * from './get_recent_cases'; +export * from './get_all_cases_selector_modal'; diff --git a/x-pack/plugins/cases/public/plugin.ts b/x-pack/plugins/cases/public/plugin.ts new file mode 100644 index 0000000000000..8c9105961c130 --- /dev/null +++ b/x-pack/plugins/cases/public/plugin.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; +import { CasesUiStart, SetupPlugins, StartPlugins } from './types'; +import { KibanaServices } from './common/lib/kibana'; +import { getCaseConnectorUi } from './components/connectors'; +import { + getAllCasesLazy, + getCaseViewLazy, + getConfigureCasesLazy, + getCreateCaseLazy, + getRecentCasesLazy, + getAllCasesSelectorModalLazy, +} from './methods'; +import { ENABLE_CASE_CONNECTOR } from '../common'; + +/** + * @public + * A plugin for retrieving Cases UI components + */ +export class CasesUiPlugin implements Plugin<void, CasesUiStart, SetupPlugins, StartPlugins> { + private kibanaVersion: string; + + constructor(initializerContext: PluginInitializerContext) { + this.kibanaVersion = initializerContext.env.packageInfo.version; + } + public setup(core: CoreSetup, plugins: SetupPlugins) { + if (ENABLE_CASE_CONNECTOR) { + plugins.triggersActionsUi.actionTypeRegistry.register(getCaseConnectorUi()); + } + } + + public start(core: CoreStart, plugins: StartPlugins): CasesUiStart { + KibanaServices.init({ ...core, ...plugins, kibanaVersion: this.kibanaVersion }); + return { + /** + * Get the all cases table + * @param props AllCasesProps + * @return {ReactElement<AllCasesProps>} + */ + getAllCases: (props) => { + return getAllCasesLazy(props); + }, + /** + * Get the case view component + * @param props CaseViewProps + * @return {ReactElement<CaseViewProps>} + */ + getCaseView: (props) => { + return getCaseViewLazy(props); + }, + /** + * Get the configure case component + * @param props ConfigureCasesProps + * @return {ReactElement<ConfigureCasesProps>} + */ + getConfigureCases: (props) => { + return getConfigureCasesLazy(props); + }, + /** + * Get the create case form + * @param props CreateCaseProps + * @return {ReactElement<CreateCaseProps>} + */ + getCreateCase: (props) => { + return getCreateCaseLazy(props); + }, + /** + * Get the recent cases component + * @param props RecentCasesProps + * @return {ReactElement<RecentCasesProps>} + */ + getRecentCases: (props) => { + return getRecentCasesLazy(props); + }, + /** + * use Modal hook for all cases selector + * @param props UseAllCasesSelectorModalProps + * @return UseAllCasesSelectorModalReturnedValues + */ + getAllCasesSelectorModal: (props) => { + return getAllCasesSelectorModalLazy(props); + }, + }; + } + + public stop() {} +} diff --git a/x-pack/plugins/cases/public/types.ts b/x-pack/plugins/cases/public/types.ts new file mode 100644 index 0000000000000..269d1773b3404 --- /dev/null +++ b/x-pack/plugins/cases/public/types.ts @@ -0,0 +1,51 @@ +/* + * 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 'kibana/public'; +import { ReactElement } from 'react'; +import { SecurityPluginSetup } from '../../security/public'; +import { + TriggersAndActionsUIPublicPluginSetup as TriggersActionsSetup, + TriggersAndActionsUIPublicPluginStart as TriggersActionsStart, +} from '../../triggers_actions_ui/public'; +import { AllCasesProps } from './components/all_cases'; +import { CaseViewProps } from './components/case_view'; +import { ConfigureCasesProps } from './components/configure_cases'; +import { CreateCaseProps } from './components/create'; +import { RecentCasesProps } from './components/recent_cases'; +import { AllCasesSelectorModalProps } from './components/all_cases/selector_modal'; + +export interface SetupPlugins { + security: SecurityPluginSetup; + triggersActionsUi: TriggersActionsSetup; +} + +export interface StartPlugins { + triggersActionsUi: TriggersActionsStart; +} + +/** + * TODO: The extra security service is one that should be implemented in the kibana context of the consuming application. + * Security is needed for access to authc for the `useCurrentUser` hook. Security_Solution currently passes it via renderApp in public/plugin.tsx + * Leaving it out currently in lieu of RBAC changes + */ + +export type StartServices = CoreStart & + StartPlugins & { + security: SecurityPluginSetup; + }; + +export interface CasesUiStart { + getAllCases: (props: AllCasesProps) => ReactElement<AllCasesProps>; + getAllCasesSelectorModal: ( + props: AllCasesSelectorModalProps + ) => ReactElement<AllCasesSelectorModalProps>; + getCaseView: (props: CaseViewProps) => ReactElement<CaseViewProps>; + getConfigureCases: (props: ConfigureCasesProps) => ReactElement<ConfigureCasesProps>; + getCreateCase: (props: CreateCaseProps) => ReactElement<CreateCaseProps>; + getRecentCases: (props: RecentCasesProps) => ReactElement<RecentCasesProps>; +} diff --git a/x-pack/plugins/cases/public/utils/use_mount_appended.ts b/x-pack/plugins/cases/public/utils/use_mount_appended.ts new file mode 100644 index 0000000000000..d43b0455f47da --- /dev/null +++ b/x-pack/plugins/cases/public/utils/use_mount_appended.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. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { mount } from 'enzyme'; + +type WrapperOf<F extends (...args: any) => any> = (...args: Parameters<F>) => ReturnType<F>; +export type MountAppended = WrapperOf<typeof mount>; + +export const useMountAppended = () => { + let root: HTMLElement; + + beforeEach(() => { + root = document.createElement('div'); + root.id = 'root'; + document.body.appendChild(root); + }); + + afterEach(() => { + document.body.removeChild(root); + }); + + const mountAppended: MountAppended = (node, options) => + mount(node, { ...options, attachTo: root }); + + return mountAppended; +}; diff --git a/x-pack/plugins/cases/server/client/alerts/update_status.test.ts b/x-pack/plugins/cases/server/client/alerts/update_status.test.ts index 5dfe6060da1db..d6456cb3183ef 100644 --- a/x-pack/plugins/cases/server/client/alerts/update_status.test.ts +++ b/x-pack/plugins/cases/server/client/alerts/update_status.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseStatuses } from '../../../common/api'; +import { CaseStatuses } from '../../../common'; import { createMockSavedObjectsRepository } from '../../routes/api/__fixtures__'; import { createCasesClientWithMockSavedObjectsClient } from '../mocks'; 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 fe301dcca37ac..9cbe2a448d3b4 100644 --- a/x-pack/plugins/cases/server/client/cases/create.test.ts +++ b/x-pack/plugins/cases/server/client/cases/create.test.ts @@ -5,12 +5,7 @@ * 2.0. */ -import { - ConnectorTypes, - CaseStatuses, - CaseType, - CasesClientPostRequest, -} from '../../../common/api'; +import { ConnectorTypes, CaseStatuses, CaseType, CasesClientPostRequest } from '../../../common'; import { isCaseError } from '../../common/error'; import { diff --git a/x-pack/plugins/cases/server/client/cases/create.ts b/x-pack/plugins/cases/server/client/cases/create.ts index 650b9aa81c990..fae60743073c1 100644 --- a/x-pack/plugins/cases/server/client/cases/create.ts +++ b/x-pack/plugins/cases/server/client/cases/create.ts @@ -22,7 +22,7 @@ import { CasePostRequest, CaseType, User, -} from '../../../common/api'; +} from '../../../common'; import { buildCaseUserActionItem } from '../../services/user_actions/helpers'; import { getConnectorFromConfiguration, diff --git a/x-pack/plugins/cases/server/client/cases/get.ts b/x-pack/plugins/cases/server/client/cases/get.ts index 50725879278e4..08fa96a3bbe6f 100644 --- a/x-pack/plugins/cases/server/client/cases/get.ts +++ b/x-pack/plugins/cases/server/client/cases/get.ts @@ -7,7 +7,7 @@ import { SavedObjectsClientContract, Logger, SavedObject } from 'kibana/server'; import { flattenCaseSavedObject } from '../../routes/api/utils'; -import { CaseResponseRt, CaseResponse, ESCaseAttributes } from '../../../common/api'; +import { CaseResponseRt, CaseResponse, ESCaseAttributes } from '../../../common'; import { CaseServiceSetup } from '../../services'; import { countAlertsForID } from '../../common'; import { createCaseError } from '../../common/error'; diff --git a/x-pack/plugins/cases/server/client/cases/mock.ts b/x-pack/plugins/cases/server/client/cases/mock.ts index 490519187f49e..0e589b901c8d1 100644 --- a/x-pack/plugins/cases/server/client/cases/mock.ts +++ b/x-pack/plugins/cases/server/client/cases/mock.ts @@ -12,7 +12,7 @@ import { CaseUserActionsResponse, AssociationType, CommentResponseAlertsType, -} from '../../../common/api'; +} from '../../../common'; import { BasicParams } from './types'; diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts index 216ef109534fb..92a9d2910d4a3 100644 --- a/x-pack/plugins/cases/server/client/cases/push.ts +++ b/x-pack/plugins/cases/server/client/cases/push.ts @@ -29,7 +29,7 @@ import { User, ESCasesConfigureAttributes, CaseType, -} from '../../../common/api'; +} from '../../../common'; import { buildCaseUserActionItem } from '../../services/user_actions/helpers'; import { createIncident, getCommentContextFromAttributes } from './utils'; diff --git a/x-pack/plugins/cases/server/client/cases/types.ts b/x-pack/plugins/cases/server/client/cases/types.ts index f1d56e7132bd1..fb400675136ef 100644 --- a/x-pack/plugins/cases/server/client/cases/types.ts +++ b/x-pack/plugins/cases/server/client/cases/types.ts @@ -19,7 +19,7 @@ import { PushToServiceApiParamsSIR as ServiceNowSIRPushToServiceApiParams, ServiceNowITSMIncident, } from '../../../../actions/server/builtin_action_types/servicenow/types'; -import { CaseResponse, ConnectorMappingsAttributes } from '../../../common/api'; +import { CaseResponse, ConnectorMappingsAttributes } from '../../../common'; export type Incident = JiraIncident | ResilientIncident | ServiceNowITSMIncident; export type PushToServiceApiParams = diff --git a/x-pack/plugins/cases/server/client/cases/update.test.ts b/x-pack/plugins/cases/server/client/cases/update.test.ts index 79c3b2838c3b2..18b4e8d9d7b66 100644 --- a/x-pack/plugins/cases/server/client/cases/update.test.ts +++ b/x-pack/plugins/cases/server/client/cases/update.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorTypes, CasesPatchRequest, CaseStatuses } from '../../../common/api'; +import { ConnectorTypes, CasesPatchRequest, CaseStatuses } from '../../../common'; import { isCaseError } from '../../common/error'; import { createMockSavedObjectsRepository, diff --git a/x-pack/plugins/cases/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts index b39bfe6ec4eb7..b9926ff6cbb14 100644 --- a/x-pack/plugins/cases/server/client/cases/update.ts +++ b/x-pack/plugins/cases/server/client/cases/update.ts @@ -38,7 +38,7 @@ import { AssociationType, CommentAttributes, User, -} from '../../../common/api'; +} from '../../../common'; import { buildCaseUserActions } from '../../services/user_actions/helpers'; import { getCaseToUpdate, diff --git a/x-pack/plugins/cases/server/client/cases/utils.test.ts b/x-pack/plugins/cases/server/client/cases/utils.test.ts index 859114a5e8fb0..c24812048376e 100644 --- a/x-pack/plugins/cases/server/client/cases/utils.test.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.test.ts @@ -539,7 +539,7 @@ describe('utils', () => { commentId: 'comment-user-1', }, { - comment: 'Elastic Security Alerts attached to the case: 3', + comment: 'Elastic Alerts attached to the case: 3', commentId: 'mock-id-1-total-alerts', }, ]); @@ -569,7 +569,7 @@ describe('utils', () => { commentId: 'comment-user-1', }, { - comment: 'Elastic Security Alerts attached to the case: 4', + comment: 'Elastic Alerts attached to the case: 4', commentId: 'mock-id-1-total-alerts', }, ]); diff --git a/x-pack/plugins/cases/server/client/cases/utils.ts b/x-pack/plugins/cases/server/client/cases/utils.ts index 7e77bf4ac84cc..9bfad7ddcec3c 100644 --- a/x-pack/plugins/cases/server/client/cases/utils.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.ts @@ -9,26 +9,26 @@ import { i18n } from '@kbn/i18n'; import { flow } from 'lodash'; import { ActionConnector, - CaseResponse, CaseFullExternalService, + CaseResponse, CaseUserActionsResponse, + CommentAttributes, + CommentRequestAlertType, + CommentRequestUserType, CommentResponse, CommentResponseAlertsType, CommentType, ConnectorMappingsAttributes, ConnectorTypes, - CommentAttributes, - CommentRequestUserType, - CommentRequestAlertType, -} from '../../../common/api'; +} from '../../../common'; import { ActionsClient } from '../../../../actions/server'; import { externalServiceFormatters, FormatterConnectorTypes } from '../../connectors'; import { CasesClientGetAlertsResponse } from '../../client/alerts/types'; import { BasicParams, EntityInformation, - ExternalServiceParams, ExternalServiceComment, + ExternalServiceParams, Incident, MapIncident, PipedField, @@ -184,7 +184,7 @@ export const createIncident = async ({ if (totalAlerts > 0) { comments.push({ - comment: `Elastic Security Alerts attached to the case: ${totalAlerts}`, + comment: `Elastic Alerts attached to the case: ${totalAlerts}`, commentId: `${theCase.id}-total-alerts`, }); } diff --git a/x-pack/plugins/cases/server/client/client.ts b/x-pack/plugins/cases/server/client/client.ts index 8f9058654d6fd..3bd25b6b61bc5 100644 --- a/x-pack/plugins/cases/server/client/client.ts +++ b/x-pack/plugins/cases/server/client/client.ts @@ -31,7 +31,7 @@ import { CaseUserActionServiceSetup, AlertServiceContract, } from '../services'; -import { CasesPatchRequest, CasePostRequest, User } from '../../common/api'; +import { CasesPatchRequest, CasePostRequest, User } from '../../common'; import { get } from './cases/get'; import { get as getUserActions } from './user_actions/get'; import { get as getAlerts } from './alerts/get'; diff --git a/x-pack/plugins/cases/server/client/comments/add.test.ts b/x-pack/plugins/cases/server/client/comments/add.test.ts index 23b7bc37dc814..bd04e0ea6ef14 100644 --- a/x-pack/plugins/cases/server/client/comments/add.test.ts +++ b/x-pack/plugins/cases/server/client/comments/add.test.ts @@ -6,7 +6,7 @@ */ import { omit } from 'lodash/fp'; -import { CommentType } from '../../../common/api'; +import { CommentType } from '../../../common'; import { isCaseError } from '../../common/error'; import { createMockSavedObjectsRepository, diff --git a/x-pack/plugins/cases/server/client/comments/add.ts b/x-pack/plugins/cases/server/client/comments/add.ts index 5a119432b3ccb..376e0e2c8868e 100644 --- a/x-pack/plugins/cases/server/client/comments/add.ts +++ b/x-pack/plugins/cases/server/client/comments/add.ts @@ -25,7 +25,7 @@ import { User, CommentRequestAlertType, AlertCommentRequestRt, -} from '../../../common/api'; +} from '../../../common'; import { buildCaseUserActionItem, buildCommentUserActionItem, @@ -36,10 +36,7 @@ import { CommentableCase, createAlertUpdateRequest } from '../../common'; import { CasesClientHandler } from '..'; import { createCaseError } from '../../common/error'; import { CASE_COMMENT_SAVED_OBJECT } from '../../saved_object_types'; -import { - ENABLE_CASE_CONNECTOR, - MAX_GENERATED_ALERTS_PER_SUB_CASE, -} from '../../../common/constants'; +import { ENABLE_CASE_CONNECTOR, MAX_GENERATED_ALERTS_PER_SUB_CASE } from '../../../common'; async function getSubCase({ caseService, diff --git a/x-pack/plugins/cases/server/client/configure/get_fields.test.ts b/x-pack/plugins/cases/server/client/configure/get_fields.test.ts index 2e2973516d0fd..c474361293da4 100644 --- a/x-pack/plugins/cases/server/client/configure/get_fields.test.ts +++ b/x-pack/plugins/cases/server/client/configure/get_fields.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorTypes } from '../../../common/api'; +import { ConnectorTypes } from '../../../common'; import { createMockSavedObjectsRepository, mockCaseMappings } from '../../routes/api/__fixtures__'; import { createCasesClientWithMockSavedObjectsClient } from '../mocks'; diff --git a/x-pack/plugins/cases/server/client/configure/get_fields.ts b/x-pack/plugins/cases/server/client/configure/get_fields.ts index deabae33810b2..8d899f0df1a76 100644 --- a/x-pack/plugins/cases/server/client/configure/get_fields.ts +++ b/x-pack/plugins/cases/server/client/configure/get_fields.ts @@ -7,7 +7,7 @@ import Boom from '@hapi/boom'; -import { GetFieldsResponse } from '../../../common/api'; +import { GetFieldsResponse } from '../../../common'; import { ConfigureFields } from '../types'; import { createDefaultMapping, formatFields } from './utils'; diff --git a/x-pack/plugins/cases/server/client/configure/get_mappings.test.ts b/x-pack/plugins/cases/server/client/configure/get_mappings.test.ts index 0ec2fc8b4621d..8f75e60260873 100644 --- a/x-pack/plugins/cases/server/client/configure/get_mappings.test.ts +++ b/x-pack/plugins/cases/server/client/configure/get_mappings.test.ts @@ -5,9 +5,13 @@ * 2.0. */ -import { ConnectorTypes } from '../../../common/api'; +import { ConnectorTypes } from '../../../common'; -import { createMockSavedObjectsRepository, mockCaseMappings } from '../../routes/api/__fixtures__'; +import { + createMockSavedObjectsRepository, + mockCaseMappingsResilient, + mockCaseMappingsBad, +} from '../../routes/api/__fixtures__'; import { createCasesClientWithMockSavedObjectsClient } from '../mocks'; import { actionsClientMock } from '../../../../actions/server/actions_client.mock'; import { mappings, mockGetFieldsResponse } from './mock'; @@ -26,7 +30,7 @@ describe('get_mappings', () => { describe('happy path', () => { test('it gets existing mappings', async () => { const savedObjectsClient = createMockSavedObjectsRepository({ - caseMappingsSavedObject: mockCaseMappings, + caseMappingsSavedObject: mockCaseMappingsResilient, }); const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); const res = await casesClient.client.getMappings({ @@ -35,7 +39,7 @@ describe('get_mappings', () => { connectorId: '123', }); - expect(res).toEqual(mappings[ConnectorTypes.jira]); + expect(res).toEqual(mappings[ConnectorTypes.resilient]); }); test('it creates new mappings', async () => { const savedObjectsClient = createMockSavedObjectsRepository({ @@ -48,6 +52,21 @@ describe('get_mappings', () => { connectorId: '123', }); + expect(res).toEqual(mappings[ConnectorTypes.jira]); + }); + }); + describe('unhappy path', () => { + test('it gets existing mappings, but attributes object is empty so it creates new mappings', async () => { + const savedObjectsClient = createMockSavedObjectsRepository({ + caseMappingsSavedObject: mockCaseMappingsBad, + }); + const casesClient = await createCasesClientWithMockSavedObjectsClient({ savedObjectsClient }); + const res = await casesClient.client.getMappings({ + actionsClient: actionsMock, + connectorType: ConnectorTypes.jira, + connectorId: '123', + }); + expect(res).toEqual(mappings[ConnectorTypes.jira]); }); }); diff --git a/x-pack/plugins/cases/server/client/configure/get_mappings.ts b/x-pack/plugins/cases/server/client/configure/get_mappings.ts index 558c961f89e5b..3560bf1dcd067 100644 --- a/x-pack/plugins/cases/server/client/configure/get_mappings.ts +++ b/x-pack/plugins/cases/server/client/configure/get_mappings.ts @@ -7,7 +7,7 @@ import { SavedObjectsClientContract, Logger } from 'src/core/server'; import { ActionsClient } from '../../../../actions/server'; -import { ConnectorMappingsAttributes, ConnectorTypes } from '../../../common/api'; +import { ConnectorMappingsAttributes, ConnectorTypes } from '../../../common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server/saved_objects'; import { ConnectorMappingsServiceSetup } from '../../services'; @@ -48,7 +48,11 @@ export const getMappings = async ({ }); let theMapping; // Create connector mappings if there are none - if (myConnectorMappings.total === 0) { + if ( + myConnectorMappings.total === 0 || + (myConnectorMappings.total > 0 && + !myConnectorMappings.saved_objects[0].attributes.hasOwnProperty('mappings')) + ) { const res = await casesClient.getFields({ actionsClient, connectorId, diff --git a/x-pack/plugins/cases/server/client/configure/mock.ts b/x-pack/plugins/cases/server/client/configure/mock.ts index ee214de9b51d4..ad982a5cc1243 100644 --- a/x-pack/plugins/cases/server/client/configure/mock.ts +++ b/x-pack/plugins/cases/server/client/configure/mock.ts @@ -5,11 +5,7 @@ * 2.0. */ -import { - ConnectorField, - ConnectorMappingsAttributes, - ConnectorTypes, -} from '../../../common/api/connectors'; +import { ConnectorField, ConnectorMappingsAttributes, ConnectorTypes } from '../../../common'; import { JiraGetFieldsResponse, ResilientGetFieldsResponse, diff --git a/x-pack/plugins/cases/server/client/configure/utils.ts b/x-pack/plugins/cases/server/client/configure/utils.ts index 10c3e1fd3c1a9..24efb6ca54b3a 100644 --- a/x-pack/plugins/cases/server/client/configure/utils.ts +++ b/x-pack/plugins/cases/server/client/configure/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorField, ConnectorMappingsAttributes, ConnectorTypes } from '../../../common/api'; +import { ConnectorField, ConnectorMappingsAttributes, ConnectorTypes } from '../../../common'; import { JiraGetFieldsResponse, ResilientGetFieldsResponse, diff --git a/x-pack/plugins/cases/server/client/types.ts b/x-pack/plugins/cases/server/client/types.ts index c62b3913da763..3311b7ac6f921 100644 --- a/x-pack/plugins/cases/server/client/types.ts +++ b/x-pack/plugins/cases/server/client/types.ts @@ -18,7 +18,7 @@ import { GetFieldsResponse, CaseUserActionsResponse, User, -} from '../../common/api'; +} from '../../common'; import { AlertInfo } from '../common'; import { CaseConfigureServiceSetup, diff --git a/x-pack/plugins/cases/server/client/user_actions/get.ts b/x-pack/plugins/cases/server/client/user_actions/get.ts index f6371b8e8b1e7..79b8ef25ab0f6 100644 --- a/x-pack/plugins/cases/server/client/user_actions/get.ts +++ b/x-pack/plugins/cases/server/client/user_actions/get.ts @@ -11,7 +11,7 @@ import { CASE_COMMENT_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT, } from '../../saved_object_types'; -import { CaseUserActionsResponseRt, CaseUserActionsResponse } from '../../../common/api'; +import { CaseUserActionsResponseRt, CaseUserActionsResponse } from '../../../common'; import { CaseUserActionServiceSetup } from '../../services'; interface GetParams { diff --git a/x-pack/plugins/cases/server/common/models/commentable_case.ts b/x-pack/plugins/cases/server/common/models/commentable_case.ts index 1ff5b7beadcaf..3daccf87bdc19 100644 --- a/x-pack/plugins/cases/server/common/models/commentable_case.ts +++ b/x-pack/plugins/cases/server/common/models/commentable_case.ts @@ -27,7 +27,7 @@ import { ESCaseAttributes, SubCaseAttributes, User, -} from '../../../common/api'; +} from '../../../common'; import { transformESConnectorToCaseConnector } from '../../routes/api/cases/helpers'; import { flattenCommentSavedObjects, diff --git a/x-pack/plugins/cases/server/common/utils.test.ts b/x-pack/plugins/cases/server/common/utils.test.ts index 5e6a86358de25..df16fe4f0a67d 100644 --- a/x-pack/plugins/cases/server/common/utils.test.ts +++ b/x-pack/plugins/cases/server/common/utils.test.ts @@ -6,7 +6,7 @@ */ import { SavedObjectsFindResponse } from 'kibana/server'; -import { AssociationType, CommentAttributes, CommentRequest, CommentType } from '../../common/api'; +import { AssociationType, CommentAttributes, CommentRequest, CommentType } from '../../common'; import { transformNewComment } from '../routes/api/utils'; import { combineFilters, countAlerts, countAlertsForID, groupTotalAlertsByID } from './utils'; diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index dce26f3d5998a..d3bc3850e4210 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -6,13 +6,7 @@ */ import { SavedObjectsFindResult, SavedObjectsFindResponse } from 'kibana/server'; -import { - CaseStatuses, - CommentAttributes, - CommentRequest, - CommentType, - User, -} from '../../common/api'; +import { CaseStatuses, CommentAttributes, CommentRequest, CommentType, User } from '../../common'; import { UpdateAlertRequest } from '../client/types'; import { getAlertInfoFromComments } from '../routes/api/utils'; diff --git a/x-pack/plugins/cases/server/connectors/case/index.test.ts b/x-pack/plugins/cases/server/connectors/case/index.test.ts index 8a025ed0f79b7..2415569392125 100644 --- a/x-pack/plugins/cases/server/connectors/case/index.test.ts +++ b/x-pack/plugins/cases/server/connectors/case/index.test.ts @@ -18,7 +18,7 @@ import { AssociationType, CaseResponse, CasesResponse, -} from '../../../common/api'; +} from '../../../common'; import { connectorMappingsServiceMock, createCaseServiceMock, diff --git a/x-pack/plugins/cases/server/connectors/case/index.ts b/x-pack/plugins/cases/server/connectors/case/index.ts index c5eb609e260ae..be519f97f2343 100644 --- a/x-pack/plugins/cases/server/connectors/case/index.ts +++ b/x-pack/plugins/cases/server/connectors/case/index.ts @@ -8,12 +8,7 @@ import { curry } from 'lodash'; import { Logger } from 'src/core/server'; import { ActionTypeExecutorResult } from '../../../../actions/common'; -import { - CasePatchRequest, - CasePostRequest, - CommentRequest, - CommentType, -} from '../../../common/api'; +import { CasePatchRequest, CasePostRequest, CommentRequest, CommentType } from '../../../common'; import { createExternalCasesClient } from '../../client'; import { CaseExecutorParamsSchema, CaseConfigurationSchema, CommentSchemaType } from './schema'; import { diff --git a/x-pack/plugins/cases/server/connectors/case/schema.ts b/x-pack/plugins/cases/server/connectors/case/schema.ts index 1637cec7520be..803b01cbbdc57 100644 --- a/x-pack/plugins/cases/server/connectors/case/schema.ts +++ b/x-pack/plugins/cases/server/connectors/case/schema.ts @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import { CommentType } from '../../../common/api'; +import { CommentType } from '../../../common'; import { validateConnector } from './validators'; // Reserved for future implementation diff --git a/x-pack/plugins/cases/server/connectors/case/types.ts b/x-pack/plugins/cases/server/connectors/case/types.ts index 6a7dfd9c2e687..a71007f0b4946 100644 --- a/x-pack/plugins/cases/server/connectors/case/types.ts +++ b/x-pack/plugins/cases/server/connectors/case/types.ts @@ -16,7 +16,7 @@ import { ConnectorSchema, CommentSchema, } from './schema'; -import { CaseResponse, CasesResponse } from '../../../common/api'; +import { CaseResponse, CasesResponse } from '../../../common'; export type CaseConfiguration = TypeOf<typeof CaseConfigurationSchema>; export type Connector = TypeOf<typeof ConnectorSchema>; diff --git a/x-pack/plugins/cases/server/connectors/index.ts b/x-pack/plugins/cases/server/connectors/index.ts index a6b6e193361be..ecf04e4f7b0f1 100644 --- a/x-pack/plugins/cases/server/connectors/index.ts +++ b/x-pack/plugins/cases/server/connectors/index.ts @@ -17,7 +17,7 @@ import { serviceNowITSMExternalServiceFormatter } from './servicenow/itsm_format import { serviceNowSIRExternalServiceFormatter } from './servicenow/sir_formatter'; import { jiraExternalServiceFormatter } from './jira/external_service_formatter'; import { resilientExternalServiceFormatter } from './resilient/external_service_formatter'; -import { CommentRequest, CommentType } from '../../common/api'; +import { CommentRequest, CommentType } from '../../common'; export * from './types'; export { transformConnectorComment } from './case'; diff --git a/x-pack/plugins/cases/server/connectors/jira/external_service_formatter.test.ts b/x-pack/plugins/cases/server/connectors/jira/external_service_formatter.test.ts index 0bfaf7cdbd9e3..f5d76aeddf313 100644 --- a/x-pack/plugins/cases/server/connectors/jira/external_service_formatter.test.ts +++ b/x-pack/plugins/cases/server/connectors/jira/external_service_formatter.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseResponse } from '../../../common/api'; +import { CaseResponse } from '../../../common'; import { jiraExternalServiceFormatter } from './external_service_formatter'; describe('Jira formatter', () => { diff --git a/x-pack/plugins/cases/server/connectors/jira/external_service_formatter.ts b/x-pack/plugins/cases/server/connectors/jira/external_service_formatter.ts index 74376d295fea5..15ee2fd468dda 100644 --- a/x-pack/plugins/cases/server/connectors/jira/external_service_formatter.ts +++ b/x-pack/plugins/cases/server/connectors/jira/external_service_formatter.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { JiraFieldsType, ConnectorJiraTypeFields } from '../../../common/api'; +import { JiraFieldsType, ConnectorJiraTypeFields } from '../../../common'; import { ExternalServiceFormatter } from '../types'; interface ExternalServiceParams extends JiraFieldsType { diff --git a/x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.test.ts b/x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.test.ts index 01280e9692b5e..b7096179b0fab 100644 --- a/x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.test.ts +++ b/x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseResponse } from '../../../common/api'; +import { CaseResponse } from '../../../common'; import { resilientExternalServiceFormatter } from './external_service_formatter'; describe('IBM Resilient formatter', () => { diff --git a/x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.ts b/x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.ts index 76554dce32797..6dea452565d7c 100644 --- a/x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.ts +++ b/x-pack/plugins/cases/server/connectors/resilient/external_service_formatter.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ResilientFieldsType, ConnectorResillientTypeFields } from '../../../common/api'; +import { ResilientFieldsType, ConnectorResillientTypeFields } from '../../../common'; import { ExternalServiceFormatter } from '../types'; const format: ExternalServiceFormatter<ResilientFieldsType>['format'] = (theCase) => { diff --git a/x-pack/plugins/cases/server/connectors/servicenow/itsm_formatter.ts b/x-pack/plugins/cases/server/connectors/servicenow/itsm_formatter.ts index b49eed6a4ad26..a4fa8a198fea7 100644 --- a/x-pack/plugins/cases/server/connectors/servicenow/itsm_formatter.ts +++ b/x-pack/plugins/cases/server/connectors/servicenow/itsm_formatter.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ServiceNowITSMFieldsType, ConnectorServiceNowITSMTypeFields } from '../../../common/api'; +import { ServiceNowITSMFieldsType, ConnectorServiceNowITSMTypeFields } from '../../../common'; import { ExternalServiceFormatter } from '../types'; const format: ExternalServiceFormatter<ServiceNowITSMFieldsType>['format'] = (theCase) => { diff --git a/x-pack/plugins/cases/server/connectors/servicenow/itsm_formmater.test.ts b/x-pack/plugins/cases/server/connectors/servicenow/itsm_formmater.test.ts index ea3a4e41e17b8..78242e4c3848a 100644 --- a/x-pack/plugins/cases/server/connectors/servicenow/itsm_formmater.test.ts +++ b/x-pack/plugins/cases/server/connectors/servicenow/itsm_formmater.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseResponse } from '../../../common/api'; +import { CaseResponse } from '../../../common'; import { serviceNowITSMExternalServiceFormatter } from './itsm_formatter'; describe('ITSM formatter', () => { diff --git a/x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.test.ts b/x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.test.ts index 4faca62c6e706..1f7716424cfa9 100644 --- a/x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.test.ts +++ b/x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseResponse } from '../../../common/api'; +import { CaseResponse } from '../../../common'; import { serviceNowSIRExternalServiceFormatter } from './sir_formatter'; describe('ITSM formatter', () => { diff --git a/x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.ts b/x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.ts index d2458e6c7ae53..1c528cd2b47bf 100644 --- a/x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.ts +++ b/x-pack/plugins/cases/server/connectors/servicenow/sir_formatter.ts @@ -5,7 +5,7 @@ * 2.0. */ import { get } from 'lodash/fp'; -import { ConnectorServiceNowSIRTypeFields } from '../../../common/api'; +import { ConnectorServiceNowSIRTypeFields } from '../../../common'; import { ExternalServiceFormatter } from '../types'; interface ExternalServiceParams { dest_ip: string | null; diff --git a/x-pack/plugins/cases/server/connectors/types.ts b/x-pack/plugins/cases/server/connectors/types.ts index f6c284b74667b..fae1ec2976bc0 100644 --- a/x-pack/plugins/cases/server/connectors/types.ts +++ b/x-pack/plugins/cases/server/connectors/types.ts @@ -13,7 +13,7 @@ import { ActionType, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../actions/server/types'; -import { CaseResponse, ConnectorTypes } from '../../common/api'; +import { CaseResponse, ConnectorTypes } from '../../common'; import { CasesClientGetAlertsResponse } from '../client/alerts/types'; import { CaseServiceSetup, diff --git a/x-pack/plugins/cases/server/plugin.ts b/x-pack/plugins/cases/server/plugin.ts index 8b53fd77d98a5..407d6583e5f3f 100644 --- a/x-pack/plugins/cases/server/plugin.ts +++ b/x-pack/plugins/cases/server/plugin.ts @@ -10,7 +10,7 @@ import { CoreSetup, CoreStart } from 'src/core/server'; import { SecurityPluginSetup } from '../../security/server'; import { PluginSetupContract as ActionsPluginSetup } from '../../actions/server'; -import { APP_ID, ENABLE_CASE_CONNECTOR } from '../common/constants'; +import { APP_ID, ENABLE_CASE_CONNECTOR } from '../common'; import { ConfigType } from './config'; import { initCaseApi } from './routes/api'; diff --git a/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts index f2318c45e6ed3..0026ee9ce4827 100644 --- a/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts +++ b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts @@ -17,7 +17,7 @@ import { ConnectorTypes, ESCaseAttributes, ESCasesConfigureAttributes, -} from '../../../../common/api'; +} from '../../../../common'; import { CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT, CASE_USER_ACTION_SAVED_OBJECT, @@ -485,6 +485,26 @@ export const mockCaseMappings: Array<SavedObject<ConnectorMappings>> = [ }, ]; +export const mockCaseMappingsResilient: Array<SavedObject<ConnectorMappings>> = [ + { + type: CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT, + id: 'mock-mappings-1', + attributes: { + mappings: mappings[ConnectorTypes.resilient], + }, + references: [], + }, +]; + +export const mockCaseMappingsBad: Array<SavedObject<Partial<ConnectorMappings>>> = [ + { + type: CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT, + id: 'mock-mappings-bad', + attributes: {}, + references: [], + }, +]; + export const mockUserActions: Array<SavedObject<CaseUserActionAttributes>> = [ { type: CASE_USER_ACTION_SAVED_OBJECT, diff --git a/x-pack/plugins/cases/server/routes/api/__mocks__/request_responses.ts b/x-pack/plugins/cases/server/routes/api/__mocks__/request_responses.ts index ae14b44e7dffe..9df94cd0923c9 100644 --- a/x-pack/plugins/cases/server/routes/api/__mocks__/request_responses.ts +++ b/x-pack/plugins/cases/server/routes/api/__mocks__/request_responses.ts @@ -10,7 +10,7 @@ import { CasePostRequest, CasesConfigureRequest, ConnectorTypes, -} from '../../../../common/api'; +} from '../../../../common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { FindActionResult } from '../../../../../actions/server/types'; diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/delete_all_comments.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/delete_all_comments.ts index 7f6cfb224fada..1e7e875a53df3 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/delete_all_comments.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/delete_all_comments.ts @@ -10,8 +10,7 @@ import { schema } from '@kbn/config-schema'; import { buildCommentUserActionItem } from '../../../../services/user_actions/helpers'; import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; -import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../../common/constants'; -import { AssociationType } from '../../../../../common/api'; +import { AssociationType, CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../../common'; export function initDeleteAllCommentsApi({ caseService, diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/delete_comment.test.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/delete_comment.test.ts index dcbcd7b9e246d..d0968c3232459 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/delete_comment.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/delete_comment.test.ts @@ -16,7 +16,7 @@ import { mockCaseComments, } from '../../__fixtures__'; import { initDeleteCommentApi } from './delete_comment'; -import { CASE_COMMENT_DETAILS_URL } from '../../../../../common/constants'; +import { CASE_COMMENT_DETAILS_URL } from '../../../../../common'; describe('DELETE comment', () => { let routeHandler: RequestHandler<any, any, any>; diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/find_comments.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/find_comments.ts index 9468b2b01fe37..654b8d532830a 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/find_comments.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/find_comments.ts @@ -19,10 +19,10 @@ import { CommentsResponseRt, SavedObjectFindOptionsRt, throwErrors, -} from '../../../../../common/api'; +} from '../../../../../common'; import { RouteDeps } from '../../types'; import { escapeHatch, transformComments, wrapError } from '../../utils'; -import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../../common/constants'; +import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../../common'; import { defaultPage, defaultPerPage } from '../..'; const FindQueryParamsRt = rt.partial({ diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/get_all_comment.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/get_all_comment.ts index 2699f7a0307f7..580bb3163bb7d 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/get_all_comment.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/get_all_comment.ts @@ -9,10 +9,10 @@ import Boom from '@hapi/boom'; import { schema } from '@kbn/config-schema'; import { SavedObjectsFindResponse } from 'kibana/server'; -import { AllCommentsResponseRt, CommentAttributes } from '../../../../../common/api'; +import { AllCommentsResponseRt, CommentAttributes } from '../../../../../common'; import { RouteDeps } from '../../types'; import { flattenCommentSavedObjects, wrapError } from '../../utils'; -import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../../common/constants'; +import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../../common'; import { defaultSortField } from '../../../../common'; export function initGetAllCommentsApi({ caseService, router, logger }: RouteDeps) { diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/get_comment.test.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/get_comment.test.ts index 8ee43eaba8a82..46accdc58d460 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/get_comment.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/get_comment.test.ts @@ -17,7 +17,7 @@ import { } from '../../__fixtures__'; import { flattenCommentSavedObject } from '../../utils'; import { initGetCommentApi } from './get_comment'; -import { CASE_COMMENT_DETAILS_URL } from '../../../../../common/constants'; +import { CASE_COMMENT_DETAILS_URL } from '../../../../../common'; describe('GET comment', () => { let routeHandler: RequestHandler<any, any, any>; diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/get_comment.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/get_comment.ts index 9dedfccd3a250..f86f733306043 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/get_comment.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/get_comment.ts @@ -7,10 +7,10 @@ import { schema } from '@kbn/config-schema'; -import { CommentResponseRt } from '../../../../../common/api'; +import { CommentResponseRt } from '../../../../../common'; import { RouteDeps } from '../../types'; import { flattenCommentSavedObject, wrapError } from '../../utils'; -import { CASE_COMMENT_DETAILS_URL } from '../../../../../common/constants'; +import { CASE_COMMENT_DETAILS_URL } from '../../../../../common'; export function initGetCommentApi({ caseService, router, logger }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.test.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.test.ts index 9cc0575f9bb94..32a0133d455c2 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.test.ts @@ -17,8 +17,8 @@ import { mockCases, } from '../../__fixtures__'; import { initPatchCommentApi } from './patch_comment'; -import { CASE_COMMENTS_URL } from '../../../../../common/constants'; -import { CommentType } from '../../../../../common/api'; +import { CASE_COMMENTS_URL } from '../../../../../common'; +import { CommentType } from '../../../../../common'; describe('PATCH comment', () => { let routeHandler: RequestHandler<any, any, any>; diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.ts index 519692d2d78a1..366fb887066f8 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/patch_comment.ts @@ -14,12 +14,12 @@ import Boom from '@hapi/boom'; import { SavedObjectsClientContract, Logger } from 'kibana/server'; import { CommentableCase } from '../../../../common'; -import { CommentPatchRequestRt, throwErrors, User } from '../../../../../common/api'; +import { CommentPatchRequestRt, throwErrors, User } from '../../../../../common'; import { CASE_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../../../saved_object_types'; import { buildCommentUserActionItem } from '../../../../services/user_actions/helpers'; import { RouteDeps } from '../../types'; import { escapeHatch, wrapError, decodeCommentRequest } from '../../utils'; -import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../../common/constants'; +import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../../common'; import { CaseServiceSetup } from '../../../../services'; interface CombinedCaseParams { diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.test.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.test.ts index 807ec0d089a52..27d5c47d47399 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.test.ts @@ -17,8 +17,8 @@ import { mockCaseComments, } from '../../__fixtures__'; import { initPostCommentApi } from './post_comment'; -import { CASE_COMMENTS_URL } from '../../../../../common/constants'; -import { CommentType } from '../../../../../common/api'; +import { CASE_COMMENTS_URL } from '../../../../../common'; +import { CommentType } from '../../../../../common'; describe('POST comment', () => { let routeHandler: RequestHandler<any, any, any>; diff --git a/x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.ts b/x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.ts index 8658f9ba0aac5..8af4b86762d33 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/comments/post_comment.ts @@ -9,8 +9,7 @@ import Boom from '@hapi/boom'; import { schema } from '@kbn/config-schema'; import { escapeHatch, wrapError } from '../../utils'; import { RouteDeps } from '../../types'; -import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../../common/constants'; -import { CommentRequest } from '../../../../../common/api'; +import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR, CommentRequest } from '../../../../../common'; export function initPostCommentApi({ router, logger }: RouteDeps) { router.post( diff --git a/x-pack/plugins/cases/server/routes/api/cases/configure/get_configure.test.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/get_configure.test.ts index f328844acfd00..626f53cdf4263 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/configure/get_configure.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/configure/get_configure.test.ts @@ -17,9 +17,8 @@ import { } from '../../__fixtures__'; import { initGetCaseConfigure } from './get_configure'; -import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; +import { CASE_CONFIGURE_URL, ConnectorTypes } from '../../../../../common'; import { mappings } from '../../../../client/configure/mock'; -import { ConnectorTypes } from '../../../../../common/api/connectors'; import { CasesClient } from '../../../../client'; describe('GET configuration', () => { diff --git a/x-pack/plugins/cases/server/routes/api/cases/configure/get_configure.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/get_configure.ts index c916bd8f4140b..03ac3dd8b13b3 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/configure/get_configure.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/configure/get_configure.ts @@ -6,10 +6,10 @@ */ import Boom from '@hapi/boom'; -import { CaseConfigureResponseRt, ConnectorMappingsAttributes } from '../../../../../common/api'; +import { CaseConfigureResponseRt, ConnectorMappingsAttributes } from '../../../../../common'; import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; -import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; +import { CASE_CONFIGURE_URL } from '../../../../../common'; import { transformESConnectorToCaseConnector } from '../helpers'; export function initGetCaseConfigure({ caseConfigureService, router, logger }: RouteDeps) { diff --git a/x-pack/plugins/cases/server/routes/api/cases/configure/get_connectors.test.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/get_connectors.test.ts index 3fa0fe2f83f79..082adf7b4803f 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/configure/get_connectors.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/configure/get_connectors.test.ts @@ -17,7 +17,7 @@ import { } from '../../__fixtures__'; import { initCaseConfigureGetActionConnector } from './get_connectors'; -import { CASE_CONFIGURE_CONNECTORS_URL } from '../../../../../common/constants'; +import { CASE_CONFIGURE_CONNECTORS_URL } from '../../../../../common'; import { getActions } from '../../__mocks__/request_responses'; describe('GET connectors', () => { diff --git a/x-pack/plugins/cases/server/routes/api/cases/configure/get_connectors.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/get_connectors.ts index 81ffc06355ff5..7aec7e4f086b4 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/configure/get_connectors.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/configure/get_connectors.ts @@ -12,10 +12,7 @@ import { ActionType } from '../../../../../../actions/common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { FindActionResult } from '../../../../../../actions/server/types'; -import { - CASE_CONFIGURE_CONNECTORS_URL, - SUPPORTED_CONNECTORS, -} from '../../../../../common/constants'; +import { CASE_CONFIGURE_CONNECTORS_URL, SUPPORTED_CONNECTORS } from '../../../../../common'; const isConnectorSupported = ( action: FindActionResult, diff --git a/x-pack/plugins/cases/server/routes/api/cases/configure/patch_configure.test.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/patch_configure.test.ts index 48d88e0f622f5..c4e2b6af1cd6b 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/configure/patch_configure.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/configure/patch_configure.test.ts @@ -17,8 +17,7 @@ import { import { mockCaseConfigure } from '../../__fixtures__/mock_saved_objects'; import { initPatchCaseConfigure } from './patch_configure'; -import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; -import { ConnectorTypes } from '../../../../../common/api/connectors'; +import { CASE_CONFIGURE_URL, ConnectorTypes } from '../../../../../common'; import { CasesClient } from '../../../../client'; describe('PATCH configuration', () => { diff --git a/x-pack/plugins/cases/server/routes/api/cases/configure/patch_configure.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/patch_configure.ts index ba0ea6eb17936..5fe38cf0efe48 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/configure/patch_configure.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/configure/patch_configure.ts @@ -15,10 +15,10 @@ import { CaseConfigureResponseRt, throwErrors, ConnectorMappingsAttributes, -} from '../../../../../common/api'; +} from '../../../../../common'; import { RouteDeps } from '../../types'; import { wrapError, escapeHatch } from '../../utils'; -import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; +import { CASE_CONFIGURE_URL } from '../../../../../common'; import { transformCaseConnectorToEsConnector, transformESConnectorToCaseConnector, diff --git a/x-pack/plugins/cases/server/routes/api/cases/configure/post_configure.test.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/post_configure.test.ts index 882a10742d733..35b662078fe9c 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/configure/post_configure.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/configure/post_configure.test.ts @@ -18,8 +18,7 @@ import { import { initPostCaseConfigure } from './post_configure'; import { newConfiguration } from '../../__mocks__/request_responses'; -import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; -import { ConnectorTypes } from '../../../../../common/api/connectors'; +import { CASE_CONFIGURE_URL, ConnectorTypes } from '../../../../../common'; import { CasesClient } from '../../../../client'; describe('POST configuration', () => { diff --git a/x-pack/plugins/cases/server/routes/api/cases/configure/post_configure.ts b/x-pack/plugins/cases/server/routes/api/cases/configure/post_configure.ts index 469151a126898..74ad02f47e178 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/configure/post_configure.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/configure/post_configure.ts @@ -15,10 +15,10 @@ import { CaseConfigureResponseRt, throwErrors, ConnectorMappingsAttributes, -} from '../../../../../common/api'; +} from '../../../../../common'; import { RouteDeps } from '../../types'; import { wrapError, escapeHatch } from '../../utils'; -import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; +import { CASE_CONFIGURE_URL } from '../../../../../common'; import { transformCaseConnectorToEsConnector, transformESConnectorToCaseConnector, diff --git a/x-pack/plugins/cases/server/routes/api/cases/delete_cases.test.ts b/x-pack/plugins/cases/server/routes/api/cases/delete_cases.test.ts index a441a027769bf..7748a079ceb4d 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/delete_cases.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/delete_cases.test.ts @@ -17,7 +17,7 @@ import { mockCaseComments, } from '../__fixtures__'; import { initDeleteCasesApi } from './delete_cases'; -import { CASES_URL } from '../../../../common/constants'; +import { CASES_URL } from '../../../../common'; describe('DELETE case', () => { let routeHandler: RequestHandler<any, any, any>; diff --git a/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts index d91859d4e8cbb..d0cfc03e69f7c 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts @@ -11,7 +11,7 @@ import { SavedObjectsClientContract } from 'src/core/server'; import { buildCaseUserActionItem } from '../../../services/user_actions/helpers'; import { RouteDeps } from '../types'; import { wrapError } from '../utils'; -import { CASES_URL, ENABLE_CASE_CONNECTOR } from '../../../../common/constants'; +import { CASES_URL, ENABLE_CASE_CONNECTOR } from '../../../../common'; import { CaseServiceSetup } from '../../../services'; async function deleteSubCases({ diff --git a/x-pack/plugins/cases/server/routes/api/cases/find_cases.test.ts b/x-pack/plugins/cases/server/routes/api/cases/find_cases.test.ts index ca9f731ca5010..75586896390fc 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/find_cases.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/find_cases.test.ts @@ -15,7 +15,7 @@ import { mockCases, } from '../__fixtures__'; import { initFindCasesApi } from './find_cases'; -import { CASES_URL } from '../../../../common/constants'; +import { CASES_URL } from '../../../../common'; import { mockCaseConfigure, mockCaseNoConnectorId } from '../__fixtures__/mock_saved_objects'; describe('FIND all cases', () => { diff --git a/x-pack/plugins/cases/server/routes/api/cases/find_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/find_cases.ts index 10406d0edcd46..77b1d6b23f912 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/find_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/find_cases.ts @@ -16,10 +16,10 @@ import { CasesFindRequestRt, throwErrors, caseStatuses, -} from '../../../../common/api'; +} from '../../../../common'; import { transformCases, wrapError, escapeHatch } from '../utils'; import { RouteDeps } from '../types'; -import { CASES_URL } from '../../../../common/constants'; +import { CASES_URL } from '../../../../common'; import { constructQueryOptions } from './helpers'; export function initFindCasesApi({ caseService, router, logger }: RouteDeps) { diff --git a/x-pack/plugins/cases/server/routes/api/cases/get_case.test.ts b/x-pack/plugins/cases/server/routes/api/cases/get_case.test.ts index b9312331b4df2..768bbca62f3fe 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/get_case.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/get_case.test.ts @@ -8,7 +8,7 @@ import { kibanaResponseFactory, RequestHandler, SavedObject } from 'src/core/server'; import { httpServerMock } from 'src/core/server/mocks'; -import { ConnectorTypes, ESCaseAttributes } from '../../../../common/api'; +import { ConnectorTypes, ESCaseAttributes } from '../../../../common'; import { createMockSavedObjectsRepository, createRoute, @@ -21,7 +21,7 @@ import { } from '../__fixtures__'; import { flattenCaseSavedObject } from '../utils'; import { initGetCaseApi } from './get_case'; -import { CASE_DETAILS_URL } from '../../../../common/constants'; +import { CASE_DETAILS_URL } from '../../../../common'; describe('GET case', () => { let routeHandler: RequestHandler<any, any, any>; diff --git a/x-pack/plugins/cases/server/routes/api/cases/get_case.ts b/x-pack/plugins/cases/server/routes/api/cases/get_case.ts index e8e35d875f42f..c69eae7fb1f94 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/get_case.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/get_case.ts @@ -10,7 +10,7 @@ import { schema } from '@kbn/config-schema'; import Boom from '@hapi/boom'; import { RouteDeps } from '../types'; import { wrapError } from '../utils'; -import { CASE_DETAILS_URL, ENABLE_CASE_CONNECTOR } from '../../../../common/constants'; +import { CASE_DETAILS_URL, ENABLE_CASE_CONNECTOR } from '../../../../common'; export function initGetCaseApi({ router, logger }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/routes/api/cases/helpers.test.ts b/x-pack/plugins/cases/server/routes/api/cases/helpers.test.ts index f7cfebeaea749..a1d25aa295799 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/helpers.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/helpers.test.ts @@ -11,7 +11,7 @@ import { ConnectorTypes, ESCaseConnector, ESCasesConfigureAttributes, -} from '../../../../common/api'; +} from '../../../../common'; import { mockCaseConfigure } from '../__fixtures__'; import { transformCaseConnectorToEsConnector, diff --git a/x-pack/plugins/cases/server/routes/api/cases/helpers.ts b/x-pack/plugins/cases/server/routes/api/cases/helpers.ts index 4e6c07d05bc17..5f51c9b1f8d8c 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/helpers.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/helpers.ts @@ -11,15 +11,15 @@ import deepEqual from 'fast-deep-equal'; import { SavedObjectsFindResponse } from 'kibana/server'; import { CaseConnector, - ESCaseConnector, - ESCasesConfigureAttributes, - ConnectorTypeFields, - ConnectorTypes, CaseStatuses, CaseType, + ConnectorTypeFields, + ConnectorTypes, + ESCaseConnector, + ESCasesConfigureAttributes, + ESConnectorFields, SavedObjectFindOptions, -} from '../../../../common/api'; -import { ESConnectorFields } from '../../../../common/api/connectors'; +} from '../../../../common'; import { CASE_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../../saved_object_types'; import { sortToSnake } from '../utils'; import { combineFilters } from '../../../common'; diff --git a/x-pack/plugins/cases/server/routes/api/cases/patch_cases.test.ts b/x-pack/plugins/cases/server/routes/api/cases/patch_cases.test.ts index b3f87211c9547..96a891441ea5f 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/patch_cases.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/patch_cases.test.ts @@ -17,7 +17,7 @@ import { } from '../__fixtures__'; import { initPatchCasesApi } from './patch_cases'; import { mockCaseConfigure, mockCaseNoConnectorId } from '../__fixtures__/mock_saved_objects'; -import { CaseStatuses } from '../../../../common/api'; +import { CaseStatuses } from '../../../../common'; describe('PATCH cases', () => { let routeHandler: RequestHandler<any, any, any>; diff --git a/x-pack/plugins/cases/server/routes/api/cases/patch_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/patch_cases.ts index 8e779087bcafe..092f88c1a8a20 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/patch_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/patch_cases.ts @@ -7,8 +7,8 @@ import { escapeHatch, wrapError } from '../utils'; import { RouteDeps } from '../types'; -import { CASES_URL } from '../../../../common/constants'; -import { CasesPatchRequest } from '../../../../common/api'; +import { CASES_URL } from '../../../../common'; +import { CasesPatchRequest } from '../../../../common'; export function initPatchCasesApi({ router, logger }: RouteDeps) { router.patch( diff --git a/x-pack/plugins/cases/server/routes/api/cases/post_case.test.ts b/x-pack/plugins/cases/server/routes/api/cases/post_case.test.ts index e1669203d3ded..669d3a5e58874 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/post_case.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/post_case.test.ts @@ -15,9 +15,9 @@ import { mockCases, } from '../__fixtures__'; import { initPostCaseApi } from './post_case'; -import { CASES_URL } from '../../../../common/constants'; +import { CASES_URL } from '../../../../common'; import { mockCaseConfigure } from '../__fixtures__/mock_saved_objects'; -import { ConnectorTypes, CaseStatuses } from '../../../../common/api'; +import { ConnectorTypes, CaseStatuses } from '../../../../common'; describe('POST cases', () => { let routeHandler: RequestHandler<any, any, any>; diff --git a/x-pack/plugins/cases/server/routes/api/cases/post_case.ts b/x-pack/plugins/cases/server/routes/api/cases/post_case.ts index e2d71c5837353..a7951a1a71344 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/post_case.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/post_case.ts @@ -8,8 +8,8 @@ import { wrapError, escapeHatch } from '../utils'; import { RouteDeps } from '../types'; -import { CASES_URL } from '../../../../common/constants'; -import { CasePostRequest } from '../../../../common/api'; +import { CASES_URL } from '../../../../common'; +import { CasePostRequest } from '../../../../common'; export function initPostCaseApi({ router, logger }: RouteDeps) { router.post( diff --git a/x-pack/plugins/cases/server/routes/api/cases/push_case.test.ts b/x-pack/plugins/cases/server/routes/api/cases/push_case.test.ts index fb0ba5e3b5d9a..378d092c8be0b 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/push_case.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/push_case.test.ts @@ -20,7 +20,7 @@ import { } from '../__fixtures__'; import { initPushCaseApi } from './push_case'; import { CasesRequestHandlerContext } from '../../../types'; -import { getCasePushUrl } from '../../../../common/api/helpers'; +import { getCasePushUrl } from '../../../../common'; describe('Push case', () => { let routeHandler: RequestHandler<any, any, any>; diff --git a/x-pack/plugins/cases/server/routes/api/cases/push_case.ts b/x-pack/plugins/cases/server/routes/api/cases/push_case.ts index 7395758210cf4..9bfb30e0d63ad 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/push_case.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/push_case.ts @@ -12,9 +12,9 @@ import { identity } from 'fp-ts/lib/function'; import { wrapError, escapeHatch } from '../utils'; -import { throwErrors, CasePushRequestParamsRt } from '../../../../common/api'; +import { throwErrors, CasePushRequestParamsRt } from '../../../../common'; import { RouteDeps } from '../types'; -import { CASE_PUSH_URL } from '../../../../common/constants'; +import { CASE_PUSH_URL } from '../../../../common'; export function initPushCaseApi({ router, logger }: RouteDeps) { router.post( diff --git a/x-pack/plugins/cases/server/routes/api/cases/reporters/get_reporters.ts b/x-pack/plugins/cases/server/routes/api/cases/reporters/get_reporters.ts index e5433f4972239..53fdc298ef267 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/reporters/get_reporters.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/reporters/get_reporters.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { UsersRt } from '../../../../../common/api'; +import { UsersRt } from '../../../../../common'; import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; -import { CASE_REPORTERS_URL } from '../../../../../common/constants'; +import { CASE_REPORTERS_URL } from '../../../../../common'; export function initGetReportersApi({ caseService, router, logger }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/routes/api/cases/status/get_status.test.ts b/x-pack/plugins/cases/server/routes/api/cases/status/get_status.test.ts index 1c399a415e470..60ad0c60f944f 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/status/get_status.test.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/status/get_status.test.ts @@ -15,8 +15,8 @@ import { mockCases, } from '../../__fixtures__'; import { initGetCasesStatusApi } from './get_status'; -import { CASE_STATUS_URL } from '../../../../../common/constants'; -import { CaseType } from '../../../../../common/api'; +import { CASE_STATUS_URL } from '../../../../../common'; +import { CaseType } from '../../../../../common'; describe('GET status', () => { let routeHandler: RequestHandler<any, any, any>; diff --git a/x-pack/plugins/cases/server/routes/api/cases/status/get_status.ts b/x-pack/plugins/cases/server/routes/api/cases/status/get_status.ts index d0addfff09124..73642fdee0eac 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/status/get_status.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/status/get_status.ts @@ -8,8 +8,8 @@ import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; -import { CasesStatusResponseRt, caseStatuses } from '../../../../../common/api'; -import { CASE_STATUS_URL } from '../../../../../common/constants'; +import { CasesStatusResponseRt, caseStatuses } from '../../../../../common'; +import { CASE_STATUS_URL } from '../../../../../common'; import { constructQueryOptions } from '../helpers'; export function initGetCasesStatusApi({ caseService, router, logger }: RouteDeps) { diff --git a/x-pack/plugins/cases/server/routes/api/cases/sub_case/delete_sub_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/sub_case/delete_sub_cases.ts index fd33afbd7df8e..ef60c743ec822 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/sub_case/delete_sub_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/sub_case/delete_sub_cases.ts @@ -10,7 +10,7 @@ import { schema } from '@kbn/config-schema'; import { buildCaseUserActionItem } from '../../../../services/user_actions/helpers'; import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; -import { SUB_CASES_PATCH_DEL_URL } from '../../../../../common/constants'; +import { SUB_CASES_PATCH_DEL_URL } from '../../../../../common'; import { CASE_SAVED_OBJECT } from '../../../../saved_object_types'; export function initDeleteSubCasesApi({ diff --git a/x-pack/plugins/cases/server/routes/api/cases/sub_case/find_sub_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/sub_case/find_sub_cases.ts index e7f9f8b4f2d73..e069ceda14df9 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/sub_case/find_sub_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/sub_case/find_sub_cases.ts @@ -17,10 +17,10 @@ import { SubCasesFindRequestRt, SubCasesFindResponseRt, throwErrors, -} from '../../../../../common/api'; +} from '../../../../../common'; import { RouteDeps } from '../../types'; import { escapeHatch, transformSubCases, wrapError } from '../../utils'; -import { SUB_CASES_URL } from '../../../../../common/constants'; +import { SUB_CASES_URL } from '../../../../../common'; import { constructQueryOptions } from '../helpers'; import { defaultPage, defaultPerPage } from '../..'; diff --git a/x-pack/plugins/cases/server/routes/api/cases/sub_case/get_sub_case.ts b/x-pack/plugins/cases/server/routes/api/cases/sub_case/get_sub_case.ts index 32dcc924e1a08..b5ebfb4de348b 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/sub_case/get_sub_case.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/sub_case/get_sub_case.ts @@ -7,10 +7,10 @@ import { schema } from '@kbn/config-schema'; -import { SubCaseResponseRt } from '../../../../../common/api'; +import { SubCaseResponseRt } from '../../../../../common'; import { RouteDeps } from '../../types'; import { flattenSubCaseSavedObject, wrapError } from '../../utils'; -import { SUB_CASE_DETAILS_URL } from '../../../../../common/constants'; +import { SUB_CASE_DETAILS_URL } from '../../../../../common'; import { countAlertsForID } from '../../../../common'; export function initGetSubCaseApi({ caseService, router, logger }: RouteDeps) { diff --git a/x-pack/plugins/cases/server/routes/api/cases/sub_case/patch_sub_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/sub_case/patch_sub_cases.ts index 08836615e1d39..0b142fb5279e5 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/sub_case/patch_sub_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/sub_case/patch_sub_cases.ts @@ -35,8 +35,8 @@ import { SubCasesResponseRt, User, CommentAttributes, -} from '../../../../../common/api'; -import { SUB_CASES_PATCH_DEL_URL } from '../../../../../common/constants'; +} from '../../../../../common'; +import { SUB_CASES_PATCH_DEL_URL } from '../../../../../common'; import { RouteDeps } from '../../types'; import { escapeHatch, diff --git a/x-pack/plugins/cases/server/routes/api/cases/tags/get_tags.ts b/x-pack/plugins/cases/server/routes/api/cases/tags/get_tags.ts index f066aa70ec472..d70d6e0b57ee9 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/tags/get_tags.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/tags/get_tags.ts @@ -7,7 +7,7 @@ import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; -import { CASE_TAGS_URL } from '../../../../../common/constants'; +import { CASE_TAGS_URL } from '../../../../../common'; export function initGetTagsApi({ caseService, router }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/routes/api/cases/user_actions/get_all_user_actions.ts b/x-pack/plugins/cases/server/routes/api/cases/user_actions/get_all_user_actions.ts index b5c564648c185..48393b6af34ae 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/user_actions/get_all_user_actions.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/user_actions/get_all_user_actions.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; -import { CASE_USER_ACTIONS_URL, SUB_CASE_USER_ACTIONS_URL } from '../../../../../common/constants'; +import { CASE_USER_ACTIONS_URL, SUB_CASE_USER_ACTIONS_URL } from '../../../../../common'; export function initGetAllCaseUserActionsApi({ router, logger }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/routes/api/utils.test.ts b/x-pack/plugins/cases/server/routes/api/utils.test.ts index f6bc1e4f71897..2df17e3abacfa 100644 --- a/x-pack/plugins/cases/server/routes/api/utils.test.ts +++ b/x-pack/plugins/cases/server/routes/api/utils.test.ts @@ -30,7 +30,7 @@ import { AssociationType, CaseType, CaseResponse, -} from '../../../common/api'; +} from '../../../common'; describe('Utils', () => { describe('transformNewCase', () => { diff --git a/x-pack/plugins/cases/server/routes/api/utils.ts b/x-pack/plugins/cases/server/routes/api/utils.ts index 8e8862f4157f1..9234472c13f5d 100644 --- a/x-pack/plugins/cases/server/routes/api/utils.ts +++ b/x-pack/plugins/cases/server/routes/api/utils.ts @@ -41,7 +41,7 @@ import { SubCasesFindResponse, User, AlertCommentRequestRt, -} from '../../../common/api'; +} from '../../../common'; import { transformESConnectorToCaseConnector } from './cases/helpers'; import { SortFieldCase } from './types'; diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations.ts b/x-pack/plugins/cases/server/saved_object_types/migrations.ts index bf9694d7e6bb0..8bbc481124870 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations.ts @@ -14,7 +14,7 @@ import { CaseType, AssociationType, ESConnectorFields, -} from '../../common/api'; +} from '../../common'; interface UnsanitizedCaseConnector { connector_id: string; diff --git a/x-pack/plugins/cases/server/scripts/sub_cases/index.ts b/x-pack/plugins/cases/server/scripts/sub_cases/index.ts index ba3bcaa65091c..56f842c10e8f5 100644 --- a/x-pack/plugins/cases/server/scripts/sub_cases/index.ts +++ b/x-pack/plugins/cases/server/scripts/sub_cases/index.ts @@ -8,9 +8,7 @@ import yargs from 'yargs'; import { ToolingLog } from '@kbn/dev-utils'; import { KbnClient } from '@kbn/test'; -import { CaseResponse, CaseType, ConnectorTypes } from '../../../common/api'; -import { CommentType } from '../../../common/api/cases/comment'; -import { CASES_URL } from '../../../common/constants'; +import { CaseResponse, CaseType, CommentType, ConnectorTypes, CASES_URL } from '../../../common'; import { ActionResult, ActionTypeExecutorResult } from '../../../../actions/common'; import { ContextTypeGeneratedAlertType, createAlertsString } from '../../connectors'; diff --git a/x-pack/plugins/cases/server/services/alerts/index.test.ts b/x-pack/plugins/cases/server/services/alerts/index.test.ts index 042e415b77e43..28c3a6278d544 100644 --- a/x-pack/plugins/cases/server/services/alerts/index.test.ts +++ b/x-pack/plugins/cases/server/services/alerts/index.test.ts @@ -6,7 +6,7 @@ */ import { KibanaRequest } from 'kibana/server'; -import { CaseStatuses } from '../../../common/api'; +import { CaseStatuses } from '../../../common'; import { AlertService, AlertServiceContract } from '.'; import { elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks'; diff --git a/x-pack/plugins/cases/server/services/alerts/index.ts b/x-pack/plugins/cases/server/services/alerts/index.ts index db8e841f45ee4..81afaf5363e1f 100644 --- a/x-pack/plugins/cases/server/services/alerts/index.ts +++ b/x-pack/plugins/cases/server/services/alerts/index.ts @@ -10,7 +10,7 @@ import { isEmpty } from 'lodash'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { ElasticsearchClient, Logger } from 'kibana/server'; -import { MAX_ALERTS_PER_SUB_CASE } from '../../../common/constants'; +import { MAX_ALERTS_PER_SUB_CASE } from '../../../common'; import { UpdateAlertRequest } from '../../client/types'; import { AlertInfo } from '../../common'; import { createCaseError } from '../../common/error'; diff --git a/x-pack/plugins/cases/server/services/configure/index.ts b/x-pack/plugins/cases/server/services/configure/index.ts index 46dca4d9a0d0e..0ca63bce2d1d0 100644 --- a/x-pack/plugins/cases/server/services/configure/index.ts +++ b/x-pack/plugins/cases/server/services/configure/index.ts @@ -13,7 +13,7 @@ import { SavedObjectsUpdateResponse, } from 'kibana/server'; -import { ESCasesConfigureAttributes, SavedObjectFindOptions } from '../../../common/api'; +import { ESCasesConfigureAttributes, SavedObjectFindOptions } from '../../../common'; import { CASE_CONFIGURE_SAVED_OBJECT } from '../../saved_object_types'; interface ClientArgs { diff --git a/x-pack/plugins/cases/server/services/connector_mappings/index.ts b/x-pack/plugins/cases/server/services/connector_mappings/index.ts index d4fda10276d2b..82f37190b4ecc 100644 --- a/x-pack/plugins/cases/server/services/connector_mappings/index.ts +++ b/x-pack/plugins/cases/server/services/connector_mappings/index.ts @@ -13,7 +13,7 @@ import { SavedObjectsFindResponse, } from 'kibana/server'; -import { ConnectorMappings, SavedObjectFindOptions } from '../../../common/api'; +import { ConnectorMappings, SavedObjectFindOptions } from '../../../common'; import { CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT } from '../../saved_object_types'; interface ClientArgs { diff --git a/x-pack/plugins/cases/server/services/index.ts b/x-pack/plugins/cases/server/services/index.ts index 48a1a1ed68432..a27a8860e96b5 100644 --- a/x-pack/plugins/cases/server/services/index.ts +++ b/x-pack/plugins/cases/server/services/index.ts @@ -20,6 +20,7 @@ import { import { AuthenticatedUser, SecurityPluginSetup } from '../../../security/server'; import { + ENABLE_CASE_CONNECTOR, ESCaseAttributes, CommentAttributes, SavedObjectFindOptions, @@ -33,8 +34,7 @@ import { CaseResponse, caseTypeField, CasesFindRequest, -} from '../../common/api'; -import { ENABLE_CASE_CONNECTOR } from '../../common/constants'; +} from '../../common'; import { combineFilters, defaultSortField, groupTotalAlertsByID } from '../common'; import { defaultPage, defaultPerPage } from '../routes/api'; import { diff --git a/x-pack/plugins/cases/server/services/reporters/read_reporters.ts b/x-pack/plugins/cases/server/services/reporters/read_reporters.ts index d2708780b2ccf..b47fa185ff78e 100644 --- a/x-pack/plugins/cases/server/services/reporters/read_reporters.ts +++ b/x-pack/plugins/cases/server/services/reporters/read_reporters.ts @@ -7,7 +7,7 @@ import { SavedObject, SavedObjectsClientContract } from 'kibana/server'; -import { CaseAttributes, User } from '../../../common/api'; +import { CaseAttributes, User } from '../../../common'; import { CASE_SAVED_OBJECT } from '../../saved_object_types'; export const convertToReporters = (caseObjects: Array<SavedObject<CaseAttributes>>): User[] => diff --git a/x-pack/plugins/cases/server/services/tags/read_tags.ts b/x-pack/plugins/cases/server/services/tags/read_tags.ts index 4c4a948453730..a00b0b6f26fb7 100644 --- a/x-pack/plugins/cases/server/services/tags/read_tags.ts +++ b/x-pack/plugins/cases/server/services/tags/read_tags.ts @@ -7,7 +7,7 @@ import { SavedObject, SavedObjectsClientContract } from 'kibana/server'; -import { CaseAttributes } from '../../../common/api'; +import { CaseAttributes } from '../../../common'; import { CASE_SAVED_OBJECT } from '../../saved_object_types'; export const convertToTags = (tagObjects: Array<SavedObject<CaseAttributes>>): string[] => diff --git a/x-pack/plugins/cases/server/services/user_actions/helpers.ts b/x-pack/plugins/cases/server/services/user_actions/helpers.ts index c600a96234b3d..be32717039d9d 100644 --- a/x-pack/plugins/cases/server/services/user_actions/helpers.ts +++ b/x-pack/plugins/cases/server/services/user_actions/helpers.ts @@ -17,7 +17,7 @@ import { User, UserActionFieldType, SubCaseAttributes, -} from '../../../common/api'; +} from '../../../common'; import { isTwoArraysDifference, transformESConnectorToCaseConnector, diff --git a/x-pack/plugins/cases/server/services/user_actions/index.ts b/x-pack/plugins/cases/server/services/user_actions/index.ts index 785c81021b584..a038d843a5331 100644 --- a/x-pack/plugins/cases/server/services/user_actions/index.ts +++ b/x-pack/plugins/cases/server/services/user_actions/index.ts @@ -12,7 +12,7 @@ import { SavedObjectReference, } from 'kibana/server'; -import { CaseUserActionAttributes } from '../../../common/api'; +import { CaseUserActionAttributes } from '../../../common'; import { CASE_USER_ACTION_SAVED_OBJECT, CASE_SAVED_OBJECT, diff --git a/x-pack/plugins/cases/server/types.ts b/x-pack/plugins/cases/server/types.ts index 31d73ea999163..420890c6f80fe 100644 --- a/x-pack/plugins/cases/server/types.ts +++ b/x-pack/plugins/cases/server/types.ts @@ -6,7 +6,6 @@ */ import type { IRouter, RequestHandlerContext } from 'src/core/server'; -import type { AppRequestContext } from '../../security_solution/server'; import type { ActionsApiRequestHandlerContext } from '../../actions/server'; import { CasesClient } from './client'; @@ -20,9 +19,6 @@ export interface CaseRequestContext { export interface CasesRequestHandlerContext extends RequestHandlerContext { cases: CaseRequestContext; actions: ActionsApiRequestHandlerContext; - // TODO: Remove when triggers_ui do not import case's types. - // PR https://github.com/elastic/kibana/pull/84587. - securitySolution: AppRequestContext; } /** diff --git a/x-pack/plugins/cases/tsconfig.json b/x-pack/plugins/cases/tsconfig.json new file mode 100644 index 0000000000000..493fe6430efa7 --- /dev/null +++ b/x-pack/plugins/cases/tsconfig.json @@ -0,0 +1,30 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + "../../../typings/**/*" + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + + // optionalPlugins from ./kibana.json + { "path": "../security/tsconfig.json" }, + { "path": "../spaces/tsconfig.json" }, + + // Required from './kibana.json' + { "path": "../actions/tsconfig.json" }, + { "path": "../triggers_actions_ui/tsconfig.json"}, + { "path": "../../../src/plugins/es_ui_shared/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_utils/tsconfig.json" } + ] +} diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 2b584b196a738..a735f3885cf2c 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ENABLE_CASE_CONNECTOR } from '../../cases/common/constants'; +import { ENABLE_CASE_CONNECTOR } from '../../cases/common'; export const APP_ID = 'securitySolution'; export const SERVER_APP_ID = 'siem'; @@ -25,6 +25,8 @@ export const DEFAULT_REFRESH_RATE_INTERVAL = 'timepicker:refreshIntervalDefaults export const DEFAULT_APP_TIME_RANGE = 'securitySolution:timeDefaults'; export const DEFAULT_APP_REFRESH_INTERVAL = 'securitySolution:refreshIntervalDefaults'; export const DEFAULT_SIGNALS_INDEX = '.siem-signals'; +// The DEFAULT_MAX_SIGNALS value exists also in `x-pack/plugins/cases/common/constants.ts` +// If either changes, engineer should ensure both values are updated export const DEFAULT_MAX_SIGNALS = 100; export const DEFAULT_SEARCH_AFTER_PAGE_SIZE = 100; export const DEFAULT_ANOMALY_SCORE = 'securitySolution:defaultAnomalyScore'; diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts index 3f3209b52120e..7f0016e39ff88 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts @@ -19,7 +19,8 @@ import { createTimeline } from '../../tasks/api_calls/timelines'; import { cleanKibana } from '../../tasks/common'; import { createCase } from '../../tasks/api_calls/cases'; -describe('attach timeline to case', () => { +// TODO: enable once attach timeline to cases is re-enabled +describe.skip('attach timeline to case', () => { context('without cases created', () => { beforeEach(() => { cleanKibana(); diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts index f46feae946242..c568aaae664a0 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases/creation.spec.ts @@ -67,8 +67,8 @@ describe('Cases', () => { .as('mycase') ); }); - - it('Creates a new case with timeline and opens the timeline', function () { + // TODO: enable once attach timeline to cases is re-enabled + it.skip('Creates a new case with timeline and opens the timeline', function () { loginAndWaitForPageWithoutDateRange(CASES_URL); goToCreateNewCase(); fillCasesMandatoryfields(this.mycase); diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json index d4551f76ae390..50a5f62740271 100644 --- a/x-pack/plugins/security_solution/kibana.json +++ b/x-pack/plugins/security_solution/kibana.json @@ -7,6 +7,7 @@ "requiredPlugins": [ "actions", "alerting", + "cases", "data", "dataEnhanced", "embeddable", diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx index 9f3e23fcde1c0..60fa0e4aafd8e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx @@ -5,574 +5,75 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { - EuiBasicTable as _EuiBasicTable, - EuiContextMenuPanel, - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingContent, - EuiProgress, - EuiTableSortingType, -} from '@elastic/eui'; -import { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types'; -import { isEmpty, memoize } from 'lodash/fp'; -import styled, { css } from 'styled-components'; -import classnames from 'classnames'; +import React, { useCallback } from 'react'; +import { useHistory } from 'react-router-dom'; -import * as i18n from './translations'; -import { CaseStatuses, CaseType } from '../../../../../cases/common/api'; -import { getCasesColumns } from './columns'; -import { Case, DeleteCase, FilterOptions, SortFieldCase, SubCase } from '../../containers/types'; -import { useGetCases, UpdateCase } from '../../containers/use_get_cases'; -import { useGetCasesStatus } from '../../containers/use_get_cases_status'; -import { useDeleteCases } from '../../containers/use_delete_cases'; -import { EuiBasicTableOnChange } from '../../../detections/pages/detection_engine/rules/types'; -import { Panel } from '../../../common/components/panel'; import { - UtilityBar, - UtilityBarAction, - UtilityBarGroup, - UtilityBarSection, - UtilityBarText, -} from '../../../common/components/utility_bar'; -import { getCreateCaseUrl, useFormatUrl } from '../../../common/components/link_to'; -import { getBulkItems } from '../bulk_actions'; -import { CaseHeaderPage } from '../case_header_page'; -import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; -import { getActions } from './actions'; -import { CasesTableFilters } from './table_filters'; -import { useUpdateCases } from '../../containers/use_bulk_update_case'; -import { useGetActionLicense } from '../../containers/use_get_action_license'; -import { getActionLicenseError } from '../use_push_to_service/helpers'; -import { CaseCallOut } from '../callout'; -import { ConfigureCaseButton } from '../configure_cases/button'; -import { ERROR_PUSH_SERVICE_CALLOUT_TITLE } from '../use_push_to_service/translations'; -import { LinkButton } from '../../../common/components/links'; + getCaseDetailsUrl, + getConfigureCasesUrl, + getCreateCaseUrl, + useFormatUrl, +} from '../../../common/components/link_to'; import { SecurityPageName } from '../../../app/types'; import { useKibana } from '../../../common/lib/kibana'; import { APP_ID } from '../../../../common/constants'; -import { Stats } from '../status'; -import { SELECTABLE_MESSAGE_COLLECTIONS } from '../../translations'; -import { getExpandedRowMap } from './expanded_row'; -import { isSelectedCasesIncludeCollections } from './helpers'; - -const Div = styled.div` - margin-top: ${({ theme }) => theme.eui.paddingSizes.m}; -`; - -const FlexItemDivider = styled(EuiFlexItem)` - ${({ theme }) => css` - .euiFlexGroup--gutterMedium > &.euiFlexItem { - border-right: ${theme.eui.euiBorderThin}; - padding-right: ${theme.eui.euiSize}; - margin-right: ${theme.eui.euiSize}; - } - `} -`; - -const ProgressLoader = styled(EuiProgress)` - ${({ theme }) => css` - top: 2px; - border-radius: ${theme.eui.euiBorderRadius}; - z-index: ${theme.eui.euiZHeader}; - `} -`; - -const getSortField = (field: string): SortFieldCase => { - if (field === SortFieldCase.createdAt) { - return SortFieldCase.createdAt; - } else if (field === SortFieldCase.closedAt) { - return SortFieldCase.closedAt; - } - return SortFieldCase.createdAt; -}; -const EuiBasicTable: any = _EuiBasicTable; // eslint-disable-line @typescript-eslint/no-explicit-any -const BasicTable = styled(EuiBasicTable)` - ${({ theme }) => ` - .euiTableRow-isExpandedRow.euiTableRow-isSelectable .euiTableCellContent { - padding: 8px 0 8px 32px; - } - - &.isModal .euiTableRow.isDisabled { - cursor: not-allowed; - background-color: ${theme.eui.euiTableHoverClickableColor}; - } - - &.isModal .euiTableRow.euiTableRow-isExpandedRow .euiTableRowCell, - &.isModal .euiTableRow.euiTableRow-isExpandedRow:hover { - background-color: transparent; - } - - &.isModal .euiTableRow.euiTableRow-isExpandedRow { - .subCase:hover { - background-color: ${theme.eui.euiTableHoverClickableColor}; - } - } - `} -`; -BasicTable.displayName = 'BasicTable'; +export interface AllCasesNavProps { + detailName: string; + search?: string; + subCaseId?: string; +} interface AllCasesProps { - onRowClick?: (theCase?: Case | SubCase) => void; - isModal?: boolean; userCanCrud: boolean; - disabledStatuses?: CaseStatuses[]; - disabledCases?: CaseType[]; } -export const AllCases = React.memo<AllCasesProps>( - ({ onRowClick, isModal = false, userCanCrud, disabledStatuses, disabledCases = [] }) => { - const { navigateToApp } = useKibana().services.application; - const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.case); - const { actionLicense } = useGetActionLicense(); - const { - countOpenCases, - countInProgressCases, - countClosedCases, - isLoading: isCasesStatusLoading, - fetchCasesStatus, - } = useGetCasesStatus(); - const { - data, - dispatchUpdateCaseProperty, - filterOptions, - loading, - queryParams, - selectedCases, - refetchCases, - setFilters, - setQueryParams, - setSelectedCases, - } = useGetCases(); - - // Delete case - const { - dispatchResetIsDeleted, - handleOnDeleteConfirm, - handleToggleModal, - isLoading: isDeleting, - isDeleted, - isDisplayConfirmDeleteModal, - } = useDeleteCases(); - - // Update case - const { - dispatchResetIsUpdated, - isLoading: isUpdating, - isUpdated, - updateBulkStatus, - } = useUpdateCases(); - const [deleteThisCase, setDeleteThisCase] = useState<DeleteCase>({ - title: '', - id: '', - type: null, - }); - const [deleteBulk, setDeleteBulk] = useState<DeleteCase[]>([]); - const filterRefetch = useRef<() => void>(); - const setFilterRefetch = useCallback( - (refetchFilter: () => void) => { - filterRefetch.current = refetchFilter; - }, - [filterRefetch] - ); - const refreshCases = useCallback( - (dataRefresh = true) => { - if (dataRefresh) refetchCases(); - fetchCasesStatus(); - setSelectedCases([]); - setDeleteBulk([]); - if (filterRefetch.current != null) { - filterRefetch.current(); - } - }, - [filterRefetch, refetchCases, setSelectedCases, fetchCasesStatus] - ); - - useEffect(() => { - if (isDeleted) { - refreshCases(); - dispatchResetIsDeleted(); - } - if (isUpdated) { - refreshCases(); - dispatchResetIsUpdated(); - } - }, [isDeleted, isUpdated, refreshCases, dispatchResetIsDeleted, dispatchResetIsUpdated]); - const confirmDeleteModal = useMemo( - () => ( - <ConfirmDeleteCaseModal - caseTitle={deleteThisCase.title} - isModalVisible={isDisplayConfirmDeleteModal} - isPlural={deleteBulk.length > 0} - onCancel={handleToggleModal} - onConfirm={handleOnDeleteConfirm.bind( - null, - deleteBulk.length > 0 ? deleteBulk : [deleteThisCase] - )} - /> - ), - [ - deleteBulk, - deleteThisCase, - isDisplayConfirmDeleteModal, - handleToggleModal, - handleOnDeleteConfirm, - ] - ); - - const toggleDeleteModal = useCallback( - (deleteCase: Case) => { - handleToggleModal(); - setDeleteThisCase({ id: deleteCase.id, title: deleteCase.title, type: deleteCase.type }); +export const AllCases = React.memo<AllCasesProps>(({ userCanCrud }) => { + const { + cases: casesUi, + application: { navigateToApp }, + } = useKibana().services; + const history = useHistory(); + const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.case); + + const goToCreateCase = useCallback( + (ev) => { + ev.preventDefault(); + navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { + path: getCreateCaseUrl(urlSearch), + }); + }, + [navigateToApp, urlSearch] + ); + + const goToCaseConfigure = useCallback( + (ev) => { + ev.preventDefault(); + history.push(getConfigureCasesUrl(urlSearch)); + }, + [history, urlSearch] + ); + + return casesUi.getAllCases({ + caseDetailsNavigation: { + href: ({ detailName, subCaseId }: AllCasesNavProps) => { + return formatUrl(getCaseDetailsUrl({ id: detailName, subCaseId })); }, - [handleToggleModal] - ); - - const toggleBulkDeleteModal = useCallback( - (cases: Case[]) => { - handleToggleModal(); - if (cases.length === 1) { - const singleCase = cases[0]; - if (singleCase) { - return setDeleteThisCase({ - id: singleCase.id, - title: singleCase.title, - type: singleCase.type, - }); - } - } - const convertToDeleteCases: DeleteCase[] = cases.map(({ id, title, type }) => ({ - id, - title, - type, - })); - setDeleteBulk(convertToDeleteCases); - }, - [setDeleteBulk, handleToggleModal] - ); - - const handleUpdateCaseStatus = useCallback( - (status: string) => { - updateBulkStatus(selectedCases, status); - }, - [selectedCases, updateBulkStatus] - ); - - const getBulkItemsPopoverContent = useCallback( - (closePopover: () => void) => ( - <EuiContextMenuPanel - data-test-subj="cases-bulk-actions" - items={getBulkItems({ - caseStatus: filterOptions.status, - closePopover, - deleteCasesAction: toggleBulkDeleteModal, - selectedCases, - updateCaseStatus: handleUpdateCaseStatus, - includeCollections: isSelectedCasesIncludeCollections(selectedCases), - })} - /> - ), - [selectedCases, filterOptions.status, toggleBulkDeleteModal, handleUpdateCaseStatus] - ); - const handleDispatchUpdate = useCallback( - (args: Omit<UpdateCase, 'refetchCasesStatus'>) => { - dispatchUpdateCaseProperty({ ...args, refetchCasesStatus: fetchCasesStatus }); - }, - [dispatchUpdateCaseProperty, fetchCasesStatus] - ); - - const goToCreateCase = useCallback( - (ev) => { - ev.preventDefault(); - if (isModal && onRowClick != null) { - onRowClick(); - } else { - navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { - path: getCreateCaseUrl(urlSearch), - }); - } - }, - [navigateToApp, isModal, onRowClick, urlSearch] - ); - - const actions = useMemo( - () => - getActions({ - caseStatus: filterOptions.status, - deleteCaseOnClick: toggleDeleteModal, - dispatchUpdate: handleDispatchUpdate, - }), - [filterOptions.status, toggleDeleteModal, handleDispatchUpdate] - ); - - const actionsErrors = useMemo(() => getActionLicenseError(actionLicense), [actionLicense]); - - const tableOnChangeCallback = useCallback( - ({ page, sort }: EuiBasicTableOnChange) => { - let newQueryParams = queryParams; - if (sort) { - newQueryParams = { - ...newQueryParams, - sortField: getSortField(sort.field), - sortOrder: sort.direction, - }; - } - if (page) { - newQueryParams = { - ...newQueryParams, - page: page.index + 1, - perPage: page.size, - }; - } - setQueryParams(newQueryParams); - refreshCases(false); - }, - [queryParams, refreshCases, setQueryParams] - ); - - const onFilterChangedCallback = useCallback( - (newFilterOptions: Partial<FilterOptions>) => { - if (newFilterOptions.status && newFilterOptions.status === CaseStatuses.closed) { - setQueryParams({ sortField: SortFieldCase.closedAt }); - } else if (newFilterOptions.status && newFilterOptions.status === CaseStatuses.open) { - setQueryParams({ sortField: SortFieldCase.createdAt }); - } else if ( - newFilterOptions.status && - newFilterOptions.status === CaseStatuses['in-progress'] - ) { - setQueryParams({ sortField: SortFieldCase.createdAt }); - } - setFilters(newFilterOptions); - refreshCases(false); - }, - [refreshCases, setQueryParams, setFilters] - ); - - const memoizedGetCasesColumns = useMemo( - () => getCasesColumns(userCanCrud ? actions : [], filterOptions.status, isModal), - [actions, filterOptions.status, userCanCrud, isModal] - ); - - const itemIdToExpandedRowMap = useMemo( - () => - getExpandedRowMap({ - columns: memoizedGetCasesColumns, - data: data.cases, - isModal, - onSubCaseClick: onRowClick, - }), - [data.cases, isModal, memoizedGetCasesColumns, onRowClick] - ); - - const memoizedPagination = useMemo( - () => ({ - pageIndex: queryParams.page - 1, - pageSize: queryParams.perPage, - totalItemCount: data.total, - pageSizeOptions: [5, 10, 15, 20, 25], - }), - [data, queryParams] - ); - - const sorting: EuiTableSortingType<Case> = { - sort: { field: queryParams.sortField, direction: queryParams.sortOrder }, - }; - - const euiBasicTableSelectionProps = useMemo<EuiTableSelectionType<Case>>( - () => ({ - onSelectionChange: setSelectedCases, - selectableMessage: (selectable) => (!selectable ? SELECTABLE_MESSAGE_COLLECTIONS : ''), - }), - [setSelectedCases] - ); - const isCasesLoading = useMemo( - () => loading.indexOf('cases') > -1 || loading.indexOf('caseUpdate') > -1, - [loading] - ); - const isDataEmpty = useMemo(() => data.total === 0, [data]); - - const TableWrap = useMemo(() => (isModal ? 'span' : Panel), [isModal]); - - const tableRowProps = useCallback( - (theCase: Case) => { - const onTableRowClick = memoize(() => { - if (onRowClick) { - onRowClick(theCase); - } + onClick: ({ detailName, subCaseId, search }: AllCasesNavProps) => { + navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { + path: getCaseDetailsUrl({ id: detailName, search, subCaseId }), }); - - return { - 'data-test-subj': `cases-table-row-${theCase.id}`, - className: classnames({ isDisabled: theCase.type === CaseType.collection }), - ...(isModal && theCase.type !== CaseType.collection ? { onClick: onTableRowClick } : {}), - }; }, - [isModal, onRowClick] - ); - - const enableBuckActions = userCanCrud && !isModal; - - return ( - <> - {!isEmpty(actionsErrors) && ( - <CaseCallOut title={ERROR_PUSH_SERVICE_CALLOUT_TITLE} messages={actionsErrors} /> - )} - {!isModal && ( - <CaseHeaderPage title={i18n.PAGE_TITLE}> - <EuiFlexGroup - alignItems="center" - gutterSize="m" - responsive={false} - wrap={true} - data-test-subj="all-cases-header" - > - <EuiFlexItem grow={false}> - <Stats - dataTestSubj="openStatsHeader" - caseCount={countOpenCases} - caseStatus={CaseStatuses.open} - isLoading={isCasesStatusLoading} - /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <Stats - dataTestSubj="inProgressStatsHeader" - caseCount={countInProgressCases} - caseStatus={CaseStatuses['in-progress']} - isLoading={isCasesStatusLoading} - /> - </EuiFlexItem> - <FlexItemDivider grow={false}> - <Stats - dataTestSubj="closedStatsHeader" - caseCount={countClosedCases} - caseStatus={CaseStatuses.closed} - isLoading={isCasesStatusLoading} - /> - </FlexItemDivider> - <EuiFlexItem grow={false}> - <ConfigureCaseButton - label={i18n.CONFIGURE_CASES_BUTTON} - isDisabled={!isEmpty(actionsErrors) || !userCanCrud} - showToolTip={!isEmpty(actionsErrors)} - msgTooltip={!isEmpty(actionsErrors) ? actionsErrors[0].description : <></>} - titleTooltip={!isEmpty(actionsErrors) ? actionsErrors[0].title : ''} - urlSearch={urlSearch} - /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <LinkButton - isDisabled={!userCanCrud} - fill - onClick={goToCreateCase} - href={formatUrl(getCreateCaseUrl())} - iconType="plusInCircle" - data-test-subj="createNewCaseBtn" - > - {i18n.CREATE_TITLE} - </LinkButton> - </EuiFlexItem> - </EuiFlexGroup> - </CaseHeaderPage> - )} - {(isCasesLoading || isDeleting || isUpdating) && !isDataEmpty && ( - <ProgressLoader size="xs" color="accent" className="essentialAnimation" /> - )} - <TableWrap data-test-subj="table-wrap" loading={!isModal ? isCasesLoading : undefined}> - <CasesTableFilters - countClosedCases={data.countClosedCases} - countOpenCases={data.countOpenCases} - countInProgressCases={data.countInProgressCases} - onFilterChanged={onFilterChangedCallback} - initial={{ - search: filterOptions.search, - reporters: filterOptions.reporters, - tags: filterOptions.tags, - status: filterOptions.status, - }} - setFilterRefetch={setFilterRefetch} - disabledStatuses={disabledStatuses} - /> - {isCasesLoading && isDataEmpty ? ( - <Div> - <EuiLoadingContent data-test-subj="initialLoadingPanelAllCases" lines={10} /> - </Div> - ) : ( - <Div> - <UtilityBar border> - <UtilityBarSection> - <UtilityBarGroup> - <UtilityBarText data-test-subj="case-table-case-count"> - {i18n.SHOWING_CASES(data.total ?? 0)} - </UtilityBarText> - </UtilityBarGroup> - {!isModal && ( - <UtilityBarGroup data-test-subj="case-table-utility-bar-actions"> - {enableBuckActions && ( - <UtilityBarText data-test-subj="case-table-selected-case-count"> - {i18n.SHOWING_SELECTED_CASES(selectedCases.length)} - </UtilityBarText> - )} - {enableBuckActions && ( - <UtilityBarAction - data-test-subj="case-table-bulk-actions" - iconSide="right" - iconType="arrowDown" - popoverContent={getBulkItemsPopoverContent} - > - {i18n.BULK_ACTIONS} - </UtilityBarAction> - )} - <UtilityBarAction iconSide="left" iconType="refresh" onClick={refreshCases}> - {i18n.REFRESH} - </UtilityBarAction> - </UtilityBarGroup> - )} - </UtilityBarSection> - </UtilityBar> - <BasicTable - columns={memoizedGetCasesColumns} - data-test-subj="cases-table" - isSelectable={enableBuckActions} - itemId="id" - items={data.cases} - itemIdToExpandedRowMap={itemIdToExpandedRowMap} - noItemsMessage={ - <EuiEmptyPrompt - title={<h3>{i18n.NO_CASES}</h3>} - titleSize="xs" - body={i18n.NO_CASES_BODY} - actions={ - <LinkButton - isDisabled={!userCanCrud} - fill - size="s" - onClick={goToCreateCase} - href={formatUrl(getCreateCaseUrl())} - iconType="plusInCircle" - data-test-subj="cases-table-add-case" - > - {i18n.ADD_NEW_CASE} - </LinkButton> - } - /> - } - onChange={tableOnChangeCallback} - pagination={memoizedPagination} - rowProps={tableRowProps} - selection={enableBuckActions ? euiBasicTableSelectionProps : undefined} - sorting={sorting} - className={classnames({ isModal })} - /> - </Div> - )} - </TableWrap> - {confirmDeleteModal} - </> - ); - } -); + }, + configureCasesNavigation: { + href: formatUrl(getConfigureCasesUrl()), + onClick: goToCaseConfigure, + }, + createCaseNavigation: { + href: formatUrl(getCreateCaseUrl()), + onClick: goToCreateCase, + }, + userCanCrud, + }); +}); AllCases.displayName = 'AllCases'; diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts b/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts deleted file mode 100644 index ad44959ecb1dc..0000000000000 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/translations.ts +++ /dev/null @@ -1,109 +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 { i18n } from '@kbn/i18n'; - -export * from '../../translations'; - -export const NO_CASES = i18n.translate('xpack.securitySolution.cases.caseTable.noCases.title', { - defaultMessage: 'No Cases', -}); -export const NO_CASES_BODY = i18n.translate('xpack.securitySolution.cases.caseTable.noCases.body', { - defaultMessage: - 'There are no cases to display. Please create a new case or change your filter settings above.', -}); - -export const ADD_NEW_CASE = i18n.translate('xpack.securitySolution.cases.caseTable.addNewCase', { - defaultMessage: 'Add New Case', -}); - -export const SHOWING_SELECTED_CASES = (totalRules: number) => - i18n.translate('xpack.securitySolution.cases.caseTable.selectedCasesTitle', { - values: { totalRules }, - defaultMessage: 'Selected {totalRules} {totalRules, plural, =1 {case} other {cases}}', - }); - -export const SHOWING_CASES = (totalRules: number) => - i18n.translate('xpack.securitySolution.cases.caseTable.showingCasesTitle', { - values: { totalRules }, - defaultMessage: 'Showing {totalRules} {totalRules, plural, =1 {case} other {cases}}', - }); - -export const UNIT = (totalCount: number) => - i18n.translate('xpack.securitySolution.cases.caseTable.unit', { - values: { totalCount }, - defaultMessage: `{totalCount, plural, =1 {case} other {cases}}`, - }); - -export const SEARCH_CASES = i18n.translate( - 'xpack.securitySolution.cases.caseTable.searchAriaLabel', - { - defaultMessage: 'Search cases', - } -); - -export const BULK_ACTIONS = i18n.translate('xpack.securitySolution.cases.caseTable.bulkActions', { - defaultMessage: 'Bulk actions', -}); - -export const EXTERNAL_INCIDENT = i18n.translate( - 'xpack.securitySolution.cases.caseTable.snIncident', - { - defaultMessage: 'External Incident', - } -); - -export const INCIDENT_MANAGEMENT_SYSTEM = i18n.translate( - 'xpack.securitySolution.cases.caseTable.incidentSystem', - { - defaultMessage: 'Incident Management System', - } -); - -export const SEARCH_PLACEHOLDER = i18n.translate( - 'xpack.securitySolution.cases.caseTable.searchPlaceholder', - { - defaultMessage: 'e.g. case name', - } -); - -export const CLOSED = i18n.translate('xpack.securitySolution.cases.caseTable.closed', { - defaultMessage: 'Closed', -}); - -export const DELETE = i18n.translate('xpack.securitySolution.cases.caseTable.delete', { - defaultMessage: 'Delete', -}); - -export const REQUIRES_UPDATE = i18n.translate( - 'xpack.securitySolution.cases.caseTable.requiresUpdate', - { - defaultMessage: ' requires update', - } -); - -export const UP_TO_DATE = i18n.translate('xpack.securitySolution.cases.caseTable.upToDate', { - defaultMessage: ' is up to date', -}); -export const NOT_PUSHED = i18n.translate('xpack.securitySolution.cases.caseTable.notPushed', { - defaultMessage: 'Not pushed', -}); - -export const REFRESH = i18n.translate('xpack.securitySolution.cases.caseTable.refreshTitle', { - defaultMessage: 'Refresh', -}); - -export const SERVICENOW_LINK_ARIA = i18n.translate( - 'xpack.securitySolution.cases.caseTable.serviceNowLinkAria', - { - defaultMessage: 'click to view the incident on servicenow', - } -); - -export const STATUS = i18n.translate('xpack.securitySolution.cases.caseTable.status', { - defaultMessage: 'Status', -}); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.test.tsx index 18a76e2766d8d..5eb0a03fc5db7 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.test.tsx @@ -5,57 +5,9 @@ * 2.0. */ -import { AssociationType, CommentType } from '../../../../../cases/common/api'; -import { Comment } from '../../containers/types'; - -import { getManualAlertIdsWithNoRuleId, buildAlertsQuery } from './helpers'; - -const comments: Comment[] = [ - { - associationType: AssociationType.case, - type: CommentType.alert, - alertId: 'alert-id-1', - index: 'alert-index-1', - id: 'comment-id', - createdAt: '2020-02-19T23:06:33.798Z', - createdBy: { username: 'elastic' }, - rule: { - id: null, - name: null, - }, - pushedAt: null, - pushedBy: null, - updatedAt: null, - updatedBy: null, - version: 'WzQ3LDFc', - }, - { - associationType: AssociationType.case, - type: CommentType.alert, - alertId: 'alert-id-2', - index: 'alert-index-2', - id: 'comment-id', - createdAt: '2020-02-19T23:06:33.798Z', - createdBy: { username: 'elastic' }, - pushedAt: null, - pushedBy: null, - rule: { - id: 'rule-id-2', - name: 'rule-name-2', - }, - updatedAt: null, - updatedBy: null, - version: 'WzQ3LDFc', - }, -]; +import { buildAlertsQuery } from './helpers'; describe('Case view helpers', () => { - describe('getAlertIdsFromComments', () => { - it('it returns the alert id from the comments where rule is not defined', () => { - expect(getManualAlertIdsWithNoRuleId(comments)).toEqual(['alert-id-1']); - }); - }); - describe('buildAlertsQuery', () => { it('it builds the alerts query', () => { expect(buildAlertsQuery(['alert-id-1', 'alert-id-2'])).toEqual({ diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts index 7211f4bca6a37..336cf20ffb3b8 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/helpers.ts @@ -5,21 +5,12 @@ * 2.0. */ -import { isEmpty } from 'lodash'; -import { CommentType } from '../../../../../cases/common/api'; -import { Comment } from '../../containers/types'; - -export const getManualAlertIdsWithNoRuleId = (comments: Comment[]): string[] => { - const dedupeAlerts = comments.reduce((alertIds, comment: Comment) => { - if (comment.type === CommentType.alert && isEmpty(comment.rule.id)) { - const ids = Array.isArray(comment.alertId) ? comment.alertId : [comment.alertId]; - ids.forEach((id) => alertIds.add(id)); - return alertIds; - } - return alertIds; - }, new Set<string>()); - return [...dedupeAlerts]; -}; +import { isObject, get, isString, isNumber } from 'lodash'; +import { useMemo } from 'react'; +import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; +import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query'; +import { Ecs } from '../../../../../cases/common'; // TODO we need to allow -> docValueFields: [{ field: "@timestamp" }], export const buildAlertsQuery = (alertIds: string[]) => { @@ -39,3 +30,102 @@ export const buildAlertsQuery = (alertIds: string[]) => { size: 10000, }; }; + +export const toStringArray = (value: unknown): string[] => { + if (Array.isArray(value)) { + return value.reduce<string[]>((acc, v) => { + if (v != null) { + switch (typeof v) { + case 'number': + case 'boolean': + return [...acc, v.toString()]; + case 'object': + try { + return [...acc, JSON.stringify(v)]; + } catch { + return [...acc, 'Invalid Object']; + } + case 'string': + return [...acc, v]; + default: + return [...acc, `${v}`]; + } + } + return acc; + }, []); + } else if (value == null) { + return []; + } else if (!Array.isArray(value) && typeof value === 'object') { + try { + return [JSON.stringify(value)]; + } catch { + return ['Invalid Object']; + } + } else { + return [`${value}`]; + } +}; + +export const formatAlertToEcsSignal = (alert: {}): Ecs => + Object.keys(alert).reduce<Ecs>((accumulator, key) => { + const item = get(alert, key); + if (item != null && isObject(item)) { + return { ...accumulator, [key]: formatAlertToEcsSignal(item) }; + } else if (Array.isArray(item) || isString(item) || isNumber(item)) { + return { ...accumulator, [key]: toStringArray(item) }; + } + return accumulator; + }, {} as Ecs); +interface Signal { + rule: { + id: string; + name: string; + to: string; + from: string; + }; +} + +interface SignalHit { + _id: string; + _index: string; + _source: { + '@timestamp': string; + signal: Signal; + }; +} + +export interface Alert { + _id: string; + _index: string; + '@timestamp': string; + signal: Signal; + [key: string]: unknown; +} +export const useFetchAlertData = (alertIds: string[]): [boolean, Record<string, Ecs>] => { + const { selectedPatterns } = useSourcererScope(SourcererScopeName.detections); + const alertsQuery = useMemo(() => buildAlertsQuery(alertIds), [alertIds]); + + const { loading: isLoadingAlerts, data: alertsData } = useQueryAlerts<SignalHit, unknown>( + alertsQuery, + selectedPatterns[0] + ); + + const alerts = useMemo( + () => + alertsData?.hits.hits.reduce<Record<string, Ecs>>( + (acc, { _id, _index, _source }) => ({ + ...acc, + [_id]: { + ...formatAlertToEcsSignal(_source), + _id, + _index, + timestamp: _source['@timestamp'], + }, + }), + {} + ) ?? {}, + [alertsData?.hits.hits] + ); + + return [isLoadingAlerts, alerts]; +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 892663c783293..b0f3ccb8c21ad 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -5,51 +5,37 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; +import React, { useCallback, useState } from 'react'; import { useDispatch } from 'react-redux'; -import styled from 'styled-components'; -import { isEmpty } from 'lodash/fp'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiLoadingContent, - EuiLoadingSpinner, - EuiHorizontalRule, -} from '@elastic/eui'; +import { useHistory } from 'react-router-dom'; +import { SearchResponse } from 'elasticsearch'; +import { isEmpty } from 'lodash'; -import { CaseStatuses, CaseAttributes, CaseType } from '../../../../../cases/common/api'; -import { Case, CaseConnector } from '../../containers/types'; -import { getCaseDetailsUrl, getCaseUrl, useFormatUrl } from '../../../common/components/link_to'; -import { gutterTimeline } from '../../../common/lib/helpers'; -import { HeaderPage } from '../../../common/components/header_page'; -import { EditableTitle } from '../../../common/components/header_page/editable_title'; -import { TagList } from '../tag_list'; -import { useGetCase } from '../../containers/use_get_case'; -import { UserActionTree } from '../user_action_tree'; -import { UserList } from '../user_list'; -import { useUpdateCase } from '../../containers/use_update_case'; -import { getTypedPayload } from '../../containers/utils'; -import { WhitePageWrapper, HeaderWrapper } from '../wrappers'; -import { CaseActionBar } from '../case_action_bar'; -import { SpyRoute } from '../../../common/utils/route/spy_routes'; -import { useGetCaseUserActions } from '../../containers/use_get_case_user_actions'; -import { usePushToService } from '../use_push_to_service'; -import { EditConnector } from '../edit_connector'; -import { useConnectors } from '../../containers/configure/use_connectors'; -import { SecurityPageName } from '../../../app/types'; import { - getConnectorById, - normalizeActionConnector, - getNoneConnector, -} from '../configure_cases/utils'; -import { DetailsPanel } from '../../../timelines/components/side_panel'; -import { useSourcererScope } from '../../../common/containers/sourcerer'; -import { SourcererScopeName } from '../../../common/store/sourcerer/model'; + getCaseDetailsUrl, + getCaseDetailsUrlWithCommentId, + getCaseUrl, + getConfigureCasesUrl, + getRuleDetailsUrl, + useFormatUrl, +} from '../../../common/components/link_to'; +import { Ecs } from '../../../../common/ecs'; +import { Case } from '../../../../../cases/common'; +import { TimelineNonEcsData } from '../../../../common/search_strategy'; import { TimelineId } from '../../../../common/types/timeline'; +import { SecurityPageName } from '../../../app/types'; +import { KibanaServices, useKibana } from '../../../common/lib/kibana'; +import { APP_ID, DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../common/constants'; import { timelineActions } from '../../../timelines/store/timeline'; -import { StatusActionButton } from '../status/button'; - -import * as i18n from './translations'; +import { useSourcererScope } from '../../../common/containers/sourcerer'; +import { SourcererScopeName } from '../../../common/store/sourcerer/model'; +import { DetailsPanel } from '../../../timelines/components/side_panel'; +import { InvestigateInTimelineAction } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_timeline_action'; +import { buildAlertsQuery, formatAlertToEcsSignal, useFetchAlertData } from './helpers'; +import { SEND_ALERT_TO_TIMELINE } from './translations'; +import { useInsertTimeline } from '../use_insert_timeline'; +import { SpyRoute } from '../../../common/utils/route/spy_routes'; +import * as timelineMarkdownPlugin from '../../../common/components/markdown_editor/plugins/timeline'; interface Props { caseId: string; @@ -64,449 +50,207 @@ export interface OnUpdateFields { onError?: () => void; } -const MyWrapper = styled.div` - padding: ${({ theme }) => - `${theme.eui.paddingSizes.l} ${theme.eui.paddingSizes.l} ${gutterTimeline} ${theme.eui.paddingSizes.l}`}; -`; - -const MyEuiFlexGroup = styled(EuiFlexGroup)` - height: 100%; -`; - -const MyEuiHorizontalRule = styled(EuiHorizontalRule)` - margin-left: 48px; - &.euiHorizontalRule--full { - width: calc(100% - 48px); - } -`; - export interface CaseProps extends Props { fetchCase: () => void; caseData: Case; updateCase: (newCase: Case) => void; } -export const CaseComponent = React.memo<CaseProps>( - ({ caseId, caseData, fetchCase, subCaseId, updateCase, userCanCrud }) => { - const dispatch = useDispatch(); - const { formatUrl, search } = useFormatUrl(SecurityPageName.case); - const allCasesLink = getCaseUrl(search); - const caseDetailsLink = formatUrl(getCaseDetailsUrl({ id: caseId }), { absolute: true }); - const [initLoadingData, setInitLoadingData] = useState(true); - const init = useRef(true); - - const { - caseUserActions, - fetchCaseUserActions, - caseServices, - hasDataToPush, - isLoading: isLoadingUserActions, - participants, - } = useGetCaseUserActions(caseId, caseData.connector.id, subCaseId); +const TimelineDetailsPanel = () => { + const { browserFields, docValueFields } = useSourcererScope(SourcererScopeName.detections); - const { isLoading, updateKey, updateCaseProperty } = useUpdateCase({ - caseId, - subCaseId, - }); - - /** - * For the future developer: useSourcererScope is security solution dependent. - * You can use useSignalIndex as an alternative. - */ - const { browserFields, docValueFields } = useSourcererScope(SourcererScopeName.detections); - - // Update Fields - const onUpdateField = useCallback( - ({ key, value, onSuccess, onError }: OnUpdateFields) => { - const handleUpdateNewCase = (newCase: Case) => - updateCase({ ...newCase, comments: caseData.comments }); - switch (key) { - case 'title': - const titleUpdate = getTypedPayload<string>(value); - if (titleUpdate.length > 0) { - updateCaseProperty({ - fetchCaseUserActions, - updateKey: 'title', - updateValue: titleUpdate, - updateCase: handleUpdateNewCase, - caseData, - onSuccess, - onError, - }); - } - break; - case 'connector': - const connector = getTypedPayload<CaseConnector>(value); - if (connector != null) { - updateCaseProperty({ - fetchCaseUserActions, - updateKey: 'connector', - updateValue: connector, - updateCase: handleUpdateNewCase, - caseData, - onSuccess, - onError, - }); - } - break; - case 'description': - const descriptionUpdate = getTypedPayload<string>(value); - if (descriptionUpdate.length > 0) { - updateCaseProperty({ - fetchCaseUserActions, - updateKey: 'description', - updateValue: descriptionUpdate, - updateCase: handleUpdateNewCase, - caseData, - onSuccess, - onError, - }); - } - break; - case 'tags': - const tagsUpdate = getTypedPayload<string[]>(value); - updateCaseProperty({ - fetchCaseUserActions, - updateKey: 'tags', - updateValue: tagsUpdate, - updateCase: handleUpdateNewCase, - caseData, - onSuccess, - onError, - }); - break; - case 'status': - const statusUpdate = getTypedPayload<CaseStatuses>(value); - if (caseData.status !== value) { - updateCaseProperty({ - fetchCaseUserActions, - updateKey: 'status', - updateValue: statusUpdate, - updateCase: handleUpdateNewCase, - caseData, - onSuccess, - onError, - }); - } - break; - case 'settings': - const settingsUpdate = getTypedPayload<CaseAttributes['settings']>(value); - if (caseData.settings !== value) { - updateCaseProperty({ - fetchCaseUserActions, - updateKey: 'settings', - updateValue: settingsUpdate, - updateCase: handleUpdateNewCase, - caseData, - onSuccess, - onError, - }); - } - break; - default: - return null; - } - }, - [fetchCaseUserActions, updateCaseProperty, updateCase, caseData] - ); - - const handleUpdateCase = useCallback( - (newCase: Case) => { - updateCase(newCase); - fetchCaseUserActions(caseId, newCase.connector.id, subCaseId); - }, - [updateCase, fetchCaseUserActions, caseId, subCaseId] - ); - - const { loading: isLoadingConnectors, connectors } = useConnectors(); - - const [connectorName, isValidConnector] = useMemo(() => { - const connector = connectors.find((c) => c.id === caseData.connector.id); - return [connector?.name ?? '', !!connector]; - }, [connectors, caseData.connector]); - - const currentExternalIncident = useMemo( - () => - caseServices != null && caseServices[caseData.connector.id] != null - ? caseServices[caseData.connector.id] - : null, - [caseServices, caseData.connector] - ); - - const { pushButton, pushCallouts } = usePushToService({ - connector: { - ...caseData.connector, - name: isEmpty(connectorName) ? caseData.connector.name : connectorName, - }, - caseServices, - caseId: caseData.id, - caseStatus: caseData.status, - connectors, - updateCase: handleUpdateCase, - userCanCrud, - isValidConnector: isLoadingConnectors ? true : isValidConnector, + return ( + <DetailsPanel + browserFields={browserFields} + docValueFields={docValueFields} + isFlyoutView + timelineId={TimelineId.casePage} + /> + ); +}; + +const InvestigateInTimelineActionComponent = (alertIds: string[]) => { + const EMPTY_ARRAY: TimelineNonEcsData[] = []; + const fetchEcsAlertsData = async (fetchAlertIds?: string[]): Promise<Ecs[]> => { + if (isEmpty(fetchAlertIds)) { + return []; + } + const alertResponse = await KibanaServices.get().http.fetch< + SearchResponse<{ '@timestamp': string; [key: string]: unknown }> + >(DETECTION_ENGINE_QUERY_SIGNALS_URL, { + method: 'POST', + body: JSON.stringify(buildAlertsQuery(fetchAlertIds ?? [])), }); - - const onSubmitConnector = useCallback( - (connectorId, connectorFields, onError, onSuccess) => { - const connector = getConnectorById(connectorId, connectors); - const connectorToUpdate = connector - ? normalizeActionConnector(connector) - : getNoneConnector(); - - onUpdateField({ - key: 'connector', - value: { ...connectorToUpdate, fields: connectorFields }, - onSuccess, - onError, - }); - }, - [onUpdateField, connectors] - ); - - const onSubmitTags = useCallback((newTags) => onUpdateField({ key: 'tags', value: newTags }), [ - onUpdateField, - ]); - - const onSubmitTitle = useCallback( - (newTitle) => onUpdateField({ key: 'title', value: newTitle }), - [onUpdateField] - ); - - const changeStatus = useCallback( - (status: CaseStatuses) => - onUpdateField({ - key: 'status', - value: status, - }), - [onUpdateField] + return ( + alertResponse?.hits.hits.reduce<Ecs[]>( + (acc, { _id, _index, _source }) => [ + ...acc, + { + ...formatAlertToEcsSignal(_source as {}), + _id, + _index, + timestamp: _source['@timestamp'], + }, + ], + [] + ) ?? [] ); + }; - const handleRefresh = useCallback(() => { - fetchCaseUserActions(caseId, caseData.connector.id, subCaseId); - fetchCase(); - }, [caseData.connector.id, caseId, fetchCase, fetchCaseUserActions, subCaseId]); - - const spyState = useMemo(() => ({ caseTitle: caseData.title }), [caseData.title]); - - const emailContent = useMemo( - () => ({ - subject: i18n.EMAIL_SUBJECT(caseData.title), - body: i18n.EMAIL_BODY(caseDetailsLink), - }), - [caseDetailsLink, caseData.title] - ); + return ( + <InvestigateInTimelineAction + ariaLabel={SEND_ALERT_TO_TIMELINE} + alertIds={alertIds} + key="investigate-in-timeline" + ecsRowData={null} + fetchEcsAlertsData={fetchEcsAlertsData} + nonEcsRowData={EMPTY_ARRAY} + /> + ); +}; - useEffect(() => { - if (initLoadingData && !isLoadingUserActions) { - setInitLoadingData(false); +export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) => { + const [spyState, setSpyState] = useState<{ caseTitle: string | undefined }>({ + caseTitle: undefined, + }); + + const onCaseDataSuccess = useCallback( + (data: Case) => { + if (spyState.caseTitle === undefined) { + setSpyState({ caseTitle: data.title }); } - }, [initLoadingData, isLoadingUserActions]); + }, + [spyState.caseTitle] + ); - const backOptions = useMemo( - () => ({ - href: allCasesLink, - text: i18n.BACK_TO_ALL, - dataTestSubj: 'backToCases', - pageId: SecurityPageName.case, - }), - [allCasesLink] - ); + const { + cases: casesUi, + application: { navigateToApp }, + } = useKibana().services; + const history = useHistory(); + const dispatch = useDispatch(); + const { formatUrl, search } = useFormatUrl(SecurityPageName.case); + const { formatUrl: detectionsFormatUrl, search: detectionsUrlSearch } = useFormatUrl( + SecurityPageName.detections + ); - const showAlert = useCallback( - (alertId: string, index: string) => { - dispatch( - timelineActions.toggleDetailPanel({ - panelView: 'eventDetail', - timelineId: TimelineId.casePage, - params: { - eventId: alertId, - indexName: index, - }, - }) - ); - }, - [dispatch] - ); + const allCasesLink = getCaseUrl(search); + const formattedAllCasesLink = formatUrl(allCasesLink); + const backToAllCasesOnClick = useCallback( + (ev) => { + ev.preventDefault(); + history.push(allCasesLink); + }, + [allCasesLink, history] + ); + const caseDetailsLink = formatUrl(getCaseDetailsUrl({ id: caseId }), { absolute: true }); + const getCaseDetailHrefWithCommentId = (commentId: string) => { + return formatUrl(getCaseDetailsUrlWithCommentId({ id: caseId, commentId, subCaseId }), { + absolute: true, + }); + }; + + const configureCasesHref = formatUrl(getConfigureCasesUrl()); + const onConfigureCasesNavClick = useCallback( + (ev) => { + ev.preventDefault(); + history.push(getConfigureCasesUrl(search)); + }, + [history, search] + ); - // useEffect used for component's initialization - useEffect(() => { - if (init.current) { - init.current = false; - // We need to create a timeline to show the details view - dispatch( - timelineActions.createTimeline({ - id: TimelineId.casePage, - columns: [], - indexNames: [], - expandedDetail: {}, - show: false, - }) - ); - } - }, [dispatch]); + const onDetectionsRuleDetailsClick = useCallback( + (ruleId: string | null | undefined) => { + navigateToApp(`${APP_ID}:${SecurityPageName.detections}`, { + path: getRuleDetailsUrl(ruleId ?? ''), + }); + }, + [navigateToApp] + ); - return ( - <> - <HeaderWrapper> - <HeaderPage - backOptions={backOptions} - data-test-subj="case-view-title" - hideSourcerer={true} - titleNode={ - <EditableTitle - disabled={!userCanCrud} - isLoading={isLoading && updateKey === 'title'} - title={caseData.title} - onSubmit={onSubmitTitle} - /> - } - title={caseData.title} - > - <CaseActionBar - currentExternalIncident={currentExternalIncident} - caseData={caseData} - disabled={!userCanCrud} - isLoading={isLoading && (updateKey === 'status' || updateKey === 'settings')} - onRefresh={handleRefresh} - onUpdateField={onUpdateField} - /> - </HeaderPage> - </HeaderWrapper> - <WhitePageWrapper> - <MyWrapper> - {!initLoadingData && pushCallouts != null && pushCallouts} - <EuiFlexGroup> - <EuiFlexItem grow={6}> - {initLoadingData && ( - <EuiLoadingContent lines={8} data-test-subj="case-view-loading-content" /> - )} - {!initLoadingData && ( - <> - <UserActionTree - caseServices={caseServices} - caseUserActions={caseUserActions} - connectors={connectors} - data={caseData} - fetchUserActions={fetchCaseUserActions.bind( - null, - caseId, - caseData.connector.id, - subCaseId - )} - isLoadingDescription={isLoading && updateKey === 'description'} - isLoadingUserActions={isLoadingUserActions} - onShowAlertDetails={showAlert} - onUpdateField={onUpdateField} - updateCase={updateCase} - userCanCrud={userCanCrud} - /> - {(caseData.type !== CaseType.collection || hasDataToPush) && ( - <> - <MyEuiHorizontalRule - margin="s" - data-test-subj="case-view-bottom-actions-horizontal-rule" - /> - <EuiFlexGroup alignItems="center" gutterSize="s" justifyContent="flexEnd"> - {caseData.type !== CaseType.collection && ( - <EuiFlexItem grow={false}> - <StatusActionButton - status={caseData.status} - onStatusChanged={changeStatus} - disabled={!userCanCrud} - isLoading={isLoading && updateKey === 'status'} - /> - </EuiFlexItem> - )} - {hasDataToPush && ( - <EuiFlexItem data-test-subj="has-data-to-push-button" grow={false}> - {pushButton} - </EuiFlexItem> - )} - </EuiFlexGroup> - </> - )} - </> - )} - </EuiFlexItem> - <EuiFlexItem grow={2}> - <UserList - data-test-subj="case-view-user-list-reporter" - email={emailContent} - headline={i18n.REPORTER} - users={[caseData.createdBy]} - /> - <UserList - data-test-subj="case-view-user-list-participants" - email={emailContent} - headline={i18n.PARTICIPANTS} - loading={isLoadingUserActions} - users={participants} - /> - <TagList - data-test-subj="case-view-tag-list" - disabled={!userCanCrud} - tags={caseData.tags} - onSubmit={onSubmitTags} - isLoading={isLoading && updateKey === 'tags'} - /> - <EditConnector - caseFields={caseData.connector.fields} - connectors={connectors} - disabled={!userCanCrud} - hideConnectorServiceNowSir={ - subCaseId != null || caseData.type === CaseType.collection - } - isLoading={isLoadingConnectors || (isLoading && updateKey === 'connector')} - onSubmit={onSubmitConnector} - selectedConnector={caseData.connector.id} - userActions={caseUserActions} - /> - </EuiFlexItem> - </EuiFlexGroup> - </MyWrapper> - </WhitePageWrapper> - <DetailsPanel - browserFields={browserFields} - docValueFields={docValueFields} - isFlyoutView - timelineId={TimelineId.casePage} - /> - <SpyRoute state={spyState} pageName={SecurityPageName.case} /> - </> - ); - } -); + const getDetectionsRuleDetailsHref = useCallback( + (ruleId) => { + return detectionsFormatUrl(getRuleDetailsUrl(ruleId ?? '', detectionsUrlSearch)); + }, + [detectionsFormatUrl, detectionsUrlSearch] + ); -export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) => { - const { data, isLoading, isError, fetchCase, updateCase } = useGetCase(caseId, subCaseId); - if (isError) { - return null; - } + const showAlertDetails = useCallback( + (alertId: string, index: string) => { + dispatch( + timelineActions.toggleDetailPanel({ + panelView: 'eventDetail', + timelineId: TimelineId.casePage, + params: { + eventId: alertId, + indexName: index, + }, + }) + ); + }, + [dispatch] + ); - if (isLoading) { - return ( - <MyEuiFlexGroup gutterSize="none" justifyContent="center" alignItems="center"> - <EuiFlexItem grow={false}> - <EuiLoadingSpinner data-test-subj="case-view-loading" size="xl" /> - </EuiFlexItem> - </MyEuiFlexGroup> + const onComponentInitialized = useCallback(() => { + dispatch( + timelineActions.createTimeline({ + id: TimelineId.casePage, + columns: [], + indexNames: [], + expandedDetail: {}, + show: false, + }) ); - } - + }, [dispatch]); return ( - data && ( - <CaseComponent - caseId={caseId} - subCaseId={subCaseId} - fetchCase={fetchCase} - caseData={data} - updateCase={updateCase} - userCanCrud={userCanCrud} - /> - ) + <> + {casesUi.getCaseView({ + allCasesNavigation: { + href: formattedAllCasesLink, + onClick: backToAllCasesOnClick, + }, + caseDetailsNavigation: { + href: caseDetailsLink, + onClick: () => { + navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { + path: getCaseDetailsUrl({ id: caseId }), + }); + }, + }, + caseId, + configureCasesNavigation: { + href: configureCasesHref, + onClick: onConfigureCasesNavClick, + }, + getCaseDetailHrefWithCommentId, + onCaseDataSuccess, + onComponentInitialized, + ruleDetailsNavigation: { + href: getDetectionsRuleDetailsHref, + onClick: onDetectionsRuleDetailsClick, + }, + showAlertDetails, + subCaseId, + timelineIntegration: { + editor_plugins: { + parsingPlugin: timelineMarkdownPlugin.parser, + processingPluginRenderer: timelineMarkdownPlugin.renderer, + uiPlugin: timelineMarkdownPlugin.plugin, + }, + hooks: { + useInsertTimeline, + }, + ui: { + renderInvestigateInTimelineActionComponent: InvestigateInTimelineActionComponent, + renderTimelineDetailsPanel: TimelineDetailsPanel, + }, + }, + useFetchAlertData, + userCanCrud, + })} + <SpyRoute state={spyState} pageName={SecurityPageName.case} /> + </> ); }); -CaseComponent.displayName = 'CaseComponent'; CaseView.displayName = 'CaseView'; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts b/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts index f4403a43af697..d7b66bbac38df 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/translations.ts @@ -6,152 +6,9 @@ */ import { i18n } from '@kbn/i18n'; - -export * from '../../translations'; - -export const SHOWING_CASES = (actionDate: string, actionName: string, userName: string) => - i18n.translate('xpack.securitySolution.cases.caseView.actionHeadline', { - values: { - actionDate, - actionName, - userName, - }, - defaultMessage: '{userName} {actionName} on {actionDate}', - }); - -export const ADDED_FIELD = i18n.translate( - 'xpack.securitySolution.cases.caseView.actionLabel.addedField', - { - defaultMessage: 'added', - } -); - -export const CHANGED_FIELD = i18n.translate( - 'xpack.securitySolution.cases.caseView.actionLabel.changededField', - { - defaultMessage: 'changed', - } -); - -export const SELECTED_THIRD_PARTY = (thirdParty: string) => - i18n.translate('xpack.securitySolution.cases.caseView.actionLabel.selectedThirdParty', { - values: { - thirdParty, - }, - defaultMessage: 'selected { thirdParty } as incident management system', - }); - -export const REMOVED_THIRD_PARTY = i18n.translate( - 'xpack.securitySolution.cases.caseView.actionLabel.removedThirdParty', - { - defaultMessage: 'removed external incident management system', - } -); - -export const EDITED_FIELD = i18n.translate( - 'xpack.securitySolution.cases.caseView.actionLabel.editedField', - { - defaultMessage: 'edited', - } -); - -export const REMOVED_FIELD = i18n.translate( - 'xpack.securitySolution.cases.caseView.actionLabel.removedField', - { - defaultMessage: 'removed', - } -); - -export const VIEW_INCIDENT = (incidentNumber: string) => - i18n.translate('xpack.securitySolution.cases.caseView.actionLabel.viewIncident', { - defaultMessage: 'View {incidentNumber}', - values: { - incidentNumber, - }, - }); - -export const PUSHED_NEW_INCIDENT = i18n.translate( - 'xpack.securitySolution.cases.caseView.actionLabel.pushedNewIncident', +export const SEND_ALERT_TO_TIMELINE = i18n.translate( + 'xpack.securitySolution.cases.caseView.sendAlertToTimelineTooltip', { - defaultMessage: 'pushed as new incident', + defaultMessage: 'Investigate in timeline', } ); - -export const UPDATE_INCIDENT = i18n.translate( - 'xpack.securitySolution.cases.caseView.actionLabel.updateIncident', - { - defaultMessage: 'updated incident', - } -); - -export const ADDED_DESCRIPTION = i18n.translate( - 'xpack.securitySolution.cases.caseView.actionLabel.addDescription', - { - defaultMessage: 'added description', - } -); - -export const EDIT_DESCRIPTION = i18n.translate( - 'xpack.securitySolution.cases.caseView.edit.description', - { - defaultMessage: 'Edit description', - } -); - -export const QUOTE = i18n.translate('xpack.securitySolution.cases.caseView.edit.quote', { - defaultMessage: 'Quote', -}); - -export const EDIT_COMMENT = i18n.translate('xpack.securitySolution.cases.caseView.edit.comment', { - defaultMessage: 'Edit comment', -}); - -export const ON = i18n.translate('xpack.securitySolution.cases.caseView.actionLabel.on', { - defaultMessage: 'on', -}); - -export const ADDED_COMMENT = i18n.translate( - 'xpack.securitySolution.cases.caseView.actionLabel.addComment', - { - defaultMessage: 'added comment', - } -); - -export const STATUS = i18n.translate('xpack.securitySolution.cases.caseView.statusLabel', { - defaultMessage: 'Status', -}); - -export const CASE = i18n.translate('xpack.securitySolution.cases.caseView.case', { - defaultMessage: 'case', -}); - -export const COMMENT = i18n.translate('xpack.securitySolution.cases.caseView.comment', { - defaultMessage: 'comment', -}); - -export const CASE_REFRESH = i18n.translate('xpack.securitySolution.cases.caseView.caseRefresh', { - defaultMessage: 'Refresh case', -}); - -export const EMAIL_SUBJECT = (caseTitle: string) => - i18n.translate('xpack.securitySolution.cases.caseView.emailSubject', { - values: { caseTitle }, - defaultMessage: 'Security Case - {caseTitle}', - }); - -export const EMAIL_BODY = (caseUrl: string) => - i18n.translate('xpack.securitySolution.cases.caseView.emailBody', { - values: { caseUrl }, - defaultMessage: 'Case reference: {caseUrl}', - }); - -export const CHANGED_CONNECTOR_FIELD = i18n.translate( - 'xpack.securitySolution.cases.caseView.fieldChanged', - { - defaultMessage: `changed connector field`, - } -); - -export const SYNC_ALERTS = i18n.translate('xpack.securitySolution.cases.caseView.syncAlertsLabel', { - defaultMessage: `Sync alerts`, -}); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/translations.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/translations.ts deleted file mode 100644 index 77c263385df0a..0000000000000 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/servicenow/translations.ts +++ /dev/null @@ -1,99 +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 { i18n } from '@kbn/i18n'; - -export const URGENCY = i18n.translate( - 'xpack.securitySolution.components.connectors.serviceNow.urgencySelectFieldLabel', - { - defaultMessage: 'Urgency', - } -); - -export const SEVERITY = i18n.translate( - 'xpack.securitySolution.components.connectors.serviceNow.severitySelectFieldLabel', - { - defaultMessage: 'Severity', - } -); - -export const IMPACT = i18n.translate( - 'xpack.securitySolution.components.connectors.serviceNow.impactSelectFieldLabel', - { - defaultMessage: 'Impact', - } -); - -export const CHOICES_API_ERROR = i18n.translate( - 'xpack.securitySolution.components.connectors.serviceNow.unableToGetChoicesMessage', - { - defaultMessage: 'Unable to get choices', - } -); - -export const MALWARE_URL = i18n.translate( - 'xpack.securitySolution.components.connectors.serviceNow.malwareURLTitle', - { - defaultMessage: 'Malware URL', - } -); - -export const MALWARE_HASH = i18n.translate( - 'xpack.securitySolution.components.connectors.serviceNow.malwareHashTitle', - { - defaultMessage: 'Malware Hash', - } -); - -export const CATEGORY = i18n.translate( - 'xpack.securitySolution.components.connectors.serviceNow.categoryTitle', - { - defaultMessage: 'Category', - } -); - -export const SUBCATEGORY = i18n.translate( - 'xpack.securitySolution.components.connectors.serviceNow.subcategoryTitle', - { - defaultMessage: 'Subcategory', - } -); - -export const SOURCE_IP = i18n.translate( - 'xpack.securitySolution.components.connectors.serviceNow.sourceIPTitle', - { - defaultMessage: 'Source IP', - } -); - -export const DEST_IP = i18n.translate( - 'xpack.securitySolution.components.connectors.serviceNow.destinationIPTitle', - { - defaultMessage: 'Destination IP', - } -); - -export const PRIORITY = i18n.translate( - 'xpack.securitySolution.components.connectors.serviceNow.prioritySelectFieldTitle', - { - defaultMessage: 'Priority', - } -); - -export const ALERT_FIELDS_LABEL = i18n.translate( - 'xpack.securitySolution.components.connectors.serviceNow.alertFieldsTitle', - { - defaultMessage: 'Select Observables to push', - } -); - -export const ALERT_FIELD_ENABLED_TEXT = i18n.translate( - 'xpack.securitySolution.components.connectors.serviceNow.alertFieldEnabledText', - { - defaultMessage: 'Yes', - } -); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/flyout.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/flyout.test.tsx index d5883b7b88cd0..d413a2d5e0018 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/flyout.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/flyout.test.tsx @@ -5,57 +5,22 @@ * 2.0. */ -/* eslint-disable react/display-name */ -import React, { ReactNode } from 'react'; +import React from 'react'; import { mount } from 'enzyme'; import '../../../common/mock/match_media'; import { CreateCaseFlyout } from './flyout'; import { TestProviders } from '../../../common/mock'; -jest.mock('../create/form_context', () => { - return { - FormContext: ({ - children, - onSuccess, - }: { - children: ReactNode; - onSuccess: ({ id }: { id: string }) => Promise<void>; - }) => { - return ( - <> - <button - type="button" - data-test-subj="form-context-on-success" - onClick={async () => { - await onSuccess({ id: 'case-id' }); - }} - > - {'submit'} - </button> - {children} - </> - ); +jest.mock('../../../common/lib/kibana', () => ({ + useKibana: () => ({ + services: { + cases: { + getCreateCase: jest.fn(), + }, }, - }; -}); - -jest.mock('../create/form', () => { - return { - CreateCaseForm: () => { - return <>{'form'}</>; - }, - }; -}); - -jest.mock('../create/submit_button', () => { - return { - SubmitCaseButton: () => { - return <>{'Submit'}</>; - }, - }; -}); - + }), +})); const onCloseFlyout = jest.fn(); const onSuccess = jest.fn(); const defaultProps = { @@ -88,30 +53,4 @@ describe('CreateCaseFlyout', () => { wrapper.find('.euiFlyout__closeButton').first().simulate('click'); expect(onCloseFlyout).toBeCalled(); }); - - it('pass the correct props to FormContext component', () => { - const wrapper = mount( - <TestProviders> - <CreateCaseFlyout {...defaultProps} /> - </TestProviders> - ); - - const props = wrapper.find('FormContext').props(); - expect(props).toEqual( - expect.objectContaining({ - onSuccess, - }) - ); - }); - - it('onSuccess called when creating a case', () => { - const wrapper = mount( - <TestProviders> - <CreateCaseFlyout {...defaultProps} /> - </TestProviders> - ); - - wrapper.find(`[data-test-subj='form-context-on-success']`).first().simulate('click'); - expect(onSuccess).toHaveBeenCalledWith({ id: 'case-id' }); - }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx b/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx index 8f76ee8f85173..0f9f64b32bdd0 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx @@ -9,25 +9,16 @@ import React, { memo } from 'react'; import styled from 'styled-components'; import { EuiFlyout, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui'; -import { FormContext } from '../create/form_context'; -import { CreateCaseForm } from '../create/form'; -import { SubmitCaseButton } from '../create/submit_button'; -import { Case } from '../../containers/types'; import * as i18n from '../../translations'; +import { useKibana } from '../../../common/lib/kibana'; +import { Case } from '../../../../../cases/common'; export interface CreateCaseModalProps { + afterCaseCreated?: (theCase: Case) => Promise<void>; onCloseFlyout: () => void; onSuccess: (theCase: Case) => Promise<void>; - afterCaseCreated?: (theCase: Case) => Promise<void>; } -const Container = styled.div` - ${({ theme }) => ` - margin-top: ${theme.eui.euiSize}; - text-align: right; - `} -`; - const StyledFlyout = styled(EuiFlyout)` ${({ theme }) => ` z-index: ${theme.eui.euiZModal}; @@ -55,10 +46,11 @@ const FormWrapper = styled.div` `; const CreateCaseFlyoutComponent: React.FC<CreateCaseModalProps> = ({ - onSuccess, afterCaseCreated, onCloseFlyout, + onSuccess, }) => { + const { cases } = useKibana().services; return ( <StyledFlyout onClose={onCloseFlyout} data-test-subj="create-case-flyout"> <EuiFlyoutHeader hasBorder> @@ -68,12 +60,12 @@ const CreateCaseFlyoutComponent: React.FC<CreateCaseModalProps> = ({ </EuiFlyoutHeader> <StyledEuiFlyoutBody> <FormWrapper> - <FormContext onSuccess={onSuccess} afterCaseCreated={afterCaseCreated}> - <CreateCaseForm withSteps={false} /> - <Container> - <SubmitCaseButton /> - </Container> - </FormContext> + {cases.getCreateCase({ + afterCaseCreated, + onCancel: onCloseFlyout, + onSuccess, + withSteps: false, + })} </FormWrapper> </StyledEuiFlyoutBody> </StyledFlyout> diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx index 7172d227f492e..2d5faef8aa009 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx @@ -6,91 +6,39 @@ */ import React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; +import { mount } from 'enzyme'; import { act, waitFor } from '@testing-library/react'; import { noop } from 'lodash/fp'; -import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; import { TestProviders } from '../../../common/mock'; -import { useGetTags } from '../../containers/use_get_tags'; -import { useConnectors } from '../../containers/configure/use_connectors'; -import { useCaseConfigure } from '../../containers/configure/use_configure'; import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; -import { useGetIncidentTypes } from '../connectors/resilient/use_get_incident_types'; -import { useGetSeverity } from '../connectors/resilient/use_get_severity'; -import { useGetIssueTypes } from '../connectors/jira/use_get_issue_types'; -import { useGetFieldsByIssueType } from '../connectors/jira/use_get_fields_by_issue_type'; -import { useCaseConfigureResponse } from '../configure_cases/__mock__'; import { useInsertTimeline } from '../use_insert_timeline'; -import { - sampleConnectorData, - sampleData, - sampleTags, - useGetIncidentTypesResponse, - useGetSeverityResponse, - useGetIssueTypesResponse, - useGetFieldsByIssueTypeResponse, -} from './mock'; import { Create } from '.'; +import { useKibana } from '../../../common/lib/kibana'; +import { Case } from '../../../../../cases/public/containers/types'; +import { basicCase } from '../../../../../cases/public/containers/mock'; -jest.mock('../../containers/api'); -jest.mock('../../containers/use_get_tags'); -jest.mock('../../containers/configure/use_connectors'); -jest.mock('../../containers/configure/use_configure'); -jest.mock('../connectors/resilient/use_get_incident_types'); -jest.mock('../connectors/resilient/use_get_severity'); -jest.mock('../connectors/jira/use_get_issue_types'); -jest.mock('../connectors/jira/use_get_fields_by_issue_type'); -jest.mock('../connectors/jira/use_get_single_issue'); -jest.mock('../connectors/jira/use_get_issues'); jest.mock('../use_insert_timeline'); +jest.mock('../../../common/lib/kibana'); -const useConnectorsMock = useConnectors as jest.Mock; -const useCaseConfigureMock = useCaseConfigure as jest.Mock; -const useGetTagsMock = useGetTags as jest.Mock; -const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock; -const useGetSeverityMock = useGetSeverity as jest.Mock; -const useGetIssueTypesMock = useGetIssueTypes as jest.Mock; -const useGetFieldsByIssueTypeMock = useGetFieldsByIssueType as jest.Mock; const useInsertTimelineMock = useInsertTimeline as jest.Mock; -const fetchTags = jest.fn(); - -const fillForm = (wrapper: ReactWrapper) => { - wrapper - .find(`[data-test-subj="caseTitle"] input`) - .first() - .simulate('change', { target: { value: sampleData.title } }); - - wrapper - .find(`[data-test-subj="caseDescription"] textarea`) - .first() - .simulate('change', { target: { value: sampleData.description } }); - - act(() => { - ((wrapper.find(EuiComboBox).props() as unknown) as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - }).onChange(sampleTags.map((tag) => ({ label: tag }))); - }); -}; describe('Create case', () => { + const mockCreateCase = jest.fn(); beforeEach(() => { jest.resetAllMocks(); jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); - useConnectorsMock.mockReturnValue(sampleConnectorData); - useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse); - useGetIncidentTypesMock.mockReturnValue(useGetIncidentTypesResponse); - useGetSeverityMock.mockReturnValue(useGetSeverityResponse); - useGetIssueTypesMock.mockReturnValue(useGetIssueTypesResponse); - useGetFieldsByIssueTypeMock.mockReturnValue(useGetFieldsByIssueTypeResponse); - useGetTagsMock.mockImplementation(() => ({ - tags: sampleTags, - fetchTags, - })); + (useKibana as jest.Mock).mockReturnValue({ + services: { + cases: { + getCreateCase: mockCreateCase, + }, + }, + }); }); - it('it renders', async () => { - const wrapper = mount( + it('it renders', () => { + mount( <TestProviders> <Router history={mockHistory}> <Create /> @@ -98,12 +46,20 @@ describe('Create case', () => { </TestProviders> ); - expect(wrapper.find(`[data-test-subj="create-case-submit"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="create-case-cancel"]`).exists()).toBeTruthy(); + expect(mockCreateCase).toHaveBeenCalled(); }); it('should redirect to all cases on cancel click', async () => { - const wrapper = mount( + (useKibana as jest.Mock).mockReturnValue({ + services: { + cases: { + getCreateCase: ({ onCancel }: { onCancel: () => Promise<void> }) => { + onCancel(); + }, + }, + }, + }); + mount( <TestProviders> <Router history={mockHistory}> <Create /> @@ -111,12 +67,20 @@ describe('Create case', () => { </TestProviders> ); - wrapper.find(`[data-test-subj="create-case-cancel"]`).first().simulate('click'); await waitFor(() => expect(mockHistory.push).toHaveBeenCalledWith('/')); }); it('should redirect to new case when posting the case', async () => { - const wrapper = mount( + (useKibana as jest.Mock).mockReturnValue({ + services: { + cases: { + getCreateCase: ({ onSuccess }: { onSuccess: (theCase: Case) => Promise<void> }) => { + onSuccess(basicCase); + }, + }, + }, + }); + mount( <TestProviders> <Router history={mockHistory}> <Create /> @@ -124,13 +88,10 @@ describe('Create case', () => { </TestProviders> ); - fillForm(wrapper); - wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); - await waitFor(() => expect(mockHistory.push).toHaveBeenNthCalledWith(1, '/basic-case-id')); }); - it('it should insert a timeline', async () => { + it.skip('it should insert a timeline', async () => { let attachTimeline = noop; useInsertTimelineMock.mockImplementation((value, onTimelineAttached) => { attachTimeline = onTimelineAttached; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx index 9f904350b772e..4a1a64f5fcb41 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx @@ -6,39 +6,16 @@ */ import React, { useCallback } from 'react'; -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; -import styled from 'styled-components'; +import { EuiPanel } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; -import { Field, getUseField, useFormContext } from '../../../shared_imports'; import { getCaseDetailsUrl } from '../../../common/components/link_to'; -import * as i18n from './translations'; -import { CreateCaseForm } from './form'; -import { FormContext } from './form_context'; +import { useKibana } from '../../../common/lib/kibana'; +import * as timelineMarkdownPlugin from '../../../common/components/markdown_editor/plugins/timeline'; import { useInsertTimeline } from '../use_insert_timeline'; -import { fieldName as descriptionFieldName } from './description'; -import { SubmitCaseButton } from './submit_button'; - -export const CommonUseField = getUseField({ component: Field }); - -const Container = styled.div` - ${({ theme }) => ` - margin-top: ${theme.eui.euiSize}; - `} -`; - -const InsertTimeline = () => { - const { setFieldValue, getFormData } = useFormContext(); - const formData = getFormData(); - const onTimelineAttached = useCallback( - (newValue: string) => setFieldValue(descriptionFieldName, newValue), - [setFieldValue] - ); - useInsertTimeline(formData[descriptionFieldName] ?? '', onTimelineAttached); - return null; -}; export const Create = React.memo(() => { + const { cases } = useKibana().services; const history = useHistory(); const onSuccess = useCallback( async ({ id }) => { @@ -53,32 +30,20 @@ export const Create = React.memo(() => { return ( <EuiPanel> - <FormContext onSuccess={onSuccess}> - <CreateCaseForm /> - <Container> - <EuiFlexGroup - alignItems="center" - justifyContent="flexEnd" - gutterSize="xs" - responsive={false} - > - <EuiFlexItem grow={false}> - <EuiButtonEmpty - data-test-subj="create-case-cancel" - size="s" - onClick={handleSetIsCancel} - iconType="cross" - > - {i18n.CANCEL} - </EuiButtonEmpty> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <SubmitCaseButton /> - </EuiFlexItem> - </EuiFlexGroup> - </Container> - <InsertTimeline /> - </FormContext> + {cases.getCreateCase({ + onCancel: handleSetIsCancel, + onSuccess, + timelineIntegration: { + editor_plugins: { + parsingPlugin: timelineMarkdownPlugin.parser, + processingPluginRenderer: timelineMarkdownPlugin.renderer, + uiPlugin: timelineMarkdownPlugin.plugin, + }, + hooks: { + useInsertTimeline, + }, + }, + })} </EuiPanel> ); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/translations.ts b/x-pack/plugins/security_solution/public/cases/components/create/translations.ts deleted file mode 100644 index d9373dade1b68..0000000000000 --- a/x-pack/plugins/security_solution/public/cases/components/create/translations.ts +++ /dev/null @@ -1,38 +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 { i18n } from '@kbn/i18n'; - -export * from '../../translations'; - -export const STEP_ONE_TITLE = i18n.translate( - 'xpack.securitySolution.components.create.stepOneTitle', - { - defaultMessage: 'Case fields', - } -); - -export const STEP_TWO_TITLE = i18n.translate( - 'xpack.securitySolution.components.create.stepTwoTitle', - { - defaultMessage: 'Case settings', - } -); - -export const STEP_THREE_TITLE = i18n.translate( - 'xpack.securitySolution.components.create.stepThreeTitle', - { - defaultMessage: 'External Connector Fields', - } -); - -export const SYNC_ALERTS_LABEL = i18n.translate( - 'xpack.securitySolution.components.create.syncAlertsLabel', - { - defaultMessage: 'Sync alert status with case status', - } -); diff --git a/x-pack/plugins/security_solution/public/cases/components/status/translations.ts b/x-pack/plugins/security_solution/public/cases/components/status/translations.ts deleted file mode 100644 index 6c26513785026..0000000000000 --- a/x-pack/plugins/security_solution/public/cases/components/status/translations.ts +++ /dev/null @@ -1,72 +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 { i18n } from '@kbn/i18n'; -export * from '../../translations'; - -export const ALL = i18n.translate('xpack.securitySolution.cases.status.all', { - defaultMessage: 'All', -}); - -export const OPEN = i18n.translate('xpack.securitySolution.cases.status.open', { - defaultMessage: 'Open', -}); - -export const IN_PROGRESS = i18n.translate('xpack.securitySolution.cases.status.inProgress', { - defaultMessage: 'In progress', -}); - -export const CLOSED = i18n.translate('xpack.securitySolution.cases.status.closed', { - defaultMessage: 'Closed', -}); - -export const STATUS_ICON_ARIA = i18n.translate('xpack.securitySolution.cases.status.iconAria', { - defaultMessage: 'Change status', -}); - -export const CASE_OPENED = i18n.translate('xpack.securitySolution.cases.caseView.caseOpened', { - defaultMessage: 'Case opened', -}); - -export const CASE_IN_PROGRESS = i18n.translate( - 'xpack.securitySolution.cases.caseView.caseInProgress', - { - defaultMessage: 'Case in progress', - } -); - -export const CASE_CLOSED = i18n.translate('xpack.securitySolution.cases.caseView.caseClosed', { - defaultMessage: 'Case closed', -}); - -export const BULK_ACTION_CLOSE_SELECTED = i18n.translate( - 'xpack.securitySolution.cases.caseTable.bulkActions.closeSelectedTitle', - { - defaultMessage: 'Close selected', - } -); - -export const BULK_ACTION_OPEN_SELECTED = i18n.translate( - 'xpack.securitySolution.cases.caseTable.bulkActions.openSelectedTitle', - { - defaultMessage: 'Open selected', - } -); - -export const BULK_ACTION_DELETE_SELECTED = i18n.translate( - 'xpack.securitySolution.cases.caseTable.bulkActions.deleteSelectedTitle', - { - defaultMessage: 'Delete selected', - } -); - -export const BULK_ACTION_MARK_IN_PROGRESS = i18n.translate( - 'xpack.securitySolution.cases.caseTable.bulkActions.markInProgressTitle', - { - defaultMessage: 'Mark in progress', - } -); diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx index 40a202f5257a7..09c94b643e8d9 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx @@ -5,21 +5,28 @@ * 2.0. */ -/* eslint-disable react/display-name */ -import React, { ReactNode } from 'react'; +import React from 'react'; import { mount } from 'enzyme'; import { EuiGlobalToastList } from '@elastic/eui'; import { useKibana, useGetUserSavedObjectPermissions } from '../../../common/lib/kibana'; import { useStateToaster } from '../../../common/components/toasters'; import { TestProviders } from '../../../common/mock'; -import { usePostComment } from '../../containers/use_post_comment'; -import { Case } from '../../containers/types'; import { AddToCaseAction } from './add_to_case_action'; +import { basicCase } from '../../../../../cases/public/containers/mock'; +import { Case } from '../../../../../cases/common'; -jest.mock('../../containers/use_post_comment'); jest.mock('../../../common/lib/kibana'); - +jest.mock('../../../common/components/link_to', () => { + const original = jest.requireActual('../../../common/components/link_to'); + return { + ...original, + useFormatUrl: jest.fn().mockReturnValue({ + formatUrl: jest.fn(), + search: '', + }), + }; +}); jest.mock('../../../common/components/toasters', () => { const actual = jest.requireActual('../../../common/components/toasters'); return { @@ -28,86 +35,7 @@ jest.mock('../../../common/components/toasters', () => { }; }); -jest.mock('../all_cases', () => { - return { - AllCases: ({ onRowClick }: { onRowClick: (theCase: Partial<Case>) => void }) => { - return ( - <button - type="button" - data-test-subj="all-cases-modal-button" - onClick={() => - onRowClick({ - id: 'selected-case', - title: 'the selected case', - settings: { syncAlerts: true }, - }) - } - > - {'case-row'} - </button> - ); - }, - }; -}); - -jest.mock('../create/form_context', () => { - return { - FormContext: ({ - children, - onSuccess, - afterCaseCreated, - }: { - children: ReactNode; - onSuccess: (theCase: Partial<Case>) => Promise<void>; - afterCaseCreated: (theCase: Partial<Case>) => Promise<void>; - }) => { - return ( - <> - <button - type="button" - data-test-subj="form-context-on-success" - onClick={() => { - afterCaseCreated({ - id: 'new-case', - title: 'the new case', - settings: { syncAlerts: true }, - }); - onSuccess({ id: 'new-case', title: 'the new case', settings: { syncAlerts: true } }); - }} - > - {'submit'} - </button> - {children} - </> - ); - }, - }; -}); - -jest.mock('../create/form', () => { - return { - CreateCaseForm: () => { - return <>{'form'}</>; - }, - }; -}); - -jest.mock('../create/submit_button', () => { - return { - SubmitCaseButton: () => { - return <>{'Submit'}</>; - }, - }; -}); - -const usePostCommentMock = usePostComment as jest.Mock; -const postComment = jest.fn(); -const defaultPostComment = { - isLoading: false, - isError: false, - postComment, -}; - +const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>; describe('AddToCaseAction', () => { const props = { ecsRowData: { @@ -119,21 +47,28 @@ describe('AddToCaseAction', () => { const mockDispatchToaster = jest.fn(); const mockNavigateToApp = jest.fn(); + const mockCreateCase = jest.fn(); + const mockAllCasesModal = jest.fn(); beforeEach(() => { jest.clearAllMocks(); - usePostCommentMock.mockImplementation(() => defaultPostComment); + useKibanaMock().services.application.navigateToApp = mockNavigateToApp; + useKibanaMock().services.cases = { + getAllCases: jest.fn(), + getCaseView: jest.fn(), + getConfigureCases: jest.fn(), + getRecentCases: jest.fn(), + getCreateCase: mockCreateCase, + getAllCasesSelectorModal: mockAllCasesModal.mockImplementation(() => <>{'test'}</>), + }; (useStateToaster as jest.Mock).mockReturnValue([jest.fn(), mockDispatchToaster]); - (useKibana as jest.Mock).mockReturnValue({ - services: { application: { navigateToApp: mockNavigateToApp } }, - }); (useGetUserSavedObjectPermissions as jest.Mock).mockReturnValue({ crud: true, read: true, }); }); - it('it renders', async () => { + it('it renders', () => { const wrapper = mount( <TestProviders> <AddToCaseAction {...props} /> @@ -143,7 +78,7 @@ describe('AddToCaseAction', () => { expect(wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).exists()).toBeTruthy(); }); - it('it opens the context menu', async () => { + it('it opens the context menu', () => { const wrapper = mount( <TestProviders> <AddToCaseAction {...props} /> @@ -155,20 +90,7 @@ describe('AddToCaseAction', () => { expect(wrapper.find(`[data-test-subj="add-existing-case-menu-item"]`).exists()).toBeTruthy(); }); - it('it opens the create case modal', async () => { - const wrapper = mount( - <TestProviders> - <AddToCaseAction {...props} /> - </TestProviders> - ); - - wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click'); - wrapper.find(`[data-test-subj="add-new-case-item"]`).first().simulate('click'); - - expect(wrapper.find(`[data-test-subj="form-context-on-success"]`).exists()).toBeTruthy(); - }); - - it('it attach the alert to case on case creation', async () => { + it('it opens the create case modal', () => { const wrapper = mount( <TestProviders> <AddToCaseAction {...props} /> @@ -177,22 +99,10 @@ describe('AddToCaseAction', () => { wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click'); wrapper.find(`[data-test-subj="add-new-case-item"]`).first().simulate('click'); - - wrapper.find(`[data-test-subj="form-context-on-success"]`).first().simulate('click'); - - expect(postComment.mock.calls[0][0].caseId).toBe('new-case'); - expect(postComment.mock.calls[0][0].data).toEqual({ - alertId: 'test-id', - index: 'test-index', - rule: { - id: 'rule-id', - name: 'rule-name', - }, - type: 'alert', - }); + expect(mockCreateCase).toHaveBeenCalled(); }); - it('it opens the all cases modal', async () => { + it('it opens the all cases modal', () => { const wrapper = mount( <TestProviders> <AddToCaseAction {...props} /> @@ -202,34 +112,14 @@ describe('AddToCaseAction', () => { wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click'); wrapper.find(`[data-test-subj="add-existing-case-menu-item"]`).first().simulate('click'); - expect(wrapper.find(`[data-test-subj="all-cases-modal-button"]`).exists()).toBeTruthy(); - }); - - it('it attach the alert to case after selecting a case', async () => { - const wrapper = mount( - <TestProviders> - <AddToCaseAction {...props} /> - </TestProviders> - ); - - wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click'); - wrapper.find(`[data-test-subj="add-existing-case-menu-item"]`).first().simulate('click'); - - wrapper.find(`[data-test-subj="all-cases-modal-button"]`).first().simulate('click'); - - expect(postComment.mock.calls[0][0].caseId).toBe('selected-case'); - expect(postComment.mock.calls[0][0].data).toEqual({ + expect(mockAllCasesModal.mock.calls[0][0].alertData).toEqual({ alertId: 'test-id', index: 'test-index', - rule: { - id: 'rule-id', - name: 'rule-name', - }, - type: 'alert', + rule: { id: 'rule-id', name: 'rule-name' }, }); }); - it('it set rule information as null when missing', async () => { + it('it set rule information as null when missing', () => { const wrapper = mount( <TestProviders> <AddToCaseAction @@ -244,23 +134,23 @@ describe('AddToCaseAction', () => { ); wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click'); - wrapper.find(`[data-test-subj="add-new-case-item"]`).first().simulate('click'); - - wrapper.find(`[data-test-subj="form-context-on-success"]`).first().simulate('click'); - - expect(postComment.mock.calls[0][0].caseId).toBe('new-case'); - expect(postComment.mock.calls[0][0].data).toEqual({ + wrapper.find(`[data-test-subj="add-existing-case-menu-item"]`).first().simulate('click'); + expect(mockAllCasesModal.mock.calls[0][0].alertData).toEqual({ alertId: 'test-id', index: 'test-index', - rule: { - id: 'rule-id', - name: null, - }, - type: 'alert', + rule: { id: 'rule-id', name: null }, }); }); - it('navigates to case view when attach to a new case', async () => { + it('onSuccess triggers toaster that links to case view', () => { + // @ts-ignore + useKibanaMock().services.cases.getCreateCase = ({ + onSuccess, + }: { + onSuccess: (theCase: Case) => Promise<void>; + }) => { + onSuccess(basicCase); + }; const wrapper = mount( <TestProviders> <AddToCaseAction {...props} /> @@ -269,46 +159,6 @@ describe('AddToCaseAction', () => { wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click'); wrapper.find(`[data-test-subj="add-new-case-item"]`).first().simulate('click'); - wrapper.find(`[data-test-subj="form-context-on-success"]`).first().simulate('click'); - - expect(mockDispatchToaster).toHaveBeenCalled(); - const toast = mockDispatchToaster.mock.calls[0][0].toast; - - const toastWrapper = mount( - <EuiGlobalToastList toasts={[toast]} toastLifeTimeMs={6000} dismissToast={() => {}} /> - ); - - toastWrapper - .find('[data-test-subj="toaster-content-case-view-link"]') - .first() - .simulate('click'); - - expect(mockNavigateToApp).toHaveBeenCalledWith('securitySolution:case', { path: '/new-case' }); - }); - - it('navigates to case view when attach to an existing case', async () => { - usePostCommentMock.mockImplementation(() => { - return { - ...defaultPostComment, - postComment: jest.fn().mockImplementation(({ caseId, data, updateCase }) => { - updateCase({ - id: 'selected-case', - title: 'the selected case', - settings: { syncAlerts: true }, - }); - }), - }; - }); - - const wrapper = mount( - <TestProviders> - <AddToCaseAction {...props} /> - </TestProviders> - ); - - wrapper.find(`[data-test-subj="attach-alert-to-case-button"]`).first().simulate('click'); - wrapper.find(`[data-test-subj="add-existing-case-menu-item"]`).first().simulate('click'); - wrapper.find(`[data-test-subj="all-cases-modal-button"]`).first().simulate('click'); expect(mockDispatchToaster).toHaveBeenCalled(); const toast = mockDispatchToaster.mock.calls[0][0].toast; @@ -323,11 +173,11 @@ describe('AddToCaseAction', () => { .simulate('click'); expect(mockNavigateToApp).toHaveBeenCalledWith('securitySolution:case', { - path: '/selected-case', + path: '/basic-case-id', }); }); - it('disabled when event type is not supported', async () => { + it('disabled when event type is not supported', () => { const wrapper = mount( <TestProviders> <AddToCaseAction @@ -345,7 +195,7 @@ describe('AddToCaseAction', () => { ).toBeTruthy(); }); - it('disabled when user does not have crud permissions', async () => { + it('disabled when user does not have crud permissions', () => { (useGetUserSavedObjectPermissions as jest.Mock).mockReturnValue({ crud: false, read: true, diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx index 45c1355cecfa7..1682b4b7e7dee 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx @@ -16,27 +16,40 @@ import { EuiToolTip, } from '@elastic/eui'; -import { CommentType, CaseStatuses } from '../../../../../cases/common/api'; +import { Case, CaseStatuses } from '../../../../../cases/common'; +import { APP_ID } from '../../../../common/constants'; import { Ecs } from '../../../../common/ecs'; -import { ActionIconItem } from '../../../timelines/components/timeline/body/actions/action_icon_item'; -import { usePostComment } from '../../containers/use_post_comment'; -import { Case } from '../../containers/types'; +import { SecurityPageName } from '../../../app/types'; +import { + getCaseDetailsUrl, + getCreateCaseUrl, + useFormatUrl, +} from '../../../common/components/link_to'; import { useStateToaster } from '../../../common/components/toasters'; -import { APP_ID } from '../../../../common/constants'; +import { useControl } from '../../../common/hooks/use_control'; import { useGetUserSavedObjectPermissions, useKibana } from '../../../common/lib/kibana'; -import { getCaseDetailsUrl } from '../../../common/components/link_to'; -import { SecurityPageName } from '../../../app/types'; -import { useAllCasesModal } from '../use_all_cases_modal'; +import { ActionIconItem } from '../../../timelines/components/timeline/body/actions/action_icon_item'; +import { CreateCaseFlyout } from '../create/flyout'; import { createUpdateSuccessToaster } from './helpers'; import * as i18n from './translations'; -import { useControl } from '../../../common/hooks/use_control'; -import { CreateCaseFlyout } from '../create/flyout'; interface AddToCaseActionProps { ariaLabel?: string; ecsRowData: Ecs; } +interface PostCommentArg { + caseId: string; + data: { + type: 'alert'; + alertId: string | string[]; + index: string | string[]; + rule: { id: string | null; name: string | null }; + }; + updateCase?: (newCase: Case) => void; + subCaseId?: string; +} + const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({ ariaLabel = i18n.ACTION_ADD_TO_CASE_ARIA_LABEL, ecsRowData, @@ -45,7 +58,10 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({ const eventIndex = ecsRowData._index; const rule = ecsRowData.signal?.rule; - const { navigateToApp } = useKibana().services.application; + const { + application: { navigateToApp }, + cases, + } = useKibana().services; const [, dispatchToaster] = useStateToaster(); const [isPopoverOpen, setIsPopoverOpen] = useState(false); const openPopover = useCallback(() => setIsPopoverOpen(true), []); @@ -61,8 +77,6 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({ : i18n.UNSUPPORTED_EVENTS_MSG : i18n.PERMISSIONS_MSG; - const { postComment } = usePostComment(); - const onViewCaseClick = useCallback( (id) => { navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { @@ -79,33 +93,52 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({ } = useControl(); const attachAlertToCase = useCallback( - async (theCase: Case, updateCase?: (newCase: Case) => void) => { + async ( + theCase: Case, + postComment?: (arg: PostCommentArg) => Promise<void>, + updateCase?: (newCase: Case) => void + ) => { closeCaseFlyoutOpen(); - await postComment({ - caseId: theCase.id, - data: { - type: CommentType.alert, - alertId: eventId, - index: eventIndex ?? '', - rule: { - id: rule?.id != null ? rule.id[0] : null, - name: rule?.name != null ? rule.name[0] : null, + if (postComment) { + await postComment({ + caseId: theCase.id, + data: { + type: 'alert', + alertId: eventId, + index: eventIndex ?? '', + rule: { + id: rule?.id != null ? rule.id[0] : null, + name: rule?.name != null ? rule.name[0] : null, + }, }, - }, - updateCase, - }); + updateCase, + }); + } }, - [closeCaseFlyoutOpen, postComment, eventId, eventIndex, rule] + [closeCaseFlyoutOpen, eventId, eventIndex, rule] ); - const onCaseSuccess = useCallback( - async (theCase: Case) => - dispatchToaster({ + async (theCase: Case) => { + closeCaseFlyoutOpen(); + return dispatchToaster({ type: 'addToaster', toast: createUpdateSuccessToaster(theCase, onViewCaseClick), - }), - [dispatchToaster, onViewCaseClick] + }); + }, + [closeCaseFlyoutOpen, dispatchToaster, onViewCaseClick] + ); + + const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.case); + const goToCreateCase = useCallback( + (ev) => { + ev.preventDefault(); + navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { + path: getCreateCaseUrl(urlSearch), + }); + }, + [navigateToApp, urlSearch] ); + const [isAllCaseModalOpen, openAllCaseModal] = useState(false); const onCaseClicked = useCallback( (theCase) => { @@ -116,19 +149,11 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({ */ if (theCase == null) { openCaseFlyoutOpen(); - return; } - - attachAlertToCase(theCase, onCaseSuccess); + openAllCaseModal(false); }, - [attachAlertToCase, onCaseSuccess, openCaseFlyoutOpen] + [openCaseFlyoutOpen] ); - - const { modal: allCasesModal, openModal: openAllCaseModal } = useAllCasesModal({ - disabledStatuses: [CaseStatuses.closed], - onRowClick: onCaseClicked, - }); - const addNewCaseClick = useCallback(() => { closePopover(); openCaseFlyoutOpen(); @@ -136,7 +161,7 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({ const addExistingCaseClick = useCallback(() => { closePopover(); - openAllCaseModal(); + openAllCaseModal(true); }, [openAllCaseModal, closePopover]); const items = useMemo( @@ -196,12 +221,30 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({ </ActionIconItem> {isCreateCaseFlyoutOpen && ( <CreateCaseFlyout - onCloseFlyout={closeCaseFlyoutOpen} afterCaseCreated={attachAlertToCase} + onCloseFlyout={closeCaseFlyoutOpen} onSuccess={onCaseSuccess} /> )} - {allCasesModal} + {isAllCaseModalOpen && + cases.getAllCasesSelectorModal({ + alertData: { + alertId: eventId, + index: eventIndex ?? '', + rule: { + id: rule?.id != null ? rule.id[0] : null, + name: rule?.name != null ? rule.name[0] : null, + }, + }, + createCaseNavigation: { + href: formatUrl(getCreateCaseUrl()), + onClick: goToCreateCase, + }, + disabledStatuses: [CaseStatuses.closed], + onRowClick: onCaseClicked, + updateCase: onCaseSuccess, + userCanCrud: userPermissions?.crud ?? false, + })} </> ); }; diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.test.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.test.tsx index 9e358323ee073..9722447b96ad5 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.test.tsx @@ -6,7 +6,7 @@ */ import { createUpdateSuccessToaster } from './helpers'; -import { Case } from '../../containers/types'; +import { Case } from '../../../../../cases/common'; const theCase = { id: 'case-id', diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.tsx index 175d7f896648b..8682b6680830d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/helpers.tsx @@ -8,9 +8,9 @@ import React from 'react'; import uuid from 'uuid'; import { AppToast } from '../../../common/components/toasters'; -import { Case } from '../../containers/types'; import { ToasterContent } from './toaster_content'; import * as i18n from './translations'; +import { Case } from '../../../../../cases/common'; export const createUpdateSuccessToaster = ( theCase: Case, diff --git a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.test.tsx b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.test.tsx deleted file mode 100644 index 57d4b585d573f..0000000000000 --- a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.test.tsx +++ /dev/null @@ -1,108 +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. - */ - -/* eslint-disable react/display-name */ -import { mount } from 'enzyme'; -import React from 'react'; -import '../../../common/mock/match_media'; -import { AllCasesModal } from './all_cases_modal'; -import { TestProviders } from '../../../common/mock'; - -jest.mock('../all_cases', () => { - return { - AllCases: ({ onRowClick }: { onRowClick: ({ id }: { id: string }) => void }) => { - return ( - <button - type="button" - data-test-subj="all-cases-row" - onClick={() => onRowClick({ id: 'case-id' })} - > - {'case-row'} - </button> - ); - }, - }; -}); - -jest.mock('../../../common/lib/kibana', () => { - const originalModule = jest.requireActual('../../../common/lib/kibana'); - return { - ...originalModule, - useGetUserSavedObjectPermissions: jest.fn(), - }; -}); - -const onCloseCaseModal = jest.fn(); -const onRowClick = jest.fn(); -const defaultProps = { - isModalOpen: true, - onCloseCaseModal, - onRowClick, -}; - -describe('AllCasesModal', () => { - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('renders', () => { - const wrapper = mount( - <TestProviders> - <AllCasesModal {...defaultProps} /> - </TestProviders> - ); - - expect(wrapper.find(`[data-test-subj='all-cases-modal']`).exists()).toBeTruthy(); - }); - - it('it does not render the modal isModalOpen=false ', () => { - const wrapper = mount( - <TestProviders> - <AllCasesModal {...defaultProps} isModalOpen={false} /> - </TestProviders> - ); - - expect(wrapper.find(`[data-test-subj='all-cases-modal']`).exists()).toBeFalsy(); - }); - - it('Closing modal calls onCloseCaseModal', () => { - const wrapper = mount( - <TestProviders> - <AllCasesModal {...defaultProps} /> - </TestProviders> - ); - - wrapper.find('.euiModal__closeIcon').first().simulate('click'); - expect(onCloseCaseModal).toBeCalled(); - }); - - it('pass the correct props to AllCases component', () => { - const wrapper = mount( - <TestProviders> - <AllCasesModal {...defaultProps} /> - </TestProviders> - ); - - const props = wrapper.find('AllCases').props(); - expect(props).toEqual({ - userCanCrud: false, - onRowClick, - isModal: true, - }); - }); - - it('onRowClick called when row is clicked', () => { - const wrapper = mount( - <TestProviders> - <AllCasesModal {...defaultProps} /> - </TestProviders> - ); - - wrapper.find(`[data-test-subj='all-cases-row']`).first().simulate('click'); - expect(onRowClick).toHaveBeenCalledWith({ id: 'case-id' }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx deleted file mode 100644 index 10ad3d35004ba..0000000000000 --- a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx +++ /dev/null @@ -1,60 +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, { memo } from 'react'; -import styled from 'styled-components'; -import { EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui'; - -import { useGetUserSavedObjectPermissions } from '../../../common/lib/kibana'; -import { CaseStatuses } from '../../../../../cases/common/api'; -import { Case, SubCase } from '../../containers/types'; -import { AllCases } from '../all_cases'; -import * as i18n from './translations'; - -export interface AllCasesModalProps { - isModalOpen: boolean; - onCloseCaseModal: () => void; - onRowClick: (theCase?: Case | SubCase) => void; - disabledStatuses?: CaseStatuses[]; -} - -const Modal = styled(EuiModal)` - ${({ theme }) => ` - width: ${theme.eui.euiBreakpoints.l}; - max-width: ${theme.eui.euiBreakpoints.l}; - `} -`; - -const AllCasesModalComponent: React.FC<AllCasesModalProps> = ({ - isModalOpen, - onCloseCaseModal, - onRowClick, - disabledStatuses, -}) => { - const userPermissions = useGetUserSavedObjectPermissions(); - const userCanCrud = userPermissions?.crud ?? false; - - return isModalOpen ? ( - <Modal onClose={onCloseCaseModal} data-test-subj="all-cases-modal"> - <EuiModalHeader> - <EuiModalHeaderTitle>{i18n.SELECT_CASE_TITLE}</EuiModalHeaderTitle> - </EuiModalHeader> - <EuiModalBody> - <AllCases - onRowClick={onRowClick} - userCanCrud={userCanCrud} - isModal - disabledStatuses={disabledStatuses} - /> - </EuiModalBody> - </Modal> - ) : null; -}; - -export const AllCasesModal = memo(AllCasesModalComponent); - -AllCasesModal.displayName = 'AllCasesModal'; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/index.test.tsx deleted file mode 100644 index 57bb39a1ab50f..0000000000000 --- a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/index.test.tsx +++ /dev/null @@ -1,135 +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. - */ - -/* eslint-disable react/display-name */ -import React from 'react'; -import { renderHook, act } from '@testing-library/react-hooks'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -import { useKibana } from '../../../common/lib/kibana'; -import '../../../common/mock/match_media'; -import { useAllCasesModal, UseAllCasesModalProps, UseAllCasesModalReturnedValues } from '.'; -import { mockTimelineModel, TestProviders } from '../../../common/mock'; -import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; - -const mockDispatch = jest.fn(); -jest.mock('react-redux', () => { - const original = jest.requireActual('react-redux'); - return { - ...original, - useDispatch: () => mockDispatch, - }; -}); - -jest.mock('../../../common/lib/kibana'); -jest.mock('../all_cases', () => { - return { - AllCases: ({ onRowClick }: { onRowClick: ({ id }: { id: string }) => void }) => { - return ( - <button type="button" onClick={() => onRowClick({ id: 'case-id' })}> - {'case-row'} - </button> - ); - }, - }; -}); - -jest.mock('../../../common/hooks/use_selector'); - -const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>; -const onRowClick = jest.fn(); - -describe('useAllCasesModal', () => { - let navigateToApp: jest.Mock; - - beforeEach(() => { - navigateToApp = jest.fn(); - useKibanaMock().services.application.navigateToApp = navigateToApp; - (useDeepEqualSelector as jest.Mock).mockReturnValue(mockTimelineModel); - }); - - it('init', async () => { - const { result } = renderHook<UseAllCasesModalProps, UseAllCasesModalReturnedValues>( - () => useAllCasesModal({ onRowClick }), - { - wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, - } - ); - - expect(result.current.isModalOpen).toBe(false); - }); - - it('opens the modal', async () => { - const { result } = renderHook<UseAllCasesModalProps, UseAllCasesModalReturnedValues>( - () => useAllCasesModal({ onRowClick }), - { - wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, - } - ); - - act(() => { - result.current.openModal(); - }); - - expect(result.current.isModalOpen).toBe(true); - }); - - it('closes the modal', async () => { - const { result } = renderHook<UseAllCasesModalProps, UseAllCasesModalReturnedValues>( - () => useAllCasesModal({ onRowClick }), - { - wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, - } - ); - - act(() => { - result.current.openModal(); - result.current.closeModal(); - }); - - expect(result.current.isModalOpen).toBe(false); - }); - - it('returns a memoized value', async () => { - const { result, rerender } = renderHook<UseAllCasesModalProps, UseAllCasesModalReturnedValues>( - () => useAllCasesModal({ onRowClick }), - { - wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, - } - ); - - const result1 = result.current; - act(() => rerender()); - const result2 = result.current; - - expect(Object.is(result1, result2)).toBe(true); - }); - - it('closes the modal when clicking a row', async () => { - const { result } = renderHook<UseAllCasesModalProps, UseAllCasesModalReturnedValues>( - () => useAllCasesModal({ onRowClick }), - { - wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, - } - ); - - act(() => { - result.current.openModal(); - }); - - const modal = result.current.modal; - render(<TestProviders>{modal}</TestProviders>); - - act(() => { - userEvent.click(screen.getByText('case-row')); - }); - - expect(result.current.isModalOpen).toBe(false); - expect(onRowClick).toHaveBeenCalledWith({ id: 'case-id' }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/index.tsx b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/index.tsx deleted file mode 100644 index 0b30f6ac94e03..0000000000000 --- a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/index.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState, useCallback, useMemo } from 'react'; -import { CaseStatuses } from '../../../../../cases/common/api'; -import { Case, SubCase } from '../../containers/types'; -import { AllCasesModal } from './all_cases_modal'; - -export interface UseAllCasesModalProps { - onRowClick: (theCase?: Case | SubCase) => void; - disabledStatuses?: CaseStatuses[]; -} - -export interface UseAllCasesModalReturnedValues { - modal: JSX.Element; - isModalOpen: boolean; - closeModal: () => void; - openModal: () => void; -} - -export const useAllCasesModal = ({ - onRowClick, - disabledStatuses, -}: UseAllCasesModalProps): UseAllCasesModalReturnedValues => { - const [isModalOpen, setIsModalOpen] = useState<boolean>(false); - const closeModal = useCallback(() => setIsModalOpen(false), []); - const openModal = useCallback(() => setIsModalOpen(true), []); - const onClick = useCallback( - (theCase?: Case | SubCase) => { - closeModal(); - onRowClick(theCase); - }, - [closeModal, onRowClick] - ); - - const state = useMemo( - () => ({ - modal: ( - <AllCasesModal - isModalOpen={isModalOpen} - onCloseCaseModal={closeModal} - onRowClick={onClick} - disabledStatuses={disabledStatuses} - /> - ), - isModalOpen, - closeModal, - openModal, - onRowClick, - }), - [isModalOpen, closeModal, onClick, disabledStatuses, openModal, onRowClick] - ); - - return state; -}; diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx deleted file mode 100644 index 0b3915c3d38d4..0000000000000 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_markdown.test.tsx +++ /dev/null @@ -1,83 +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 { mount } from 'enzyme'; -import { Router, mockHistory } from '../__mock__/router'; -import { UserActionMarkdown } from './user_action_markdown'; -import { TestProviders } from '../../../common/mock'; -import * as timelineHelpers from '../../../timelines/components/open_timeline/helpers'; -const onChangeEditable = jest.fn(); -const onSaveContent = jest.fn(); - -const timelineId = '1e10f150-949b-11ea-b63c-2bc51864784c'; -const timelineMarkdown = `[timeline](http://localhost:5601/app/security/timelines?timeline=(id:'${timelineId}',isOpen:!t))`; -const defaultProps = { - content: `A link to a timeline ${timelineMarkdown}`, - id: 'markdown-id', - isEditable: false, - onChangeEditable, - onSaveContent, -}; - -describe('UserActionMarkdown ', () => { - const queryTimelineByIdSpy = jest.spyOn(timelineHelpers, 'queryTimelineById'); - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('Opens timeline when timeline link clicked - isEditable: false', async () => { - const wrapper = mount( - <TestProviders> - <Router history={mockHistory}> - <UserActionMarkdown {...defaultProps} /> - </Router> - </TestProviders> - ); - - wrapper - .find(`[data-test-subj="markdown-timeline-link-${timelineId}"]`) - .first() - .simulate('click'); - - expect(queryTimelineByIdSpy).toBeCalledWith({ - graphEventId: '', - timelineId, - updateIsLoading: expect.any(Function), - updateTimeline: expect.any(Function), - }); - }); - - it('Opens timeline when timeline link clicked - isEditable: true ', async () => { - const wrapper = mount( - <TestProviders> - <Router history={mockHistory}> - <UserActionMarkdown {...{ ...defaultProps, isEditable: true }} /> - </Router> - </TestProviders> - ); - - // Preview button of Markdown editor - wrapper - .find( - `[data-test-subj="user-action-markdown-form"] .euiMarkdownEditorToolbar .euiButtonEmpty` - ) - .first() - .simulate('click'); - - wrapper - .find(`[data-test-subj="markdown-timeline-link-${timelineId}"]`) - .first() - .simulate('click'); - expect(queryTimelineByIdSpy).toBeCalledWith({ - graphEventId: '', - timelineId, - updateIsLoading: expect.any(Function), - updateTimeline: expect.any(Function), - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/cases/components/wrappers/index.tsx b/x-pack/plugins/security_solution/public/cases/components/wrappers/index.tsx index 3b33e9304da83..477fb77d98ee8 100644 --- a/x-pack/plugins/security_solution/public/cases/components/wrappers/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/wrappers/index.tsx @@ -19,8 +19,3 @@ export const SectionWrapper = styled.div` max-width: 1175px; width: 100%; `; - -export const HeaderWrapper = styled.div` - padding: ${({ theme }) => - `${theme.eui.paddingSizes.l} ${theme.eui.paddingSizes.l} 0 ${theme.eui.paddingSizes.l}`}; -`; diff --git a/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx b/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx index 60cdb37628ba3..3e838f47e6dc2 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx @@ -13,15 +13,15 @@ import { SecurityPageName } from '../../app/types'; import { getCaseUrl } from '../../common/components/link_to'; import { useGetUrlSearch } from '../../common/components/navigation/use_get_url_search'; import { WrapperPage } from '../../common/components/wrapper_page'; -import { useGetUserSavedObjectPermissions } from '../../common/lib/kibana'; +import { useGetUserSavedObjectPermissions, useKibana } from '../../common/lib/kibana'; import { SpyRoute } from '../../common/utils/route/spy_routes'; import { navTabs } from '../../app/home/home_navigations'; import { CaseHeaderPage } from '../components/case_header_page'; -import { ConfigureCases } from '../components/configure_cases'; import { WhitePageWrapper, SectionWrapper } from '../components/wrappers'; import * as i18n from './translations'; const ConfigureCasesPageComponent: React.FC = () => { + const { cases } = useKibana().services; const history = useHistory(); const userPermissions = useGetUserSavedObjectPermissions(); const search = useGetUrlSearch(navTabs.case); @@ -53,7 +53,9 @@ const ConfigureCasesPageComponent: React.FC = () => { </HeaderWrapper> </SectionWrapper> <WhitePageWrapper> - <ConfigureCases userCanCrud={userPermissions?.crud ?? false} /> + {cases.getConfigureCases({ + userCanCrud: userPermissions?.crud ?? false, + })} </WhitePageWrapper> </WrapperPage> <SpyRoute pageName={SecurityPageName.case} /> diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts index bc0da84133e68..c744ace91f434 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts @@ -6,18 +6,36 @@ */ import { - EuiMarkdownEditorUiPlugin, + EuiLinkAnchorProps, getDefaultEuiMarkdownParsingPlugins, getDefaultEuiMarkdownProcessingPlugins, getDefaultEuiMarkdownUiPlugins, } from '@elastic/eui'; - +// Remove after this issue is resolved: https://github.com/elastic/eui/issues/4688 +// eslint-disable-next-line import/no-extraneous-dependencies +import { Options as Remark2RehypeOptions } from 'mdast-util-to-hast'; +import { FunctionComponent } from 'react'; +// eslint-disable-next-line import/no-extraneous-dependencies +import rehype2react from 'rehype-react'; +import { Plugin, PluggableList } from 'unified'; import * as timelineMarkdownPlugin from './timeline'; -const uiPlugins: EuiMarkdownEditorUiPlugin[] = getDefaultEuiMarkdownUiPlugins(); + +export const { uiPlugins, parsingPlugins, processingPlugins } = { + uiPlugins: getDefaultEuiMarkdownUiPlugins(), + parsingPlugins: getDefaultEuiMarkdownParsingPlugins(), + processingPlugins: getDefaultEuiMarkdownProcessingPlugins() as [ + [Plugin, Remark2RehypeOptions], + [ + typeof rehype2react, + Parameters<typeof rehype2react>[0] & { + components: { a: FunctionComponent<EuiLinkAnchorProps>; timeline: unknown }; + } + ], + ...PluggableList + ], +}; + uiPlugins.push(timelineMarkdownPlugin.plugin); -export { uiPlugins }; -export const parsingPlugins = getDefaultEuiMarkdownParsingPlugins(); -export const processingPlugins = getDefaultEuiMarkdownProcessingPlugins(); parsingPlugins.push(timelineMarkdownPlugin.parser); diff --git a/x-pack/plugins/security_solution/public/common/containers/local_storage/use_messages_storage.tsx b/x-pack/plugins/security_solution/public/common/containers/local_storage/use_messages_storage.tsx index e3f78cee0faae..ab8e7cf97d34c 100644 --- a/x-pack/plugins/security_solution/public/common/containers/local_storage/use_messages_storage.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/local_storage/use_messages_storage.tsx @@ -6,7 +6,7 @@ */ import { useCallback } from 'react'; -import { useKibana } from '../../lib/kibana'; +import { useKibana } from '../../../common/lib/kibana'; export interface UseMessagesStorage { getMessages: (plugin: string) => string[]; diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts index df7fad5443062..6b5599292f6d4 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts @@ -10,10 +10,11 @@ import moment from 'moment-timezone'; import { useCallback, useEffect, useState, useRef } from 'react'; import { i18n } from '@kbn/i18n'; +import { camelCase, isArray, isObject } from 'lodash'; +import { set } from '@elastic/safer-lodash-set'; import { DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../../../common/constants'; import { errorToToaster, useStateToaster } from '../../components/toasters'; import { AuthenticatedUser } from '../../../../../security/common/model'; -import { convertToCamelCase } from '../../../cases/containers/utils'; import { StartServices } from '../../../types'; import { useUiSetting, useKibana } from './kibana_react'; @@ -50,6 +51,27 @@ export interface AuthenticatedElasticUser { authenticationProvider: string; } +export const convertArrayToCamelCase = (arrayOfSnakes: unknown[]): unknown[] => + arrayOfSnakes.reduce((acc: unknown[], value) => { + if (isArray(value)) { + return [...acc, convertArrayToCamelCase(value)]; + } else if (isObject(value)) { + return [...acc, convertToCamelCase(value)]; + } else { + return [...acc, value]; + } + }, []); +export const convertToCamelCase = <T, U extends {}>(snakeCase: T): U => + Object.entries(snakeCase).reduce((acc, [key, value]) => { + if (isArray(value)) { + set(acc, camelCase(key), convertArrayToCamelCase(value)); + } else if (isObject(value)) { + set(acc, camelCase(key), convertToCamelCase(value)); + } else { + set(acc, camelCase(key), value); + } + return acc; + }, {} as U); export const useCurrentUser = (): AuthenticatedElasticUser | null => { const isMounted = useRef(false); const [user, setUser] = useState<AuthenticatedElasticUser | null>(null); diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts index e504344f3d25f..1527ea7dccac5 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts @@ -92,6 +92,13 @@ export const createStartServicesMock = (): StartServices => { return ({ ...core, + cases: { + getAllCases: jest.fn(), + getCaseView: jest.fn(), + getConfigureCases: jest.fn(), + getCreateCase: jest.fn(), + getRecentCases: jest.fn(), + }, data: { ...data, query: { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/use_manage_case_action.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/use_manage_case_action.tsx index 875bc5e647077..c19e5c26bdc94 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/use_manage_case_action.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/use_manage_case_action.tsx @@ -6,7 +6,7 @@ */ import { useEffect, useRef, useState } from 'react'; -import { ACTION_URL } from '../../../../../../cases/common/constants'; +import { ACTION_URL } from '../../../../../../cases/common'; import { KibanaServices } from '../../../../common/lib/kibana'; interface CaseAction { diff --git a/x-pack/plugins/security_solution/public/index.ts b/x-pack/plugins/security_solution/public/index.ts index f1d1bc3e6280b..55262fe039b4e 100644 --- a/x-pack/plugins/security_solution/public/index.ts +++ b/x-pack/plugins/security_solution/public/index.ts @@ -7,8 +7,8 @@ import { PluginInitializerContext } from '../../../../src/core/public'; import { Plugin } from './plugin'; -import { PluginSetup, PluginStart } from './types'; +import { PluginSetup } from './types'; export const plugin = (context: PluginInitializerContext): Plugin => new Plugin(context); -export { Plugin, PluginSetup, PluginStart }; +export { Plugin, PluginSetup }; diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx index faf8e4f2ddafc..bcf9953d70d83 100644 --- a/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx @@ -5,93 +5,62 @@ * 2.0. */ -import { EuiHorizontalRule, EuiText } from '@elastic/eui'; -import React, { useEffect, useMemo, useRef, useCallback } from 'react'; +import React, { useCallback } from 'react'; -import { FilterOptions, QueryParams } from '../../../cases/containers/types'; -import { DEFAULT_QUERY_PARAMS, useGetCases } from '../../../cases/containers/use_get_cases'; -import { LoadingPlaceholders } from '../loading_placeholders'; -import { NoCases } from './no_cases'; -import { RecentCases } from './recent_cases'; -import * as i18n from './translations'; +import { + getCaseDetailsUrl, + getCaseUrl, + getCreateCaseUrl, +} from '../../../common/components/link_to/redirect_to_case'; +import { useFormatUrl } from '../../../common/components/link_to'; import { useKibana } from '../../../common/lib/kibana'; import { APP_ID } from '../../../../common/constants'; import { SecurityPageName } from '../../../app/types'; -import { useFormatUrl } from '../../../common/components/link_to'; -import { LinkAnchor } from '../../../common/components/links'; - -const usePrevious = (value: FilterOptions) => { - const ref = useRef(); - useEffect(() => { - (ref.current as unknown) = value; - }); - return ref.current; -}; +import { AllCasesNavProps } from '../../../cases/components/all_cases'; const MAX_CASES_TO_SHOW = 3; +const RecentCasesComponent = () => { + const { formatUrl } = useFormatUrl(SecurityPageName.case); + const { + cases: casesUi, + application: { navigateToApp }, + } = useKibana().services; -const queryParams: QueryParams = { - ...DEFAULT_QUERY_PARAMS, - perPage: MAX_CASES_TO_SHOW, -}; - -const StatefulRecentCasesComponent = React.memo( - ({ filterOptions }: { filterOptions: FilterOptions }) => { - const { formatUrl } = useFormatUrl(SecurityPageName.case); - const { navigateToApp } = useKibana().services.application; - const previousFilterOptions = usePrevious(filterOptions); - const { data, loading, setFilters } = useGetCases(queryParams); - const isLoadingCases = useMemo( - () => loading.indexOf('cases') > -1 || loading.indexOf('caseUpdate') > -1, - [loading] - ); + const goToCases = useCallback( + (ev) => { + ev.preventDefault(); + navigateToApp(`${APP_ID}:${SecurityPageName.case}`); + }, + [navigateToApp] + ); - const goToCases = useCallback( - (ev) => { - ev.preventDefault(); - navigateToApp(`${APP_ID}:${SecurityPageName.case}`); + return casesUi.getRecentCases({ + allCasesNavigation: { + href: formatUrl(getCaseUrl()), + onClick: goToCases, + }, + caseDetailsNavigation: { + href: ({ detailName, subCaseId }: AllCasesNavProps) => { + return formatUrl(getCaseDetailsUrl({ id: detailName, subCaseId })); }, - [navigateToApp] - ); - - const allCasesLink = useMemo( - () => ( - <LinkAnchor onClick={goToCases} href={formatUrl('')}> - {' '} - {i18n.VIEW_ALL_CASES} - </LinkAnchor> - ), - [goToCases, formatUrl] - ); - - useEffect(() => { - if (previousFilterOptions !== undefined && previousFilterOptions !== filterOptions) { - setFilters(filterOptions); - } - }, [previousFilterOptions, filterOptions, setFilters]); - - const content = useMemo( - () => - isLoadingCases ? ( - <LoadingPlaceholders lines={2} placeholders={3} /> - ) : !isLoadingCases && data.cases.length === 0 ? ( - <NoCases /> - ) : ( - <RecentCases cases={data.cases} /> - ), - [isLoadingCases, data] - ); - - return ( - <EuiText color="subdued" size="s"> - {content} - <EuiHorizontalRule margin="s" /> - <EuiText size="xs">{allCasesLink}</EuiText> - </EuiText> - ); - } -); + onClick: ({ detailName, subCaseId, search }: AllCasesNavProps) => { + navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { + path: getCaseDetailsUrl({ id: detailName, search, subCaseId }), + }); + }, + }, + createCaseNavigation: { + href: formatUrl(getCreateCaseUrl()), + onClick: () => { + navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { + path: getCreateCaseUrl(), + }); + }, + }, + maxCasesToShow: MAX_CASES_TO_SHOW, + }); +}; -StatefulRecentCasesComponent.displayName = 'StatefulRecentCasesComponent'; +RecentCasesComponent.displayName = 'RecentCasesComponent'; -export const StatefulRecentCases = React.memo(StatefulRecentCasesComponent); +export const RecentCases = React.memo(RecentCasesComponent); diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.test.tsx deleted file mode 100644 index ccb2d776f6e61..0000000000000 --- a/x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.test.tsx +++ /dev/null @@ -1,40 +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 { mount } from 'enzyme'; - -import { useKibana } from '../../../../common/lib/kibana'; -import '../../../../common/mock/match_media'; -import { TestProviders } from '../../../../common/mock'; -import { NoCases } from '.'; - -jest.mock('../../../../common/lib/kibana'); - -const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>; - -describe('RecentCases', () => { - let navigateToApp: jest.Mock; - - beforeEach(() => { - navigateToApp = jest.fn(); - useKibanaMock().services.application.navigateToApp = navigateToApp; - }); - - it('if no cases, you should be able to create a case by clicking on the link "start a new case"', () => { - const wrapper = mount( - <TestProviders> - <NoCases /> - </TestProviders> - ); - wrapper.find(`[data-test-subj="no-cases-create-case"]`).first().simulate('click'); - expect(navigateToApp).toHaveBeenCalledWith('securitySolution:case', { - path: - "/create?sourcerer=(default:!('apm-*-transaction*','auditbeat-*','endgame-*','filebeat-*','logs-*','packetbeat-*','winlogbeat-*'))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now%2Fd,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now%2Fd)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now%2Fd,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now%2Fd)))", - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.tsx deleted file mode 100644 index 9d538dcf88a89..0000000000000 --- a/x-pack/plugins/security_solution/public/overview/components/recent_cases/no_cases/index.tsx +++ /dev/null @@ -1,53 +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, useCallback } from 'react'; - -import { APP_ID } from '../../../../../common/constants'; -import { getCreateCaseUrl } from '../../../../common/components/link_to/redirect_to_case'; -import { LinkAnchor } from '../../../../common/components/links'; -import { useFormatUrl } from '../../../../common/components/link_to'; -import * as i18n from '../translations'; -import { useKibana } from '../../../../common/lib/kibana'; -import { SecurityPageName } from '../../../../app/types'; - -const NoCasesComponent = () => { - const { formatUrl, search } = useFormatUrl(SecurityPageName.case); - const { navigateToApp } = useKibana().services.application; - - const goToCreateCase = useCallback( - (ev) => { - ev.preventDefault(); - navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { - path: getCreateCaseUrl(search), - }); - }, - [navigateToApp, search] - ); - const newCaseLink = useMemo( - () => ( - <LinkAnchor - data-test-subj="no-cases-create-case" - onClick={goToCreateCase} - href={formatUrl(getCreateCaseUrl())} - >{` ${i18n.START_A_NEW_CASE}`}</LinkAnchor> - ), - [formatUrl, goToCreateCase] - ); - - return ( - <> - <span>{i18n.NO_CASES}</span> - {newCaseLink} - {'!'} - </> - ); -}; - -NoCasesComponent.displayName = 'NoCasesComponent'; - -export const NoCases = React.memo(NoCasesComponent); diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/recent_cases.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_cases/recent_cases.tsx deleted file mode 100644 index 7cc60878bcfe2..0000000000000 --- a/x-pack/plugins/security_solution/public/overview/components/recent_cases/recent_cases.tsx +++ /dev/null @@ -1,70 +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 { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; -import React from 'react'; -import styled from 'styled-components'; - -import { Case } from '../../../cases/containers/types'; -import { getCaseDetailsUrl } from '../../../common/components/link_to/redirect_to_case'; -import { MarkdownRenderer } from '../../../common/components/markdown_editor'; -import { useFormatUrl } from '../../../common/components/link_to'; -import { IconWithCount } from '../recent_timelines/counts'; -import { LinkAnchor } from '../../../common/components/links'; -import * as i18n from './translations'; -import { useKibana } from '../../../common/lib/kibana'; -import { APP_ID } from '../../../../common/constants'; -import { SecurityPageName } from '../../../app/types'; - -const MarkdownContainer = styled.div` - max-height: 150px; - overflow-y: auto; - width: 300px; -`; - -const RecentCasesComponent = ({ cases }: { cases: Case[] }) => { - const { formatUrl, search } = useFormatUrl(SecurityPageName.case); - const { navigateToApp } = useKibana().services.application; - - return ( - <> - {cases.map((c, i) => ( - <EuiFlexGroup key={c.id} gutterSize="none" justifyContent="spaceBetween"> - <EuiFlexItem grow={false}> - <EuiText size="s"> - <LinkAnchor - onClick={(ev: { preventDefault: () => void }) => { - ev.preventDefault(); - navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { - path: getCaseDetailsUrl({ id: c.id, search }), - }); - }} - href={formatUrl(getCaseDetailsUrl({ id: c.id }))} - > - {c.title} - </LinkAnchor> - </EuiText> - - <IconWithCount count={c.totalComment} icon={'editorComment'} tooltip={i18n.COMMENTS} /> - {c.description && c.description.length && ( - <MarkdownContainer> - <EuiText color="subdued" size="xs"> - <MarkdownRenderer disableLinks={true}>{c.description}</MarkdownRenderer> - </EuiText> - </MarkdownContainer> - )} - {i !== cases.length - 1 && <EuiSpacer size="l" />} - </EuiFlexItem> - </EuiFlexGroup> - ))} - </> - ); -}; - -RecentCasesComponent.displayName = 'RecentCasesComponent'; - -export const RecentCases = React.memo(RecentCasesComponent); diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/translations.ts b/x-pack/plugins/security_solution/public/overview/components/recent_cases/translations.ts deleted file mode 100644 index 37588c0c4bbed..0000000000000 --- a/x-pack/plugins/security_solution/public/overview/components/recent_cases/translations.ts +++ /dev/null @@ -1,51 +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 { i18n } from '@kbn/i18n'; - -export const COMMENTS = i18n.translate('xpack.securitySolution.recentCases.commentsTooltip', { - defaultMessage: 'Comments', -}); - -export const MY_RECENTLY_REPORTED_CASES = i18n.translate( - 'xpack.securitySolution.overview.myRecentlyReportedCasesButtonLabel', - { - defaultMessage: 'My recently reported cases', - } -); - -export const NO_CASES = i18n.translate('xpack.securitySolution.recentCases.noCasesMessage', { - defaultMessage: 'No cases have been created yet. Put your detective hat on and', -}); - -export const RECENTLY_CREATED_CASES = i18n.translate( - 'xpack.securitySolution.overview.recentlyCreatedCasesButtonLabel', - { - defaultMessage: 'Recently created cases', - } -); - -export const START_A_NEW_CASE = i18n.translate( - 'xpack.securitySolution.recentCases.startNewCaseLink', - { - defaultMessage: 'start a new case', - } -); - -export const VIEW_ALL_CASES = i18n.translate( - 'xpack.securitySolution.recentCases.viewAllCasesLink', - { - defaultMessage: 'View all cases', - } -); - -export const CASES_FILTER_CONTROL = i18n.translate( - 'xpack.securitySolution.recentCases.controlLegend', - { - defaultMessage: 'Cases filter', - } -); diff --git a/x-pack/plugins/security_solution/public/overview/components/sidebar/index.tsx b/x-pack/plugins/security_solution/public/overview/components/sidebar/index.tsx index 7ae15ecb6f215..811078bd2e45f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/sidebar/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/sidebar/index.tsx @@ -8,7 +8,6 @@ import React, { useState } from 'react'; import { FilterMode as RecentTimelinesFilterMode } from '../recent_timelines/types'; -import { FilterMode as RecentCasesFilterMode } from '../recent_cases/types'; import { Sidebar } from './sidebar'; @@ -16,14 +15,9 @@ export const StatefulSidebar = React.memo(() => { const [recentTimelinesFilterBy, setRecentTimelinesFilterBy] = useState<RecentTimelinesFilterMode>( 'favorites' ); - const [recentCasesFilterBy, setRecentCasesFilterBy] = useState<RecentCasesFilterMode>( - 'recentlyCreated' - ); return ( <Sidebar - recentCasesFilterBy={recentCasesFilterBy} - setRecentCasesFilterBy={setRecentCasesFilterBy} recentTimelinesFilterBy={recentTimelinesFilterBy} setRecentTimelinesFilterBy={setRecentTimelinesFilterBy} /> diff --git a/x-pack/plugins/security_solution/public/overview/components/sidebar/sidebar.tsx b/x-pack/plugins/security_solution/public/overview/components/sidebar/sidebar.tsx index cd88b8f44dc7b..77cfa220f0722 100644 --- a/x-pack/plugins/security_solution/public/overview/components/sidebar/sidebar.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/sidebar/sidebar.tsx @@ -10,18 +10,14 @@ import React, { useMemo } from 'react'; import styled from 'styled-components'; import { ENABLE_NEWS_FEED_SETTING, NEWS_FEED_URL_SETTING } from '../../../../common/constants'; -import { Filters as RecentCasesFilters } from '../recent_cases/filters'; import { Filters as RecentTimelinesFilters } from '../recent_timelines/filters'; -import { StatefulRecentCases } from '../recent_cases'; import { StatefulRecentTimelines } from '../recent_timelines'; import { StatefulNewsFeed } from '../../../common/components/news_feed'; import { FilterMode as RecentTimelinesFilterMode } from '../recent_timelines/types'; -import { FilterMode as RecentCasesFilterMode } from '../recent_cases/types'; -import { DEFAULT_FILTER_OPTIONS } from '../../../cases/containers/use_get_cases'; import { SidebarHeader } from '../../../common/components/sidebar_header'; -import { useCurrentUser } from '../../../common/lib/kibana'; import * as i18n from '../../pages/translations'; +import { RecentCases } from '../recent_cases'; const SidebarFlexGroup = styled(EuiFlexGroup)` width: 305px; @@ -37,79 +33,42 @@ SidebarSpacerComponent.displayName = 'SidebarSpacerComponent'; const Spacer = React.memo(SidebarSpacerComponent); export const Sidebar = React.memo<{ - recentCasesFilterBy: RecentCasesFilterMode; recentTimelinesFilterBy: RecentTimelinesFilterMode; - setRecentCasesFilterBy: (filterBy: RecentCasesFilterMode) => void; setRecentTimelinesFilterBy: (filterBy: RecentTimelinesFilterMode) => void; -}>( - ({ - recentCasesFilterBy, - recentTimelinesFilterBy, - setRecentCasesFilterBy, - setRecentTimelinesFilterBy, - }) => { - const currentUser = useCurrentUser(); - const recentCasesFilters = useMemo( - () => ( - <RecentCasesFilters - filterBy={recentCasesFilterBy} - setFilterBy={setRecentCasesFilterBy} - showMyRecentlyReported={currentUser != null} - /> - ), - [currentUser, recentCasesFilterBy, setRecentCasesFilterBy] - ); - const recentCasesFilterOptions = useMemo( - () => - recentCasesFilterBy === 'myRecentlyReported' && currentUser != null - ? { - ...DEFAULT_FILTER_OPTIONS, - reporters: [ - { - email: currentUser.email, - full_name: currentUser.fullName, - username: currentUser.username, - }, - ], - } - : DEFAULT_FILTER_OPTIONS, - [currentUser, recentCasesFilterBy] - ); - const recentTimelinesFilters = useMemo( - () => ( - <RecentTimelinesFilters - filterBy={recentTimelinesFilterBy} - setFilterBy={setRecentTimelinesFilterBy} - /> - ), - [recentTimelinesFilterBy, setRecentTimelinesFilterBy] - ); +}>(({ recentTimelinesFilterBy, setRecentTimelinesFilterBy }) => { + const recentTimelinesFilters = useMemo( + () => ( + <RecentTimelinesFilters + filterBy={recentTimelinesFilterBy} + setFilterBy={setRecentTimelinesFilterBy} + /> + ), + [recentTimelinesFilterBy, setRecentTimelinesFilterBy] + ); - return ( - <SidebarFlexGroup direction="column" gutterSize="none"> - <EuiFlexItem grow={false}> - <SidebarHeader title={i18n.RECENT_CASES}>{recentCasesFilters}</SidebarHeader> - <StatefulRecentCases filterOptions={recentCasesFilterOptions} /> - </EuiFlexItem> + return ( + <SidebarFlexGroup direction="column" gutterSize="none"> + <EuiFlexItem grow={false}> + <RecentCases /> + </EuiFlexItem> - <Spacer /> + <Spacer /> - <EuiFlexItem grow={false}> - <SidebarHeader title={i18n.RECENT_TIMELINES}>{recentTimelinesFilters}</SidebarHeader> - <StatefulRecentTimelines filterBy={recentTimelinesFilterBy} /> - </EuiFlexItem> + <EuiFlexItem grow={false}> + <SidebarHeader title={i18n.RECENT_TIMELINES}>{recentTimelinesFilters}</SidebarHeader> + <StatefulRecentTimelines filterBy={recentTimelinesFilterBy} /> + </EuiFlexItem> - <Spacer /> + <Spacer /> - <EuiFlexItem grow={false}> - <StatefulNewsFeed - enableNewsFeedSetting={ENABLE_NEWS_FEED_SETTING} - newsFeedSetting={NEWS_FEED_URL_SETTING} - /> - </EuiFlexItem> - </SidebarFlexGroup> - ); - } -); + <EuiFlexItem grow={false}> + <StatefulNewsFeed + enableNewsFeedSetting={ENABLE_NEWS_FEED_SETTING} + newsFeedSetting={NEWS_FEED_URL_SETTING} + /> + </EuiFlexItem> + </SidebarFlexGroup> + ); +}); Sidebar.displayName = 'Sidebar'; diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 23f3472b470b5..efbe857d168d8 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -63,7 +63,6 @@ import { IndexFieldsStrategyResponse, } from '../common/search_strategy/index_fields'; import { SecurityAppStore } from './common/store/store'; -import { getCaseConnectorUI } from './cases/components/connectors'; import { licenseService } from './common/hooks/use_license'; import { SecuritySolutionUiConfigType } from './common/types'; @@ -327,8 +326,6 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S }, }); - plugins.triggersActionsUi.actionTypeRegistry.register(getCaseConnectorUI()); - return { resolver: async () => { /** diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx index b959e80e2cc98..bc9876b207284 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx @@ -11,9 +11,18 @@ import { mount } from 'enzyme'; import { useKibana } from '../../../../common/lib/kibana'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { mockTimelineModel, TestProviders } from '../../../../common/mock'; -import { useAllCasesModal } from '../../../../cases/components/use_all_cases_modal'; import { AddToCaseButton } from '.'; +jest.mock('../../../../common/components/link_to', () => { + const original = jest.requireActual('../../../../common/components/link_to'); + return { + ...original, + useFormatUrl: jest.fn().mockReturnValue({ + formatUrl: jest.fn(), + search: '', + }), + }; +}); const mockDispatch = jest.fn(); jest.mock('react-redux', () => { const original = jest.requireActual('react-redux'); @@ -25,57 +34,51 @@ jest.mock('react-redux', () => { jest.mock('../../../../common/lib/kibana'); jest.mock('../../../../common/hooks/use_selector'); -jest.mock('../../../../cases/components/use_all_cases_modal'); const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>; -const useAllCasesModalMock = useAllCasesModal as jest.Mock; -describe('EventColumnView', () => { +describe('AddToCaseButton', () => { const navigateToApp = jest.fn(); beforeEach(() => { useKibanaMock().services.application.navigateToApp = navigateToApp; - (useDeepEqualSelector as jest.Mock).mockReturnValue(mockTimelineModel); }); it('navigates to the correct path without id', async () => { - useAllCasesModalMock.mockImplementation(({ onRowClick }) => { - onRowClick(); - - return { - modal: <>{'test'}</>, - openModal: jest.fn(), - isModalOpen: true, - closeModal: jest.fn(), - }; - }); - - mount( + const here = jest.fn(); + useKibanaMock().services.cases.getAllCasesSelectorModal = here.mockImplementation( + ({ onRowClick }) => { + onRowClick(); + return <></>; + } + ); + (useDeepEqualSelector as jest.Mock).mockReturnValue(mockTimelineModel); + const wrapper = mount( <TestProviders> <AddToCaseButton timelineId={'timeline-1'} /> </TestProviders> ); + wrapper.find(`[data-test-subj="attach-timeline-case-button"]`).first().simulate('click'); + wrapper.find(`[data-test-subj="attach-timeline-existing-case"]`).first().simulate('click'); expect(navigateToApp).toHaveBeenCalledWith('securitySolution:case', { path: '/create' }); }); it('navigates to the correct path with id', async () => { - useAllCasesModalMock.mockImplementation(({ onRowClick }) => { - onRowClick({ id: 'case-id' }); - - return { - modal: <>{'test'}</>, - openModal: jest.fn(), - isModalOpen: true, - closeModal: jest.fn(), - }; - }); - - mount( + useKibanaMock().services.cases.getAllCasesSelectorModal = jest + .fn() + .mockImplementation(({ onRowClick }) => { + onRowClick({ id: 'case-id' }); + return <></>; + }); + (useDeepEqualSelector as jest.Mock).mockReturnValue(mockTimelineModel); + const wrapper = mount( <TestProviders> <AddToCaseButton timelineId={'timeline-1'} /> </TestProviders> ); + wrapper.find(`[data-test-subj="attach-timeline-case-button"]`).first().simulate('click'); + wrapper.find(`[data-test-subj="attach-timeline-existing-case"]`).first().simulate('click'); expect(navigateToApp).toHaveBeenCalledWith('securitySolution:case', { path: '/case-id' }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx index 5cba64299ee9d..a4c6fe1e344b3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx @@ -10,17 +10,20 @@ import { EuiButton, EuiContextMenuPanel, EuiContextMenuItem, EuiPopover } from ' import React, { useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; +import { Case, SubCase } from '../../../../../../cases/common'; import { APP_ID } from '../../../../../common/constants'; import { timelineSelectors } from '../../../../timelines/store/timeline'; -import { useAllCasesModal } from '../../../../cases/components/use_all_cases_modal'; import { setInsertTimeline, showTimeline } from '../../../store/timeline/actions'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; -import { useKibana } from '../../../../common/lib/kibana'; +import { useGetUserSavedObjectPermissions, useKibana } from '../../../../common/lib/kibana'; import { TimelineStatus, TimelineId, TimelineType } from '../../../../../common/types/timeline'; -import { getCreateCaseUrl, getCaseDetailsUrl } from '../../../../common/components/link_to'; +import { + getCreateCaseUrl, + getCaseDetailsUrl, + useFormatUrl, +} from '../../../../common/components/link_to'; import { SecurityPageName } from '../../../../app/types'; import { timelineDefaults } from '../../../../timelines/store/timeline/defaults'; -import { Case, SubCase } from '../../../../cases/containers/types'; import * as i18n from '../../timeline/properties/translations'; interface Props { @@ -29,7 +32,10 @@ interface Props { const AddToCaseButtonComponent: React.FC<Props> = ({ timelineId }) => { const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); - const { navigateToApp } = useKibana().services.application; + const { + cases, + application: { navigateToApp }, + } = useKibana().services; const dispatch = useDispatch(); const { graphEventId, @@ -44,13 +50,14 @@ const AddToCaseButtonComponent: React.FC<Props> = ({ timelineId }) => { ) ); const [isPopoverOpen, setPopover] = useState(false); + const [isCaseModalOpen, openCaseModal] = useState(false); const onRowClick = useCallback( async (theCase?: Case | SubCase) => { + openCaseModal(false); await navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { path: theCase != null ? getCaseDetailsUrl({ id: theCase.id }) : getCreateCaseUrl(), }); - dispatch( setInsertTimeline({ graphEventId, @@ -63,7 +70,15 @@ const AddToCaseButtonComponent: React.FC<Props> = ({ timelineId }) => { [dispatch, graphEventId, navigateToApp, savedObjectId, timelineId, timelineTitle] ); - const { modal: allCasesModal, openModal: openCaseModal } = useAllCasesModal({ onRowClick }); + const { formatUrl } = useFormatUrl(SecurityPageName.case); + const userPermissions = useGetUserSavedObjectPermissions(); + const goToCreateCase = useCallback( + (ev) => { + ev.preventDefault(); + onRowClick(); + }, + [onRowClick] + ); const handleButtonClick = useCallback(() => { setPopover((currentIsOpen) => !currentIsOpen); @@ -73,12 +88,9 @@ const AddToCaseButtonComponent: React.FC<Props> = ({ timelineId }) => { const handleNewCaseClick = useCallback(() => { handlePopoverClose(); - - dispatch(showTimeline({ id: TimelineId.active, show: false })); - navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { path: getCreateCaseUrl(), - }).then(() => + }).then(() => { dispatch( setInsertTimeline({ graphEventId, @@ -86,8 +98,9 @@ const AddToCaseButtonComponent: React.FC<Props> = ({ timelineId }) => { timelineSavedObjectId: savedObjectId, timelineTitle: timelineTitle.length > 0 ? timelineTitle : i18n.UNTITLED_TIMELINE, }) - ) - ); + ); + dispatch(showTimeline({ id: TimelineId.active, show: false })); + }); }, [ dispatch, graphEventId, @@ -100,7 +113,7 @@ const AddToCaseButtonComponent: React.FC<Props> = ({ timelineId }) => { const handleExistingCaseClick = useCallback(() => { handlePopoverClose(); - openCaseModal(); + openCaseModal(true); }, [openCaseModal, handlePopoverClose]); const closePopover = useCallback(() => { @@ -156,7 +169,15 @@ const AddToCaseButtonComponent: React.FC<Props> = ({ timelineId }) => { > <EuiContextMenuPanel items={items} /> </EuiPopover> - {allCasesModal} + {isCaseModalOpen && + cases.getAllCasesSelectorModal({ + createCaseNavigation: { + href: formatUrl(getCreateCaseUrl()), + onClick: goToCreateCase, + }, + onRowClick, + userCanCrud: userPermissions?.crud ?? false, + })} </> ); }; diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.ts index a6c2126f95e8d..d1c798a27b6c4 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/api.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/api.ts @@ -12,7 +12,7 @@ import { pipe } from 'fp-ts/lib/pipeable'; // eslint-disable-next-line no-restricted-imports import isEmpty from 'lodash/isEmpty'; -import { throwErrors } from '../../../../cases/common/api'; +import { throwErrors } from '../../../../cases/common'; import { TimelineResponse, TimelineResponseType, @@ -42,8 +42,7 @@ import { import { KibanaServices } from '../../common/lib/kibana'; import { ExportSelectedData } from '../../common/components/generic_downloader'; - -import { createToasterPlainError } from '../../cases/containers/utils'; +import { ToasterError } from '../../common/components/toasters'; import { ImportDataProps, ImportDataResponse, @@ -61,7 +60,7 @@ interface RequestPatchTimeline<T = string> extends RequestPostTimeline { } type RequestPersistTimeline = RequestPostTimeline & Partial<RequestPatchTimeline<null | string>>; - +const createToasterPlainError = (message: string) => new ToasterError([message]); const decodeTimelineResponse = (respTimeline?: TimelineResponse | TimelineErrorResponse) => pipe( TimelineResponseType.decode(respTimeline), diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 7b9cd2f6e1db5..d4e2601554187 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -21,6 +21,7 @@ import { TriggersAndActionsUIPublicPluginSetup as TriggersActionsSetup, TriggersAndActionsUIPublicPluginStart as TriggersActionsStart, } from '../../triggers_actions_ui/public'; +import { CasesUiStart } from '../../cases/public'; import { SecurityPluginSetup } from '../../security/public'; import { ResolverPluginSetup } from './resolver/types'; import { Inspect } from '../common/search_strategy'; @@ -46,6 +47,7 @@ export interface SetupPlugins { } export interface StartPlugins { + cases: CasesUiStart; data: DataPublicPluginStart; embeddable: EmbeddableStart; inspector: InspectorStart; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index dfa8bd387cbd7..fe9bf6eac14d0 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17801,201 +17801,58 @@ "xpack.securitySolution.cases.allCases.actions": "アクション", "xpack.securitySolution.cases.allCases.comments": "コメント", "xpack.securitySolution.cases.allCases.noTagsAvailable": "利用可能なタグがありません", - "xpack.securitySolution.cases.caseModal.title": "ケースを選択", "xpack.securitySolution.cases.caseSavedObjectNoPermissionsMessage": "ケースを表示するには、Kibana スペースで保存されたオブジェクト管理機能の権限が必要です。詳細については、Kibana管理者に連絡してください。", "xpack.securitySolution.cases.caseSavedObjectNoPermissionsTitle": "Kibana機能権限が必要です", - "xpack.securitySolution.cases.caseTable.addNewCase": "新規ケースの追加", - "xpack.securitySolution.cases.caseTable.bulkActions": "一斉アクション", - "xpack.securitySolution.cases.caseTable.bulkActions.closeSelectedTitle": "選択した項目を閉じる", - "xpack.securitySolution.cases.caseTable.bulkActions.deleteSelectedTitle": "選択した項目を削除", - "xpack.securitySolution.cases.caseTable.bulkActions.openSelectedTitle": "選択した項目を再開", "xpack.securitySolution.cases.caseTable.caseDetailsLinkAria": "クリックすると、タイトル{detailName}のケースを表示します", - "xpack.securitySolution.cases.caseTable.closed": "終了", "xpack.securitySolution.cases.caseTable.closedCases": "終了したケース", - "xpack.securitySolution.cases.caseTable.delete": "削除", - "xpack.securitySolution.cases.caseTable.incidentSystem": "インシデント管理システム", "xpack.securitySolution.cases.caseTable.inProgressCases": "進行中のケース", - "xpack.securitySolution.cases.caseTable.noCases.body": "表示するケースがありません。新しいケースを作成するか、または上記のフィルター設定を変更してください。", - "xpack.securitySolution.cases.caseTable.noCases.title": "ケースなし", - "xpack.securitySolution.cases.caseTable.notPushed": "プッシュされません", "xpack.securitySolution.cases.caseTable.openCases": "ケースを開く", - "xpack.securitySolution.cases.caseTable.refreshTitle": "更新", - "xpack.securitySolution.cases.caseTable.requiresUpdate": " 更新が必要", - "xpack.securitySolution.cases.caseTable.searchAriaLabel": "ケースの検索", - "xpack.securitySolution.cases.caseTable.searchPlaceholder": "例:ケース名", - "xpack.securitySolution.cases.caseTable.serviceNowLinkAria": "クリックすると、servicenowでインシデントを表示します", - "xpack.securitySolution.cases.caseTable.snIncident": "外部インシデント", - "xpack.securitySolution.cases.caseTable.status": "ステータス", - "xpack.securitySolution.cases.caseTable.upToDate": " は最新です", - "xpack.securitySolution.cases.caseView.actionHeadline": "{actionDate} の {userName} {actionName}", - "xpack.securitySolution.cases.caseView.actionLabel.addComment": "コメントを追加しました", - "xpack.securitySolution.cases.caseView.actionLabel.addDescription": "説明を追加しました", - "xpack.securitySolution.cases.caseView.actionLabel.addedField": "追加しました", - "xpack.securitySolution.cases.caseView.actionLabel.changededField": "変更しました", - "xpack.securitySolution.cases.caseView.actionLabel.editedField": "編集しました", - "xpack.securitySolution.cases.caseView.actionLabel.on": "日付", - "xpack.securitySolution.cases.caseView.actionLabel.pushedNewIncident": "新しいインシデントとしてプッシュしました", - "xpack.securitySolution.cases.caseView.actionLabel.removedField": "削除しました", - "xpack.securitySolution.cases.caseView.actionLabel.removedThirdParty": "外部のインシデント管理システムを削除しました", - "xpack.securitySolution.cases.caseView.actionLabel.selectedThirdParty": "インシデント管理システムとして{ thirdParty }を選択しました", - "xpack.securitySolution.cases.caseView.actionLabel.updateIncident": "インシデントを更新しました", - "xpack.securitySolution.cases.caseView.actionLabel.viewIncident": "{incidentNumber}を表示", - "xpack.securitySolution.cases.caseView.alertCommentLabelTitle": "アラートを追加しました", - "xpack.securitySolution.cases.caseView.alertRuleDeletedLabelTitle": "アラートを追加しました", - "xpack.securitySolution.cases.caseView.alreadyPushedToExternalService": "すでに{ externalService }インシデントにプッシュしました", - "xpack.securitySolution.cases.caseView.appropiateLicense": "適切なライセンス", "xpack.securitySolution.cases.caseView.backLabel": "ケースに戻る", "xpack.securitySolution.cases.caseView.breadcrumb": "作成", "xpack.securitySolution.cases.caseView.cancel": "キャンセル", - "xpack.securitySolution.cases.caseView.case": "ケース", - "xpack.securitySolution.cases.caseView.caseClosed": "ケースを閉じました", - "xpack.securitySolution.cases.caseView.caseInProgress": "進行中のケース", "xpack.securitySolution.cases.caseView.caseName": "ケース名", - "xpack.securitySolution.cases.caseView.caseOpened": "ケースを開きました", - "xpack.securitySolution.cases.caseView.caseRefresh": "ケースを更新", "xpack.securitySolution.cases.caseView.closeCase": "ケースを閉じる", "xpack.securitySolution.cases.caseView.closedOn": "終了日", - "xpack.securitySolution.cases.caseView.cloudDeploymentLink": "クラウド展開", - "xpack.securitySolution.cases.caseView.comment": "コメント", "xpack.securitySolution.cases.caseView.comment.addComment": "コメントを追加", "xpack.securitySolution.cases.caseView.comment.addCommentHelpText": "新しいコメントを追加...", "xpack.securitySolution.cases.caseView.commentFieldRequiredError": "コメントが必要です。", - "xpack.securitySolution.cases.caseView.connectorConfigureLink": "コネクター", "xpack.securitySolution.cases.caseView.connectors": "外部インシデント管理システム", - "xpack.securitySolution.cases.caseView.copyCommentLinkAria": "参照リンクをコピー", "xpack.securitySolution.cases.caseView.create": "新規ケースを作成", "xpack.securitySolution.cases.caseView.createCase": "ケースを作成", "xpack.securitySolution.cases.caseView.description": "説明", "xpack.securitySolution.cases.caseView.description.save": "保存", "xpack.securitySolution.cases.caseView.edit": "編集", - "xpack.securitySolution.cases.caseView.edit.comment": "コメントを編集", - "xpack.securitySolution.cases.caseView.edit.description": "説明を編集", - "xpack.securitySolution.cases.caseView.edit.quote": "お客様の声", - "xpack.securitySolution.cases.caseView.editActionsLinkAria": "クリックすると、すべてのアクションを表示します", "xpack.securitySolution.cases.caseView.editConnector": "外部インシデント管理システムを変更", - "xpack.securitySolution.cases.caseView.editTagsLinkAria": "クリックすると、タグを編集します", - "xpack.securitySolution.cases.caseView.emailBody": "ケースリファレンス:{caseUrl}", - "xpack.securitySolution.cases.caseView.emailSubject": "セキュリティケース - {caseTitle}", - "xpack.securitySolution.cases.caseView.errorsPushServiceCallOutTitle": "ケースを外部システムにプッシュするには、以下が必要です。", - "xpack.securitySolution.cases.caseView.fieldChanged": "変更されたコネクターフィールド", "xpack.securitySolution.cases.caseView.fieldRequiredError": "必須フィールド", - "xpack.securitySolution.cases.caseView.generatedAlertCommentLabelTitle": "から追加されました", "xpack.securitySolution.cases.caseView.goToDocumentationButton": "ドキュメンテーションを表示", "xpack.securitySolution.cases.caseView.markedCaseAs": "ケースを設定", "xpack.securitySolution.cases.caseView.markInProgress": "実行中に設定", - "xpack.securitySolution.cases.caseView.moveToCommentAria": "参照されたコメントをハイライト", "xpack.securitySolution.cases.caseView.name": "名前", "xpack.securitySolution.cases.caseView.noReportersAvailable": "利用可能なレポートがありません。", "xpack.securitySolution.cases.caseView.noTags": "現在、このケースにタグは割り当てられていません。", "xpack.securitySolution.cases.caseView.openedOn": "開始日", "xpack.securitySolution.cases.caseView.optional": "オプション", "xpack.securitySolution.cases.caseView.particpantsLabel": "参加者", - "xpack.securitySolution.cases.caseView.pushNamedIncident": "{ thirdParty }インシデントとしてプッシュ", - "xpack.securitySolution.cases.caseView.pushThirdPartyIncident": "外部インシデントとしてプッシュ", - "xpack.securitySolution.cases.caseView.pushToServiceDisableBecauseCaseClosedDescription": "終了したケースは外部システムに送信できません。外部システムでケースを開始または更新したい場合にはケースを再開します。", - "xpack.securitySolution.cases.caseView.pushToServiceDisableBecauseCaseClosedTitle": "ケースを再開する", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByConfigDescription": "kibana.ymlファイルは、特定のコネクターのみを許可するように構成されています。外部システムでケースを開けるようにするには、xpack.actions.enabledActiontypes設定に.[actionTypeId] (例:.servicenow | .jira) を追加します。詳細は{link}をご覧ください。", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByConfigTitle": "Kibanaの構成ファイルで外部サービスを有効にする", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByInvalidConnector": "外部サービスに更新を送信するために使用されるコネクターが削除されました。外部システムでケースを更新するには、別のコネクターを選択するか、新しいコネクターを作成してください。", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByLicenseDescription": "{appropriateLicense}があるか、{cloud}を使用しているか、無償試用版をテストしているときには、外部システムでケースを開くことができます。", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByLicenseTitle": "適切なライセンスにアップグレード", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoCaseConfigDescription": "外部システムでケースを開いて更新するには、このケースの外部インシデント管理システムを選択する必要があります。", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoCaseConfigTitle": "外部コネクターを選択", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoConfigTitle": "外部コネクターを構成", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoConnectors": "外部システムでケースを開いて更新するには、{link}を設定する必要があります。", "xpack.securitySolution.cases.caseView.reopenCase": "ケースを再開", "xpack.securitySolution.cases.caseView.reporterLabel": "報告者", - "xpack.securitySolution.cases.caseView.requiredUpdateToExternalService": "{ externalService }インシデントの更新が必要です", "xpack.securitySolution.cases.caseView.sendAlertToTimelineTooltip": "タイムラインで調査", - "xpack.securitySolution.cases.caseView.sendEmalLinkAria": "クリックすると、{user}に電子メールを送信します", - "xpack.securitySolution.cases.caseView.showAlertTooltip": "アラートの詳細を表示", - "xpack.securitySolution.cases.caseView.statusLabel": "ステータス", - "xpack.securitySolution.cases.caseView.syncAlertsLabel": "アラートの同期", "xpack.securitySolution.cases.caseView.tags": "タグ", "xpack.securitySolution.cases.caseView.to": "に", "xpack.securitySolution.cases.caseView.unknown": "不明", - "xpack.securitySolution.cases.caseView.unknownRule.label": "不明なルール", - "xpack.securitySolution.cases.caseView.updateNamedIncident": "{ thirdParty }インシデントを更新", - "xpack.securitySolution.cases.caseView.updateThirdPartyIncident": "外部インシデントを更新", "xpack.securitySolution.cases.common.noConnector": "コネクターを選択していません", - "xpack.securitySolution.cases.components.connectors.cases.actionTypeTitle": "ケース", - "xpack.securitySolution.cases.components.connectors.cases.addNewCaseOption": "新規ケースの追加", - "xpack.securitySolution.cases.components.connectors.cases.caseRequired": "ケースの選択が必要です。", - "xpack.securitySolution.cases.components.connectors.cases.casesDropdownPlaceholder": "ケースを選択", - "xpack.securitySolution.cases.components.connectors.cases.casesDropdownRowLabel": "ケース", - "xpack.securitySolution.cases.components.connectors.cases.commentLabel": "コメント", - "xpack.securitySolution.cases.components.connectors.cases.commentRequired": "コメントが必要です。", - "xpack.securitySolution.cases.components.connectors.cases.connectedCaseLabel": "接続されたケース", - "xpack.securitySolution.cases.components.connectors.cases.createCaseLabel": "ケースを作成", - "xpack.securitySolution.cases.components.connectors.cases.optionAddNewCase": "新しいケースに追加", - "xpack.securitySolution.cases.components.connectors.cases.optionAddToExistingCase": "既存のケースに追加", - "xpack.securitySolution.cases.components.connectors.cases.selectMessageText": "ケースを作成または更新します。", - "xpack.securitySolution.cases.configure.errorGetFields": "サービスからのフィールドの取得中にエラーが発生しました", - "xpack.securitySolution.cases.configure.successSaveToast": "保存された外部接続設定", - "xpack.securitySolution.cases.configureCases.addNewConnector": "新しいコネクターを追加", - "xpack.securitySolution.cases.configureCases.blankMappings": "1 つ以上のフィールドを { connectorName } にマッピングする必要があります", - "xpack.securitySolution.cases.configureCases.cancelButton": "キャンセル", - "xpack.securitySolution.cases.configureCases.caseClosureOptionsClosedIncident": "新しいインシデントが外部システムで閉じたときにセキュリティケースを自動的に閉じる", - "xpack.securitySolution.cases.configureCases.caseClosureOptionsDesc": "セキュリティケースの終了のしかたを定義します。自動ケース終了のためには、外部のインシデント管理システムへの接続を確立する必要がいります。", - "xpack.securitySolution.cases.configureCases.caseClosureOptionsLabel": "ケース終了オプション", - "xpack.securitySolution.cases.configureCases.caseClosureOptionsManual": "セキュリティケースを手動で閉じる", - "xpack.securitySolution.cases.configureCases.caseClosureOptionsNewIncident": "新しいインシデントを外部システムにプッシュするときにセキュリティケースを自動的に閉じる", - "xpack.securitySolution.cases.configureCases.caseClosureOptionsTitle": "ケースのクローズ", - "xpack.securitySolution.cases.configureCases.commentMapping": "コメント", - "xpack.securitySolution.cases.configureCases.editFieldMappingTitle": "{ thirdPartyName } フィールドマッピングを編集", - "xpack.securitySolution.cases.configureCases.fieldMappingDesc": "データを { thirdPartyName } にプッシュするときに、セキュリティケースフィールドを { thirdPartyName } フィールドにマッピングします。フィールドマッピングでは、{ thirdPartyName } への接続を確立する必要があります。", - "xpack.securitySolution.cases.configureCases.fieldMappingDescErr": "フィールドマッピングでは、{ thirdPartyName } への接続を確立する必要があります。接続資格情報を確認してください。", - "xpack.securitySolution.cases.configureCases.fieldMappingEditAppend": "末尾に追加", - "xpack.securitySolution.cases.configureCases.fieldMappingEditNothing": "何もしない", - "xpack.securitySolution.cases.configureCases.fieldMappingEditOverwrite": "上書き", - "xpack.securitySolution.cases.configureCases.fieldMappingFirstCol": "セキュリティケースフィールド", - "xpack.securitySolution.cases.configureCases.fieldMappingSecondCol": "{ thirdPartyName } フィールド", - "xpack.securitySolution.cases.configureCases.fieldMappingThirdCol": "編集時と更新時", - "xpack.securitySolution.cases.configureCases.fieldMappingTitle": "{ thirdPartyName } フィールドマッピング", "xpack.securitySolution.cases.configureCases.headerTitle": "ケースを構成", - "xpack.securitySolution.cases.configureCases.incidentManagementSystemDesc": "オプションとして、セキュリティケースを選択した外部のインシデント管理システムに接続できます。そうすると、選択したサードパーティシステム内でケースデータをインシデントとしてプッシュできます。", - "xpack.securitySolution.cases.configureCases.incidentManagementSystemLabel": "インシデント管理システム", - "xpack.securitySolution.cases.configureCases.incidentManagementSystemTitle": "外部のインシデント管理システムに接続", - "xpack.securitySolution.cases.configureCases.mappingFieldNotMapped": "マップされません", - "xpack.securitySolution.cases.configureCases.noFieldsError": "{ connectorName } フィールドが見つかりません。解決する { connectorName } コネクター設定または { connectorName } インスタンス設定を確認してください。", - "xpack.securitySolution.cases.configureCases.requiredMappings": "1 つ以上のケースフィールドを次の { connectorName } フィールドにマッピングする必要があります:{ fields }", - "xpack.securitySolution.cases.configureCases.saveAndCloseButton": "保存して閉じる", - "xpack.securitySolution.cases.configureCases.saveButton": "保存", - "xpack.securitySolution.cases.configureCases.updateConnector": "フィールドマッピングを更新", - "xpack.securitySolution.cases.configureCases.updateSelectedConnector": "{ connectorName }を更新", - "xpack.securitySolution.cases.configureCases.warningMessage": "選択したコネクターが削除されました。別のコネクターを選択するか、新しいコネクターを作成してください。", - "xpack.securitySolution.cases.configureCases.warningTitle": "警告", "xpack.securitySolution.cases.configureCasesButton": "外部接続を編集", - "xpack.securitySolution.cases.confirmDeleteCase.confirmQuestion": "このケースを削除すると、関連するすべてのケースデータが完全に削除され、外部インシデント管理システムにデータをプッシュできなくなります。続行していいですか?", - "xpack.securitySolution.cases.confirmDeleteCase.confirmQuestionPlural": "これらのケースを削除すると、関連するすべてのケースデータが完全に削除され、外部インシデント管理システムにデータをプッシュできなくなります。続行していいですか?", "xpack.securitySolution.cases.confirmDeleteCase.deleteCase": "ケースを削除", "xpack.securitySolution.cases.confirmDeleteCase.deleteCases": "ケースを削除", - "xpack.securitySolution.cases.confirmDeleteCase.deleteThisCase": "このケースを削除", - "xpack.securitySolution.cases.confirmDeleteCase.deleteTitle": "「{caseTitle}」を削除", - "xpack.securitySolution.cases.confirmDeleteCase.selectedCases": "選択したケースを削除", - "xpack.securitySolution.cases.connectors.jira.issueTypesSelectFieldLabel": "問題タイプ", - "xpack.securitySolution.cases.connectors.jira.parentIssueSearchLabel": "親問題", - "xpack.securitySolution.cases.connectors.jira.prioritySelectFieldLabel": "優先度", - "xpack.securitySolution.cases.connectors.resilient.incidentTypesLabel": "インシデントタイプ", - "xpack.securitySolution.cases.connectors.resilient.incidentTypesPlaceholder": "タイプを選択", - "xpack.securitySolution.cases.connectors.resilient.severityLabel": "深刻度", - "xpack.securitySolution.cases.connectors.resilient.unableToGetIncidentTypesMessage": "インシデントタイプを取得できません", - "xpack.securitySolution.cases.connectors.resilient.unableToGetSeverityMessage": "深刻度を取得できません", - "xpack.securitySolution.cases.containers.statusChangeToasterText": "このケースのアラートはステータスが更新されました", "xpack.securitySolution.cases.createCase.descriptionFieldRequiredError": "説明が必要です。", "xpack.securitySolution.cases.createCase.fieldTagsHelpText": "このケースの1つ以上のカスタム識別タグを入力します。新しいタグを開始するには、各タグの後でEnterを押します。", "xpack.securitySolution.cases.createCase.titleFieldRequiredError": "タイトルが必要です。", "xpack.securitySolution.cases.dismissErrorsPushServiceCallOutTitle": "閉じる", - "xpack.securitySolution.cases.editConnector.editConnectorLinkAria": "クリックしてコネクターを編集", "xpack.securitySolution.cases.pageTitle": "ケース", "xpack.securitySolution.cases.readOnlySavedObjectDescription": "ケースを表示する権限のみが付与されています。ケースを開いて更新する必要がある場合は、Kibana管理者に連絡してください。", "xpack.securitySolution.cases.readOnlySavedObjectTitle": "新しいケースを開いたり、既存のケースを更新したりすることはできません", "xpack.securitySolution.cases.settings.syncAlertsSwitchLabelOff": "オフ", "xpack.securitySolution.cases.settings.syncAlertsSwitchLabelOn": "オン", - "xpack.securitySolution.cases.status.closed": "終了", - "xpack.securitySolution.cases.status.iconAria": "ステータスの変更", - "xpack.securitySolution.cases.status.inProgress": "進行中", - "xpack.securitySolution.cases.status.open": "開く", "xpack.securitySolution.cases.timeline.actions.addCase": "ケースに追加", "xpack.securitySolution.cases.timeline.actions.addExistingCase": "既存のケースに追加", "xpack.securitySolution.cases.timeline.actions.addNewCase": "新しいケースに追加", @@ -18004,8 +17861,6 @@ "xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToast": "アラートが「{title}」に追加されました", "xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToastText": "このケースのアラートはステータスがケースステータスと同期されました", "xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToastViewCaseLink": "ケースの表示", - "xpack.securitySolution.caseConnectorsRegistry.get.missingCaseConnectorErrorMessage": "オブジェクトタイプ「{id}」は登録されていません。", - "xpack.securitySolution.caseConnectorsRegistry.register.duplicateCaseConnectorErrorMessage": "オブジェクトタイプ「{id}」はすでに登録されています。", "xpack.securitySolution.certificate.fingerprint.clientCertLabel": "クライアント証明書", "xpack.securitySolution.certificate.fingerprint.serverCertLabel": "サーバー証明書", "xpack.securitySolution.chart.allOthersGroupingLabel": "その他すべて", @@ -18020,31 +17875,7 @@ "xpack.securitySolution.clipboard.to.the.clipboard": "クリップボードに", "xpack.securitySolution.common.alertAddedToCase": "ケースに追加", "xpack.securitySolution.common.alertLabel": "アラート", - "xpack.securitySolution.components.connectors.jira.searchIssuesComboBoxAriaLabel": "入力して検索", - "xpack.securitySolution.components.connectors.jira.searchIssuesComboBoxPlaceholder": "入力して検索", - "xpack.securitySolution.components.connectors.jira.searchIssuesLoading": "読み込み中...", - "xpack.securitySolution.components.connectors.jira.unableToGetFieldsMessage": "コネクターを取得できません", - "xpack.securitySolution.components.connectors.jira.unableToGetIssueMessage": "ID {id}の問題を取得できません", - "xpack.securitySolution.components.connectors.jira.unableToGetIssuesMessage": "問題を取得できません", - "xpack.securitySolution.components.connectors.jira.unableToGetIssueTypesMessage": "問題タイプを取得できません", - "xpack.securitySolution.components.connectors.serviceNow.alertFieldEnabledText": "はい", - "xpack.securitySolution.components.connectors.serviceNow.alertFieldsTitle": "アラートに関連付けられたフィールド", - "xpack.securitySolution.components.connectors.serviceNow.categoryTitle": "カテゴリー", - "xpack.securitySolution.components.connectors.serviceNow.destinationIPTitle": "デスティネーション IP", - "xpack.securitySolution.components.connectors.serviceNow.impactSelectFieldLabel": "インパクト", - "xpack.securitySolution.components.connectors.serviceNow.malwareHashTitle": "マルウェアハッシュ", - "xpack.securitySolution.components.connectors.serviceNow.malwareURLTitle": "マルウェアURL", - "xpack.securitySolution.components.connectors.serviceNow.prioritySelectFieldTitle": "優先度", - "xpack.securitySolution.components.connectors.serviceNow.severitySelectFieldLabel": "深刻度", - "xpack.securitySolution.components.connectors.serviceNow.sourceIPTitle": "ソース IP", - "xpack.securitySolution.components.connectors.serviceNow.subcategoryTitle": "サブカテゴリ", - "xpack.securitySolution.components.connectors.serviceNow.unableToGetChoicesMessage": "選択肢を取得できません", - "xpack.securitySolution.components.connectors.serviceNow.urgencySelectFieldLabel": "緊急", - "xpack.securitySolution.components.create.stepOneTitle": "ケースフィールド", - "xpack.securitySolution.components.create.stepThreeTitle": "外部コネクターフィールド", - "xpack.securitySolution.components.create.stepTwoTitle": "ケース設定", "xpack.securitySolution.components.create.syncAlertHelpText": "このオプションを有効にすると、このケースのアラートのステータスをケースステータスと同期します。", - "xpack.securitySolution.components.create.syncAlertsLabel": "アラートステータスをケースステータスと同期", "xpack.securitySolution.components.embeddables.embeddedMap.clientLayerLabel": "クライアントポイント", "xpack.securitySolution.components.embeddables.embeddedMap.destinationLayerLabel": "デスティネーションポイント", "xpack.securitySolution.components.embeddables.embeddedMap.embeddableHeaderHelp": "マップ構成ヘルプ", @@ -18116,14 +17947,6 @@ "xpack.securitySolution.containers.anomalies.errorFetchingAnomaliesData": "異常データをクエリできませんでした", "xpack.securitySolution.containers.anomalies.stackByJobId": "ジョブ", "xpack.securitySolution.containers.anomalies.title": "異常", - "xpack.securitySolution.containers.cases.closedCases": "{totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases}件のケース}}をクローズしました", - "xpack.securitySolution.containers.cases.deletedCases": "{totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases}件のケース}}を削除しました", - "xpack.securitySolution.containers.cases.errorDeletingTitle": "データの削除エラー", - "xpack.securitySolution.containers.cases.errorTitle": "データの取得中にエラーが発生", - "xpack.securitySolution.containers.cases.pushToExternalService": "{ serviceName }への送信が正常に完了しました", - "xpack.securitySolution.containers.cases.reopenedCases": "{totalCases, plural, =1 {\"{caseTitle}\"} other {{totalCases}件のケース}}を再オープンしました", - "xpack.securitySolution.containers.cases.syncCase": "\"{caseTitle}\"のアラートが同期されました", - "xpack.securitySolution.containers.cases.updatedCase": "\"{caseTitle}\"を更新しました", "xpack.securitySolution.containers.detectionEngine.addRuleFailDescription": "ルールを追加できませんでした", "xpack.securitySolution.containers.detectionEngine.alerts.createListsIndex.errorDescription": "リストインデックスを作成できませんでした", "xpack.securitySolution.containers.detectionEngine.alerts.errorFetchingAlertsDescription": "アラートをクエリできませんでした", @@ -19908,7 +19731,6 @@ "xpack.securitySolution.overview.hostStatGroupFilebeat": "Filebeat", "xpack.securitySolution.overview.hostStatGroupWinlogbeat": "Winlogbeat", "xpack.securitySolution.overview.hostsTitle": "ホストイベント", - "xpack.securitySolution.overview.myRecentlyReportedCasesButtonLabel": "最近レポートしたケース", "xpack.securitySolution.overview.networkAction": "ネットワークを表示", "xpack.securitySolution.overview.networkStatGroupAuditbeat": "Auditbeat", "xpack.securitySolution.overview.networkStatGroupFilebeat": "Filebeat", @@ -19921,7 +19743,6 @@ "xpack.securitySolution.overview.pageSubtitle": "Elastic Stackによるセキュリティ情報とイベント管理", "xpack.securitySolution.overview.pageTitle": "セキュリティ", "xpack.securitySolution.overview.recentCasesSidebarTitle": "最近のケース", - "xpack.securitySolution.overview.recentlyCreatedCasesButtonLabel": "最近作成したケース", "xpack.securitySolution.overview.recentTimelinesSidebarTitle": "最近のタイムライン", "xpack.securitySolution.overview.showTopTooltip": "上位の{fieldName}を表示", "xpack.securitySolution.overview.signalCountTitle": "検出アラート傾向", @@ -19955,11 +19776,6 @@ "xpack.securitySolution.policyStatusText.success": "成功", "xpack.securitySolution.policyStatusText.unsupported": "サポートされていない", "xpack.securitySolution.policyStatusText.warning": "警告", - "xpack.securitySolution.recentCases.commentsTooltip": "コメント", - "xpack.securitySolution.recentCases.controlLegend": "ケースフィルター", - "xpack.securitySolution.recentCases.noCasesMessage": "まだケースを作成していません。準備して", - "xpack.securitySolution.recentCases.startNewCaseLink": "新しいケースの開始", - "xpack.securitySolution.recentCases.viewAllCasesLink": "すべてのケースを表示", "xpack.securitySolution.recentTimelines.errorRetrievingUserDetailsMessage": "最近のタイムライン:ユーザー詳細の取得中にエラーが発生しました", "xpack.securitySolution.recentTimelines.favoritesButtonLabel": "お気に入り", "xpack.securitySolution.recentTimelines.filterControlLegend": "タイムラインフィルター", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ce47b76c71949..7eab0f835a556 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -18055,205 +18055,58 @@ "xpack.securitySolution.cases.allCases.actions": "操作", "xpack.securitySolution.cases.allCases.comments": "注释", "xpack.securitySolution.cases.allCases.noTagsAvailable": "没有可用标签", - "xpack.securitySolution.cases.caseModal.title": "选择案例", "xpack.securitySolution.cases.caseSavedObjectNoPermissionsMessage": "要查看案例,必须对 Kibana 工作区中的已保存对象管理功能有权限。有关详细信息,请联系您的 Kibana 管理员。", "xpack.securitySolution.cases.caseSavedObjectNoPermissionsTitle": "需要 Kibana 功能权限", - "xpack.securitySolution.cases.caseTable.addNewCase": "添加新案例", - "xpack.securitySolution.cases.caseTable.bulkActions": "批处理操作", - "xpack.securitySolution.cases.caseTable.bulkActions.closeSelectedTitle": "关闭所选", - "xpack.securitySolution.cases.caseTable.bulkActions.deleteSelectedTitle": "删除所选", - "xpack.securitySolution.cases.caseTable.bulkActions.openSelectedTitle": "重新打开所选", "xpack.securitySolution.cases.caseTable.caseDetailsLinkAria": "单击以访问标题为 {detailName} 的案例", - "xpack.securitySolution.cases.caseTable.closed": "已关闭", "xpack.securitySolution.cases.caseTable.closedCases": "已关闭案例", - "xpack.securitySolution.cases.caseTable.delete": "删除", - "xpack.securitySolution.cases.caseTable.incidentSystem": "事件管理系统", "xpack.securitySolution.cases.caseTable.inProgressCases": "进行中的案例", - "xpack.securitySolution.cases.caseTable.noCases.body": "没有可显示的案例。请创建新案例或在上面更改您的筛选设置。", - "xpack.securitySolution.cases.caseTable.noCases.title": "无案例", - "xpack.securitySolution.cases.caseTable.notPushed": "未推送", "xpack.securitySolution.cases.caseTable.openCases": "未结案例", - "xpack.securitySolution.cases.caseTable.refreshTitle": "刷新", - "xpack.securitySolution.cases.caseTable.requiresUpdate": " 需要更新", - "xpack.securitySolution.cases.caseTable.searchAriaLabel": "搜索案例", - "xpack.securitySolution.cases.caseTable.searchPlaceholder": "例如案例名", - "xpack.securitySolution.cases.caseTable.selectedCasesTitle": "已选择 {totalRules} 个{totalRules, plural, other {案例}}", - "xpack.securitySolution.cases.caseTable.serviceNowLinkAria": "单击可在 servicenow 上查看该事件", - "xpack.securitySolution.cases.caseTable.showingCasesTitle": "正在显示 {totalRules} 个{totalRules, plural, other {案例}}", - "xpack.securitySolution.cases.caseTable.snIncident": "外部事件", - "xpack.securitySolution.cases.caseTable.status": "状态", - "xpack.securitySolution.cases.caseTable.unit": "{totalCount, plural, other {案例}}", - "xpack.securitySolution.cases.caseTable.upToDate": " 是最新的", - "xpack.securitySolution.cases.caseView.actionHeadline": "{userName} 在 {actionDate}{actionName}", - "xpack.securitySolution.cases.caseView.actionLabel.addComment": "添加了注释", - "xpack.securitySolution.cases.caseView.actionLabel.addDescription": "添加了描述", - "xpack.securitySolution.cases.caseView.actionLabel.addedField": "添加了", - "xpack.securitySolution.cases.caseView.actionLabel.changededField": "更改了", - "xpack.securitySolution.cases.caseView.actionLabel.editedField": "编辑了", - "xpack.securitySolution.cases.caseView.actionLabel.on": "在", - "xpack.securitySolution.cases.caseView.actionLabel.pushedNewIncident": "已推送为新事件", - "xpack.securitySolution.cases.caseView.actionLabel.removedField": "移除了", - "xpack.securitySolution.cases.caseView.actionLabel.removedThirdParty": "已移除外部事件管理系统", - "xpack.securitySolution.cases.caseView.actionLabel.selectedThirdParty": "已选择 { thirdParty } 作为事件管理系统", - "xpack.securitySolution.cases.caseView.actionLabel.updateIncident": "更新了事件", - "xpack.securitySolution.cases.caseView.actionLabel.viewIncident": "查看 {incidentNumber}", - "xpack.securitySolution.cases.caseView.alertCommentLabelTitle": "添加了告警,从", - "xpack.securitySolution.cases.caseView.alertRuleDeletedLabelTitle": "添加了告警", - "xpack.securitySolution.cases.caseView.alreadyPushedToExternalService": "已推送到 { externalService } 事件", - "xpack.securitySolution.cases.caseView.appropiateLicense": "适当的许可证", "xpack.securitySolution.cases.caseView.backLabel": "返回到案例", "xpack.securitySolution.cases.caseView.breadcrumb": "创建", "xpack.securitySolution.cases.caseView.cancel": "取消", - "xpack.securitySolution.cases.caseView.case": "案例", - "xpack.securitySolution.cases.caseView.caseClosed": "案例已关闭", - "xpack.securitySolution.cases.caseView.caseInProgress": "案例进行中", "xpack.securitySolution.cases.caseView.caseName": "案例名称", - "xpack.securitySolution.cases.caseView.caseOpened": "案例已打开", - "xpack.securitySolution.cases.caseView.caseRefresh": "刷新案例", "xpack.securitySolution.cases.caseView.closeCase": "关闭案例", "xpack.securitySolution.cases.caseView.closedOn": "关闭日期", - "xpack.securitySolution.cases.caseView.cloudDeploymentLink": "云部署", - "xpack.securitySolution.cases.caseView.comment": "注释", "xpack.securitySolution.cases.caseView.comment.addComment": "添加注释", "xpack.securitySolution.cases.caseView.comment.addCommentHelpText": "添加新注释......", "xpack.securitySolution.cases.caseView.commentFieldRequiredError": "注释必填。", - "xpack.securitySolution.cases.caseView.connectorConfigureLink": "连接器", "xpack.securitySolution.cases.caseView.connectors": "外部事件管理系统", - "xpack.securitySolution.cases.caseView.copyCommentLinkAria": "复制引用链接", "xpack.securitySolution.cases.caseView.create": "创建新案例", "xpack.securitySolution.cases.caseView.createCase": "创建案例", "xpack.securitySolution.cases.caseView.description": "描述", "xpack.securitySolution.cases.caseView.description.save": "保存", "xpack.securitySolution.cases.caseView.edit": "编辑", - "xpack.securitySolution.cases.caseView.edit.comment": "编辑注释", - "xpack.securitySolution.cases.caseView.edit.description": "编辑描述", - "xpack.securitySolution.cases.caseView.edit.quote": "引述", - "xpack.securitySolution.cases.caseView.editActionsLinkAria": "单击可查看所有操作", "xpack.securitySolution.cases.caseView.editConnector": "更改外部事件管理系统", - "xpack.securitySolution.cases.caseView.editTagsLinkAria": "单击可编辑标签", - "xpack.securitySolution.cases.caseView.emailBody": "案例参考:{caseUrl}", - "xpack.securitySolution.cases.caseView.emailSubject": "Security 案例 - {caseTitle}", - "xpack.securitySolution.cases.caseView.errorsPushServiceCallOutTitle": "要将案例发送到外部系统,您需要:", - "xpack.securitySolution.cases.caseView.fieldChanged": "已更改连接器字段", "xpack.securitySolution.cases.caseView.fieldRequiredError": "必填字段", - "xpack.securitySolution.cases.caseView.generatedAlertCommentLabelTitle": "添加自", - "xpack.securitySolution.cases.caseView.generatedAlertCountCommentLabelTitle": "{totalCount} 个{totalCount, plural, other {告警}}", "xpack.securitySolution.cases.caseView.goToDocumentationButton": "查看文档", "xpack.securitySolution.cases.caseView.markedCaseAs": "将案例标记为", "xpack.securitySolution.cases.caseView.markInProgress": "标记为进行中", - "xpack.securitySolution.cases.caseView.moveToCommentAria": "高亮显示引用的注释", "xpack.securitySolution.cases.caseView.name": "名称", "xpack.securitySolution.cases.caseView.noReportersAvailable": "没有报告者。", "xpack.securitySolution.cases.caseView.noTags": "当前没有为此案例分配标签。", "xpack.securitySolution.cases.caseView.openedOn": "打开时间", "xpack.securitySolution.cases.caseView.optional": "可选", "xpack.securitySolution.cases.caseView.particpantsLabel": "参与者", - "xpack.securitySolution.cases.caseView.pushNamedIncident": "推送为 { thirdParty } 事件", - "xpack.securitySolution.cases.caseView.pushThirdPartyIncident": "推送为外部事件", - "xpack.securitySolution.cases.caseView.pushToServiceDisableBecauseCaseClosedDescription": "关闭的案例无法发送到外部系统。如果希望在外部系统中打开或更新案例,请重新打开案例。", - "xpack.securitySolution.cases.caseView.pushToServiceDisableBecauseCaseClosedTitle": "重新打开案例", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByConfigDescription": "kibana.yml 文件已配置为仅允许特定连接器。要在外部系统中打开案例,请将 .[actionTypeId] (例如:.servicenow | .jira) 添加到 xpack.actions.enabledActiontypes 设置。有关更多信息,请参阅{link}。", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByConfigTitle": "在 Kibana 配置文件中启用外部服务", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByInvalidConnector": "用于将更新发送到外部服务的连接器已删除。要在外部系统中更新案例,请选择不同的连接器或创建新的连接器。", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByLicenseDescription": "有{appropriateLicense}、正使用{cloud}或正在免费试用时,可在外部系统中创建案例。", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByLicenseTitle": "升级适当的许可", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoCaseConfigDescription": "要在外部系统中打开和更新案例,必须为此案例选择外部事件管理系统。", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoCaseConfigTitle": "选择外部连接器", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoConfigTitle": "配置外部连接器", - "xpack.securitySolution.cases.caseView.pushToServiceDisableByNoConnectors": "要在外部系统上打开和更新案例,必须配置{link}。", "xpack.securitySolution.cases.caseView.reopenCase": "重新打开案例", "xpack.securitySolution.cases.caseView.reporterLabel": "报告者", - "xpack.securitySolution.cases.caseView.requiredUpdateToExternalService": "需要更新 { externalService } 事件", "xpack.securitySolution.cases.caseView.sendAlertToTimelineTooltip": "在时间线中调查", - "xpack.securitySolution.cases.caseView.sendEmalLinkAria": "单击可向 {user} 发送电子邮件", - "xpack.securitySolution.cases.caseView.showAlertTooltip": "显示告警详情", - "xpack.securitySolution.cases.caseView.statusLabel": "状态", - "xpack.securitySolution.cases.caseView.syncAlertsLabel": "同步告警", "xpack.securitySolution.cases.caseView.tags": "标签", "xpack.securitySolution.cases.caseView.to": "到", "xpack.securitySolution.cases.caseView.unknown": "未知", - "xpack.securitySolution.cases.caseView.unknownRule.label": "未知规则", - "xpack.securitySolution.cases.caseView.updateNamedIncident": "更新 { thirdParty } 事件", - "xpack.securitySolution.cases.caseView.updateThirdPartyIncident": "更新外部事件", "xpack.securitySolution.cases.common.noConnector": "未选择任何连接器", - "xpack.securitySolution.cases.components.connectors.cases.actionTypeTitle": "案例", - "xpack.securitySolution.cases.components.connectors.cases.addNewCaseOption": "添加新案例", - "xpack.securitySolution.cases.components.connectors.cases.caseRequired": "必须选择策略。", - "xpack.securitySolution.cases.components.connectors.cases.casesDropdownPlaceholder": "选择案例", - "xpack.securitySolution.cases.components.connectors.cases.casesDropdownRowLabel": "案例", - "xpack.securitySolution.cases.components.connectors.cases.commentLabel": "注释", - "xpack.securitySolution.cases.components.connectors.cases.commentRequired": "“注释”必填。", - "xpack.securitySolution.cases.components.connectors.cases.connectedCaseLabel": "已连接案例", - "xpack.securitySolution.cases.components.connectors.cases.createCaseLabel": "创建案例", - "xpack.securitySolution.cases.components.connectors.cases.optionAddNewCase": "添加到新案例", - "xpack.securitySolution.cases.components.connectors.cases.optionAddToExistingCase": "添加到现有案例", - "xpack.securitySolution.cases.components.connectors.cases.selectMessageText": "创建或更新案例。", - "xpack.securitySolution.cases.configure.errorGetFields": "从服务中获取字段时出错", - "xpack.securitySolution.cases.configure.successSaveToast": "已保存外部连接设置", - "xpack.securitySolution.cases.configureCases.addNewConnector": "添加新连接器", - "xpack.securitySolution.cases.configureCases.blankMappings": "至少一个字段需映射到 { connectorName }", - "xpack.securitySolution.cases.configureCases.cancelButton": "取消", - "xpack.securitySolution.cases.configureCases.caseClosureOptionsClosedIncident": "在外部系统中关闭事件时自动关闭 Security 案例", - "xpack.securitySolution.cases.configureCases.caseClosureOptionsDesc": "定义关闭 Security 案例的方式。要自动关闭案例,需要与外部事件管理系统建立连接。", - "xpack.securitySolution.cases.configureCases.caseClosureOptionsLabel": "案例关闭选项", - "xpack.securitySolution.cases.configureCases.caseClosureOptionsManual": "手动关闭 Security 案例", - "xpack.securitySolution.cases.configureCases.caseClosureOptionsNewIncident": "将新事件推送到外部系统时自动关闭 Security 案例", - "xpack.securitySolution.cases.configureCases.caseClosureOptionsTitle": "案例关闭", - "xpack.securitySolution.cases.configureCases.commentMapping": "注释", - "xpack.securitySolution.cases.configureCases.editFieldMappingTitle": "编辑 { thirdPartyName } 字段映射", - "xpack.securitySolution.cases.configureCases.fieldMappingDesc": "将数据推送到 { thirdPartyName } 时,将 Security 案例字段映射到 { thirdPartyName } 字段。字段映射需要与 { thirdPartyName } 建立连接。", - "xpack.securitySolution.cases.configureCases.fieldMappingDescErr": "字段映射需要与 { thirdPartyName } 建立连接。请检查您的连接凭据。", - "xpack.securitySolution.cases.configureCases.fieldMappingEditAppend": "追加", - "xpack.securitySolution.cases.configureCases.fieldMappingEditNothing": "无内容", - "xpack.securitySolution.cases.configureCases.fieldMappingEditOverwrite": "覆盖", - "xpack.securitySolution.cases.configureCases.fieldMappingFirstCol": "Security 案例字段", - "xpack.securitySolution.cases.configureCases.fieldMappingSecondCol": "{ thirdPartyName } 字段", - "xpack.securitySolution.cases.configureCases.fieldMappingThirdCol": "编辑和更新时", - "xpack.securitySolution.cases.configureCases.fieldMappingTitle": "{ thirdPartyName } 字段映射", "xpack.securitySolution.cases.configureCases.headerTitle": "配置案例", - "xpack.securitySolution.cases.configureCases.incidentManagementSystemDesc": "您可能会根据需要将 Security 案例连接到选择的外部事件管理系统。这将允许您将案例数据作为事件推送到所选第三方系统。", - "xpack.securitySolution.cases.configureCases.incidentManagementSystemLabel": "事件管理系统", - "xpack.securitySolution.cases.configureCases.incidentManagementSystemTitle": "连接到外部事件管理系统", - "xpack.securitySolution.cases.configureCases.mappingFieldNotMapped": "未映射", - "xpack.securitySolution.cases.configureCases.noFieldsError": "未找到任何 { connectorName } 字段。请检查您的 { connectorName } 连接器设置或 { connectorName } 实例设置以解决问题。", - "xpack.securitySolution.cases.configureCases.requiredMappings": "至少有一个案例字段需要映射到以下所需的 { connectorName } 字段:{ fields }", - "xpack.securitySolution.cases.configureCases.saveAndCloseButton": "保存并关闭", - "xpack.securitySolution.cases.configureCases.saveButton": "保存", - "xpack.securitySolution.cases.configureCases.updateConnector": "更新字段映射", - "xpack.securitySolution.cases.configureCases.updateSelectedConnector": "更新 { connectorName }", - "xpack.securitySolution.cases.configureCases.warningMessage": "选定的连接器已删除。选择不同的连接器或创建新的连接器。", - "xpack.securitySolution.cases.configureCases.warningTitle": "警告", "xpack.securitySolution.cases.configureCasesButton": "编辑外部连接", - "xpack.securitySolution.cases.confirmDeleteCase.confirmQuestion": "删除此案例即会永久移除所有相关案例数据,而且您将无法再将数据推送到外部事件管理系统。是否确定要继续?", - "xpack.securitySolution.cases.confirmDeleteCase.confirmQuestionPlural": "删除这些案例即会永久移除所有相关案例数据,而且您将无法再将数据推送到外部事件管理系统。是否确定要继续?", "xpack.securitySolution.cases.confirmDeleteCase.deleteCase": "删除案例", "xpack.securitySolution.cases.confirmDeleteCase.deleteCases": "删除案例", - "xpack.securitySolution.cases.confirmDeleteCase.deleteThisCase": "删除此案例", - "xpack.securitySolution.cases.confirmDeleteCase.deleteTitle": "删除“{caseTitle}”", - "xpack.securitySolution.cases.confirmDeleteCase.selectedCases": "删除选定案例", - "xpack.securitySolution.cases.connectors.jira.issueTypesSelectFieldLabel": "问题类型", - "xpack.securitySolution.cases.connectors.jira.parentIssueSearchLabel": "父问题", - "xpack.securitySolution.cases.connectors.jira.prioritySelectFieldLabel": "优先级", - "xpack.securitySolution.cases.connectors.resilient.incidentTypesLabel": "事件类型", - "xpack.securitySolution.cases.connectors.resilient.incidentTypesPlaceholder": "选择类型", - "xpack.securitySolution.cases.connectors.resilient.severityLabel": "严重性", - "xpack.securitySolution.cases.connectors.resilient.unableToGetIncidentTypesMessage": "无法获取事件类型", - "xpack.securitySolution.cases.connectors.resilient.unableToGetSeverityMessage": "无法获取严重性", - "xpack.securitySolution.cases.containers.statusChangeToasterText": "此案例中的告警也更新了状态", "xpack.securitySolution.cases.createCase.descriptionFieldRequiredError": "描述必填。", "xpack.securitySolution.cases.createCase.fieldTagsHelpText": "为此案例键入一个或多个定制识别标签。在每个标签后按 Enter 键可开始新的标签。", "xpack.securitySolution.cases.createCase.titleFieldRequiredError": "标题必填。", "xpack.securitySolution.cases.dismissErrorsPushServiceCallOutTitle": "关闭", - "xpack.securitySolution.cases.editConnector.editConnectorLinkAria": "单击以编辑连接器", "xpack.securitySolution.cases.pageTitle": "案例", "xpack.securitySolution.cases.readOnlySavedObjectDescription": "您仅有权查看案例。如果需要创建和更新案例,请联系您的 Kibana 管理员。", "xpack.securitySolution.cases.readOnlySavedObjectTitle": "您无法创建新案例或更新现有案例", "xpack.securitySolution.cases.settings.syncAlertsSwitchLabelOff": "关闭", "xpack.securitySolution.cases.settings.syncAlertsSwitchLabelOn": "开启", - "xpack.securitySolution.cases.status.closed": "已关闭", - "xpack.securitySolution.cases.status.iconAria": "更改状态", - "xpack.securitySolution.cases.status.inProgress": "进行中", - "xpack.securitySolution.cases.status.open": "未结", "xpack.securitySolution.cases.timeline.actions.addCase": "添加到案例", "xpack.securitySolution.cases.timeline.actions.addExistingCase": "添加到现有案例", "xpack.securitySolution.cases.timeline.actions.addNewCase": "添加到新案例", @@ -18262,8 +18115,6 @@ "xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToast": "告警已添加到“{title}”", "xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToastText": "此案例中的告警的状态已经与案例状态同步", "xpack.securitySolution.cases.timeline.actions.caseCreatedSuccessToastViewCaseLink": "查看案例", - "xpack.securitySolution.caseConnectorsRegistry.get.missingCaseConnectorErrorMessage": "对象类型“{id}”未注册。", - "xpack.securitySolution.caseConnectorsRegistry.register.duplicateCaseConnectorErrorMessage": "已注册对象类型“{id}”。", "xpack.securitySolution.certificate.fingerprint.clientCertLabel": "客户端证书", "xpack.securitySolution.certificate.fingerprint.serverCertLabel": "服务器证书", "xpack.securitySolution.chart.allOthersGroupingLabel": "所有其他", @@ -18278,31 +18129,7 @@ "xpack.securitySolution.clipboard.to.the.clipboard": "至剪贴板", "xpack.securitySolution.common.alertAddedToCase": "已添加到案例", "xpack.securitySolution.common.alertLabel": "告警", - "xpack.securitySolution.components.connectors.jira.searchIssuesComboBoxAriaLabel": "键入内容进行搜索", - "xpack.securitySolution.components.connectors.jira.searchIssuesComboBoxPlaceholder": "键入内容进行搜索", - "xpack.securitySolution.components.connectors.jira.searchIssuesLoading": "正在加载……", - "xpack.securitySolution.components.connectors.jira.unableToGetFieldsMessage": "无法获取连接器", - "xpack.securitySolution.components.connectors.jira.unableToGetIssueMessage": "无法获取 ID 为 {id} 的问题", - "xpack.securitySolution.components.connectors.jira.unableToGetIssuesMessage": "无法获取问题", - "xpack.securitySolution.components.connectors.jira.unableToGetIssueTypesMessage": "无法获取问题类型", - "xpack.securitySolution.components.connectors.serviceNow.alertFieldEnabledText": "是", - "xpack.securitySolution.components.connectors.serviceNow.alertFieldsTitle": "与告警关联的字段", - "xpack.securitySolution.components.connectors.serviceNow.categoryTitle": "类别", - "xpack.securitySolution.components.connectors.serviceNow.destinationIPTitle": "目标 IP", - "xpack.securitySolution.components.connectors.serviceNow.impactSelectFieldLabel": "影响", - "xpack.securitySolution.components.connectors.serviceNow.malwareHashTitle": "恶意软件哈希", - "xpack.securitySolution.components.connectors.serviceNow.malwareURLTitle": "恶意软件 URL", - "xpack.securitySolution.components.connectors.serviceNow.prioritySelectFieldTitle": "优先级", - "xpack.securitySolution.components.connectors.serviceNow.severitySelectFieldLabel": "严重性", - "xpack.securitySolution.components.connectors.serviceNow.sourceIPTitle": "源 IP", - "xpack.securitySolution.components.connectors.serviceNow.subcategoryTitle": "子类别", - "xpack.securitySolution.components.connectors.serviceNow.unableToGetChoicesMessage": "无法获取选项", - "xpack.securitySolution.components.connectors.serviceNow.urgencySelectFieldLabel": "紧急性", - "xpack.securitySolution.components.create.stepOneTitle": "案例字段", - "xpack.securitySolution.components.create.stepThreeTitle": "外部连接器字段", - "xpack.securitySolution.components.create.stepTwoTitle": "案例设置", "xpack.securitySolution.components.create.syncAlertHelpText": "启用此选项将使本案例中的告警状态与案例状态同步。", - "xpack.securitySolution.components.create.syncAlertsLabel": "将告警状态与案例状态同步", "xpack.securitySolution.components.embeddables.embeddedMap.clientLayerLabel": "客户端点", "xpack.securitySolution.components.embeddables.embeddedMap.destinationLayerLabel": "目标点", "xpack.securitySolution.components.embeddables.embeddedMap.embeddableHeaderHelp": "地图配置帮助", @@ -18377,14 +18204,6 @@ "xpack.securitySolution.containers.anomalies.errorFetchingAnomaliesData": "无法查询异常数据", "xpack.securitySolution.containers.anomalies.stackByJobId": "作业", "xpack.securitySolution.containers.anomalies.title": "异常", - "xpack.securitySolution.containers.cases.closedCases": "已关闭{totalCases, plural, =1 {“{caseTitle}”} other { {totalCases} 个案例}}", - "xpack.securitySolution.containers.cases.deletedCases": "已删除{totalCases, plural, =1 {“{caseTitle}”} other { {totalCases} 个案例}}", - "xpack.securitySolution.containers.cases.errorDeletingTitle": "删除数据时出错", - "xpack.securitySolution.containers.cases.errorTitle": "提取数据时出错", - "xpack.securitySolution.containers.cases.pushToExternalService": "已成功发送到 { serviceName }", - "xpack.securitySolution.containers.cases.reopenedCases": "已重新打开{totalCases, plural, =1 {“{caseTitle}”} other { {totalCases} 个案例}}", - "xpack.securitySolution.containers.cases.syncCase": "“{caseTitle}”中的告警已同步", - "xpack.securitySolution.containers.cases.updatedCase": "已更新“{caseTitle}”", "xpack.securitySolution.containers.detectionEngine.addRuleFailDescription": "无法添加规则", "xpack.securitySolution.containers.detectionEngine.alerts.createListsIndex.errorDescription": "无法创建列表索引", "xpack.securitySolution.containers.detectionEngine.alerts.errorFetchingAlertsDescription": "无法查询告警", @@ -20227,7 +20046,6 @@ "xpack.securitySolution.overview.hostStatGroupFilebeat": "Filebeat", "xpack.securitySolution.overview.hostStatGroupWinlogbeat": "Winlogbeat", "xpack.securitySolution.overview.hostsTitle": "主机事件", - "xpack.securitySolution.overview.myRecentlyReportedCasesButtonLabel": "我最近报告的案例", "xpack.securitySolution.overview.networkAction": "查看网络", "xpack.securitySolution.overview.networkStatGroupAuditbeat": "Auditbeat", "xpack.securitySolution.overview.networkStatGroupFilebeat": "Filebeat", @@ -20242,7 +20060,6 @@ "xpack.securitySolution.overview.pageSubtitle": "Elastic Stack 的安全信息和事件管理功能", "xpack.securitySolution.overview.pageTitle": "安全", "xpack.securitySolution.overview.recentCasesSidebarTitle": "最近案例", - "xpack.securitySolution.overview.recentlyCreatedCasesButtonLabel": "最近创建的案例", "xpack.securitySolution.overview.recentTimelinesSidebarTitle": "最近的时间线", "xpack.securitySolution.overview.showTopTooltip": "显示排名靠前的{fieldName}", "xpack.securitySolution.overview.signalCountTitle": "检测告警趋势", @@ -20278,11 +20095,6 @@ "xpack.securitySolution.policyStatusText.success": "成功", "xpack.securitySolution.policyStatusText.unsupported": "不支持", "xpack.securitySolution.policyStatusText.warning": "警告", - "xpack.securitySolution.recentCases.commentsTooltip": "注释", - "xpack.securitySolution.recentCases.controlLegend": "案例筛选", - "xpack.securitySolution.recentCases.noCasesMessage": "尚未创建任何案例。以侦探的眼光", - "xpack.securitySolution.recentCases.startNewCaseLink": "建立新案例", - "xpack.securitySolution.recentCases.viewAllCasesLink": "查看所有案例", "xpack.securitySolution.recentTimelines.errorRetrievingUserDetailsMessage": "最近的时间线:检索用户详情时发生错误", "xpack.securitySolution.recentTimelines.favoritesButtonLabel": "收藏夹", "xpack.securitySolution.recentTimelines.filterControlLegend": "时间线筛选", diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 8757b39a0b3ac..87f6ad20e6040 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -42,6 +42,7 @@ { "path": "../plugins/apm/tsconfig.json" }, { "path": "../plugins/banners/tsconfig.json" }, { "path": "../plugins/beats_management/tsconfig.json" }, + { "path": "../plugins/cases/tsconfig.json" }, { "path": "../plugins/cloud/tsconfig.json" }, { "path": "../plugins/console_extensions/tsconfig.json" }, { "path": "../plugins/dashboard_mode/tsconfig.json" }, From 8ac4892abd1af46763d14207975ab8bcd493ea17 Mon Sep 17 00:00:00 2001 From: Shahzad <shahzad.muhammad@elastic.co> Date: Thu, 29 Apr 2021 14:14:11 +0200 Subject: [PATCH 65/70] [Exploratory View] Fixed brushing/scrubbing on time series charts (#98623) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Casper Hübertz <casper@formgeist.com> --- .../exploratory_view/exploratory_view.tsx | 22 ++++++++++++++++-- .../series_builder/columns/chart_types.tsx | 1 + .../columns/operation_type_select.tsx | 1 + .../columns/report_definition_col.tsx | 6 ++--- .../series_builder/custom_report_field.tsx | 23 +++++++++---------- .../series_editor/columns/series_filter.tsx | 2 +- .../field_value_combobox.tsx | 6 ++--- .../public/hooks/use_values_list.ts | 4 ++-- 8 files changed, 41 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx index bc39bf5b27daa..19136cda6387c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx @@ -21,7 +21,7 @@ import { SeriesBuilder } from './series_builder/series_builder'; export function ExploratoryView() { const { - services: { lens }, + services: { lens, notifications }, } = useKibana<ObservabilityPublicPluginsStart>(); const seriesBuilderRef = useRef<HTMLDivElement>(null); @@ -37,7 +37,7 @@ export function ExploratoryView() { const LensComponent = lens?.EmbeddableComponent; - const { firstSeriesId: seriesId, firstSeries: series } = useUrlStorage(); + const { firstSeriesId: seriesId, firstSeries: series, setSeries } = useUrlStorage(); const lensAttributesT = useLensAttributes({ seriesId, @@ -77,6 +77,24 @@ export function ExploratoryView() { id="exploratoryView" timeRange={series?.time} attributes={lensAttributes} + onBrushEnd={({ range }) => { + if (series?.reportType !== 'pld') { + setSeries(seriesId, { + ...series, + time: { + from: new Date(range[0]).toISOString(), + to: new Date(range[1]).toISOString(), + }, + }); + } else { + notifications?.toasts.add( + i18n.translate('xpack.observability.exploratoryView.noBrusing', { + defaultMessage: + 'Zoom by brush selection is only available on time series charts.', + }) + ); + } + }} /> ) : ( <EmptyView series={series} loading={loading} height={height} /> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx index df5b57124f0e7..d3c4cee6d7dc1 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx @@ -94,6 +94,7 @@ export function XYChartTypesSelect({ return ( <EuiSuperSelect + fullWidth compressed prepend="Chart type" valueOfSelected={value} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx index b33671f78bfe9..6377165d7473f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx @@ -74,6 +74,7 @@ export function OperationTypeSelect({ return ( <EuiSuperSelect + fullWidth prepend="Calculation" data-test-subj="operationTypeSelect" compressed diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx index f7520fb64f211..717309e064ba3 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx @@ -29,8 +29,6 @@ function getColumnType(dataView: DataSeries, selectedDefinition: URLReportDefini return null; } -const MaxWidthStyle = { maxWidth: 250 }; - export function ReportDefinitionCol({ dataViewSeries, seriesId, @@ -89,14 +87,14 @@ export function ReportDefinitionCol({ </EuiFlexItem> ))} {(hasOperationType || columnType === 'operation') && ( - <EuiFlexItem style={MaxWidthStyle}> + <EuiFlexItem> <OperationTypeSelect seriesId={seriesId} defaultOperationType={yAxisColumns[0].operationType} /> </EuiFlexItem> )} - <EuiFlexItem style={MaxWidthStyle}> + <EuiFlexItem> <SeriesChartTypesSelect seriesId={seriesId} defaultChartType={defaultSeriesType} /> </EuiFlexItem> </FlexGroup> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx index e0d043504d50f..6b74ad45b2c07 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/custom_report_field.tsx @@ -31,17 +31,16 @@ export function CustomReportField({ field, seriesId, options: opts, defaultValue const options = opts ?? []; return ( - <div style={{ maxWidth: 250 }}> - <EuiSuperSelect - compressed - prepend={'Metric'} - options={options.map(({ label, field: fd }) => ({ - value: fd, - inputDisplay: label, - }))} - valueOfSelected={reportDefinitions?.[field]?.[0] || defaultValue || options?.[0].field} - onChange={(value) => onChange(value)} - /> - </div> + <EuiSuperSelect + fullWidth + compressed + prepend={'Metric'} + options={options.map(({ label, field: fd }) => ({ + value: fd, + inputDisplay: label, + }))} + valueOfSelected={reportDefinitions?.[field]?.[0] || defaultValue || options?.[0].field} + onChange={(value) => onChange(value)} + /> ); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx index fc7c84ffcc1f4..926852fda5cbc 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx @@ -60,7 +60,7 @@ export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: P flush="left" iconType="plus" onClick={() => { - setIsPopoverVisible(true); + setIsPopoverVisible((prevState) => !prevState); }} size="s" > diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx index 1c0e1fdb00770..55c65ce175fe0 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx @@ -6,7 +6,7 @@ */ import React, { useEffect, useState } from 'react'; -import { merge } from 'lodash'; +import { union } from 'lodash'; import { EuiComboBox, EuiFormControlLayout, EuiComboBoxOptionOption } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; @@ -31,11 +31,11 @@ export function FieldValueCombobox({ onChange: onSelectionChange, }: FieldValueSelectionProps) { const [options, setOptions] = useState<ValueOption[]>( - formatOptions(merge(values ?? [], selectedValue ?? [])) + formatOptions(union(values ?? [], selectedValue ?? [])) ); useEffect(() => { - setOptions(formatOptions(merge(values ?? [], selectedValue ?? []))); + setOptions(formatOptions(union(values ?? [], selectedValue ?? []))); }, [selectedValue, values]); const onChange = (selectedValuesN: ValueOption[]) => { diff --git a/x-pack/plugins/observability/public/hooks/use_values_list.ts b/x-pack/plugins/observability/public/hooks/use_values_list.ts index 69e889f0069ee..8d6e0abb896b3 100644 --- a/x-pack/plugins/observability/public/hooks/use_values_list.ts +++ b/x-pack/plugins/observability/public/hooks/use_values_list.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { capitalize, merge } from 'lodash'; +import { capitalize, union } from 'lodash'; import { useEffect, useState } from 'react'; import { useDebounce } from 'react-use'; import { IndexPattern } from '../../../../../src/plugins/data/common'; @@ -98,7 +98,7 @@ export const useValuesList = ({ if (keepHistory && query) { setValues((prevState) => { - return merge(newValues, prevState); + return union(newValues, prevState); }); } else { setValues(newValues); From c03a8306adc661eee59aad2936b8c45dd40cec94 Mon Sep 17 00:00:00 2001 From: Rudolf Meijering <skaapgif@gmail.com> Date: Thu, 29 Apr 2021 14:19:29 +0200 Subject: [PATCH 66/70] v2 migration algorithm docs for rewriting saved object id's (#93002) * Document v2 migration algorithm using control state names from source code * v2 migrations support for rewriting document _id's * Minor edits and clarifications * Fix markdown formatting * Review comments: improve algorithm description * add WAIT_FOR_YELLOW_SOURCE step Co-authored-by: restrry <restrry@gmail.com> --- rfcs/text/0013_saved_object_migrations.md | 24 +- .../saved_objects/migrationsv2/README.md | 382 +++++++++++++++++- 2 files changed, 373 insertions(+), 33 deletions(-) diff --git a/rfcs/text/0013_saved_object_migrations.md b/rfcs/text/0013_saved_object_migrations.md index 88879e5e706eb..2f7ed796bf0e6 100644 --- a/rfcs/text/0013_saved_object_migrations.md +++ b/rfcs/text/0013_saved_object_migrations.md @@ -265,12 +265,12 @@ Note: 3. If the clone operation fails because the target index already exist, ignore the error and wait for the target index to become green before proceeding. 4. (The `001` postfix in the target index name isn't used by Kibana, but allows for re-indexing an index should this be required by an Elasticsearch upgrade. E.g. re-index `.kibana_7.10.0_001` into `.kibana_7.10.0_002` and point the `.kibana_7.10.0` alias to `.kibana_7.10.0_002`.) 9. Transform documents by reading batches of outdated documents from the target index then transforming and updating them with optimistic concurrency control. - 1. Ignore any version conflict errors. - 2. If a document transform throws an exception, add the document to a failure list and continue trying to transform all other documents. If any failures occured, log the complete list of documents that failed to transform. Fail the migration. + 1. Ignore any version conflict errors. + 2. If a document transform throws an exception, add the document to a failure list and continue trying to transform all other documents. If any failures occured, log the complete list of documents that failed to transform. Fail the migration. 10. Update the mappings of the target index - 1. Retrieve the existing mappings including the `migrationMappingPropertyHashes` metadata. - 2. Update the mappings with `PUT /.kibana_7.10.0_001/_mapping`. The API deeply merges any updates so this won't remove the mappings of any plugins that are disabled on this instance but have been enabled on another instance that also migrated this index. - 3. Ensure that fields are correctly indexed using the target index's latest mappings `POST /.kibana_7.10.0_001/_update_by_query?conflicts=proceed`. In the future we could optimize this query by only targeting documents: + 1. Retrieve the existing mappings including the `migrationMappingPropertyHashes` metadata. + 2. Update the mappings with `PUT /.kibana_7.10.0_001/_mapping`. The API deeply merges any updates so this won't remove the mappings of any plugins that are disabled on this instance but have been enabled on another instance that also migrated this index. + 3. Ensure that fields are correctly indexed using the target index's latest mappings `POST /.kibana_7.10.0_001/_update_by_query?conflicts=proceed`. In the future we could optimize this query by only targeting documents: 1. That belong to a known saved object type. 11. Mark the migration as complete. This is done as a single atomic operation (requires https://github.com/elastic/elasticsearch/pull/58100) @@ -278,12 +278,12 @@ Note: migration in parallel, only one version will win. E.g. if 7.11 and 7.12 are started in parallel and migrate from a 7.9 index, either 7.11 or 7.12 should succeed and accept writes, but not both. - 1. Check that `.kibana` alias is still pointing to the source index - 2. Point the `.kibana_7.10.0` and `.kibana` aliases to the target index. - 3. Remove the temporary index `.kibana_7.10.0_reindex_temp` - 4. If this fails with a "required alias [.kibana] does not exist" error or "index_not_found_exception" for the temporary index, fetch `.kibana` again: - 1. If `.kibana` is _not_ pointing to our target index fail the migration. - 2. If `.kibana` is pointing to our target index the migration has succeeded and we can proceed to step (12). + 1. Check that `.kibana` alias is still pointing to the source index + 2. Point the `.kibana_7.10.0` and `.kibana` aliases to the target index. + 3. Remove the temporary index `.kibana_7.10.0_reindex_temp` + 4. If this fails with a "required alias [.kibana] does not exist" error or "index_not_found_exception" for the temporary index, fetch `.kibana` again: + 1. If `.kibana` is _not_ pointing to our target index fail the migration. + 2. If `.kibana` is pointing to our target index the migration has succeeded and we can proceed to step (12). 12. Start serving traffic. All saved object reads/writes happen through the version-specific alias `.kibana_7.10.0`. @@ -821,4 +821,4 @@ to enumarate some scenarios and their worst case impact: until we re-index. Is it sufficient to only re-index every major? How do we track the field count as it grows over every upgrade? 2. More generally, how do we deal with the growing field count approaching the - default limit of 1000? + default limit of 1000? \ No newline at end of file diff --git a/src/core/server/saved_objects/migrationsv2/README.md b/src/core/server/saved_objects/migrationsv2/README.md index fcfff14ec98be..c92a5245e6c91 100644 --- a/src/core/server/saved_objects/migrationsv2/README.md +++ b/src/core/server/saved_objects/migrationsv2/README.md @@ -1,17 +1,358 @@ -## TODO - - [ ] Should we adopt the naming convention of event log `.kibana-event-log-8.0.0-000001`? - - [ ] Can we detect and throw if there's an auto-created `.kibana` index - with inferred mappings? If we detect this we cannot assume that `.kibana` - contains all the latest documents. Our algorithm might also fail because we - clone the `.kibana` index with it's faulty mappings which can prevent us - from updating the mappings to the correct ones. We can ask users to verify - their indices to identify where the most up to date documents are located - (e.g. in `.kibana`, `.kibana_N` or perhaps a combination of both). We can - prepare a `.kibana_7.11.0_001` index and ask users to manually reindex - documents into this index. - -## Manual QA Test Plan -### 1. Legacy pre-migration +- [Introduction](#introduction) +- [Algorithm steps](#algorithm-steps) + - [INIT](#init) + - [CREATE_NEW_TARGET](#create_new_target) + - [LEGACY_SET_WRITE_BLOCK](#legacy_set_write_block) + - [LEGACY_CREATE_REINDEX_TARGET](#legacy_create_reindex_target) + - [LEGACY_REINDEX](#legacy_reindex) + - [LEGACY_REINDEX_WAIT_FOR_TASK](#legacy_reindex_wait_for_task) + - [LEGACY_DELETE](#legacy_delete) + - [WAIT_FOR_YELLOW_SOURCE](#wait_for_yellow_source) + - [SET_SOURCE_WRITE_BLOCK](#set_source_write_block) + - [CREATE_REINDEX_TEMP](#create_reindex_temp) + - [REINDEX_SOURCE_TO_TEMP_OPEN_PIT](#reindex_source_to_temp_open_pit) + - [REINDEX_SOURCE_TO_TEMP_READ](#reindex_source_to_temp_read) + - [REINDEX_SOURCE_TO_TEMP_INDEX](#reindex_source_to_temp_index) + - [REINDEX_SOURCE_TO_TEMP_CLOSE_PIT](#reindex_source_to_temp_close_pit) + - [SET_TEMP_WRITE_BLOCK](#set_temp_write_block) + - [CLONE_TEMP_TO_TARGET](#clone_temp_to_target) + - [OUTDATED_DOCUMENTS_SEARCH](#outdated_documents_search) + - [OUTDATED_DOCUMENTS_TRANSFORM](#outdated_documents_transform) + - [UPDATE_TARGET_MAPPINGS](#update_target_mappings) + - [UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK](#update_target_mappings_wait_for_task) + - [MARK_VERSION_INDEX_READY_CONFLICT](#mark_version_index_ready_conflict) +- [Manual QA Test Plan](#manual-qa-test-plan) + - [1. Legacy pre-migration](#1-legacy-pre-migration) + - [2. Plugins enabled/disabled](#2-plugins-enableddisabled) + - [Test scenario 1 (enable a plugin after migration):](#test-scenario-1-enable-a-plugin-after-migration) + - [Test scenario 2 (disable a plugin after migration):](#test-scenario-2-disable-a-plugin-after-migration) + - [Test scenario 3 (multiple instances, enable a plugin after migration):](#test-scenario-3-multiple-instances-enable-a-plugin-after-migration) + - [Test scenario 4 (multiple instances, mixed plugin enabled configs):](#test-scenario-4-multiple-instances-mixed-plugin-enabled-configs) + +# Introduction +In the past, the risk of downtime caused by Kibana's saved object upgrade +migrations have discouraged users from adopting the latest features. v2 +migrations aims to solve this problem by minimizing the operational impact on +our users. + +To achieve this it uses a new migration algorithm where every step of the +algorithm is idempotent. No matter at which step a Kibana instance gets +interrupted, it can always restart the migration from the beginning and repeat +all the steps without requiring any user intervention. This doesn't mean +migrations will never fail, but when they fail for intermittent reasons like +an Elasticsearch cluster running out of heap, Kibana will automatically be +able to successfully complete the migration once the cluster has enough heap. + +For more background information on the problem see the [saved object +migrations +RFC](https://github.com/elastic/kibana/blob/master/rfcs/text/0013_saved_object_migrations.md). + +# Algorithm steps +The design goals for the algorithm was to keep downtime below 10 minutes for +100k saved objects while guaranteeing no data loss and keeping steps as simple +and explicit as possible. + +The algorithm is implemented as a state-action machine based on https://www.microsoft.com/en-us/research/uploads/prod/2016/12/Computation-and-State-Machines.pdf + +The state-action machine defines it's behaviour in steps. Each step is a +transition from a control state s_i to the contral state s_i+1 caused by an +action a_i. + +``` +s_i -> a_i -> s_i+1 +s_i+1 -> a_i+1 -> s_i+2 +``` + +Given a control state s1, `next(s1)` returns the next action to execute. +Actions are asynchronous, once the action resolves, we can use the action +response to determine the next state to transition to as defined by the +function `model(state, response)`. + +We can then loosely define a step as: +``` +s_i+1 = model(s_i, await next(s_i)()) +``` + +When there are no more actions returned by `next` the state-action machine +terminates such as in the DONE and FATAL control states. + +What follows is a list of all control states. For each control state the +following is described: + - _next action_: the next action triggered by the current control state + - _new control state_: based on the action response, the possible new control states that the machine will transition to + +Since the algorithm runs once for each saved object index the steps below +always reference a single saved object index `.kibana`. When Kibana starts up, +all the steps are also repeated for the `.kibana_task_manager` index but this +is left out of the description for brevity. + +## INIT +### Next action +`fetchIndices` + +Fetch the saved object indices, mappings and aliases to find the source index +and determine whether we’re migrating from a legacy index or a v1 migrations +index. + +### New control state +1. If `.kibana` and the version specific aliases both exists and are pointing +to the same index. This version's migration has already been completed. Since +the same version could have plugins enabled at any time that would introduce +new transforms or mappings. + → `OUTDATED_DOCUMENTS_SEARCH` + +2. If `.kibana` is pointing to an index that belongs to a later version of +Kibana .e.g. a 7.11.0 instance found the `.kibana` alias pointing to +`.kibana_7.12.0_001` fail the migration + → `FATAL` + +3. If the `.kibana` alias exists we’re migrating from either a v1 or v2 index +and the migration source index is the index the `.kibana` alias points to. + → `WAIT_FOR_YELLOW_SOURCE` + +4. If `.kibana` is a concrete index, we’re migrating from a legacy index + → `LEGACY_SET_WRITE_BLOCK` + +5. If there are no `.kibana` indices, this is a fresh deployment. Initialize a + new saved objects index + → `CREATE_NEW_TARGET` + +## CREATE_NEW_TARGET +### Next action +`createIndex` + +Create the target index. This operation is idempotent, if the index already exist, we wait until its status turns yellow + +### New control state + → `MARK_VERSION_INDEX_READY` + +## LEGACY_SET_WRITE_BLOCK +### Next action +`setWriteBlock` + +Set a write block on the legacy index to prevent any older Kibana instances +from writing to the index while the migration is in progress which could cause +lost acknowledged writes. + +This is the first of a series of `LEGACY_*` control states that will: + - reindex the concrete legacy `.kibana` index into a `.kibana_pre6.5.0_001` index + - delete the concrete `.kibana` _index_ so that we're able to create a `.kibana` _alias_ + +### New control state +1. If the write block was successfully added + → `LEGACY_CREATE_REINDEX_TARGET` +2. If the write block failed because the index doesn't exist, it means another instance already completed the legacy pre-migration. Proceed to the next step. + → `LEGACY_CREATE_REINDEX_TARGET` + +## LEGACY_CREATE_REINDEX_TARGET +### Next action +`createIndex` + +Create a new `.kibana_pre6.5.0_001` index into which we can reindex the legacy +index. (Since the task manager index was converted from a data index into a +saved objects index in 7.4 it will be reindexed into `.kibana_pre7.4.0_001`) +### New control state + → `LEGACY_REINDEX` + +## LEGACY_REINDEX +### Next action +`reindex` + +Let Elasticsearch reindex the legacy index into `.kibana_pre6.5.0_001`. (For +the task manager index we specify a `preMigrationScript` to convert the +original task manager documents into valid saved objects) +### New control state + → `LEGACY_REINDEX_WAIT_FOR_TASK` + + +## LEGACY_REINDEX_WAIT_FOR_TASK +### Next action +`waitForReindexTask` + +Wait for up to 60s for the reindex task to complete. +### New control state +1. If the reindex task completed + → `LEGACY_DELETE` +2. If the reindex task failed with a `target_index_had_write_block` or + `index_not_found_exception` another instance already completed this step + → `LEGACY_DELETE` +3. If the reindex task is still in progress + → `LEGACY_REINDEX_WAIT_FOR_TASK` + +## LEGACY_DELETE +### Next action +`updateAliases` + +Use the updateAliases API to atomically remove the legacy index and create a +new `.kibana` alias that points to `.kibana_pre6.5.0_001`. +### New control state +1. If the action succeeds + → `SET_SOURCE_WRITE_BLOCK` +2. If the action fails with `remove_index_not_a_concrete_index` or + `index_not_found_exception` another instance has already completed this step. + → `SET_SOURCE_WRITE_BLOCK` + +## WAIT_FOR_YELLOW_SOURCE +### Next action +`waitForIndexStatusYellow` + +Wait for the Elasticsearch cluster to be in "yellow" state. It means the index's primary shard is allocated and the index is ready for searching/indexing documents, but ES wasn't able to allocate the replicas. +We don't have as much data redundancy as we could have, but it's enough to start the migration. + +### New control state + → `SET_SOURCE_WRITE_BLOCK` + +## SET_SOURCE_WRITE_BLOCK +### Next action +`setWriteBlock` + +Set a write block on the source index to prevent any older Kibana instances from writing to the index while the migration is in progress which could cause lost acknowledged writes. + +### New control state + → `CREATE_REINDEX_TEMP` + +## CREATE_REINDEX_TEMP +### Next action +`createIndex` + +This operation is idempotent, if the index already exist, we wait until its status turns yellow. + +- Because we will be transforming documents before writing them into this index, we can already set the mappings to the target mappings for this version. The source index might contain documents belonging to a disabled plugin. So set `dynamic: false` mappings for any unknown saved object types. +- (Since we never query the temporary index we can potentially disable refresh to speed up indexing performance. Profile to see if gains justify complexity) + +### New control state + → `REINDEX_SOURCE_TO_TEMP_OPEN_PIT` + +## REINDEX_SOURCE_TO_TEMP_OPEN_PIT +### Next action +`openPIT` + +Open a PIT. Since there is a write block on the source index there is basically no overhead to keeping the PIT so we can lean towards a larger `keep_alive` value like 10 minutes. +### New control state + → `REINDEX_SOURCE_TO_TEMP_READ` + +## REINDEX_SOURCE_TO_TEMP_READ +### Next action +`readNextBatchOfSourceDocuments` + +Read the next batch of outdated documents from the source index by using search after with our PIT. + +### New control state +1. If the batch contained > 0 documents + → `REINDEX_SOURCE_TO_TEMP_INDEX` +2. If there are no more documents returned + → `REINDEX_SOURCE_TO_TEMP_CLOSE_PIT` + +## REINDEX_SOURCE_TO_TEMP_INDEX +### Next action +`transformRawDocs` + `bulkIndexTransformedDocuments` + +1. Transform the current batch of documents +2. Use the bulk API create action to write a batch of up-to-date documents. The create action ensures that there will be only one write per reindexed document even if multiple Kibana instances are performing this step. Ignore any create errors because of documents that already exist in the temporary index. Use `refresh=false` to speed up the create actions, the `UPDATE_TARGET_MAPPINGS` step will ensure that the index is refreshed before we start serving traffic. + +In order to support sharing saved objects to multiple spaces in 8.0, the +transforms will also regenerate document `_id`'s. To ensure that this step +remains idempotent, the new `_id` is deterministically generated using UUIDv5 +ensuring that each Kibana instance generates the same new `_id` for the same document. +### New control state + → `REINDEX_SOURCE_TO_TEMP_READ` + +## REINDEX_SOURCE_TO_TEMP_CLOSE_PIT +### Next action +`closePIT` + +### New control state + → `SET_TEMP_WRITE_BLOCK` + +## SET_TEMP_WRITE_BLOCK +### Next action +`setWriteBlock` + +Set a write block on the temporary index so that we can clone it. +### New control state + → `CLONE_TEMP_TO_TARGET` + +## CLONE_TEMP_TO_TARGET +### Next action +`cloneIndex` + +Ask elasticsearch to clone the temporary index into the target index. If the target index already exists (because another node already started the clone operation), wait until the clone is complete by waiting for a yellow index status. + +We can’t use the temporary index as our target index because one instance can complete the migration, delete a document, and then a second instance starts the reindex operation and re-creates the deleted document. By cloning the temporary index and only accepting writes/deletes from the cloned target index, we prevent lost acknowledged deletes. + +### New control state + → `OUTDATED_DOCUMENTS_SEARCH` + +## OUTDATED_DOCUMENTS_SEARCH +### Next action +`searchForOutdatedDocuments` + +Search for outdated saved object documents. Will return one batch of +documents. + +If another instance has a disabled plugin it will reindex that plugin's +documents without transforming them. Because this instance doesn't know which +plugins were disabled by the instance that performed the +`REINDEX_SOURCE_TO_TEMP_INDEX` step, we need to search for outdated documents +and transform them to ensure that everything is up to date. + +### New control state +1. Found outdated documents? + → `OUTDATED_DOCUMENTS_TRANSFORM` +2. All documents up to date + → `UPDATE_TARGET_MAPPINGS` + +## OUTDATED_DOCUMENTS_TRANSFORM +### Next action +`transformRawDocs` + `bulkOverwriteTransformedDocuments` + +Once transformed we use an index operation to overwrite the outdated document with the up-to-date version. Optimistic concurrency control ensures that we only overwrite the document once so that any updates/writes by another instance which already completed the migration aren’t overwritten and lost. + +### New control state + → `OUTDATED_DOCUMENTS_SEARCH` + +## UPDATE_TARGET_MAPPINGS +### Next action +`updateAndPickupMappings` + +If another instance has some plugins disabled it will disable the mappings of that plugin's types when creating the temporary index. This action will +update the mappings and then use an update_by_query to ensure that all fields are “picked-up” and ready to be searched over. + +### New control state + → `UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK` + +## UPDATE_TARGET_MAPPINGS_WAIT_FOR_TASK +### Next action +`updateAliases` + +Atomically apply the `versionIndexReadyActions` using the _alias actions API. By performing the following actions we guarantee that if multiple versions of Kibana started the upgrade in parallel, only one version will succeed. + +1. verify that the current alias is still pointing to the source index +2. Point the version alias and the current alias to the target index. +3. Remove the temporary index + +### New control state +1. If all the actions succeed we’re ready to serve traffic + → `DONE` +2. If action (1) fails with alias_not_found_exception or action (3) fails with index_not_found_exception another instance already completed the migration + → `MARK_VERSION_INDEX_READY_CONFLICT` + +## MARK_VERSION_INDEX_READY_CONFLICT +### Next action +`fetchIndices` + +Fetch the saved object indices + +### New control state +If another instance completed a migration from the same source we need to verify that it is running the same version. + +1. If the current and version aliases are pointing to the same index the instance that completed the migration was on the same version and it’s safe to start serving traffic. + → `DONE` +2. If the other instance was running a different version we fail the migration. Once we restart one of two things can happen: the other instance is an older version and we will restart the migration, or, it’s a newer version and we will refuse to start up. + → `FATAL` + +# Manual QA Test Plan +## 1. Legacy pre-migration When upgrading from a legacy index additional steps are required before the regular migration process can start. @@ -45,7 +386,7 @@ Test plan: get restarted. Given enough time, it should always be able to successfully complete the migration. -For a successful migration the following behaviour should be observed: +For a successful migration the following behaviour should be observed: 1. The `.kibana` index should be reindexed into a `.kibana_pre6.5.0` index 2. The `.kibana` index should be deleted 3. The `.kibana_index_template` should be deleted @@ -54,12 +395,12 @@ For a successful migration the following behaviour should be observed: 6. Once migration has completed, the `.kibana_current` and `.kibana_7.11.0` aliases should point to the `.kibana_7.11.0_001` index. -### 2. Plugins enabled/disabled +## 2. Plugins enabled/disabled Kibana plugins can be disabled/enabled at any point in time. We need to ensure that Saved Object documents are migrated for all the possible sequences of enabling, disabling, before or after a version upgrade. -#### Test scenario 1 (enable a plugin after migration): +### Test scenario 1 (enable a plugin after migration): 1. Start an old version of Kibana (< 7.11) 2. Create a document that we know will be migrated in a later version (i.e. create a `dashboard`) @@ -70,7 +411,7 @@ enabling, disabling, before or after a version upgrade. 7. Ensure that the document from step (2) has been migrated (`migrationVersion` contains 7.11.0) -#### Test scenario 2 (disable a plugin after migration): +### Test scenario 2 (disable a plugin after migration): 1. Start an old version of Kibana (< 7.11) 2. Create a document that we know will be migrated in a later version (i.e. create a `dashboard`) @@ -80,11 +421,11 @@ enabling, disabling, before or after a version upgrade. 7. Ensure that Kibana logs a warning, but continues to start even though there are saved object documents which don't belong to an enable plugin -#### Test scenario 2 (multiple instances, enable a plugin after migration): +### Test scenario 3 (multiple instances, enable a plugin after migration): Follow the steps from 'Test scenario 1', but perform the migration with multiple instances of Kibana -#### Test scenario 3 (multiple instances, mixed plugin enabled configs): +### Test scenario 4 (multiple instances, mixed plugin enabled configs): We don't support this upgrade scenario, but it's worth making sure we don't have data loss when there's a user error. 1. Start an old version of Kibana (< 7.11) @@ -97,4 +438,3 @@ have data loss when there's a user error. 5. Ensure that the document from step (2) has been migrated (`migrationVersion` contains 7.11.0) -### \ No newline at end of file From c93e028e0c3e18b4b7add48a086f3acd6d8a0d06 Mon Sep 17 00:00:00 2001 From: Ashokaditya <am.struktr@gmail.com> Date: Thu, 29 Apr 2021 14:54:19 +0200 Subject: [PATCH 67/70] [Security Solution][Endpoint] Allow wildcard in trusted app paths (#97623) * show operator dropdown for path field refs elastic/security-team/issues/543 * update translation to use consistent values refs elastic/security-team/issues/543 * update schema to validate path values refs elastic/security-team/issues/543 * add tests for field and operator values refs elastic/security-team/issues/543 * review changes refs elastic/security-team/issues/543 * update schema to enforce dropdown validation for PATH field refs elastic/security-team/issues/543 * add tests for schema updates refs 1deab394531 refs elastic/security-team/issues/543 * optimise dropdown list for re-renders refs elastic/security-team/issues/543 * align input fields and keep alignments when resized refs elastic/security-team/issues/543 * correctly enter operator data on trusted app CRUD refs elastic/security-team/issues/543 * update tests refs 2ac56ee839342c520928487b4a49f25938fa08ca refs elastic/security-team/issues/543 * remove redundant code review changes * better type assertion review changes * move operator options out of component - these do not depend on component props and thus no need to have it within a useMemo callback. - review changes * derive keys from operator entry field review changes * update type * use custom styles for aligning input fields review changes * add a custom type for trusted_apps operator undo changes from list plugin and server/lib/detection_engine refs 2ac56ee839342c520928487b4a49f25938fa08ca refs elastic/security-team/issues/543 * add wildcard entry type refs elastic/security-team/issues/543 refs https://github.com/elastic/kibana/pull/97623#pullrequestreview-642618462 * use the new entry type refs elastic/security-team/issues/543 refs https://github.com/elastic/kibana/pull/97623#pullrequestreview-642618462 * update tests refs elastic/security-team/issues/543 refs https://github.com/elastic/kibana/pull/97623#pullrequestreview-642618462 * update name for wildcard type so that it can be used also for cased inputs refs elastic/security-team/issues/543 refs f9cb7eddda64e8198470cfb9f19a099c19e19dae * update artifacts to support wildcard entries refs elastic/security-team/issues/543 * add tests for list schemas refs f9cb7eddda64e8198470cfb9f19a099c19e19dae refs elastic/security-team/issues/543 * add placeholders for path values review changes elastic/kibana/pull/97623#discussion_r620617999 * ignore type check for now * add type assertion refs 284352ec9a00041cd261bc69a3207708a2e80ef2 * remove unnecessary test refs 2ac56ee839342c520928487b4a49f25938fa08ca * fix types refs f9cb7eddda64e8198470cfb9f19a099c19e19dae refs b3f5dc45539da80e66f5ae8240737717f2a14766 * add a note to entries review changes refs dbd353214930afd5f0b4bb14ffebb94904a9c477 * remove redundant type assertions review changes refs bcf615ac9810edfef0f5037cbd8a82b6972a05dc refs b3f5dc45539da80e66f5ae8240737717f2a14766 * move placeholder text logic to utils review changes elastic/kibana/pull/97623#discussion_r621673881 refs 6f2d0d78104673a4b18a6c0d04a93842ffaf06a5 * pass the style as prop review changes * update api doc CI check suggestion * make placeholderText a function expression review suggestion elastic/kibana/pull/97623/commits/2dc4fd390cf5ea0e4fa67b3f5fc2561cbb29555e * use semantic names for functions refs 330731ebfcfd356707b5ff38d60106e6f1e409d9 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- api_docs/security_solution.json | 38 ++--- x-pack/plugins/lists/common/constants.mock.ts | 1 + .../lists/common/schemas/common/schemas.ts | 1 + .../types/endpoint/entry_match_wildcard.ts | 21 +++ .../lists/common/schemas/types/entries.ts | 20 ++- .../types/entry_match_wildcard.mock.ts | 22 +++ .../types/entry_match_wildcard.test.ts | 106 ++++++++++++++ .../schemas/types/entry_match_wildcard.ts | 21 +++ .../lists/common/schemas/types/index.ts | 1 + x-pack/plugins/lists/common/shared_exports.ts | 2 + .../exceptions/components/builder/types.ts | 5 +- .../endpoint/schema/trusted_apps.test.ts | 24 ++++ .../common/endpoint/schema/trusted_apps.ts | 7 +- .../common/endpoint/types/trusted_apps.ts | 9 +- .../common/shared_imports.ts | 2 + .../common/utils/path_placeholder.test.ts | 69 ++++++++++ .../common/utils/path_placeholder.ts | 43 ++++++ .../common/components/exceptions/types.ts | 3 + .../condition_entry_input/index.test.tsx | 37 +++-- .../condition_entry_input/index.tsx | 80 ++++++++--- .../components/trusted_app_card/index.tsx | 8 +- .../pages/trusted_apps/view/translations.ts | 9 +- .../server/endpoint/lib/artifacts/lists.ts | 17 +++ .../routes/trusted_apps/handlers.test.ts | 8 +- .../routes/trusted_apps/mapping.test.ts | 130 ++++++++++++++---- .../endpoint/routes/trusted_apps/mapping.ts | 35 ++++- .../routes/trusted_apps/service.test.ts | 35 ++++- .../endpoint/schemas/artifacts/lists.ts | 19 +++ .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 30 files changed, 678 insertions(+), 97 deletions(-) create mode 100644 x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_wildcard.ts create mode 100644 x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.mock.ts create mode 100644 x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.test.ts create mode 100644 x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.ts create mode 100644 x-pack/plugins/security_solution/common/utils/path_placeholder.test.ts create mode 100644 x-pack/plugins/security_solution/common/utils/path_placeholder.ts diff --git a/api_docs/security_solution.json b/api_docs/security_solution.json index aea50fdbfecaa..1e932a807d7d6 100644 --- a/api_docs/security_solution.json +++ b/api_docs/security_solution.json @@ -207,7 +207,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/public/plugin.tsx", - "lineNumber": 353 + "lineNumber": 346 } }, { @@ -221,7 +221,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/public/plugin.tsx", - "lineNumber": 353 + "lineNumber": 346 } } ], @@ -229,7 +229,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/public/plugin.tsx", - "lineNumber": 353 + "lineNumber": 346 } }, { @@ -245,7 +245,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/public/plugin.tsx", - "lineNumber": 398 + "lineNumber": 391 } } ], @@ -276,7 +276,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/public/types.ts", - "lineNumber": 69 + "lineNumber": 68 }, "signature": [ "() => Promise<", @@ -287,7 +287,7 @@ ], "source": { "path": "x-pack/plugins/security_solution/public/types.ts", - "lineNumber": 68 + "lineNumber": 67 }, "lifecycle": "setup", "initialIsOpen": true @@ -301,7 +301,7 @@ "children": [], "source": { "path": "x-pack/plugins/security_solution/public/types.ts", - "lineNumber": 72 + "lineNumber": 71 }, "lifecycle": "start", "initialIsOpen": true @@ -453,7 +453,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 147 + "lineNumber": 145 } } ], @@ -461,7 +461,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 147 + "lineNumber": 145 } }, { @@ -521,7 +521,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 159 + "lineNumber": 157 } }, { @@ -535,7 +535,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 159 + "lineNumber": 157 } } ], @@ -543,7 +543,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 159 + "lineNumber": 157 } }, { @@ -582,7 +582,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 341 + "lineNumber": 338 } }, { @@ -596,7 +596,7 @@ "description": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 341 + "lineNumber": 338 } } ], @@ -604,7 +604,7 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 341 + "lineNumber": 338 } }, { @@ -620,13 +620,13 @@ "returnComment": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 423 + "lineNumber": 412 } } ], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 131 + "lineNumber": 129 }, "initialIsOpen": false } @@ -1484,7 +1484,7 @@ "children": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 107 + "lineNumber": 105 }, "lifecycle": "setup", "initialIsOpen": true @@ -1498,7 +1498,7 @@ "children": [], "source": { "path": "x-pack/plugins/security_solution/server/plugin.ts", - "lineNumber": 110 + "lineNumber": 108 }, "lifecycle": "start", "initialIsOpen": true diff --git a/x-pack/plugins/lists/common/constants.mock.ts b/x-pack/plugins/lists/common/constants.mock.ts index 27e0fa29b1e55..177f0a4b291d5 100644 --- a/x-pack/plugins/lists/common/constants.mock.ts +++ b/x-pack/plugins/lists/common/constants.mock.ts @@ -51,6 +51,7 @@ export const OPERATOR_EXCLUDED = 'excluded'; export const ENTRY_VALUE = 'some host name'; export const MATCH = 'match'; export const MATCH_ANY = 'match_any'; +export const WILDCARD = 'wildcard'; export const MAX_IMPORT_PAYLOAD_BYTES = 9000000; export const IMPORT_BUFFER_SIZE = 1000; export const LIST = 'list'; diff --git a/x-pack/plugins/lists/common/schemas/common/schemas.ts b/x-pack/plugins/lists/common/schemas/common/schemas.ts index f261e4e3eefa6..7e43e7dd5f4ab 100644 --- a/x-pack/plugins/lists/common/schemas/common/schemas.ts +++ b/x-pack/plugins/lists/common/schemas/common/schemas.ts @@ -287,6 +287,7 @@ export enum OperatorTypeEnum { NESTED = 'nested', MATCH = 'match', MATCH_ANY = 'match_any', + WILDCARD = 'wildcard', EXISTS = 'exists', LIST = 'list', } diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_wildcard.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_wildcard.ts new file mode 100644 index 0000000000000..dfcaa963666de --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_wildcard.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 * as t from 'io-ts'; + +import { NonEmptyString } from '../../../shared_imports'; +import { operatorIncluded } from '../../common/schemas'; + +export const endpointEntryMatchWildcard = t.exact( + t.type({ + field: NonEmptyString, + operator: operatorIncluded, + type: t.keyof({ wildcard: null }), + value: NonEmptyString, + }) +); +export type EndpointEntryMatchWildcard = t.TypeOf<typeof endpointEntryMatchWildcard>; diff --git a/x-pack/plugins/lists/common/schemas/types/entries.ts b/x-pack/plugins/lists/common/schemas/types/entries.ts index 277751bf1c271..26cfed568cea8 100644 --- a/x-pack/plugins/lists/common/schemas/types/entries.ts +++ b/x-pack/plugins/lists/common/schemas/types/entries.ts @@ -12,12 +12,28 @@ import { entriesMatch } from './entry_match'; import { entriesExists } from './entry_exists'; import { entriesList } from './entry_list'; import { entriesNested } from './entry_nested'; +import { entriesMatchWildcard } from './entry_match_wildcard'; -export const entry = t.union([entriesMatch, entriesMatchAny, entriesList, entriesExists]); +// NOTE: Type nested is not included here to denote it's non-recursive nature. +// So a nested entry is really just a collection of `Entry` types. +export const entry = t.union([ + entriesMatch, + entriesMatchAny, + entriesList, + entriesExists, + entriesMatchWildcard, +]); export type Entry = t.TypeOf<typeof entry>; export const entriesArray = t.array( - t.union([entriesMatch, entriesMatchAny, entriesList, entriesExists, entriesNested]) + t.union([ + entriesMatch, + entriesMatchAny, + entriesList, + entriesExists, + entriesNested, + entriesMatchWildcard, + ]) ); export type EntriesArray = t.TypeOf<typeof entriesArray>; diff --git a/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.mock.ts b/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.mock.ts new file mode 100644 index 0000000000000..3204bbe064496 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.mock.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 { ENTRY_VALUE, FIELD, OPERATOR, WILDCARD } from '../../constants.mock'; + +import { EntryMatchWildcard } from './entry_match_wildcard'; + +export const getEntryMatchWildcardMock = (): EntryMatchWildcard => ({ + field: FIELD, + operator: OPERATOR, + type: WILDCARD, + value: ENTRY_VALUE, +}); + +export const getEntryMatchWildcardExcludeMock = (): EntryMatchWildcard => ({ + ...getEntryMatchWildcardMock(), + operator: 'excluded', +}); diff --git a/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.test.ts b/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.test.ts new file mode 100644 index 0000000000000..53cfc4fdff1f5 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.test.ts @@ -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 { pipe } from 'fp-ts/lib/pipeable'; +import { left } from 'fp-ts/lib/Either'; + +import { foldLeftRight, getPaths } from '../../shared_imports'; + +import { getEntryMatchWildcardMock } from './entry_match_wildcard.mock'; +import { EntryMatchWildcard, entriesMatchWildcard } from './entry_match_wildcard'; + +describe('entriesMatchWildcard', () => { + test('it should validate an entry', () => { + const payload = getEntryMatchWildcardMock(); + const decoded = entriesMatchWildcard.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should validate when operator is "included"', () => { + const payload = getEntryMatchWildcardMock(); + const decoded = entriesMatchWildcard.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should validate when "operator" is "excluded"', () => { + const payload = getEntryMatchWildcardMock(); + payload.operator = 'excluded'; + const decoded = entriesMatchWildcard.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should FAIL validation when "field" is empty string', () => { + const payload: Omit<EntryMatchWildcard, 'field'> & { field: string } = { + ...getEntryMatchWildcardMock(), + field: '', + }; + const decoded = entriesMatchWildcard.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']); + expect(message.schema).toEqual({}); + }); + + test('it should FAIL validation when "value" is not string', () => { + const payload: Omit<EntryMatchWildcard, 'value'> & { value: string[] } = { + ...getEntryMatchWildcardMock(), + value: ['some value'], + }; + const decoded = entriesMatchWildcard.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "["some value"]" supplied to "value"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should FAIL validation when "value" is empty string', () => { + const payload: Omit<EntryMatchWildcard, 'value'> & { value: string } = { + ...getEntryMatchWildcardMock(), + value: '', + }; + const decoded = entriesMatchWildcard.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "value"']); + expect(message.schema).toEqual({}); + }); + + test('it should FAIL validation when "type" is not "wildcard"', () => { + const payload: Omit<EntryMatchWildcard, 'type'> & { type: string } = { + ...getEntryMatchWildcardMock(), + type: 'match', + }; + const decoded = entriesMatchWildcard.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']); + expect(message.schema).toEqual({}); + }); + + test('it should strip out extra keys', () => { + const payload: EntryMatchWildcard & { + extraKey?: string; + } = getEntryMatchWildcardMock(); + payload.extraKey = 'some value'; + const decoded = entriesMatchWildcard.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(getEntryMatchWildcardMock()); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.ts b/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.ts new file mode 100644 index 0000000000000..14522256df354 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/entry_match_wildcard.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 * as t from 'io-ts'; + +import { NonEmptyString } from '../../shared_imports'; +import { operator } from '../common/schemas'; + +export const entriesMatchWildcard = t.exact( + t.type({ + field: NonEmptyString, + operator, + type: t.keyof({ wildcard: null }), + value: NonEmptyString, + }) +); +export type EntryMatchWildcard = t.TypeOf<typeof entriesMatchWildcard>; diff --git a/x-pack/plugins/lists/common/schemas/types/index.ts b/x-pack/plugins/lists/common/schemas/types/index.ts index 98342f3b9c153..ebe21174570cb 100644 --- a/x-pack/plugins/lists/common/schemas/types/index.ts +++ b/x-pack/plugins/lists/common/schemas/types/index.ts @@ -15,6 +15,7 @@ export * from './default_namespace'; export * from './entries'; export * from './entry_match'; export * from './entry_match_any'; +export * from './entry_match_wildcard'; export * from './entry_list'; export * from './entry_exists'; export * from './entry_nested'; diff --git a/x-pack/plugins/lists/common/shared_exports.ts b/x-pack/plugins/lists/common/shared_exports.ts index 286fee6de5425..8be53cb8cddbc 100644 --- a/x-pack/plugins/lists/common/shared_exports.ts +++ b/x-pack/plugins/lists/common/shared_exports.ts @@ -20,6 +20,7 @@ export { EntryExists, EntryMatch, EntryMatchAny, + EntryMatchWildcard, EntryNested, EntryList, EntriesArray, @@ -39,6 +40,7 @@ export { nestedEntryItem, entriesMatch, entriesMatchAny, + entriesMatchWildcard, entriesExists, entriesList, namespaceType, diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/types.ts b/x-pack/plugins/lists/public/exceptions/components/builder/types.ts index cdb4f735aa103..800f1445217b9 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/types.ts +++ b/x-pack/plugins/lists/public/exceptions/components/builder/types.ts @@ -13,6 +13,7 @@ import { EntryExists, EntryMatch, EntryMatchAny, + EntryMatchWildcard, EntryNested, ExceptionListItemSchema, OperatorEnum, @@ -34,7 +35,7 @@ export interface EmptyEntry { id: string; field: string | undefined; operator: OperatorEnum; - type: OperatorTypeEnum.MATCH | OperatorTypeEnum.MATCH_ANY; + type: OperatorTypeEnum.MATCH | OperatorTypeEnum.MATCH_ANY | OperatorTypeEnum.WILDCARD; value: string | string[] | undefined; } @@ -53,6 +54,7 @@ export interface EmptyNestedEntry { entries: Array< | (EntryMatch & { id?: string }) | (EntryMatchAny & { id?: string }) + | (EntryMatchWildcard & { id?: string }) | (EntryExists & { id?: string }) >; } @@ -69,6 +71,7 @@ export type BuilderEntryNested = Omit<EntryNested, 'entries'> & { entries: Array< | (EntryMatch & { id?: string }) | (EntryMatchAny & { id?: string }) + | (EntryMatchWildcard & { id?: string }) | (EntryExists & { id?: string }) >; }; diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts index 326795ae55662..df0d0d7acf4c7 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.test.ts @@ -247,6 +247,30 @@ describe('When invoking Trusted Apps Schema', () => { expect(() => body.validate(bodyMsg)).not.toThrow(); }); + it('should validate `entry.type` does not accept `wildcard` when field is NOT PATH', () => { + const bodyMsg = createNewTrustedApp({ + entries: [ + createConditionEntry({ + field: ConditionEntryField.HASH, + type: 'wildcard', + }), + ], + }); + expect(() => body.validate(bodyMsg)).toThrow(); + }); + + it('should validate `entry.type` accepts `wildcard` when field is PATH', () => { + const bodyMsg = createNewTrustedApp({ + entries: [ + createConditionEntry({ + field: ConditionEntryField.PATH, + type: 'wildcard', + }), + ], + }); + expect(() => body.validate(bodyMsg)).not.toThrow(); + }); + it('should validate `entry.value` required', () => { const { value, ...entry } = createConditionEntry(); expect(() => body.validate(createNewTrustedApp({ entries: [entry] }))).toThrow(); diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts index e582744e1a141..54d0becd2446e 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts @@ -29,7 +29,12 @@ export const GetTrustedAppsRequestSchema = { }), }; -const ConditionEntryTypeSchema = schema.literal('match'); +const ConditionEntryTypeSchema = schema.conditional( + schema.siblingRef('field'), + ConditionEntryField.PATH, + schema.oneOf([schema.literal('match'), schema.literal('wildcard')]), + schema.literal('match') +); const ConditionEntryOperatorSchema = schema.literal('included'); /* diff --git a/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts b/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts index d36958c11d2a1..8d66370fea4d3 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/trusted_apps.ts @@ -7,6 +7,7 @@ import { TypeOf } from '@kbn/config-schema'; import { ApplicationStart } from 'kibana/public'; + import { DeleteTrustedAppsRequestSchema, GetOneTrustedAppRequestSchema, @@ -69,9 +70,15 @@ export enum ConditionEntryField { SIGNER = 'process.Ext.code_signature', } +export enum OperatorFieldIds { + is = 'is', + matches = 'matches', +} + +export type TrustedAppEntryTypes = 'match' | 'wildcard'; export interface ConditionEntry<T extends ConditionEntryField = ConditionEntryField> { field: T; - type: 'match'; + type: TrustedAppEntryTypes; operator: 'included'; value: string; } diff --git a/x-pack/plugins/security_solution/common/shared_imports.ts b/x-pack/plugins/security_solution/common/shared_imports.ts index 033df0df6c458..e987775a8e768 100644 --- a/x-pack/plugins/security_solution/common/shared_imports.ts +++ b/x-pack/plugins/security_solution/common/shared_imports.ts @@ -20,6 +20,7 @@ export { EntryExists, EntryMatch, EntryMatchAny, + EntryMatchWildcard, EntryNested, EntryList, EntriesArray, @@ -38,6 +39,7 @@ export { nestedEntryItem, entriesMatch, entriesMatchAny, + entriesMatchWildcard, entriesExists, entriesList, namespaceType, diff --git a/x-pack/plugins/security_solution/common/utils/path_placeholder.test.ts b/x-pack/plugins/security_solution/common/utils/path_placeholder.test.ts new file mode 100644 index 0000000000000..9618440c105dc --- /dev/null +++ b/x-pack/plugins/security_solution/common/utils/path_placeholder.test.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. + */ + +import { getPlaceholderTextByOSType, getPlaceholderText } from './path_placeholder'; +import { ConditionEntryField, OperatingSystem, TrustedAppEntryTypes } from '../endpoint/types'; + +const trustedAppEntry = { + os: OperatingSystem.LINUX, + field: ConditionEntryField.HASH, + type: 'match' as TrustedAppEntryTypes, +}; + +describe('Trusted Apps: Path placeholder text', () => { + it('returns no placeholder text when field IS NOT PATH', () => { + expect(getPlaceholderTextByOSType({ ...trustedAppEntry })).toEqual(undefined); + }); + + it('returns a placeholder text when field IS PATH', () => { + expect( + getPlaceholderTextByOSType({ ...trustedAppEntry, field: ConditionEntryField.PATH }) + ).toEqual(getPlaceholderText().others.exact); + }); + + it('returns LINUX/MAC equivalent placeholder when field IS PATH', () => { + expect( + getPlaceholderTextByOSType({ + ...trustedAppEntry, + os: OperatingSystem.MAC, + field: ConditionEntryField.PATH, + }) + ).toEqual(getPlaceholderText().others.exact); + }); + + it('returns LINUX/MAC equivalent placeholder text when field IS PATH and WILDCARD operator is selected', () => { + expect( + getPlaceholderTextByOSType({ + ...trustedAppEntry, + os: OperatingSystem.LINUX, + field: ConditionEntryField.PATH, + type: 'wildcard', + }) + ).toEqual(getPlaceholderText().others.wildcard); + }); + + it('returns WINDOWS equivalent placeholder text when field IS PATH', () => { + expect( + getPlaceholderTextByOSType({ + ...trustedAppEntry, + os: OperatingSystem.WINDOWS, + field: ConditionEntryField.PATH, + }) + ).toEqual(getPlaceholderText().windows.exact); + }); + + it('returns WINDOWS equivalent placeholder text when field IS PATH and WILDCARD operator is selected', () => { + expect( + getPlaceholderTextByOSType({ + ...trustedAppEntry, + os: OperatingSystem.WINDOWS, + field: ConditionEntryField.PATH, + type: 'wildcard', + }) + ).toEqual(getPlaceholderText().windows.wildcard); + }); +}); diff --git a/x-pack/plugins/security_solution/common/utils/path_placeholder.ts b/x-pack/plugins/security_solution/common/utils/path_placeholder.ts new file mode 100644 index 0000000000000..bba01b6d05b65 --- /dev/null +++ b/x-pack/plugins/security_solution/common/utils/path_placeholder.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 { ConditionEntryField, OperatingSystem, TrustedAppEntryTypes } from '../endpoint/types'; + +export const getPlaceholderText = () => ({ + windows: { + wildcard: 'C:\\sample\\**\\*', + exact: 'C:\\sample\\path.exe', + }, + others: { + wildcard: '/opt/**/*', + exact: '/opt/bin', + }, +}); + +export const getPlaceholderTextByOSType = ({ + os, + field, + type, +}: { + os: OperatingSystem; + field: ConditionEntryField; + type: TrustedAppEntryTypes; +}): string | undefined => { + if (field === ConditionEntryField.PATH) { + if (os === OperatingSystem.WINDOWS) { + if (type === 'wildcard') { + return getPlaceholderText().windows.wildcard; + } + return getPlaceholderText().windows.exact; + } else { + if (type === 'wildcard') { + return getPlaceholderText().others.wildcard; + } + return getPlaceholderText().others.exact; + } + } +}; diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/types.ts b/x-pack/plugins/security_solution/public/common/components/exceptions/types.ts index c7a125daa54f8..92a3cb2cfac93 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/types.ts @@ -15,6 +15,7 @@ import { Entry, EntryMatch, EntryMatchAny, + EntryMatchWildcard, EntryExists, ExceptionListItemSchema, CreateExceptionListItemSchema, @@ -92,6 +93,7 @@ export interface EmptyNestedEntry { type: OperatorTypeEnum.NESTED; entries: Array< | (EntryMatch & { id?: string }) + | (EntryMatchWildcard & { id?: string }) | (EntryMatchAny & { id?: string }) | (EntryExists & { id?: string }) >; @@ -108,6 +110,7 @@ export type BuilderEntryNested = Omit<EntryNested, 'entries'> & { id?: string; entries: Array< | (EntryMatch & { id?: string }) + | (EntryMatchWildcard & { id?: string }) | (EntryMatchAny & { id?: string }) | (EntryExists & { id?: string }) >; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.test.tsx index 4e9ec3a0883a2..9d6c35d64b2d5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.test.tsx @@ -21,7 +21,7 @@ let onRemoveMock: jest.Mock; let onChangeMock: jest.Mock; let onVisitedMock: jest.Mock; -const entry: Readonly<ConditionEntry> = { +const baseEntry: Readonly<ConditionEntry> = { field: ConditionEntryField.HASH, type: 'match', operator: 'included', @@ -38,7 +38,8 @@ describe('Condition entry input', () => { const getElement = ( subject: string, os: OperatingSystem = OperatingSystem.WINDOWS, - isRemoveDisabled: boolean = false + isRemoveDisabled: boolean = false, + entry: ConditionEntry = baseEntry ) => ( <ConditionEntryInput os={os} @@ -64,10 +65,10 @@ describe('Condition entry input', () => { expect(onChangeMock).toHaveBeenCalledTimes(1); expect(onChangeMock).toHaveBeenCalledWith( { - ...entry, + ...baseEntry, field: { target: { value: field } }, }, - entry + baseEntry ); } ); @@ -77,7 +78,7 @@ describe('Condition entry input', () => { expect(onRemoveMock).toHaveBeenCalledTimes(0); element.find('[data-test-subj="testOnRemove-remove"]').first().simulate('click'); expect(onRemoveMock).toHaveBeenCalledTimes(1); - expect(onRemoveMock).toHaveBeenCalledWith(entry); + expect(onRemoveMock).toHaveBeenCalledWith(baseEntry); }); it('should not be able to call on remove for field input because disabled', () => { @@ -92,7 +93,7 @@ describe('Condition entry input', () => { expect(onVisitedMock).toHaveBeenCalledTimes(0); element.find('[data-test-subj="testOnVisited-value"]').first().simulate('blur'); expect(onVisitedMock).toHaveBeenCalledTimes(1); - expect(onVisitedMock).toHaveBeenCalledWith(entry); + expect(onVisitedMock).toHaveBeenCalledWith(baseEntry); }); it('should change value for field input', () => { @@ -105,10 +106,10 @@ describe('Condition entry input', () => { expect(onChangeMock).toHaveBeenCalledTimes(1); expect(onChangeMock).toHaveBeenCalledWith( { - ...entry, + ...baseEntry, value: 'new value', }, - entry + baseEntry ); }); @@ -138,4 +139,24 @@ describe('Condition entry input', () => { .props() as EuiSuperSelectProps<string>; expect(superSelectProps.options.length).toBe(2); }); + + it('should have operator value selected when field is HASH', () => { + const element = shallow(getElement('testOperatorOptions')); + const inputField = element.find('[data-test-subj="testOperatorOptions-operator"]'); + expect(inputField.contains('is')); + }); + + it('should show operator dorpdown with two values when field is PATH', () => { + const element = shallow( + getElement('testOperatorOptions', undefined, undefined, { + ...baseEntry, + field: ConditionEntryField.PATH, + }) + ); + const superSelectProps = element + .find('[data-test-subj="testOperatorOptions-operator"]') + .first() + .props() as EuiSuperSelectProps<string>; + expect(superSelectProps.options.length).toBe(2); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.tsx index 633adde4fdfbb..d052138d309ac 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.tsx @@ -6,12 +6,11 @@ */ import React, { ChangeEventHandler, memo, useCallback, useMemo } from 'react'; +import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { EuiButtonIcon, EuiFieldText, - EuiFlexGroup, - EuiFlexItem, EuiFormRow, EuiSuperSelect, EuiSuperSelectOption, @@ -21,6 +20,7 @@ import { import { ConditionEntry, ConditionEntryField, + OperatorFieldIds, OperatingSystem, } from '../../../../../../../common/endpoint/types'; @@ -28,9 +28,10 @@ import { CONDITION_FIELD_DESCRIPTION, CONDITION_FIELD_TITLE, ENTRY_PROPERTY_TITLES, - OPERATOR_TITLE, + OPERATOR_TITLES, } from '../../translations'; import { useTestIdGenerator } from '../../../../../components/hooks/use_test_id_generator'; +import { getPlaceholderTextByOSType } from '../../../../../../../common/utils/path_placeholder'; const ConditionEntryCell = memo<{ showLabel: boolean; @@ -66,6 +67,27 @@ export interface ConditionEntryInputProps { 'data-test-subj'?: string; } +// adding a style prop on EuiFlexGroup works only partially +// and for some odd reason garbles up gridTemplateAreas entry +const InputGroup = styled.div` + display: grid; + grid-template-columns: 25% 25% 45% 5%; + grid-template-areas: 'field operator value remove'; +`; + +const InputItem = styled.div<{ gridArea: string }>` + grid-area: ${({ gridArea }) => gridArea}; + align-self: center; + margin: 4px; + vertical-align: baseline; +`; + +const operatorOptions = (Object.keys(OperatorFieldIds) as OperatorFieldIds[]).map((value) => ({ + dropdownDisplay: OPERATOR_TITLES[value], + inputDisplay: OPERATOR_TITLES[value], + value: value === 'matches' ? 'wildcard' : 'match', +})); + export const ConditionEntryInput = memo<ConditionEntryInputProps>( ({ os, @@ -122,6 +144,11 @@ export const ConditionEntryInput = memo<ConditionEntryInputProps>( [entry, onChange] ); + const handleOperatorUpdate = useCallback( + (newOperator) => onChange({ ...entry, type: newOperator }, entry), + [entry, onChange] + ); + const handleRemoveClick = useCallback(() => onRemove(entry), [entry, onRemove]); const handleValueOnBlur = useCallback(() => { @@ -131,14 +158,8 @@ export const ConditionEntryInput = memo<ConditionEntryInputProps>( }, [entry, onVisited]); return ( - <EuiFlexGroup - gutterSize="s" - alignItems="center" - direction="row" - data-test-subj={dataTestSubj} - responsive={false} - > - <EuiFlexItem grow={2}> + <InputGroup data-test-subj={dataTestSubj}> + <InputItem gridArea="field"> <ConditionEntryCell showLabel={showLabels} label={ENTRY_PROPERTY_TITLES.field}> <EuiSuperSelect options={fieldOptions} @@ -147,17 +168,36 @@ export const ConditionEntryInput = memo<ConditionEntryInputProps>( data-test-subj={getTestId('field')} /> </ConditionEntryCell> - </EuiFlexItem> - <EuiFlexItem> + </InputItem> + <InputItem gridArea="operator"> <ConditionEntryCell showLabel={showLabels} label={ENTRY_PROPERTY_TITLES.operator}> - <EuiFieldText name="operator" value={OPERATOR_TITLE.included} readOnly /> + {entry.field === ConditionEntryField.PATH ? ( + <EuiSuperSelect + options={operatorOptions} + onChange={handleOperatorUpdate} + valueOfSelected={entry.type} + data-test-subj={getTestId('operator')} + /> + ) : ( + <EuiFieldText + name="operator" + value={OPERATOR_TITLES.is} + data-test-subj={getTestId('operator')} + readOnly + /> + )} </ConditionEntryCell> - </EuiFlexItem> - <EuiFlexItem grow={3}> + </InputItem> + <InputItem gridArea="value"> <ConditionEntryCell showLabel={showLabels} label={ENTRY_PROPERTY_TITLES.value}> <EuiFieldText name="value" value={entry.value} + placeholder={getPlaceholderTextByOSType({ + os, + field: entry.field, + type: entry.type, + })} fullWidth required onChange={handleValueUpdate} @@ -165,8 +205,8 @@ export const ConditionEntryInput = memo<ConditionEntryInputProps>( data-test-subj={getTestId('value')} /> </ConditionEntryCell> - </EuiFlexItem> - <EuiFlexItem grow={false}> + </InputItem> + <InputItem gridArea="remove"> {/* Unicode `nbsp` is used below so that Remove button is property displayed */} <ConditionEntryCell showLabel={showLabels} label={'\u00A0'}> <EuiButtonIcon @@ -181,8 +221,8 @@ export const ConditionEntryInput = memo<ConditionEntryInputProps>( data-test-subj={getTestId('remove')} /> </ConditionEntryCell> - </EuiFlexItem> - </EuiFlexGroup> + </InputItem> + </InputGroup> ); } ); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.tsx index 0520f760d7343..8289792b81f89 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.tsx @@ -31,7 +31,7 @@ import { ENTRY_PROPERTY_TITLES, CARD_DELETE_BUTTON_LABEL, CONDITION_FIELD_TITLE, - OPERATOR_TITLE, + OPERATOR_TITLES, CARD_EDIT_BUTTON_LABEL, } from '../../translations'; @@ -45,7 +45,7 @@ const getEntriesColumnDefinitions = (): Array<EuiTableFieldDataColumnType<Entry> truncateText: true, textOnly: true, width: '30%', - render(field: Entry['field'], entry: Entry) { + render(field: Entry['field'], _entry: Entry) { return CONDITION_FIELD_TITLE[field]; }, }, @@ -55,8 +55,8 @@ const getEntriesColumnDefinitions = (): Array<EuiTableFieldDataColumnType<Entry> sortable: false, truncateText: true, width: '20%', - render(field: Entry['operator'], entry: Entry) { - return OPERATOR_TITLE[field]; + render(_field: Entry['operator'], entry: Entry) { + return entry.type === 'wildcard' ? OPERATOR_TITLES.matches : OPERATOR_TITLES.is; }, }, { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts index c3e2a372fd6dc..fc031a63b84b1 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts @@ -10,8 +10,8 @@ import { TrustedApp, MacosLinuxConditionEntry, WindowsConditionEntry, - ConditionEntry, ConditionEntryField, + OperatorFieldIds, } from '../../../../../common/endpoint/types'; export { OS_TITLES } from '../../../common/translations'; @@ -52,10 +52,13 @@ export const CONDITION_FIELD_DESCRIPTION: { [K in ConditionEntryField]: string } ), }; -export const OPERATOR_TITLE: { [K in ConditionEntry['operator']]: string } = { - included: i18n.translate('xpack.securitySolution.trustedapps.card.operator.includes', { +export const OPERATOR_TITLES: { [K in OperatorFieldIds]: string } = { + is: i18n.translate('xpack.securitySolution.trustedapps.card.operator.is', { defaultMessage: 'is', }), + matches: i18n.translate('xpack.securitySolution.trustedapps.card.operator.matches', { + defaultMessage: 'matches', + }), }; export const PROPERTY_TITLES: Readonly< 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 1c3c92c50afd3..54b6971eec58e 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 @@ -22,6 +22,8 @@ import { translatedEntryMatchAnyMatcher, TranslatedEntryMatcher, translatedEntryMatchMatcher, + TranslatedEntryMatchWildcardMatcher, + translatedEntryMatchWildcardMatcher, TranslatedEntryNestedEntry, translatedEntryNestedEntry, TranslatedExceptionListItem, @@ -203,6 +205,10 @@ function getMatcherFunction(field: string, matchAny?: boolean): TranslatedEntryM : 'exact_cased'; } +function getMatcherWildcardFunction(field: string): TranslatedEntryMatchWildcardMatcher { + return field.endsWith('.caseless') ? 'wildcard_caseless' : 'wildcard_cased'; +} + function normalizeFieldName(field: string): string { return field.endsWith('.caseless') ? field.substring(0, field.lastIndexOf('.')) : field; } @@ -272,6 +278,17 @@ function translateEntry( } : undefined; } + case 'wildcard': { + const matcher = getMatcherWildcardFunction(entry.field); + return translatedEntryMatchWildcardMatcher.is(matcher) + ? { + field: normalizeFieldName(entry.field), + operator: entry.operator, + type: matcher, + value: entry.value, + } + : undefined; + } } } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts index 42a2e0f43d970..0b4e1cb2b09b1 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts @@ -66,8 +66,8 @@ const NEW_TRUSTED_APP: NewTrustedApp = { os: OperatingSystem.LINUX, effectScope: { type: 'global' }, entries: [ - createConditionEntry(ConditionEntryField.PATH, '/bin/malware'), - createConditionEntry(ConditionEntryField.HASH, '1234234659af249ddf3e40864e9fb241'), + createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware'), + createConditionEntry(ConditionEntryField.HASH, 'match', '1234234659af249ddf3e40864e9fb241'), ], }; @@ -83,8 +83,8 @@ const TRUSTED_APP: TrustedApp = { os: OperatingSystem.LINUX, effectScope: { type: 'global' }, entries: [ - createConditionEntry(ConditionEntryField.HASH, '1234234659af249ddf3e40864e9fb241'), - createConditionEntry(ConditionEntryField.PATH, '/bin/malware'), + createConditionEntry(ConditionEntryField.HASH, 'match', '1234234659af249ddf3e40864e9fb241'), + createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware'), ], }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.test.ts index 68ff7d03e413a..9ee2ece627841 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.test.ts @@ -79,13 +79,19 @@ describe('mapping', () => { description: 'Linux Trusted App', effectScope: { type: 'global' }, os: OperatingSystem.LINUX, - entries: [createConditionEntry(ConditionEntryField.PATH, '/bin/malware')], + entries: [createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware')], }, createExceptionListItemOptions({ name: 'linux trusted app', description: 'Linux Trusted App', osTypes: ['linux'], - entries: [createEntryMatch('process.executable.caseless', '/bin/malware')], + entries: [ + createEntryMatch( + 'process.executable.caseless', + + '/bin/malware' + ), + ], }) ); }); @@ -97,13 +103,19 @@ describe('mapping', () => { description: 'MacOS Trusted App', effectScope: { type: 'global' }, os: OperatingSystem.MAC, - entries: [createConditionEntry(ConditionEntryField.PATH, '/bin/malware')], + entries: [createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware')], }, createExceptionListItemOptions({ name: 'macos trusted app', description: 'MacOS Trusted App', osTypes: ['macos'], - entries: [createEntryMatch('process.executable.caseless', '/bin/malware')], + entries: [ + createEntryMatch( + 'process.executable.caseless', + + '/bin/malware' + ), + ], }) ); }); @@ -115,13 +127,21 @@ describe('mapping', () => { description: 'Windows Trusted App', effectScope: { type: 'global' }, os: OperatingSystem.WINDOWS, - entries: [createConditionEntry(ConditionEntryField.PATH, 'C:\\Program Files\\Malware')], + entries: [ + createConditionEntry(ConditionEntryField.PATH, 'match', 'C:\\Program Files\\Malware'), + ], }, createExceptionListItemOptions({ name: 'windows trusted app', description: 'Windows Trusted App', osTypes: ['windows'], - entries: [createEntryMatch('process.executable.caseless', 'C:\\Program Files\\Malware')], + entries: [ + createEntryMatch( + 'process.executable.caseless', + + 'C:\\Program Files\\Malware' + ), + ], }) ); }); @@ -133,7 +153,7 @@ describe('mapping', () => { description: 'Signed Trusted App', effectScope: { type: 'global' }, os: OperatingSystem.WINDOWS, - entries: [createConditionEntry(ConditionEntryField.SIGNER, 'Microsoft Windows')], + entries: [createConditionEntry(ConditionEntryField.SIGNER, 'match', 'Microsoft Windows')], }, createExceptionListItemOptions({ name: 'Signed trusted app', @@ -157,14 +177,24 @@ describe('mapping', () => { effectScope: { type: 'global' }, os: OperatingSystem.LINUX, entries: [ - createConditionEntry(ConditionEntryField.HASH, '1234234659af249ddf3e40864e9fb241'), + createConditionEntry( + ConditionEntryField.HASH, + 'match', + '1234234659af249ddf3e40864e9fb241' + ), ], }, createExceptionListItemOptions({ name: 'MD5 trusted app', description: 'MD5 Trusted App', osTypes: ['linux'], - entries: [createEntryMatch('process.hash.md5', '1234234659af249ddf3e40864e9fb241')], + entries: [ + createEntryMatch( + 'process.hash.md5', + + '1234234659af249ddf3e40864e9fb241' + ), + ], }) ); }); @@ -179,6 +209,7 @@ describe('mapping', () => { entries: [ createConditionEntry( ConditionEntryField.HASH, + 'match', 'f635da961234234659af249ddf3e40864e9fb241' ), ], @@ -188,7 +219,11 @@ describe('mapping', () => { description: 'SHA1 Trusted App', osTypes: ['linux'], entries: [ - createEntryMatch('process.hash.sha1', 'f635da961234234659af249ddf3e40864e9fb241'), + createEntryMatch( + 'process.hash.sha1', + + 'f635da961234234659af249ddf3e40864e9fb241' + ), ], }) ); @@ -204,6 +239,7 @@ describe('mapping', () => { entries: [ createConditionEntry( ConditionEntryField.HASH, + 'match', 'f635da96124659af249ddf3e40864e9fb234234659af249ddf3e40864e9fb241' ), ], @@ -215,6 +251,7 @@ describe('mapping', () => { entries: [ createEntryMatch( 'process.hash.sha256', + 'f635da96124659af249ddf3e40864e9fb234234659af249ddf3e40864e9fb241' ), ], @@ -230,14 +267,24 @@ describe('mapping', () => { effectScope: { type: 'global' }, os: OperatingSystem.LINUX, entries: [ - createConditionEntry(ConditionEntryField.HASH, '1234234659Af249ddf3e40864E9FB241'), + createConditionEntry( + ConditionEntryField.HASH, + 'match', + '1234234659Af249ddf3e40864E9FB241' + ), ], }, createExceptionListItemOptions({ name: 'MD5 trusted app', description: 'MD5 Trusted App', osTypes: ['linux'], - entries: [createEntryMatch('process.hash.md5', '1234234659af249ddf3e40864e9fb241')], + entries: [ + createEntryMatch( + 'process.hash.md5', + + '1234234659af249ddf3e40864e9fb241' + ), + ], }) ); }); @@ -257,7 +304,13 @@ describe('mapping', () => { created_at: '11/11/2011T11:11:11.111', created_by: 'admin', os_types: ['linux'], - entries: [createEntryMatch('process.executable.caseless', '/bin/malware')], + entries: [ + createEntryMatch( + 'process.executable.caseless', + + '/bin/malware' + ), + ], }), { id: '123', @@ -270,7 +323,7 @@ describe('mapping', () => { updated_at: '11/11/2011T11:11:11.111', updated_by: 'admin', os: OperatingSystem.LINUX, - entries: [createConditionEntry(ConditionEntryField.PATH, '/bin/malware')], + entries: [createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware')], } ); }); @@ -284,7 +337,13 @@ describe('mapping', () => { created_at: '11/11/2011T11:11:11.111', created_by: 'admin', os_types: ['macos'], - entries: [createEntryMatch('process.executable.caseless', '/bin/malware')], + entries: [ + createEntryMatch( + 'process.executable.caseless', + + '/bin/malware' + ), + ], }), { id: '123', @@ -297,7 +356,7 @@ describe('mapping', () => { updated_at: '11/11/2011T11:11:11.111', updated_by: 'admin', os: OperatingSystem.MAC, - entries: [createConditionEntry(ConditionEntryField.PATH, '/bin/malware')], + entries: [createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware')], } ); }); @@ -311,7 +370,13 @@ describe('mapping', () => { created_at: '11/11/2011T11:11:11.111', created_by: 'admin', os_types: ['windows'], - entries: [createEntryMatch('process.executable.caseless', 'C:\\Program Files\\Malware')], + entries: [ + createEntryMatch( + 'process.executable.caseless', + + 'C:\\Program Files\\Malware' + ), + ], }), { id: '123', @@ -324,7 +389,9 @@ describe('mapping', () => { updated_at: '11/11/2011T11:11:11.111', updated_by: 'admin', os: OperatingSystem.WINDOWS, - entries: [createConditionEntry(ConditionEntryField.PATH, 'C:\\Program Files\\Malware')], + entries: [ + createConditionEntry(ConditionEntryField.PATH, 'match', 'C:\\Program Files\\Malware'), + ], } ); }); @@ -356,7 +423,7 @@ describe('mapping', () => { updated_at: '11/11/2011T11:11:11.111', updated_by: 'admin', os: OperatingSystem.WINDOWS, - entries: [createConditionEntry(ConditionEntryField.SIGNER, 'Microsoft Windows')], + entries: [createConditionEntry(ConditionEntryField.SIGNER, 'match', 'Microsoft Windows')], } ); }); @@ -370,7 +437,13 @@ describe('mapping', () => { created_at: '11/11/2011T11:11:11.111', created_by: 'admin', os_types: ['linux'], - entries: [createEntryMatch('process.hash.md5', '1234234659af249ddf3e40864e9fb241')], + entries: [ + createEntryMatch( + 'process.hash.md5', + + '1234234659af249ddf3e40864e9fb241' + ), + ], }), { id: '123', @@ -384,7 +457,11 @@ describe('mapping', () => { updated_by: 'admin', os: OperatingSystem.LINUX, entries: [ - createConditionEntry(ConditionEntryField.HASH, '1234234659af249ddf3e40864e9fb241'), + createConditionEntry( + ConditionEntryField.HASH, + 'match', + '1234234659af249ddf3e40864e9fb241' + ), ], } ); @@ -400,7 +477,11 @@ describe('mapping', () => { created_by: 'admin', os_types: ['linux'], entries: [ - createEntryMatch('process.hash.sha1', 'f635da961234234659af249ddf3e40864e9fb241'), + createEntryMatch( + 'process.hash.sha1', + + 'f635da961234234659af249ddf3e40864e9fb241' + ), ], }), { @@ -417,6 +498,7 @@ describe('mapping', () => { entries: [ createConditionEntry( ConditionEntryField.HASH, + 'match', 'f635da961234234659af249ddf3e40864e9fb241' ), ], @@ -436,6 +518,7 @@ describe('mapping', () => { entries: [ createEntryMatch( 'process.hash.sha256', + 'f635da96124659af249ddf3e40864e9fb234234659af249ddf3e40864e9fb241' ), ], @@ -454,6 +537,7 @@ describe('mapping', () => { entries: [ createConditionEntry( ConditionEntryField.HASH, + 'match', 'f635da96124659af249ddf3e40864e9fb234234659af249ddf3e40864e9fb241' ), ], @@ -469,7 +553,7 @@ describe('mapping', () => { description: 'Linux Trusted App', effectScope: { type: 'global' }, os: OperatingSystem.LINUX, - entries: [createConditionEntry(ConditionEntryField.PATH, '/bin/malware')], + entries: [createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware')], version: 'abc', }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.ts index c6048e5725c88..786a74e91b51a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.ts @@ -11,6 +11,7 @@ import { OsType } from '../../../../../lists/common/schemas'; import { EntriesArray, EntryMatch, + EntryMatchWildcard, EntryNested, ExceptionListItemSchema, NestedEntriesArray, @@ -28,6 +29,7 @@ import { OperatingSystem, TrustedApp, UpdateTrustedApp, + TrustedAppEntryTypes, } from '../../../../common/endpoint/types'; type ConditionEntriesMap = { [K in ConditionEntryField]?: ConditionEntry<K> }; @@ -46,6 +48,7 @@ const OPERATING_SYSTEM_TO_OS_TYPE: Mapping<OperatingSystem, OsType> = { }; const POLICY_REFERENCE_PREFIX = 'policy:'; +const OPERATOR_VALUE = 'included'; const filterUndefined = <T>(list: Array<T | undefined>): T[] => { return list.filter((item: T | undefined): item is T => item !== undefined); @@ -53,9 +56,10 @@ const filterUndefined = <T>(list: Array<T | undefined>): T[] => { export const createConditionEntry = <T extends ConditionEntryField>( field: T, + type: TrustedAppEntryTypes, value: string ): ConditionEntry<T> => { - return { field, value, type: 'match', operator: 'included' }; + return { field, value, type, operator: OPERATOR_VALUE }; }; export const tagsToEffectScope = (tags: string[]): EffectScope => { @@ -78,12 +82,23 @@ export const entriesToConditionEntriesMap = (entries: EntriesArray): ConditionEn if (entry.field.startsWith('process.hash') && entry.type === 'match') { return { ...result, - [ConditionEntryField.HASH]: createConditionEntry(ConditionEntryField.HASH, entry.value), + [ConditionEntryField.HASH]: createConditionEntry( + ConditionEntryField.HASH, + entry.type, + entry.value + ), }; - } else if (entry.field === 'process.executable.caseless' && entry.type === 'match') { + } else if ( + entry.field === 'process.executable.caseless' && + (entry.type === 'match' || entry.type === 'wildcard') + ) { return { ...result, - [ConditionEntryField.PATH]: createConditionEntry(ConditionEntryField.PATH, entry.value), + [ConditionEntryField.PATH]: createConditionEntry( + ConditionEntryField.PATH, + entry.type, + entry.value + ), }; } else if (entry.field === 'process.Ext.code_signature' && entry.type === 'nested') { const subjectNameCondition = entry.entries.find((subEntry): subEntry is EntryMatch => { @@ -95,6 +110,7 @@ export const entriesToConditionEntriesMap = (entries: EntriesArray): ConditionEn ...result, [ConditionEntryField.SIGNER]: createConditionEntry( ConditionEntryField.SIGNER, + subjectNameCondition.type, subjectNameCondition.value ), }; @@ -166,7 +182,11 @@ const hashType = (hash: string): 'md5' | 'sha256' | 'sha1' | undefined => { }; export const createEntryMatch = (field: string, value: string): EntryMatch => { - return { field, value, type: 'match', operator: 'included' }; + return { field, value, type: 'match', operator: OPERATOR_VALUE }; +}; + +export const createEntryMatchWildcard = (field: string, value: string): EntryMatchWildcard => { + return { field, value, type: 'wildcard', operator: OPERATOR_VALUE }; }; export const createEntryNested = (field: string, entries: NestedEntriesArray): EntryNested => { @@ -193,6 +213,11 @@ export const conditionEntriesToEntries = (conditionEntries: ConditionEntry[]): E createEntryMatch('trusted', 'true'), createEntryMatch('subject_name', conditionEntry.value), ]); + } else if ( + conditionEntry.field === ConditionEntryField.PATH && + conditionEntry.type === 'wildcard' + ) { + return createEntryMatchWildcard(`process.executable.caseless`, conditionEntry.value); } else { return createEntryMatch(`process.executable.caseless`, conditionEntry.value); } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.test.ts index 42f4c6d157389..d99a89ce11137 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.test.ts @@ -65,8 +65,8 @@ const TRUSTED_APP: TrustedApp = { os: OperatingSystem.LINUX, effectScope: { type: 'global' }, entries: [ - createConditionEntry(ConditionEntryField.HASH, '1234234659af249ddf3e40864e9fb241'), - createConditionEntry(ConditionEntryField.PATH, '/bin/malware'), + createConditionEntry(ConditionEntryField.HASH, 'match', '1234234659af249ddf3e40864e9fb241'), + createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware'), ], }; @@ -109,8 +109,35 @@ describe('service', () => { effectScope: { type: 'global' }, os: OperatingSystem.LINUX, entries: [ - createConditionEntry(ConditionEntryField.PATH, '/bin/malware'), - createConditionEntry(ConditionEntryField.HASH, '1234234659af249ddf3e40864e9fb241'), + createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware'), + createConditionEntry( + ConditionEntryField.HASH, + 'match', + '1234234659af249ddf3e40864e9fb241' + ), + ], + }); + + expect(result).toEqual({ data: TRUSTED_APP }); + + expect(exceptionsListClient.createTrustedAppsList).toHaveBeenCalled(); + }); + + it('should create trusted app with correct wildcard type', async () => { + exceptionsListClient.createExceptionListItem.mockResolvedValue(EXCEPTION_LIST_ITEM); + + const result = await createTrustedApp(exceptionsListClient, { + name: 'linux trusted app 1', + description: 'Linux trusted app 1', + effectScope: { type: 'global' }, + os: OperatingSystem.LINUX, + entries: [ + createConditionEntry(ConditionEntryField.PATH, 'wildcard', '/bin/malware'), + createConditionEntry( + ConditionEntryField.HASH, + 'wildcard', + '1234234659af249ddf3e40864e9fb241' + ), ], }); 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 4c11325652f80..1b1370472f633 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 @@ -30,6 +30,24 @@ export const translatedEntryMatchMatcher = t.keyof({ }); export type TranslatedEntryMatchMatcher = t.TypeOf<typeof translatedEntryMatchMatcher>; +export const translatedEntryMatchWildcardMatcher = t.keyof({ + wildcard_cased: null, + wildcard_caseless: null, +}); +export type TranslatedEntryMatchWildcardMatcher = t.TypeOf< + typeof translatedEntryMatchWildcardMatcher +>; + +export const translatedEntryMatchWildcard = t.exact( + t.type({ + field: t.string, + operator, + type: translatedEntryMatchWildcardMatcher, + value: t.string, + }) +); +export type TranslatedEntryMatchWildcard = t.TypeOf<typeof translatedEntryMatchWildcard>; + export const translatedEntryMatch = t.exact( t.type({ field: t.string, @@ -61,6 +79,7 @@ export type TranslatedEntryNested = t.TypeOf<typeof translatedEntryNested>; export const translatedEntry = t.union([ translatedEntryNested, translatedEntryMatch, + translatedEntryMatchWildcard, translatedEntryMatchAny, ]); export type TranslatedEntry = t.TypeOf<typeof translatedEntry>; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index fe9bf6eac14d0..746af100cb733 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -20118,7 +20118,6 @@ "xpack.securitySolution.topN.closeButtonLabel": "閉じる", "xpack.securitySolution.topN.rawEventsSelectLabel": "未加工イベント", "xpack.securitySolution.trustedapps.aboutInfo": "パフォーマンスを改善したり、ホストで実行されている他のアプリケーションとの競合を解消したりするには、信頼できるアプリケーションを追加します。信頼できるアプリケーションは、Endpoint Securityを実行しているホストに適用されます。", - "xpack.securitySolution.trustedapps.card.operator.includes": "is", "xpack.securitySolution.trustedapps.card.removeButtonLabel": "削除", "xpack.securitySolution.trustedapps.create.conditionFieldValueRequiredMsg": "[{row}] フィールドエントリには値が必要です", "xpack.securitySolution.trustedapps.create.conditionRequiredMsg": "1つ以上のフィールド定義が必要です", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7eab0f835a556..163d9af5eeafb 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -20439,7 +20439,6 @@ "xpack.securitySolution.topN.closeButtonLabel": "关闭", "xpack.securitySolution.topN.rawEventsSelectLabel": "原始事件", "xpack.securitySolution.trustedapps.aboutInfo": "添加受信任的应用程序,以提高性能或缓解与主机上运行的其他应用程序的冲突。受信任的应用程序将应用于运行 Endpoint Security 的主机。", - "xpack.securitySolution.trustedapps.card.operator.includes": "是", "xpack.securitySolution.trustedapps.card.removeButtonLabel": "移除", "xpack.securitySolution.trustedapps.create.conditionFieldValueRequiredMsg": "[{row}] 字段条目必须包含值", "xpack.securitySolution.trustedapps.create.conditionRequiredMsg": "至少需要一个字段定义", From 7eb733f7bda65f57023af5e5ba12a4fe72a15e9c Mon Sep 17 00:00:00 2001 From: Sandra Gonzales <neptunian@users.noreply.github.com> Date: Thu, 29 Apr 2021 09:14:22 -0400 Subject: [PATCH 68/70] remove object dependency (#98686) --- .../ml/anomaly_detection/anomalies_table/anomalies_table.tsx | 3 +-- .../inventory_view/hooks/use_metrics_hosts_anomalies.ts | 3 ++- .../metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/anomalies_table.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/anomalies_table.tsx index 04772860c9fe7..8c8a5ae56c3ba 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/anomalies_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/anomalies_table.tsx @@ -248,9 +248,8 @@ export const AnomaliesTable = (props: Props) => { }, defaultPaginationOptions: { pageSize: 10 }, }), - [timeRange, sorting?.field, sorting?.direction, anomalyThreshold] + [timeRange.start, timeRange.end, sorting?.field, sorting?.direction, anomalyThreshold] ); - const { metricsHostsAnomalies, getMetricsHostsAnomalies, diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts index b1401f268dc51..b28a0ff0b4788 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_hosts_anomalies.ts @@ -224,7 +224,8 @@ export const useMetricsHostsAnomaliesResults = ({ sourceId, anomalyThreshold, dispatch, - reducerState.timeRange, + reducerState.timeRange.start, + reducerState.timeRange.end, reducerState.sortOptions, reducerState.paginationOptions, reducerState.paginationCursor, diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts index ad26c14df32b4..384cefa691d96 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts @@ -221,7 +221,8 @@ export const useMetricsK8sAnomaliesResults = ({ sourceId, anomalyThreshold, dispatch, - reducerState.timeRange, + reducerState.timeRange.start, + reducerState.timeRange.end, reducerState.sortOptions, reducerState.paginationOptions, reducerState.paginationCursor, From ed2e5440eeffaafa86a54a32b84f2ed4d8de6791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= <alejandro.haro@elastic.co> Date: Thu, 29 Apr 2021 15:38:06 +0200 Subject: [PATCH 69/70] [API DOCS] Telemetry (#98610) --- api_docs/telemetry.json | 1505 +++++------------ api_docs/telemetry.mdx | 6 - src/plugins/telemetry/public/index.ts | 13 +- src/plugins/telemetry/public/plugin.ts | 80 +- .../telemetry_notifications.ts | 22 + .../public/services/telemetry_sender.test.ts | 20 +- .../public/services/telemetry_service.ts | 35 + src/plugins/telemetry/server/index.ts | 14 +- src/plugins/telemetry/server/plugin.ts | 6 + .../telemetry_collection/get_cluster_stats.ts | 2 + .../get_data_telemetry/get_data_telemetry.ts | 20 + .../get_data_telemetry/index.ts | 6 +- .../telemetry_collection/get_local_stats.ts | 10 +- .../telemetry_collection/get_nodes_usage.ts | 43 +- .../server/telemetry_collection/index.ts | 8 +- 15 files changed, 613 insertions(+), 1177 deletions(-) diff --git a/api_docs/telemetry.json b/api_docs/telemetry.json index bfb19a79bdb1e..61b984aad4882 100644 --- a/api_docs/telemetry.json +++ b/api_docs/telemetry.json @@ -1,1127 +1,415 @@ { "id": "telemetry", "client": { - "classes": [ + "classes": [], + "functions": [], + "interfaces": [ { - "id": "def-public.TelemetryNotifications", - "type": "Class", + "id": "def-public.TelemetryPluginConfig", + "type": "Interface", + "label": "TelemetryPluginConfig", + "description": [ + "\nPublic-exposed configuration" + ], "tags": [], - "label": "TelemetryNotifications", - "description": [], "children": [ { - "id": "def-public.TelemetryNotifications.Unnamed", - "type": "Function", - "label": "Constructor", - "signature": [ - "any" - ], - "description": [], - "children": [ - { - "id": "def-public.TelemetryNotifications.Unnamed.$1", - "type": "Object", - "label": "{ http, overlays, telemetryService }", - "isRequired": true, - "signature": [ - "TelemetryNotificationsConstructor" - ], - "description": [], - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts", - "lineNumber": 27 - } - } - ], "tags": [], - "returnComment": [], - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts", - "lineNumber": 27 - } - }, - { - "id": "def-public.TelemetryNotifications.shouldShowOptedInNoticeBanner", - "type": "Function", - "children": [], - "signature": [ - "() => boolean" + "id": "def-public.TelemetryPluginConfig.enabled", + "type": "boolean", + "label": "enabled", + "description": [ + "Is the plugin enabled?" ], - "description": [], - "label": "shouldShowOptedInNoticeBanner", "source": { - "path": "src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts", - "lineNumber": 33 - }, - "tags": [], - "returnComment": [] + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 84 + } }, { - "id": "def-public.TelemetryNotifications.renderOptedInNoticeBanner", - "type": "Function", - "children": [], - "signature": [ - "() => void" - ], - "description": [], - "label": "renderOptedInNoticeBanner", - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts", - "lineNumber": 39 - }, "tags": [], - "returnComment": [] - }, - { - "id": "def-public.TelemetryNotifications.shouldShowOptInBanner", - "type": "Function", - "children": [], - "signature": [ - "() => boolean" + "id": "def-public.TelemetryPluginConfig.url", + "type": "string", + "label": "url", + "description": [ + "Remote telemetry service's URL" ], - "description": [], - "label": "shouldShowOptInBanner", "source": { - "path": "src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts", - "lineNumber": 49 - }, - "tags": [], - "returnComment": [] + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 86 + } }, { - "id": "def-public.TelemetryNotifications.renderOptInBanner", - "type": "Function", - "children": [], - "signature": [ - "() => void" - ], - "description": [], - "label": "renderOptInBanner", - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts", - "lineNumber": 55 - }, "tags": [], - "returnComment": [] - }, - { - "id": "def-public.TelemetryNotifications.setOptedInNoticeSeen", - "type": "Function", - "children": [], - "signature": [ - "() => Promise<void>" + "id": "def-public.TelemetryPluginConfig.banner", + "type": "boolean", + "label": "banner", + "description": [ + "The banner is expected to be shown when needed" ], - "description": [], - "label": "setOptedInNoticeSeen", - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts", - "lineNumber": 73 - }, - "tags": [], - "returnComment": [] - } - ], - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts", - "lineNumber": 20 - }, - "initialIsOpen": false - }, - { - "id": "def-public.TelemetryService", - "type": "Class", - "tags": [], - "label": "TelemetryService", - "description": [], - "children": [ - { - "tags": [], - "id": "def-public.TelemetryService.currentKibanaVersion", - "type": "string", - "label": "currentKibanaVersion", - "description": [], "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 28 + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 88 } }, { - "id": "def-public.TelemetryService.Unnamed", - "type": "Function", - "label": "Constructor", - "signature": [ - "any" - ], - "description": [], - "children": [ - { - "id": "def-public.TelemetryService.Unnamed.$1", - "type": "Object", - "label": "{\n config,\n http,\n notifications,\n currentKibanaVersion,\n reportOptInStatusChange = true,\n }", - "isRequired": true, - "signature": [ - "TelemetryServiceConstructor" - ], - "description": [], - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 30 - } - } - ], "tags": [], - "returnComment": [], + "id": "def-public.TelemetryPluginConfig.allowChangingOptInStatus", + "type": "boolean", + "label": "allowChangingOptInStatus", + "description": [ + "Does the cluster allow changing the opt-in/out status via the UI?" + ], "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 30 + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 90 } }, { - "id": "def-public.TelemetryService.config", - "type": "Object", - "label": "config", "tags": [], - "description": [], + "id": "def-public.TelemetryPluginConfig.optIn", + "type": "CompoundType", + "label": "optIn", + "description": [ + "Is the cluster opted-in?" + ], "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 44 + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 92 }, "signature": [ - { - "pluginId": "telemetry", - "scope": "public", - "docId": "kibTelemetryPluginApi", - "section": "def-public.TelemetryPluginConfig", - "text": "TelemetryPluginConfig" - } + "boolean | null" ] }, { - "id": "def-public.TelemetryService.config", - "type": "Object", - "label": "config", "tags": [], - "description": [], + "id": "def-public.TelemetryPluginConfig.optInStatusUrl", + "type": "string", + "label": "optInStatusUrl", + "description": [ + "Opt-in/out notification URL" + ], "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 48 - }, - "signature": [ - { - "pluginId": "telemetry", - "scope": "public", - "docId": "kibTelemetryPluginApi", - "section": "def-public.TelemetryPluginConfig", - "text": "TelemetryPluginConfig" - } - ] + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 94 + } }, { - "id": "def-public.TelemetryService.isOptedIn", - "type": "CompoundType", - "label": "isOptedIn", "tags": [], - "description": [], - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 52 - }, - "signature": [ - "boolean | null" - ] - }, - { - "id": "def-public.TelemetryService.isOptedIn", + "id": "def-public.TelemetryPluginConfig.sendUsageFrom", "type": "CompoundType", - "label": "isOptedIn", - "tags": [], - "description": [], + "label": "sendUsageFrom", + "description": [ + "Should the telemetry payloads be sent from the server or the browser?" + ], "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 56 + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 96 }, "signature": [ - "boolean | null" + "\"browser\" | \"server\"" ] }, { - "id": "def-public.TelemetryService.userHasSeenOptedInNotice", - "type": "CompoundType", - "label": "userHasSeenOptedInNotice", "tags": [], - "description": [], + "id": "def-public.TelemetryPluginConfig.telemetryNotifyUserAboutOptInDefault", + "type": "CompoundType", + "label": "telemetryNotifyUserAboutOptInDefault", + "description": [ + "Should notify the user about the opt-in status?" + ], "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 60 + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 98 }, "signature": [ "boolean | undefined" ] }, { - "id": "def-public.TelemetryService.userHasSeenOptedInNotice", - "type": "CompoundType", - "label": "userHasSeenOptedInNotice", "tags": [], - "description": [], + "id": "def-public.TelemetryPluginConfig.userCanChangeSettings", + "type": "CompoundType", + "label": "userCanChangeSettings", + "description": [ + "Does the user have enough privileges to change the settings?" + ], "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 64 + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 100 }, "signature": [ "boolean | undefined" ] - }, - { - "id": "def-public.TelemetryService.getCanChangeOptInStatus", - "type": "Function", - "children": [], - "signature": [ - "() => boolean" - ], - "description": [], - "label": "getCanChangeOptInStatus", - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 68 - }, - "tags": [], - "returnComment": [] - }, + } + ], + "source": { + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 82 + }, + "initialIsOpen": false + }, + { + "id": "def-public.TelemetryServicePublicApis", + "type": "Interface", + "label": "TelemetryServicePublicApis", + "description": [ + "\nPublicly exposed APIs from the Telemetry Service" + ], + "tags": [], + "children": [ { - "id": "def-public.TelemetryService.getOptInStatusUrl", - "type": "Function", - "children": [], - "signature": [ - "() => string" - ], - "description": [], - "label": "getOptInStatusUrl", - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 73 - }, "tags": [], - "returnComment": [] - }, - { - "id": "def-public.TelemetryService.getTelemetryUrl", + "id": "def-public.TelemetryServicePublicApis.getIsOptedIn", "type": "Function", - "children": [], - "signature": [ - "() => string" + "label": "getIsOptedIn", + "description": [ + "Is the cluster opted-in to telemetry?" ], - "description": [], - "label": "getTelemetryUrl", "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 78 + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 38 }, - "tags": [], - "returnComment": [] - }, - { - "id": "def-public.TelemetryService.getUserShouldSeeOptInNotice", - "type": "Function", - "label": "getUserShouldSeeOptInNotice", "signature": [ - "() => boolean" - ], - "description": [ - "\nReturns if an user should be shown the notice about Opt-In/Out telemetry.\nThe decision is made based on whether any user has already dismissed the message or\nthe user can't actually change the settings (in which case, there's no point on bothering them)" - ], - "children": [], - "tags": [], - "returnComment": [], - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 88 - } + "() => boolean | null" + ] }, { - "id": "def-public.TelemetryService.userCanChangeSettings", - "type": "boolean", - "label": "userCanChangeSettings", "tags": [], - "description": [], - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 95 - } - }, - { - "id": "def-public.TelemetryService.userCanChangeSettings", + "id": "def-public.TelemetryServicePublicApis.userCanChangeSettings", "type": "boolean", "label": "userCanChangeSettings", - "tags": [], - "description": [], + "description": [ + "Is the user allowed to change the opt-in/out status?" + ], "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 99 + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 40 } }, { - "id": "def-public.TelemetryService.getIsOptedIn", - "type": "Function", - "children": [], - "signature": [ - "() => boolean | null" - ], - "description": [], - "label": "getIsOptedIn", - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 103 - }, "tags": [], - "returnComment": [] - }, - { - "id": "def-public.TelemetryService.fetchExample", + "id": "def-public.TelemetryServicePublicApis.getCanChangeOptInStatus", "type": "Function", - "children": [], - "signature": [ - "() => Promise<any>" + "label": "getCanChangeOptInStatus", + "description": [ + "Is the cluster allowed to change the opt-in/out status?" ], - "description": [], - "label": "fetchExample", "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 107 + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 42 }, - "tags": [], - "returnComment": [] - }, - { - "id": "def-public.TelemetryService.fetchTelemetry", - "type": "Function", - "children": [ - { - "id": "def-public.TelemetryService.fetchTelemetry.$1", - "type": "Object", - "label": "{ unencrypted = false }", - "isRequired": true, - "signature": [ - "{ unencrypted?: boolean | undefined; }" - ], - "description": [], - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 111 - } - } - ], "signature": [ - "({ unencrypted }?: { unencrypted?: boolean | undefined; }) => Promise<any>" - ], - "description": [], - "label": "fetchTelemetry", - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 111 - }, - "tags": [], - "returnComment": [] + "() => boolean" + ] }, { - "id": "def-public.TelemetryService.setOptIn", - "type": "Function", - "children": [ - { - "id": "def-public.TelemetryService.setOptIn.$1", - "type": "boolean", - "label": "optedIn", - "isRequired": true, - "signature": [ - "boolean" - ], - "description": [], - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 119 - } - } - ], - "signature": [ - "(optedIn: boolean) => Promise<boolean>" - ], - "description": [], - "label": "setOptIn", - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 119 - }, "tags": [], - "returnComment": [] - }, - { - "id": "def-public.TelemetryService.setUserHasSeenNotice", + "id": "def-public.TelemetryServicePublicApis.fetchExample", "type": "Function", - "children": [], - "signature": [ - "() => Promise<void>" + "label": "fetchExample", + "description": [ + "Fetches an unencrypted telemetry payload so we can show it to the user" ], - "description": [], - "label": "setUserHasSeenNotice", - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 153 - }, - "tags": [], - "returnComment": [] - } - ], - "source": { - "path": "src/plugins/telemetry/public/services/telemetry_service.ts", - "lineNumber": 21 - }, - "initialIsOpen": false - } - ], - "functions": [], - "interfaces": [ - { - "id": "def-public.TelemetryPluginConfig", - "type": "Interface", - "label": "TelemetryPluginConfig", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-public.TelemetryPluginConfig.enabled", - "type": "boolean", - "label": "enabled", - "description": [], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 46 - } - }, - { - "tags": [], - "id": "def-public.TelemetryPluginConfig.url", - "type": "string", - "label": "url", - "description": [], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 47 - } - }, - { - "tags": [], - "id": "def-public.TelemetryPluginConfig.banner", - "type": "boolean", - "label": "banner", - "description": [], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 48 - } - }, - { - "tags": [], - "id": "def-public.TelemetryPluginConfig.allowChangingOptInStatus", - "type": "boolean", - "label": "allowChangingOptInStatus", - "description": [], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 49 - } - }, - { - "tags": [], - "id": "def-public.TelemetryPluginConfig.optIn", - "type": "CompoundType", - "label": "optIn", - "description": [], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 50 - }, - "signature": [ - "boolean | null" - ] - }, - { - "tags": [], - "id": "def-public.TelemetryPluginConfig.optInStatusUrl", - "type": "string", - "label": "optInStatusUrl", - "description": [], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 51 - } - }, - { - "tags": [], - "id": "def-public.TelemetryPluginConfig.sendUsageFrom", - "type": "CompoundType", - "label": "sendUsageFrom", - "description": [], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 52 - }, - "signature": [ - "\"browser\" | \"server\"" - ] - }, - { - "tags": [], - "id": "def-public.TelemetryPluginConfig.telemetryNotifyUserAboutOptInDefault", - "type": "CompoundType", - "label": "telemetryNotifyUserAboutOptInDefault", - "description": [], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 53 - }, - "signature": [ - "boolean | undefined" - ] - }, - { - "tags": [], - "id": "def-public.TelemetryPluginConfig.userCanChangeSettings", - "type": "CompoundType", - "label": "userCanChangeSettings", - "description": [], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 54 - }, - "signature": [ - "boolean | undefined" - ] - } - ], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 45 - }, - "initialIsOpen": false - } - ], - "enums": [], - "misc": [], - "objects": [], - "start": { - "id": "def-public.TelemetryPluginStart", - "type": "Interface", - "label": "TelemetryPluginStart", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-public.TelemetryPluginStart.telemetryService", - "type": "Object", - "label": "telemetryService", - "description": [], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 38 - }, - "signature": [ - { - "pluginId": "telemetry", - "scope": "public", - "docId": "kibTelemetryPluginApi", - "section": "def-public.TelemetryService", - "text": "TelemetryService" - } - ] - }, - { - "tags": [], - "id": "def-public.TelemetryPluginStart.telemetryNotifications", - "type": "Object", - "label": "telemetryNotifications", - "description": [], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 39 - }, - "signature": [ - { - "pluginId": "telemetry", - "scope": "public", - "docId": "kibTelemetryPluginApi", - "section": "def-public.TelemetryNotifications", - "text": "TelemetryNotifications" - } - ] - }, - { - "tags": [], - "id": "def-public.TelemetryPluginStart.telemetryConstants", - "type": "Object", - "label": "telemetryConstants", - "description": [], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 40 - }, - "signature": [ - "{ getPrivacyStatementUrl: () => string; }" - ] - } - ], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 37 - }, - "lifecycle": "start", - "initialIsOpen": true - }, - "setup": { - "id": "def-public.TelemetryPluginSetup", - "type": "Interface", - "label": "TelemetryPluginSetup", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-public.TelemetryPluginSetup.telemetryService", - "type": "Object", - "label": "telemetryService", - "description": [], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 34 - }, - "signature": [ - { - "pluginId": "telemetry", - "scope": "public", - "docId": "kibTelemetryPluginApi", - "section": "def-public.TelemetryService", - "text": "TelemetryService" - } - ] - } - ], - "source": { - "path": "src/plugins/telemetry/public/plugin.ts", - "lineNumber": 33 - }, - "lifecycle": "setup", - "initialIsOpen": true - } - }, - "server": { - "classes": [], - "functions": [ - { - "id": "def-server.buildDataTelemetryPayload", - "type": "Function", - "label": "buildDataTelemetryPayload", - "signature": [ - "(indices: ", - { - "pluginId": "telemetry", - "scope": "server", - "docId": "kibTelemetryPluginApi", - "section": "def-server.DataTelemetryIndex", - "text": "DataTelemetryIndex" - }, - "[]) => ", - { - "pluginId": "telemetry", - "scope": "server", - "docId": "kibTelemetryPluginApi", - "section": "def-server.DataTelemetryPayload", - "text": "DataTelemetryPayload" - } - ], - "description": [], - "children": [ - { - "id": "def-server.buildDataTelemetryPayload.$1", - "type": "Array", - "label": "indices", - "isRequired": true, - "signature": [ - { - "pluginId": "telemetry", - "scope": "server", - "docId": "kibTelemetryPluginApi", - "section": "def-server.DataTelemetryIndex", - "text": "DataTelemetryIndex" - }, - "[]" - ], - "description": [], - "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", - "lineNumber": 122 - } - } - ], - "tags": [], - "returnComment": [], - "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", - "lineNumber": 122 - }, - "initialIsOpen": false - }, - { - "id": "def-server.getClusterUuids", - "type": "Function", - "children": [ - { - "id": "def-server.getClusterUuids.$1", - "type": "Object", - "label": "{ esClient }", - "isRequired": true, - "signature": [ - { - "pluginId": "telemetryCollectionManager", - "scope": "server", - "docId": "kibTelemetryCollectionManagerPluginApi", - "section": "def-server.StatsCollectionConfig", - "text": "StatsCollectionConfig" - } - ], - "description": [], - "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts", - "lineNumber": 25 - } - } - ], - "signature": [ - "({ esClient }: ", - { - "pluginId": "telemetryCollectionManager", - "scope": "server", - "docId": "kibTelemetryCollectionManagerPluginApi", - "section": "def-server.StatsCollectionConfig", - "text": "StatsCollectionConfig" - }, - ") => Promise<{ clusterUuid: string; }[]>" - ], - "description": [ - "\nGet the cluster uuids from the connected cluster." - ], - "label": "getClusterUuids", - "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts", - "lineNumber": 25 - }, - "tags": [], - "returnComment": [], - "initialIsOpen": false - }, - { - "id": "def-server.getLocalStats", - "type": "Function", - "children": [ - { - "id": "def-server.getLocalStats.$1", - "type": "Array", - "label": "clustersDetails", - "isRequired": true, - "signature": [ - { - "pluginId": "telemetryCollectionManager", - "scope": "server", - "docId": "kibTelemetryCollectionManagerPluginApi", - "section": "def-server.ClusterDetails", - "text": "ClusterDetails" - }, - "[]" - ], - "description": [], - "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts", - "lineNumber": 60 - } - }, - { - "id": "def-server.getLocalStats.$2", - "type": "Object", - "label": "config", - "isRequired": true, - "signature": [ - { - "pluginId": "telemetryCollectionManager", - "scope": "server", - "docId": "kibTelemetryCollectionManagerPluginApi", - "section": "def-server.StatsCollectionConfig", - "text": "StatsCollectionConfig" - } - ], - "description": [], - "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts", - "lineNumber": 61 - } - }, - { - "id": "def-server.getLocalStats.$3", - "type": "Object", - "label": "context", - "isRequired": true, - "signature": [ - { - "pluginId": "telemetryCollectionManager", - "scope": "server", - "docId": "kibTelemetryCollectionManagerPluginApi", - "section": "def-server.StatsCollectionContext", - "text": "StatsCollectionContext" - } - ], - "description": [], - "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts", - "lineNumber": 62 - } - } - ], - "signature": [ - "(clustersDetails: ", - { - "pluginId": "telemetryCollectionManager", - "scope": "server", - "docId": "kibTelemetryCollectionManagerPluginApi", - "section": "def-server.ClusterDetails", - "text": "ClusterDetails" - }, - "[], config: ", - { - "pluginId": "telemetryCollectionManager", - "scope": "server", - "docId": "kibTelemetryCollectionManagerPluginApi", - "section": "def-server.StatsCollectionConfig", - "text": "StatsCollectionConfig" - }, - ", context: ", - { - "pluginId": "telemetryCollectionManager", - "scope": "server", - "docId": "kibTelemetryCollectionManagerPluginApi", - "section": "def-server.StatsCollectionContext", - "text": "StatsCollectionContext" - }, - ") => Promise<{ timestamp: string; cluster_uuid: string; cluster_name: string; version: string; cluster_stats: Pick<{ nodes: { usage: { nodes: ", - { - "pluginId": "telemetry", - "scope": "server", - "docId": "kibTelemetryPluginApi", - "section": "def-server.NodeUsage", - "text": "NodeUsage" - }, - "[] | {}[]; }; count: ", - "ClusterNodeCount" - ], - "description": [ - "\nGet statistics for all products joined by Elasticsearch cluster." - ], - "label": "getLocalStats", - "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts", - "lineNumber": 59 - }, - "tags": [], - "returnComment": [], - "initialIsOpen": false - }, - { - "id": "def-server.handleOldSettings", - "type": "Function", - "label": "handleOldSettings", - "signature": [ - "(savedObjectsClient: Pick<", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.SavedObjectsClient", - "text": "SavedObjectsClient" - }, - ", \"get\" | \"delete\" | \"create\" | \"bulkCreate\" | \"checkConflicts\" | \"find\" | \"bulkGet\" | \"resolve\" | \"update\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"bulkUpdate\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\" | \"errors\">, uiSettingsClient: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.IUiSettingsClient", - "text": "IUiSettingsClient" - }, - ") => Promise<void>" - ], - "description": [], - "children": [ - { - "id": "def-server.handleOldSettings.$1", - "type": "Object", - "label": "savedObjectsClient", - "isRequired": true, - "signature": [ - "Pick<", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.SavedObjectsClient", - "text": "SavedObjectsClient" - }, - ", \"get\" | \"delete\" | \"create\" | \"bulkCreate\" | \"checkConflicts\" | \"find\" | \"bulkGet\" | \"resolve\" | \"update\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"bulkUpdate\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\" | \"createPointInTimeFinder\" | \"errors\">" - ], - "description": [], - "source": { - "path": "src/plugins/telemetry/server/handle_old_settings/handle_old_settings.ts", - "lineNumber": 25 - } - }, - { - "id": "def-server.handleOldSettings.$2", - "type": "Object", - "label": "uiSettingsClient", - "isRequired": true, - "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.IUiSettingsClient", - "text": "IUiSettingsClient" - } - ], - "description": [], - "source": { - "path": "src/plugins/telemetry/server/handle_old_settings/handle_old_settings.ts", - "lineNumber": 26 - } - } - ], - "tags": [], - "returnComment": [], - "source": { - "path": "src/plugins/telemetry/server/handle_old_settings/handle_old_settings.ts", - "lineNumber": 24 - }, - "initialIsOpen": false - } - ], - "interfaces": [ - { - "id": "def-server.DataTelemetryIndex", - "type": "Interface", - "label": "DataTelemetryIndex", - "description": [], - "tags": [], - "children": [ - { - "tags": [], - "id": "def-server.DataTelemetryIndex.name", - "type": "string", - "label": "name", - "description": [], - "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", - "lineNumber": 39 - } - }, - { - "tags": [], - "id": "def-server.DataTelemetryIndex.packageName", - "type": "string", - "label": "packageName", - "description": [], - "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", - "lineNumber": 40 - }, - "signature": [ - "string | undefined" - ] - }, - { - "tags": [], - "id": "def-server.DataTelemetryIndex.managedBy", - "type": "string", - "label": "managedBy", - "description": [], - "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", - "lineNumber": 41 - }, - "signature": [ - "string | undefined" - ] - }, - { - "tags": [], - "id": "def-server.DataTelemetryIndex.dataStreamDataset", - "type": "string", - "label": "dataStreamDataset", - "description": [], "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", - "lineNumber": 42 + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 44 }, "signature": [ - "string | undefined" + "() => Promise<unknown[]>" ] }, { "tags": [], - "id": "def-server.DataTelemetryIndex.dataStreamType", - "type": "string", - "label": "dataStreamType", - "description": [], + "id": "def-public.TelemetryServicePublicApis.setOptIn", + "type": "Function", + "label": "setOptIn", + "description": [ + "\nOverwrite the opt-in status.\nIt will send a final request to the remote telemetry cluster to report about the opt-in/out change." + ], "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", - "lineNumber": 43 + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 50 }, "signature": [ - "string | undefined" + "(optedIn: boolean) => Promise<boolean>" ] + } + ], + "source": { + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 36 + }, + "initialIsOpen": false + } + ], + "enums": [], + "misc": [], + "objects": [], + "start": { + "id": "def-public.TelemetryPluginStart", + "type": "Interface", + "label": "TelemetryPluginStart", + "description": [ + "\nPublic's start exposed APIs by the telemetry plugin" + ], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.TelemetryPluginStart.telemetryService", + "type": "Object", + "label": "telemetryService", + "description": [ + "{@link TelemetryServicePublicApis}" + ], + "source": { + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 66 + }, + "signature": [ + { + "pluginId": "telemetry", + "scope": "public", + "docId": "kibTelemetryPluginApi", + "section": "def-public.TelemetryServicePublicApis", + "text": "TelemetryServicePublicApis" + } + ] + }, + { + "tags": [], + "id": "def-public.TelemetryPluginStart.telemetryNotifications", + "type": "Object", + "label": "telemetryNotifications", + "description": [ + "Notification helpers" + ], + "source": { + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 68 + }, + "signature": [ + "{ setOptedInNoticeSeen: () => Promise<void>; }" + ] + }, + { + "tags": [], + "id": "def-public.TelemetryPluginStart.telemetryConstants", + "type": "Object", + "label": "telemetryConstants", + "description": [ + "Set of publicly exposed telemetry constants" + ], + "source": { + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 73 + }, + "signature": [ + "{ getPrivacyStatementUrl: () => string; }" + ] + } + ], + "source": { + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 64 + }, + "lifecycle": "start", + "initialIsOpen": true + }, + "setup": { + "id": "def-public.TelemetryPluginSetup", + "type": "Interface", + "label": "TelemetryPluginSetup", + "description": [ + "\nPublic's setup exposed APIs by the telemetry plugin" + ], + "tags": [], + "children": [ + { + "tags": [], + "id": "def-public.TelemetryPluginSetup.telemetryService", + "type": "Object", + "label": "telemetryService", + "description": [ + "{@link TelemetryService}" + ], + "source": { + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 58 }, + "signature": [ + { + "pluginId": "telemetry", + "scope": "public", + "docId": "kibTelemetryPluginApi", + "section": "def-public.TelemetryServicePublicApis", + "text": "TelemetryServicePublicApis" + } + ] + } + ], + "source": { + "path": "src/plugins/telemetry/public/plugin.ts", + "lineNumber": 56 + }, + "lifecycle": "setup", + "initialIsOpen": true + } + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [ + { + "id": "def-server.DataTelemetryBasePayload", + "type": "Interface", + "label": "DataTelemetryBasePayload", + "description": [ + "\nCommon counters for the {@link DataTelemetryDocument}s" + ], + "tags": [], + "children": [ { "tags": [], - "id": "def-server.DataTelemetryIndex.shipper", - "type": "string", - "label": "shipper", - "description": [], + "id": "def-server.DataTelemetryBasePayload.index_count", + "type": "number", + "label": "index_count", + "description": [ + "How many indices match the declared pattern" + ], "source": { "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", - "lineNumber": 44 - }, - "signature": [ - "string | undefined" - ] + "lineNumber": 22 + } }, { "tags": [], - "id": "def-server.DataTelemetryIndex.isECS", - "type": "CompoundType", - "label": "isECS", - "description": [], + "id": "def-server.DataTelemetryBasePayload.ecs_index_count", + "type": "number", + "label": "ecs_index_count", + "description": [ + "How many indices match the declared pattern follow ECS conventions" + ], "source": { "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", - "lineNumber": 45 + "lineNumber": 24 }, "signature": [ - "boolean | undefined" + "number | undefined" ] }, { "tags": [], - "id": "def-server.DataTelemetryIndex.docCount", + "id": "def-server.DataTelemetryBasePayload.doc_count", "type": "number", - "label": "docCount", - "description": [], + "label": "doc_count", + "description": [ + "How many documents are among all the identified indices" + ], "source": { "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", - "lineNumber": 49 + "lineNumber": 26 }, "signature": [ "number | undefined" @@ -1129,13 +417,15 @@ }, { "tags": [], - "id": "def-server.DataTelemetryIndex.sizeInBytes", + "id": "def-server.DataTelemetryBasePayload.size_in_bytes", "type": "number", - "label": "sizeInBytes", - "description": [], + "label": "size_in_bytes", + "description": [ + "Total size in bytes among all the identified indices" + ], "source": { "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", - "lineNumber": 50 + "lineNumber": 28 }, "signature": [ "number | undefined" @@ -1144,154 +434,161 @@ ], "source": { "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", - "lineNumber": 38 + "lineNumber": 20 }, "initialIsOpen": false }, { - "id": "def-server.NodeUsage", + "id": "def-server.DataTelemetryDocument", "type": "Interface", - "label": "NodeUsage", - "description": [], + "label": "DataTelemetryDocument", + "signature": [ + { + "pluginId": "telemetry", + "scope": "server", + "docId": "kibTelemetryPluginApi", + "section": "def-server.DataTelemetryDocument", + "text": "DataTelemetryDocument" + }, + " extends ", + { + "pluginId": "telemetry", + "scope": "server", + "docId": "kibTelemetryPluginApi", + "section": "def-server.DataTelemetryBasePayload", + "text": "DataTelemetryBasePayload" + } + ], + "description": [ + "\nDepending on the type of index, we'll populate different keys as we identify them." + ], "tags": [], "children": [ { "tags": [], - "id": "def-server.NodeUsage.node_id", - "type": "string", - "label": "node_id", - "description": [], + "id": "def-server.DataTelemetryDocument.data_stream", + "type": "Object", + "label": "data_stream", + "description": [ + "For data-stream indices. Reporting their details" + ], "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts", - "lineNumber": 18 + "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", + "lineNumber": 36 }, "signature": [ - "string | undefined" + "{ dataset?: string | undefined; type?: string | undefined; } | undefined" ] }, { "tags": [], - "id": "def-server.NodeUsage.timestamp", - "type": "CompoundType", - "label": "timestamp", - "description": [], + "id": "def-server.DataTelemetryDocument.package", + "type": "Object", + "label": "package", + "description": [ + "When available, reporting the package details" + ], "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts", - "lineNumber": 19 + "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", + "lineNumber": 43 }, "signature": [ - "React.ReactText" + "{ name: string; } | undefined" ] }, { "tags": [], - "id": "def-server.NodeUsage.since", - "type": "number", - "label": "since", - "description": [], - "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts", - "lineNumber": 20 - } - }, - { - "tags": [], - "id": "def-server.NodeUsage.rest_actions", - "type": "Object", - "label": "rest_actions", - "description": [], + "id": "def-server.DataTelemetryDocument.shipper", + "type": "string", + "label": "shipper", + "description": [ + "What's the process indexing the data? (i.e.: \"beats\", \"logstash\")" + ], "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts", - "lineNumber": 21 + "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", + "lineNumber": 48 }, "signature": [ - "{ [key: string]: number; }" + "string | undefined" ] }, { "tags": [], - "id": "def-server.NodeUsage.aggregations", - "type": "Object", - "label": "aggregations", - "description": [], + "id": "def-server.DataTelemetryDocument.pattern_name", + "type": "CompoundType", + "label": "pattern_name", + "description": [ + "When the data comes from a matching index-pattern, the name of the pattern" + ], "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts", - "lineNumber": 24 + "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", + "lineNumber": 50 }, "signature": [ - "{ [key: string]: ", - { - "pluginId": "telemetry", - "scope": "server", - "docId": "kibTelemetryPluginApi", - "section": "def-server.NodeUsageAggregation", - "text": "NodeUsageAggregation" - }, - "; } | undefined" + "\"search\" | \"logstash\" | \"enterprise-search\" | \"app-search\" | \"magento2\" | \"magento\" | \"shopify\" | \"wordpress\" | \"drupal\" | \"joomla\" | \"sharepoint\" | \"squarespace\" | \"sitecore\" | \"weebly\" | \"acquia\" | \"filebeat\" | \"metricbeat\" | \"apm\" | \"functionbeat\" | \"heartbeat\" | \"fluentd\" | \"telegraf\" | \"prometheusbeat\" | \"fluentbit\" | \"nginx\" | \"apache\" | \"endgame\" | \"logs-endpoint\" | \"metrics-endpoint\" | \"siem-signals\" | \"auditbeat\" | \"winlogbeat\" | \"packetbeat\" | \"tomcat\" | \"artifactory\" | \"aruba\" | \"barracuda\" | \"bluecoat\" | \"arcsight\" | \"checkpoint\" | \"cisco\" | \"citrix\" | \"cyberark\" | \"cylance\" | \"fireeye\" | \"fortinet\" | \"infoblox\" | \"kaspersky\" | \"mcafee\" | \"paloaltonetworks\" | \"rsa\" | \"snort\" | \"sonicwall\" | \"sophos\" | \"squid\" | \"symantec\" | \"tippingpoint\" | \"trendmicro\" | \"tripwire\" | \"zscaler\" | \"zeek\" | \"sigma_doc\" | \"ecs-corelight\" | \"suricata\" | \"wazuh\" | \"meow\" | undefined" ] } ], "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts", - "lineNumber": 17 + "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", + "lineNumber": 34 }, "initialIsOpen": false }, { - "id": "def-server.NodeUsageAggregation", + "id": "def-server.NodeUsage", "type": "Interface", - "label": "NodeUsageAggregation", - "description": [], + "label": "NodeUsage", + "signature": [ + { + "pluginId": "telemetry", + "scope": "server", + "docId": "kibTelemetryPluginApi", + "section": "def-server.NodeUsage", + "text": "NodeUsage" + }, + " extends ", + "NodeUsageInformation" + ], + "description": [ + "\nData returned by GET /_nodes/usage, but flattened as an array of {@link estypes.NodeUsageInformation}\nwith the node ID set in the field `node_id`." + ], "tags": [], "children": [ { - "id": "def-server.NodeUsageAggregation.Unnamed", - "type": "Any", - "label": "Unnamed", "tags": [], - "description": [], + "id": "def-server.NodeUsage.node_id", + "type": "string", + "label": "node_id", + "description": [ + "\nThe Node ID as reported by ES" + ], "source": { "path": "src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts", - "lineNumber": 13 - }, - "signature": [ - "any" - ] + "lineNumber": 21 + } } ], "source": { "path": "src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts", - "lineNumber": 12 + "lineNumber": 17 }, "initialIsOpen": false } ], "enums": [], "misc": [ - { - "tags": [], - "id": "def-server.DATA_TELEMETRY_ID", - "type": "string", - "label": "DATA_TELEMETRY_ID", - "description": [], - "source": { - "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/constants.ts", - "lineNumber": 9 - }, - "signature": [ - "\"data\"" - ], - "initialIsOpen": false - }, { "id": "def-server.DataTelemetryPayload", "type": "Type", "label": "DataTelemetryPayload", "tags": [], - "description": [], + "description": [ + "\nThe Data Telemetry is reported as an array of {@link DataTelemetryDocument}" + ], "source": { "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", - "lineNumber": 36 + "lineNumber": 56 }, "signature": [ "DataTelemetryDocument[]" @@ -1303,10 +600,12 @@ "type": "Type", "label": "TelemetryLocalStats", "tags": [], - "description": [], + "description": [ + "\nThe payload structure as composed by the OSS telemetry collection mechanism." + ], "source": { "path": "src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts", - "lineNumber": 51 + "lineNumber": 54 }, "signature": [ "{ timestamp: string; cluster_uuid: string; cluster_name: string; version: string; cluster_stats: Pick<estypes.ClusterStatsResponse, \"status\" | \"indices\" | \"nodes\" | \"cluster_uuid\" | \"timestamp\">; collection: string; stack_stats: { data: DataTelemetryPayload | undefined; kibana: { count: number; indices: number; os: {}; versions: { version: string; count: number; }[]; plugins: { [plugin: string]: Record<string, unknown>; }; } | undefined; }; }" @@ -1319,7 +618,9 @@ "id": "def-server.TelemetryPluginSetup", "type": "Interface", "label": "TelemetryPluginSetup", - "description": [], + "description": [ + "\nServer's setup exposed APIs by the telemetry plugin" + ], "tags": [], "children": [ { @@ -1332,7 +633,7 @@ ], "source": { "path": "src/plugins/telemetry/server/plugin.ts", - "lineNumber": 53 + "lineNumber": 56 }, "signature": [ "() => Promise<", @@ -1343,7 +644,7 @@ ], "source": { "path": "src/plugins/telemetry/server/plugin.ts", - "lineNumber": 48 + "lineNumber": 51 }, "lifecycle": "setup", "initialIsOpen": true @@ -1352,7 +653,9 @@ "id": "def-server.TelemetryPluginStart", "type": "Interface", "label": "TelemetryPluginStart", - "description": [], + "description": [ + "\nServer's start exposed APIs by the telemetry plugin" + ], "tags": [], "children": [ { @@ -1365,7 +668,7 @@ ], "source": { "path": "src/plugins/telemetry/server/plugin.ts", - "lineNumber": 62 + "lineNumber": 68 }, "signature": [ "() => Promise<boolean>" @@ -1374,7 +677,7 @@ ], "source": { "path": "src/plugins/telemetry/server/plugin.ts", - "lineNumber": 56 + "lineNumber": 62 }, "lifecycle": "start", "initialIsOpen": true diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index f9a58d29ebd86..995c9b22e268a 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -19,9 +19,6 @@ import telemetryObj from './telemetry.json'; ### Start <DocDefinitionList data={[telemetryObj.client.start]}/> -### Classes -<DocDefinitionList data={telemetryObj.client.classes}/> - ### Interfaces <DocDefinitionList data={telemetryObj.client.interfaces}/> @@ -33,9 +30,6 @@ import telemetryObj from './telemetry.json'; ### Start <DocDefinitionList data={[telemetryObj.server.start]}/> -### Functions -<DocDefinitionList data={telemetryObj.server.functions}/> - ### Interfaces <DocDefinitionList data={telemetryObj.server.interfaces}/> diff --git a/src/plugins/telemetry/public/index.ts b/src/plugins/telemetry/public/index.ts index aef955e228dd3..8d1747d9c33f1 100644 --- a/src/plugins/telemetry/public/index.ts +++ b/src/plugins/telemetry/public/index.ts @@ -6,10 +6,15 @@ * Side Public License, v 1. */ -import { PluginInitializerContext } from 'kibana/public'; -import { TelemetryPlugin, TelemetryPluginConfig } from './plugin'; -export type { TelemetryPluginStart, TelemetryPluginSetup, TelemetryPluginConfig } from './plugin'; -export type { TelemetryNotifications, TelemetryService } from './services'; +import type { PluginInitializerContext } from 'src/core/public'; +import type { TelemetryPluginConfig } from './plugin'; +import { TelemetryPlugin } from './plugin'; +export type { + TelemetryPluginStart, + TelemetryPluginSetup, + TelemetryPluginConfig, + TelemetryServicePublicApis, +} from './plugin'; export function plugin(initializerContext: PluginInitializerContext<TelemetryPluginConfig>) { return new TelemetryPlugin(initializerContext); diff --git a/src/plugins/telemetry/public/plugin.ts b/src/plugins/telemetry/public/plugin.ts index f7af01f0190ae..5e85fa7ea2d51 100644 --- a/src/plugins/telemetry/public/plugin.ts +++ b/src/plugins/telemetry/public/plugin.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { +import type { Plugin, CoreStart, CoreSetup, @@ -15,10 +15,10 @@ import { SavedObjectsClientContract, SavedObjectsBatchResponse, ApplicationStart, -} from '../../../core/public'; +} from 'src/core/public'; import { TelemetrySender, TelemetryService, TelemetryNotifications } from './services'; -import { +import type { TelemetrySavedObjectAttributes, TelemetrySavedObject, } from '../common/telemetry_config/types'; @@ -30,27 +30,73 @@ import { import { getNotifyUserAboutOptInDefault } from '../common/telemetry_config/get_telemetry_notify_user_about_optin_default'; import { PRIVACY_STATEMENT_URL } from '../common/constants'; +/** + * Publicly exposed APIs from the Telemetry Service + */ +export interface TelemetryServicePublicApis { + /** Is the cluster opted-in to telemetry? **/ + getIsOptedIn: () => boolean | null; + /** Is the user allowed to change the opt-in/out status? **/ + userCanChangeSettings: boolean; + /** Is the cluster allowed to change the opt-in/out status? **/ + getCanChangeOptInStatus: () => boolean; + /** Fetches an unencrypted telemetry payload so we can show it to the user **/ + fetchExample: () => Promise<unknown[]>; + /** + * Overwrite the opt-in status. + * It will send a final request to the remote telemetry cluster to report about the opt-in/out change. + * @param optedIn Whether the user is opting-in (`true`) or out (`false`). + */ + setOptIn: (optedIn: boolean) => Promise<boolean>; +} + +/** + * Public's setup exposed APIs by the telemetry plugin + */ export interface TelemetryPluginSetup { - telemetryService: TelemetryService; + /** {@link TelemetryService} **/ + telemetryService: TelemetryServicePublicApis; } +/** + * Public's start exposed APIs by the telemetry plugin + */ export interface TelemetryPluginStart { - telemetryService: TelemetryService; - telemetryNotifications: TelemetryNotifications; + /** {@link TelemetryServicePublicApis} **/ + telemetryService: TelemetryServicePublicApis; + /** Notification helpers **/ + telemetryNotifications: { + /** Notify that the user has been presented with the opt-in/out notice. */ + setOptedInNoticeSeen: () => Promise<void>; + }; + /** Set of publicly exposed telemetry constants **/ telemetryConstants: { + /** Elastic's privacy statement url **/ getPrivacyStatementUrl: () => string; }; } +/** + * Public-exposed configuration + */ export interface TelemetryPluginConfig { + /** Is the plugin enabled? **/ enabled: boolean; + /** Remote telemetry service's URL **/ url: string; + /** The banner is expected to be shown when needed **/ banner: boolean; + /** Does the cluster allow changing the opt-in/out status via the UI? **/ allowChangingOptInStatus: boolean; + /** Is the cluster opted-in? **/ optIn: boolean | null; + /** Opt-in/out notification URL **/ optInStatusUrl: string; + /** Should the telemetry payloads be sent from the server or the browser? **/ sendUsageFrom: 'browser' | 'server'; + /** Should notify the user about the opt-in status? **/ telemetryNotifyUserAboutOptInDefault?: boolean; + /** Does the user have enough privileges to change the settings? **/ userCanChangeSettings?: boolean; } @@ -80,7 +126,7 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl this.telemetrySender = new TelemetrySender(this.telemetryService); return { - telemetryService: this.telemetryService, + telemetryService: this.getTelemetryServicePublicApis(), }; } @@ -92,11 +138,12 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl this.canUserChangeSettings = this.getCanUserChangeSettings(application); this.telemetryService.userCanChangeSettings = this.canUserChangeSettings; - this.telemetryNotifications = new TelemetryNotifications({ + const telemetryNotifications = new TelemetryNotifications({ http, overlays, telemetryService: this.telemetryService, }); + this.telemetryNotifications = telemetryNotifications; application.currentAppId$.subscribe(async () => { const isUnauthenticated = this.getIsUnauthenticated(http); @@ -119,14 +166,27 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl }); return { - telemetryService: this.telemetryService, - telemetryNotifications: this.telemetryNotifications, + telemetryService: this.getTelemetryServicePublicApis(), + telemetryNotifications: { + setOptedInNoticeSeen: () => telemetryNotifications.setOptedInNoticeSeen(), + }, telemetryConstants: { getPrivacyStatementUrl: () => PRIVACY_STATEMENT_URL, }, }; } + private getTelemetryServicePublicApis(): TelemetryServicePublicApis { + const telemetryService = this.telemetryService!; + return { + getIsOptedIn: () => telemetryService.getIsOptedIn(), + setOptIn: (optedIn) => telemetryService.setOptIn(optedIn), + userCanChangeSettings: telemetryService.userCanChangeSettings, + getCanChangeOptInStatus: () => telemetryService.getCanChangeOptInStatus(), + fetchExample: () => telemetryService.fetchExample(), + }; + } + /** * Can the user edit the saved objects? * This is a security feature, not included in the OSS build, so we need to fallback to `true` diff --git a/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts b/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts index 5caf68b1981ea..0070cf7452767 100644 --- a/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts +++ b/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts @@ -17,6 +17,9 @@ interface TelemetryNotificationsConstructor { telemetryService: TelemetryService; } +/** + * Helpers to the Telemetry banners spread through the code base in Welcome and Home landing pages. + */ export class TelemetryNotifications { private readonly http: CoreStart['http']; private readonly overlays: CoreStart['overlays']; @@ -30,12 +33,18 @@ export class TelemetryNotifications { this.overlays = overlays; } + /** + * Should the opted-in banner be shown to the user? + */ public shouldShowOptedInNoticeBanner = (): boolean => { const userShouldSeeOptInNotice = this.telemetryService.getUserShouldSeeOptInNotice(); const bannerOnScreen = typeof this.optedInNoticeBannerId !== 'undefined'; return !bannerOnScreen && userShouldSeeOptInNotice; }; + /** + * Renders the banner that claims the cluster is opted-in, and gives the option to opt-out. + */ public renderOptedInNoticeBanner = (): void => { const bannerId = renderOptedInNoticeBanner({ http: this.http, @@ -46,12 +55,18 @@ export class TelemetryNotifications { this.optedInNoticeBannerId = bannerId; }; + /** + * Should the banner to opt-in be shown to the user? + */ public shouldShowOptInBanner = (): boolean => { const isOptedIn = this.telemetryService.getIsOptedIn(); const bannerOnScreen = typeof this.optInBannerId !== 'undefined'; return !bannerOnScreen && isOptedIn === null; }; + /** + * Renders the banner that claims the cluster is opted-out, and gives the option to opt-in. + */ public renderOptInBanner = (): void => { const bannerId = renderOptInBanner({ setOptIn: this.onSetOptInClick, @@ -61,6 +76,10 @@ export class TelemetryNotifications { this.optInBannerId = bannerId; }; + /** + * Opt-in/out button handler + * @param isOptIn true/false whether the user opts-in/out + */ private onSetOptInClick = async (isOptIn: boolean) => { if (this.optInBannerId) { this.overlays.banners.remove(this.optInBannerId); @@ -70,6 +89,9 @@ export class TelemetryNotifications { await this.telemetryService.setOptIn(isOptIn); }; + /** + * Clears the banner and stores the user's dismissal of the banner. + */ public setOptedInNoticeSeen = async (): Promise<void> => { if (this.optedInNoticeBannerId) { this.overlays.banners.remove(this.optedInNoticeBannerId); diff --git a/src/plugins/telemetry/public/services/telemetry_sender.test.ts b/src/plugins/telemetry/public/services/telemetry_sender.test.ts index 82dbdb49f38f5..4dd1fe37a7569 100644 --- a/src/plugins/telemetry/public/services/telemetry_sender.test.ts +++ b/src/plugins/telemetry/public/services/telemetry_sender.test.ts @@ -71,20 +71,20 @@ describe('TelemetrySender', () => { const telemetryService = mockTelemetryService(); telemetryService.getIsOptedIn = jest.fn().mockReturnValue(false); const telemetrySender = new TelemetrySender(telemetryService); - const shouldSendRerpot = telemetrySender['shouldSendReport'](); + const shouldSendReport = telemetrySender['shouldSendReport'](); expect(telemetryService.getIsOptedIn).toBeCalledTimes(1); - expect(shouldSendRerpot).toBe(false); + expect(shouldSendReport).toBe(false); }); it('returns true if lastReported is undefined', () => { const telemetryService = mockTelemetryService(); telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true); const telemetrySender = new TelemetrySender(telemetryService); - const shouldSendRerpot = telemetrySender['shouldSendReport'](); + const shouldSendReport = telemetrySender['shouldSendReport'](); expect(telemetrySender['lastReported']).toBeUndefined(); - expect(shouldSendRerpot).toBe(true); + expect(shouldSendReport).toBe(true); }); it('returns true if lastReported passed REPORT_INTERVAL_MS', () => { @@ -94,8 +94,8 @@ describe('TelemetrySender', () => { telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true); const telemetrySender = new TelemetrySender(telemetryService); telemetrySender['lastReported'] = `${lastReported}`; - const shouldSendRerpot = telemetrySender['shouldSendReport'](); - expect(shouldSendRerpot).toBe(true); + const shouldSendReport = telemetrySender['shouldSendReport'](); + expect(shouldSendReport).toBe(true); }); it('returns false if lastReported is within REPORT_INTERVAL_MS', () => { @@ -105,8 +105,8 @@ describe('TelemetrySender', () => { telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true); const telemetrySender = new TelemetrySender(telemetryService); telemetrySender['lastReported'] = `${lastReported}`; - const shouldSendRerpot = telemetrySender['shouldSendReport'](); - expect(shouldSendRerpot).toBe(false); + const shouldSendReport = telemetrySender['shouldSendReport'](); + expect(shouldSendReport).toBe(false); }); it('returns true if lastReported is malformed', () => { @@ -114,8 +114,8 @@ describe('TelemetrySender', () => { telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true); const telemetrySender = new TelemetrySender(telemetryService); telemetrySender['lastReported'] = `random_malformed_string`; - const shouldSendRerpot = telemetrySender['shouldSendReport'](); - expect(shouldSendRerpot).toBe(true); + const shouldSendReport = telemetrySender['shouldSendReport'](); + expect(shouldSendReport).toBe(true); }); describe('sendIfDue', () => { diff --git a/src/plugins/telemetry/public/services/telemetry_service.ts b/src/plugins/telemetry/public/services/telemetry_service.ts index a3232a42d6b73..4ae2956902092 100644 --- a/src/plugins/telemetry/public/services/telemetry_service.ts +++ b/src/plugins/telemetry/public/services/telemetry_service.ts @@ -18,6 +18,10 @@ interface TelemetryServiceConstructor { reportOptInStatusChange?: boolean; } +/** + * Handles caching telemetry config in the user's session and requests the + * backend to fetch telemetry payload requests or notify about config changes. + */ export class TelemetryService { private readonly http: CoreStart['http']; private readonly reportOptInStatusChange: boolean; @@ -25,6 +29,7 @@ export class TelemetryService { private readonly defaultConfig: TelemetryPluginConfig; private updatedConfig?: TelemetryPluginConfig; + /** Current version of Kibana */ public readonly currentKibanaVersion: string; constructor({ @@ -41,40 +46,54 @@ export class TelemetryService { this.http = http; } + /** + * Config setter to locally persist the updated configuration. + * Useful for caching the configuration throughout the users' session, + * so they don't need to refresh the page. + * @param updatedConfig + */ public set config(updatedConfig: TelemetryPluginConfig) { this.updatedConfig = updatedConfig; } + /** Returns the latest configuration **/ public get config() { return { ...this.defaultConfig, ...this.updatedConfig }; } + /** Is the cluster opted-in to telemetry **/ public get isOptedIn() { return this.config.optIn; } + /** Changes the opt-in status **/ public set isOptedIn(optIn) { this.config = { ...this.config, optIn }; } + /** true if the user has already seen the opt-in/out notice **/ public get userHasSeenOptedInNotice() { return this.config.telemetryNotifyUserAboutOptInDefault; } + /** Changes the notice visibility options **/ public set userHasSeenOptedInNotice(telemetryNotifyUserAboutOptInDefault) { this.config = { ...this.config, telemetryNotifyUserAboutOptInDefault }; } + /** Is the cluster allowed to change the opt-in/out status **/ public getCanChangeOptInStatus = () => { const allowChangingOptInStatus = this.config.allowChangingOptInStatus; return allowChangingOptInStatus; }; + /** Retrieve the opt-in/out notification URL **/ public getOptInStatusUrl = () => { const telemetryOptInStatusUrl = this.config.optInStatusUrl; return telemetryOptInStatusUrl; }; + /** Retrieve the URL to report telemetry **/ public getTelemetryUrl = () => { const telemetryUrl = this.config.url; return telemetryUrl; @@ -92,22 +111,30 @@ export class TelemetryService { ); } + /** Is the user allowed to change the opt-in/out status **/ public get userCanChangeSettings() { return this.config.userCanChangeSettings ?? false; } + /** Change the user's permissions to change the opt-in/out status **/ public set userCanChangeSettings(userCanChangeSettings: boolean) { this.config = { ...this.config, userCanChangeSettings }; } + /** Is the cluster opted-in to telemetry **/ public getIsOptedIn = () => { return this.isOptedIn; }; + /** Fetches an unencrypted telemetry payload so we can show it to the user **/ public fetchExample = async () => { return await this.fetchTelemetry({ unencrypted: true }); }; + /** + * Fetches telemetry payload + * @param unencrypted Default `false`. Whether the returned payload should be encrypted or not. + */ public fetchTelemetry = async ({ unencrypted = false } = {}) => { return this.http.post('/api/telemetry/v2/clusters/_stats', { body: JSON.stringify({ @@ -116,6 +143,11 @@ export class TelemetryService { }); }; + /** + * Overwrite the opt-in status. + * It will send a final request to the remote telemetry cluster to report about the opt-in/out change. + * @param optedIn Whether the user is opting-in (`true`) or out (`false`). + */ public setOptIn = async (optedIn: boolean): Promise<boolean> => { const canChangeOptInStatus = this.getCanChangeOptInStatus(); if (!canChangeOptInStatus) { @@ -150,6 +182,9 @@ export class TelemetryService { return true; }; + /** + * Discards the notice about usage collection and stores it so we don't bother any other users. + */ public setUserHasSeenNotice = async (): Promise<void> => { try { await this.http.put('/api/telemetry/v2/userHasSeenNotice'); diff --git a/src/plugins/telemetry/server/index.ts b/src/plugins/telemetry/server/index.ts index 005f50721e778..530f7c499c3f2 100644 --- a/src/plugins/telemetry/server/index.ts +++ b/src/plugins/telemetry/server/index.ts @@ -8,10 +8,8 @@ import { PluginInitializerContext, PluginConfigDescriptor } from 'kibana/server'; import { TelemetryPlugin } from './plugin'; -import * as constants from '../common/constants'; import { configSchema, TelemetryConfigType } from './config'; -export { handleOldSettings } from './handle_old_settings'; export type { TelemetryPluginSetup, TelemetryPluginStart } from './plugin'; export const config: PluginConfigDescriptor<TelemetryConfigType> = { @@ -29,18 +27,12 @@ export const config: PluginConfigDescriptor<TelemetryConfigType> = { export const plugin = (initializerContext: PluginInitializerContext<TelemetryConfigType>) => new TelemetryPlugin(initializerContext); -export { constants }; -export { - getClusterUuids, - getLocalStats, - DATA_TELEMETRY_ID, - buildDataTelemetryPayload, -} from './telemetry_collection'; +export { getClusterUuids, getLocalStats } from './telemetry_collection'; export type { TelemetryLocalStats, - DataTelemetryIndex, DataTelemetryPayload, + DataTelemetryDocument, + DataTelemetryBasePayload, NodeUsage, - NodeUsageAggregation, } from './telemetry_collection'; diff --git a/src/plugins/telemetry/server/plugin.ts b/src/plugins/telemetry/server/plugin.ts index 46b7bc89ca6f9..40714bf4cf2be 100644 --- a/src/plugins/telemetry/server/plugin.ts +++ b/src/plugins/telemetry/server/plugin.ts @@ -45,6 +45,9 @@ interface TelemetryPluginsDepsStart { telemetryCollectionManager: TelemetryCollectionManagerPluginStart; } +/** + * Server's setup exposed APIs by the telemetry plugin + */ export interface TelemetryPluginSetup { /** * Resolves into the telemetry Url used to send telemetry. @@ -53,6 +56,9 @@ export interface TelemetryPluginSetup { getTelemetryUrl: () => Promise<URL>; } +/** + * Server's start exposed APIs by the telemetry plugin + */ export interface TelemetryPluginStart { /** * Resolves `true` if the user has opted into send Elastic usage data. diff --git a/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts b/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts index 122fee5667bdf..dd5f4f97c6b02 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts @@ -21,6 +21,8 @@ export async function getClusterStats(esClient: ElasticsearchClient) { /** * Get the cluster uuids from the connected cluster. + * @internal only used externally by the X-Pack Telemetry extension + * @param esClient Scoped Elasticsearch client */ export const getClusterUuids: ClusterDetailsGetter = async ({ esClient }) => { const { body } = await esClient.cluster.stats({ timeout: TIMEOUT }); diff --git a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts index c79c46072e11b..8a0b86cf3b0f0 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts @@ -14,25 +14,45 @@ import { DataTelemetryType, } from './constants'; +/** + * Common counters for the {@link DataTelemetryDocument}s + */ export interface DataTelemetryBasePayload { + /** How many indices match the declared pattern **/ index_count: number; + /** How many indices match the declared pattern follow ECS conventions **/ ecs_index_count?: number; + /** How many documents are among all the identified indices **/ doc_count?: number; + /** Total size in bytes among all the identified indices **/ size_in_bytes?: number; } +/** + * Depending on the type of index, we'll populate different keys as we identify them. + */ export interface DataTelemetryDocument extends DataTelemetryBasePayload { + /** For data-stream indices. Reporting their details **/ data_stream?: { + /** Name of the dataset in the data-stream **/ dataset?: string; + /** Type of the data-stream: "logs", "metrics", "traces" **/ type?: DataTelemetryType | string; // The union of types is to help autocompletion with some known `data_stream.type`s }; + /** When available, reporting the package details **/ package?: { + /** The package's name. Typically populated in the indices' _meta.package.name by Fleet. **/ name: string; }; + /** What's the process indexing the data? (i.e.: "beats", "logstash") **/ shipper?: string; + /** When the data comes from a matching index-pattern, the name of the pattern **/ pattern_name?: DataPatternName; } +/** + * The Data Telemetry is reported as an array of {@link DataTelemetryDocument} + */ export type DataTelemetryPayload = DataTelemetryDocument[]; export interface DataTelemetryIndex { diff --git a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/index.ts b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/index.ts index c93b7e872924b..c5219e419efe7 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/index.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/index.ts @@ -8,4 +8,8 @@ export { DATA_TELEMETRY_ID } from './constants'; export { getDataTelemetry, buildDataTelemetryPayload } from './get_data_telemetry'; -export type { DataTelemetryPayload, DataTelemetryIndex } from './get_data_telemetry'; +export type { + DataTelemetryPayload, + DataTelemetryDocument, + DataTelemetryBasePayload, +} from './get_data_telemetry'; diff --git a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts index 72f6ba855096c..7fdcb50b704af 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts @@ -48,13 +48,17 @@ export function handleLocalStats<ClusterStats extends estypes.ClusterStatsRespon }; } +/** + * The payload structure as composed by the OSS telemetry collection mechanism. + */ export type TelemetryLocalStats = ReturnType<typeof handleLocalStats>; /** * Get statistics for all products joined by Elasticsearch cluster. - * @param {Array} cluster uuids array of cluster uuid's - * @param {Object} config contains the usageCollection, callCluster (deprecated), the esClient and Saved Objects client scoped to the request or the internal repository, and the kibana request - * @param {Object} StatsCollectionContext contains logger and version (string) + * @internal only used externally by the X-Pack Telemetry extension + * @param clustersDetails uuids array of cluster uuid's + * @param config contains the usageCollection, callCluster (deprecated), the esClient and Saved Objects client scoped to the request or the internal repository, and the kibana request + * @param context contains logger and version (string) */ export const getLocalStats: StatsGetter<TelemetryLocalStats> = async ( clustersDetails, diff --git a/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts b/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts index 544142c8d742f..c35b8a3d24498 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts @@ -6,36 +6,22 @@ * Side Public License, v 1. */ -import { ElasticsearchClient } from 'src/core/server'; +import type { ElasticsearchClient } from 'src/core/server'; +import type { estypes } from '@elastic/elasticsearch'; import { TIMEOUT } from './constants'; -export interface NodeUsageAggregation { - [key: string]: number; -} - -// we set aggregations as an optional type because it was only added in v7.8.0 -export interface NodeUsage { - node_id?: string; - timestamp: number | string; - since: number; - rest_actions: { - [key: string]: number; - }; - aggregations?: { - [key: string]: NodeUsageAggregation; - }; -} - -export interface NodesFeatureUsageResponse { - cluster_name: string; - nodes: { - [key: string]: NodeUsage; - }; +/** + * Data returned by GET /_nodes/usage, but flattened as an array of {@link estypes.NodeUsageInformation} + * with the node ID set in the field `node_id`. + */ +export interface NodeUsage extends estypes.NodeUsageInformation { + /** + * The Node ID as reported by ES + */ + node_id: string; } -export type NodesUsageGetter = ( - esClient: ElasticsearchClient -) => Promise<{ nodes: NodeUsage[] | Array<{}> }>; +export type NodesUsageGetter = (esClient: ElasticsearchClient) => Promise<{ nodes: NodeUsage[] }>; /** * Get the nodes usage data from the connected cluster. * @@ -45,11 +31,10 @@ export type NodesUsageGetter = ( */ export async function fetchNodesUsage( esClient: ElasticsearchClient -): Promise<NodesFeatureUsageResponse> { +): Promise<estypes.NodesUsageResponse> { const { body } = await esClient.nodes.usage({ timeout: TIMEOUT, }); - // @ts-expect-error TODO: Does the client parse `timestamp` to a Date object? Expected a number return body; } @@ -61,7 +46,7 @@ export async function fetchNodesUsage( export const getNodesUsage: NodesUsageGetter = async (esClient) => { const result = await fetchNodesUsage(esClient); const transformedNodes = Object.entries(result?.nodes || {}).map(([key, value]) => ({ - ...(value as NodeUsage), + ...value, node_id: key, })); return { nodes: transformedNodes }; diff --git a/src/plugins/telemetry/server/telemetry_collection/index.ts b/src/plugins/telemetry/server/telemetry_collection/index.ts index f55147a0a083f..1126cbd1aa189 100644 --- a/src/plugins/telemetry/server/telemetry_collection/index.ts +++ b/src/plugins/telemetry/server/telemetry_collection/index.ts @@ -7,9 +7,13 @@ */ export { DATA_TELEMETRY_ID, buildDataTelemetryPayload } from './get_data_telemetry'; -export type { DataTelemetryIndex, DataTelemetryPayload } from './get_data_telemetry'; +export type { + DataTelemetryPayload, + DataTelemetryDocument, + DataTelemetryBasePayload, +} from './get_data_telemetry'; export { getLocalStats } from './get_local_stats'; export type { TelemetryLocalStats } from './get_local_stats'; -export type { NodeUsage, NodeUsageAggregation } from './get_nodes_usage'; +export type { NodeUsage } from './get_nodes_usage'; export { getClusterUuids } from './get_cluster_stats'; export { registerCollection } from './register_collection'; From b7719d65ee5f1f5689235fd6c99e17f0cabefdb3 Mon Sep 17 00:00:00 2001 From: Maja Grubic <maja.grubic@elastic.co> Date: Thu, 29 Apr 2021 14:55:52 +0100 Subject: [PATCH 70/70] [Discover] Do not set fieldsFromSource when not using fields API (#98575) * [Discover] Do not set fieldsFromSource when not using fields API * Add Search Source unit test * Add Search Source unit test --- .../search_source/search_source.test.ts | 7 +++++ .../helpers/update_search_source.test.ts | 30 +++++++++++++++++++ .../helpers/update_search_source.ts | 2 -- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/plugins/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index a3f043a5e2657..8e246b625706e 100644 --- a/src/plugins/data/common/search/search_source/search_source.test.ts +++ b/src/plugins/data/common/search/search_source/search_source.test.ts @@ -352,6 +352,13 @@ describe('SearchSource', () => { const request = searchSource.getSearchRequestBody(); expect(request.stored_fields).toEqual(['*']); }); + + test('_source is not set when using the fields API', async () => { + searchSource.setField('fields', ['*']); + const request = searchSource.getSearchRequestBody(); + expect(request.fields).toEqual(['*']); + expect(request._source).toEqual(false); + }); }); describe('source filters handling', () => { diff --git a/src/plugins/discover/public/application/helpers/update_search_source.test.ts b/src/plugins/discover/public/application/helpers/update_search_source.test.ts index 97e2de3541d35..d4e52c4e7d4fe 100644 --- a/src/plugins/discover/public/application/helpers/update_search_source.test.ts +++ b/src/plugins/discover/public/application/helpers/update_search_source.test.ts @@ -136,4 +136,34 @@ describe('updateSearchSource', () => { ]); expect(volatileSearchSourceMock.getField('fieldsFromSource')).toBe(undefined); }); + + test('does not explicitly request fieldsFromSource when not using fields API', async () => { + const persistentSearchSourceMock = createSearchSourceMock({}); + const volatileSearchSourceMock = createSearchSourceMock({}); + const sampleSize = 250; + updateSearchSource({ + persistentSearchSource: persistentSearchSourceMock, + volatileSearchSource: volatileSearchSourceMock, + indexPattern: indexPatternMock, + services: ({ + data: dataPluginMock.createStartContract(), + uiSettings: ({ + get: (key: string) => { + if (key === SAMPLE_SIZE_SETTING) { + return sampleSize; + } + return false; + }, + } as unknown) as IUiSettingsClient, + } as unknown) as DiscoverServices, + sort: [] as SortOrder[], + columns: [], + useNewFieldsApi: false, + showUnmappedFields: false, + }); + expect(persistentSearchSourceMock.getField('index')).toEqual(indexPatternMock); + expect(volatileSearchSourceMock.getField('size')).toEqual(sampleSize); + expect(volatileSearchSourceMock.getField('fields')).toEqual(undefined); + expect(volatileSearchSourceMock.getField('fieldsFromSource')).toBe(undefined); + }); }); diff --git a/src/plugins/discover/public/application/helpers/update_search_source.ts b/src/plugins/discover/public/application/helpers/update_search_source.ts index ba5ac0e822796..07529ac8cb0d6 100644 --- a/src/plugins/discover/public/application/helpers/update_search_source.ts +++ b/src/plugins/discover/public/application/helpers/update_search_source.ts @@ -65,8 +65,6 @@ export function updateSearchSource({ volatileSearchSource.setField('fields', [fields]); } else { volatileSearchSource.removeField('fields'); - const fieldNames = indexPattern.fields.map((field) => field.name); - volatileSearchSource.setField('fieldsFromSource', fieldNames); } } }

    n{3l5eMy{%a3HI$lp_pH0-W!3vUbY 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�^HI{H^FIlO1yVe9^#yA; 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Π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 z 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�H%F?b^Rk~+G~whiu+9r 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

P8MsV|Lg5p*%#=vO$u#;i3#?$V7zh;(0 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 z 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%0D$&Je(PQ&rX6PW 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