diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/wrapper.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/wrapper.test.tsx new file mode 100644 index 0000000000000..2df9978cee082 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/wrapper.test.tsx @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { act, render } from '@testing-library/react'; +import type { PackageListItem } from '@kbn/fleet-plugin/common'; +import { installationStatuses } from '@kbn/fleet-plugin/common/constants'; +import { + CONTENT_TEST_ID, + DATA_VIEW_ERROR_TEST_ID, + DATA_VIEW_LOADING_PROMPT_TEST_ID, + SKELETON_TEST_ID, + Wrapper, +} from './wrapper'; +import { useKibana } from '../../../common/lib/kibana'; + +jest.mock('../../../common/lib/kibana'); + +const packages: PackageListItem[] = [ + { + id: 'splunk', + name: 'splunk', + status: installationStatuses.NotInstalled, + title: 'Splunk', + version: '', + }, +]; + +describe('', () => { + it('should render a loading skeleton while creating the dataView', async () => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + data: { + dataViews: { + create: jest.fn(), + clearInstanceCache: jest.fn(), + }, + }, + }, + }); + + await act(async () => { + const { getByTestId } = render(); + + expect(getByTestId(DATA_VIEW_LOADING_PROMPT_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(SKELETON_TEST_ID)).toBeInTheDocument(); + }); + }); + + it('should render an error if the dataView fail to be created correctly', async () => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + data: { + dataViews: { + create: jest.fn().mockReturnValue(undefined), + clearInstanceCache: jest.fn(), + }, + }, + }, + }); + + jest.mock('react', () => ({ + ...jest.requireActual('react'), + useEffect: jest.fn((f) => f()), + })); + + await act(async () => { + const { getByTestId } = render(); + + await new Promise(process.nextTick); + + expect(getByTestId(DATA_VIEW_LOADING_PROMPT_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(DATA_VIEW_ERROR_TEST_ID)).toHaveTextContent('Unable to create data view'); + }); + }); + + it('should render the content if the dataView is created correctly', async () => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + data: { + dataViews: { + create: jest.fn().mockReturnValue({ id: 'id' }), + clearInstanceCache: jest.fn(), + }, + }, + }, + }); + + jest.mock('react', () => ({ + ...jest.requireActual('react'), + useEffect: jest.fn((f) => f()), + })); + + await act(async () => { + const { getByTestId } = render(); + + await new Promise(process.nextTick); + + expect(getByTestId(DATA_VIEW_LOADING_PROMPT_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(CONTENT_TEST_ID)).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/wrapper.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/wrapper.tsx index d6393d718dcd4..d12edd1f4c560 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/wrapper.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alert_summary/wrapper.tsx @@ -5,11 +5,29 @@ * 2.0. */ -import React, { memo } from 'react'; +import React, { memo, useEffect, useState } from 'react'; +import { + EuiEmptyPrompt, + EuiHorizontalRule, + EuiSkeletonLoading, + EuiSkeletonRectangle, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/common'; import type { PackageListItem } from '@kbn/fleet-plugin/common'; -import { EuiText } from '@elastic/eui'; +import { useKibana } from '../../../common/lib/kibana'; + +const DATAVIEW_ERROR = i18n.translate('xpack.securitySolution.alertSummary.dataViewError', { + defaultMessage: 'Unable to create data view', +}); export const DATA_VIEW_LOADING_PROMPT_TEST_ID = 'alert-summary-data-view-loading-prompt'; +export const DATA_VIEW_ERROR_TEST_ID = 'alert-summary-data-view-error'; +export const SKELETON_TEST_ID = 'alert-summary-skeleton'; +export const CONTENT_TEST_ID = 'alert-summary-content'; + +const dataViewSpec: DataViewSpec = { title: '.alerts-security.alerts-default' }; export interface WrapperProps { /** @@ -19,12 +37,64 @@ export interface WrapperProps { } /** - * Creates a new dataView with the alert indices while displaying a loading skeleton. - * Display the alert summary page content if the dataView is correctly created. - * This page is rendered when there are AI for SOC packages installed. + * Creates a new adhoc dataView for the alert summary page. The dataView is created just with the alert indices. + * During the creating, we display a loading skeleton, mimicking the future alert summary page content. + * Once the dataView is correctly created, we render the content. + * If the creation fails, we show an error message. */ export const Wrapper = memo(({ packages }: WrapperProps) => { - return {'Wrapper'}; + const { data } = useKibana().services; + const [dataView, setDataView] = useState(undefined); + const [loading, setLoading] = useState(true); + + useEffect(() => { + let dv: DataView; + const createDataView = async () => { + dv = await data.dataViews.create(dataViewSpec); + setDataView(dv); + setLoading(false); + }; + createDataView(); + + // clearing after leaving the page + return () => { + if (dv?.id) { + data.dataViews.clearInstanceCache(dv?.id); + } + }; + }, [data.dataViews]); + + return ( + + + + + + + + + + } + loadedContent={ + <> + {!dataView || !dataView.id ? ( + {DATAVIEW_ERROR}} + /> + ) : ( +
{'wrapper'}
+ )} + + } + /> + ); }); Wrapper.displayName = 'Wrapper'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alert_summary/alert_summary.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alert_summary/alert_summary.test.tsx index 90aafcacfbe46..ba91a9242eeb1 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alert_summary/alert_summary.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/pages/alert_summary/alert_summary.test.tsx @@ -6,15 +6,17 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; import { AlertSummaryPage, LOADING_INTEGRATIONS_TEST_ID } from './alert_summary'; import { useFetchIntegrations } from '../../hooks/alert_summary/use_fetch_integrations'; import { LANDING_PAGE_PROMPT_TEST_ID } from '../../components/alert_summary/landing_page/landing_page'; import { useAddIntegrationsUrl } from '../../../common/hooks/use_add_integrations_url'; import { DATA_VIEW_LOADING_PROMPT_TEST_ID } from '../../components/alert_summary/wrapper'; +import { useKibana } from '../../../common/lib/kibana'; jest.mock('../../hooks/alert_summary/use_fetch_integrations'); jest.mock('../../../common/hooks/use_add_integrations_url'); +jest.mock('../../../common/lib/kibana'); describe('', () => { it('should render loading logo', () => { @@ -40,16 +42,27 @@ describe('', () => { expect(getByTestId(LANDING_PAGE_PROMPT_TEST_ID)).toBeInTheDocument(); }); - it('should render wrapper if packages are installed', () => { + it('should render wrapper if packages are installed', async () => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + data: { + dataViews: { + create: jest.fn(), + }, + }, + }, + }); (useFetchIntegrations as jest.Mock).mockReturnValue({ availablePackage: [], installedPackages: [{ id: 'id' }], isLoading: false, }); - const { getByTestId, queryByTestId } = render(); - expect(queryByTestId(LOADING_INTEGRATIONS_TEST_ID)).not.toBeInTheDocument(); - expect(queryByTestId(LANDING_PAGE_PROMPT_TEST_ID)).not.toBeInTheDocument(); - expect(getByTestId(DATA_VIEW_LOADING_PROMPT_TEST_ID)).toBeInTheDocument(); + await act(async () => { + const { getByTestId, queryByTestId } = render(); + expect(queryByTestId(LOADING_INTEGRATIONS_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(LANDING_PAGE_PROMPT_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(DATA_VIEW_LOADING_PROMPT_TEST_ID)).toBeInTheDocument(); + }); }); });