diff --git a/src/plugins/kibana_react/public/page_template/no_data_page/__snapshots__/no_data_page.test.tsx.snap b/src/plugins/kibana_react/public/page_template/no_data_page/__snapshots__/no_data_page.test.tsx.snap index 8842a3c9f5842..0554e64c5ecb6 100644 --- a/src/plugins/kibana_react/public/page_template/no_data_page/__snapshots__/no_data_page.test.tsx.snap +++ b/src/plugins/kibana_react/public/page_template/no_data_page/__snapshots__/no_data_page.test.tsx.snap @@ -3,6 +3,108 @@ exports[`NoDataPage render 1`] = `
& { export type NoDataPageActionsProps = Record; -export interface NoDataPageProps { +export interface NoDataPageProps extends CommonProps { /** * Single name for the current solution, used to auto-generate the title, logo, description, and button label */ @@ -94,6 +96,7 @@ export const NoDataPage: FunctionComponent = ({ actions, docsLink, pageTitle, + ...rest }) => { // Convert obj data into an iterable array const entries = Object.entries(actions); @@ -131,7 +134,7 @@ export const NoDataPage: FunctionComponent = ({ }, [actions, sortedData, actionsKeys]); return ( -
+
getTimelineShowStatus(state, TimelineId.active) ); - const endpointMetadataIndex = useMemo(() => { - return [ENDPOINT_METADATA_INDEX]; - }, []); - const [, { indexExists: metadataIndexExists }] = useFetchIndex(endpointMetadataIndex, true); - const { indicesExist } = useSourcererScope(); - const securityIndicesExist = indicesExist || metadataIndexExists; + const showEmptyState = useShowPagesWithEmptyView(); + const emptyStateProps = showEmptyState ? NO_DATA_PAGE_TEMPLATE_PROPS : {}; // StyledKibanaPageTemplate is a styled EuiPageTemplate. Security solution currently passes the header and page content as the children of StyledKibanaPageTemplate, as opposed to using the pageHeader prop, which may account for any style discrepancies, such as the bottom border not extending the full width of the page, between EuiPageTemplate and the security solution pages. - return securityIndicesExist ? ( + return ( - - - {children} - + {showEmptyState ? ( + children + ) : ( + <> + + + {children} + + + )} - ) : ( - ); }); diff --git a/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.test.tsx b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.test.tsx new file mode 100644 index 0000000000000..981f7e9e876ea --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.test.tsx @@ -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 { renderHook, act } from '@testing-library/react-hooks'; +import { useShowPagesWithEmptyView } from './use_show_pages_with_empty_view'; + +jest.mock('../route/use_route_spy', () => ({ + useRouteSpy: jest + .fn() + .mockImplementationOnce(() => [{ pageName: 'hosts' }]) + .mockImplementationOnce(() => [{ pageName: 'rules' }]) + .mockImplementationOnce(() => [{ pageName: 'network' }]), +})); +jest.mock('../../../common/containers/sourcerer', () => ({ + useSourcererScope: jest + .fn() + .mockImplementationOnce(() => [{ indicesExist: false }]) + .mockImplementationOnce(() => [{ indicesExist: false }]) + .mockImplementationOnce(() => [{ indicesExist: true }]), +})); + +describe('use show pages with empty view', () => { + it('shows empty view when on an elligible page and indices do not exist', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useShowPagesWithEmptyView()); + await waitForNextUpdate(); + const emptyResult = result.current; + expect(emptyResult).toEqual(true); + }); + }); + it('does not show empty view when on an inelligible page and indices do not exist', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useShowPagesWithEmptyView()); + await waitForNextUpdate(); + const emptyResult = result.current; + expect(emptyResult).toEqual(false); + }); + }); + it('shows empty view when on an elligible page and indices do exist', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useShowPagesWithEmptyView()); + await waitForNextUpdate(); + const emptyResult = result.current; + expect(emptyResult).toEqual(true); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.tsx b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.tsx new file mode 100644 index 0000000000000..10cc4be10a61b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/utils/empty_view/use_show_pages_with_empty_view.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useState, useEffect } from 'react'; +import { useRouteSpy } from '../route/use_route_spy'; +import { SecurityPageName } from '../../../app/types'; +import { useSourcererScope } from '../../../common/containers/sourcerer'; + +// Used to detect if we're on a top level page that is empty and set page background color to match the subdued Empty State +const isPageNameWithEmptyView = (currentName: string) => { + const pageNamesWithEmptyView: string[] = [ + SecurityPageName.hosts, + SecurityPageName.network, + SecurityPageName.timelines, + SecurityPageName.overview, + ]; + return pageNamesWithEmptyView.includes(currentName); +}; + +export const useShowPagesWithEmptyView = () => { + const [{ pageName }] = useRouteSpy(); + const { indicesExist } = useSourcererScope(); + + const shouldShowEmptyState = isPageNameWithEmptyView(pageName) && !indicesExist; + + const [showEmptyState, setShowEmptyState] = useState(shouldShowEmptyState); + + useEffect(() => { + if (shouldShowEmptyState) { + setShowEmptyState(true); + } else { + setShowEmptyState(false); + } + }, [shouldShowEmptyState]); + + return showEmptyState; +}; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index 627f5e5aef507..9bf046f40edb4 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -219,8 +219,6 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta ) : ( - - )} diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index f647819798ab7..8e6a635624bfe 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -216,8 +216,6 @@ const HostsComponent = () => { ) : ( - - )} diff --git a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx index a3f953fc24fe2..5aee4022a7f30 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx @@ -302,8 +302,6 @@ const NetworkDetailsComponent: React.FC = () => { ) : ( - - )} diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx index 928570417c524..fe8a9a93f97c7 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx @@ -221,7 +221,6 @@ const NetworkComponent = React.memo( ) : ( - )} diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx index 61e9e66f1bb87..36ecc3371c056 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx @@ -42,17 +42,13 @@ describe('OverviewEmpty', () => { }); it('render with correct actions ', () => { - expect(wrapper.find('[data-test-subj="empty-page"]').prop('noDataConfig')).toEqual({ - actions: { - elasticAgent: { - category: 'security', - description: - 'Use Elastic Agent to collect security events and protect your endpoints from threats.', - title: 'Add a Security integration', - }, + expect(wrapper.find('[data-test-subj="empty-page"]').prop('actions')).toEqual({ + elasticAgent: { + category: 'security', + description: + 'Use Elastic Agent to collect security events and protect your endpoints from threats.', + title: 'Add a Security integration', }, - docsLink: 'https://www.elastic.co/guide/en/security/mocked-test-branch/index.html', - solution: 'Security', }); }); }); @@ -67,17 +63,13 @@ describe('OverviewEmpty', () => { }); it('render with correct actions ', () => { - expect(wrapper.find('[data-test-subj="empty-page"]').prop('noDataConfig')).toEqual({ - actions: { - elasticAgent: { - category: 'security', - description: - 'Use Elastic Agent to collect security events and protect your endpoints from threats.', - title: 'Add a Security integration', - }, + expect(wrapper.find('[data-test-subj="empty-page"]').prop('actions')).toEqual({ + elasticAgent: { + category: 'security', + description: + 'Use Elastic Agent to collect security events and protect your endpoints from threats.', + title: 'Add a Security integration', }, - docsLink: 'https://www.elastic.co/guide/en/security/mocked-test-branch/index.html', - solution: 'Security', }); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx index 9b20c079002e6..023d010ec9a9b 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx @@ -11,7 +11,7 @@ import { useKibana } from '../../../common/lib/kibana'; import { SOLUTION_NAME } from '../../../../public/common/translations'; import { - KibanaPageTemplate, + NoDataPage, NoDataPageActionsProps, } from '../../../../../../../src/plugins/kibana_react/public'; @@ -32,13 +32,11 @@ const OverviewEmptyComponent: React.FC = () => { }; return ( - ); }; diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx index 728153f47abd7..f79d513380349 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx @@ -93,7 +93,6 @@ export const TimelinesPageComponent: React.FC = () => { ) : ( - )}