From 09da11047df7348e05a8eb75da82bd3fd27a294c Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Thu, 9 Jul 2020 15:33:44 -0400 Subject: [PATCH] [SECURITY_SOLUTION] adjust policy onboarding view, check for Ingest permissions (#70536) * adjust policy onboarding view * correct test subj * fix tests * re-enable tests * add no permissions view * adjust onbording look * adjust text * use ingest hook, add tests * adjust text * address comments * beta badges * fix test * correct timeline flyout Co-authored-by: Elastic Machine --- .../utils/timeline/use_show_timeline.tsx | 2 +- .../components/management_empty_state.tsx | 324 ++++++++++-------- .../pages/endpoint_hosts/view/index.tsx | 28 +- .../public/management/pages/index.test.tsx | 36 ++ .../public/management/pages/index.tsx | 42 +++ .../pages/policy/view/policy_list.test.tsx | 4 +- .../pages/policy/view/policy_list.tsx | 26 +- .../public/overview/pages/overview.test.tsx | 25 ++ .../public/overview/pages/overview.tsx | 4 +- 9 files changed, 327 insertions(+), 164 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/pages/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx index a9c6660ba9c68..14c38c5d6dab6 100644 --- a/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx +++ b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.tsx @@ -7,7 +7,7 @@ import { useState, useEffect } from 'react'; import { useRouteSpy } from '../route/use_route_spy'; -const hideTimelineForRoutes = [`/cases/configure`, '/management']; +const hideTimelineForRoutes = [`/cases/configure`, '/administration']; export const useShowTimeline = () => { const [{ pageName, pathName }] = useRouteSpy(); diff --git a/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx b/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx index c3d6cb48e4dae..6486b1f3be6d1 100644 --- a/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx +++ b/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx @@ -16,6 +16,7 @@ import { EuiSelectable, EuiSelectableMessage, EuiSelectableProps, + EuiIcon, EuiLoadingSpinner, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -35,71 +36,121 @@ const PolicyEmptyState = React.memo<{ onActionClick: (event: MouseEvent) => void; actionDisabled?: boolean; }>(({ loading, onActionClick, actionDisabled }) => { - const policySteps = useMemo( - () => [ - { - title: i18n.translate('xpack.securitySolution.endpoint.policyList.stepOneTitle', { - defaultMessage: 'Head over to Ingest Manager.', - }), - children: ( - - - - ), - }, - { - title: i18n.translate('xpack.securitySolution.endpoint.policyList.stepTwoTitle', { - defaultMessage: 'We’ll create a recommended security policy for you.', - }), - children: ( - - - - ), - }, - { - title: i18n.translate('xpack.securitySolution.endpoint.policyList.stepThreeTitle', { - defaultMessage: 'Enroll your agents through Fleet.', - }), - children: ( - - - - ), - }, - ], - [] - ); - return ( - - } - bodyComponent={ - - } - /> +
+ {loading ? ( + + + + + + ) : ( + + + +

+ +

+
+ + + + + + + + + + + + + + + + + +

+ +

+
+
+
+ + + + +
+ + + + + + + +

+ +

+
+
+
+ + + + +
+
+ + + + + + + + + + + + +
+ + + +
+ )} +
); }); @@ -114,17 +165,17 @@ const HostsEmptyState = React.memo<{ () => [ { title: i18n.translate('xpack.securitySolution.endpoint.hostList.stepOneTitle', { - defaultMessage: 'Select a policy you created from the list below.', + defaultMessage: 'Select the policy you want to use to protect your hosts', }), children: ( <> - + - + - - + + + + + + + + + + + + ), }, ], - [selectionOptions, handleSelectableOnChange, loading] + [selectionOptions, handleSelectableOnChange, loading, actionDisabled, onActionClick] ); return ( } bodyComponent={ } /> @@ -198,80 +265,45 @@ const HostsEmptyState = React.memo<{ const ManagementEmptyState = React.memo<{ loading: boolean; - onActionClick?: (event: MouseEvent) => void; - actionDisabled?: boolean; - actionButton?: JSX.Element; dataTestSubj: string; steps?: ManagementStep[]; headerComponent: JSX.Element; bodyComponent: JSX.Element; -}>( - ({ - loading, - onActionClick, - actionDisabled, - dataTestSubj, - steps, - actionButton, - headerComponent, - bodyComponent, - }) => { - return ( -
- {loading ? ( - - - - - - ) : ( - <> - - -

{headerComponent}

-
- - - {bodyComponent} - - - {steps && ( - - - - - - )} +}>(({ loading, dataTestSubj, steps, headerComponent, bodyComponent }) => { + return ( +
+ {loading ? ( + + + + + + ) : ( + <> + + +

{headerComponent}

+
+ + + {bodyComponent} + + + {steps && ( - <> - {actionButton ? ( - actionButton - ) : ( - - - - )} - + - - )} -
- ); - } -); + )} + + )} +
+ ); +}); PolicyEmptyState.displayName = 'PolicyEmptyState'; HostsEmptyState.displayName = 'HostsEmptyState'; ManagementEmptyState.displayName = 'ManagementEmptyState'; -export { PolicyEmptyState, HostsEmptyState, ManagementEmptyState }; +export { PolicyEmptyState, HostsEmptyState }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index 492c75607a255..8edeab15d6a09 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -16,6 +16,9 @@ import { EuiHealth, EuiToolTip, EuiSelectableProps, + EuiBetaBadge, + EuiFlexGroup, + EuiFlexItem, } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; @@ -374,14 +377,25 @@ export const HostList = () => { data-test-subj="hostPage" headerLeft={ <> - -

- + + +

+ +

+
+
+ + -

-
+ +

diff --git a/x-pack/plugins/security_solution/public/management/pages/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/index.test.tsx new file mode 100644 index 0000000000000..5ec42671ec3d2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/index.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { ManagementContainer } from './index'; +import { AppContextTestRender, createAppRootMockRenderer } from '../../common/mock/endpoint'; +import { useIngestEnabledCheck } from '../../common/hooks/endpoint/ingest_enabled'; + +jest.mock('../../common/hooks/endpoint/ingest_enabled'); + +describe('when in the Admistration tab', () => { + let render: () => ReturnType; + + beforeEach(() => { + const mockedContext = createAppRootMockRenderer(); + render = () => mockedContext.render(); + }); + + it('should display the No Permissions view when Ingest is OFF', async () => { + (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: false }); + const renderResult = render(); + const noIngestPermissions = await renderResult.findByTestId('noIngestPermissions'); + expect(noIngestPermissions).not.toBeNull(); + }); + + it('should display the Management view when Ingest is ON', async () => { + (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: true }); + const renderResult = render(); + const hostPage = await renderResult.findByTestId('hostPage'); + expect(hostPage).not.toBeNull(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/index.tsx b/x-pack/plugins/security_solution/public/management/pages/index.tsx index 2cf07b9b4382e..30800234ab24c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/index.tsx @@ -7,6 +7,8 @@ import React, { memo } from 'react'; import { useHistory, Route, Switch } from 'react-router-dom'; +import { EuiText, EuiEmptyPrompt } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; import { PolicyContainer } from './policy'; import { MANAGEMENT_ROUTING_HOSTS_PATH, @@ -16,9 +18,49 @@ import { import { NotFoundPage } from '../../app/404'; import { HostsContainer } from './endpoint_hosts'; import { getHostListPath } from '../common/routing'; +import { SpyRoute } from '../../common/utils/route/spy_routes'; +import { SecurityPageName } from '../../app/types'; +import { useIngestEnabledCheck } from '../../common/hooks/endpoint/ingest_enabled'; + +const NoPermissions = memo(() => { + return ( + <> + + } + body={ +

+ + + +

+ } + /> + + + ); +}); +NoPermissions.displayName = 'NoPermissions'; export const ManagementContainer = memo(() => { const history = useHistory(); + const { allEnabled: isIngestEnabled } = useIngestEnabledCheck(); + + if (!isIngestEnabled) { + return ; + } + return ( diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx index db622ceb87b63..047aa6918736e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx @@ -37,9 +37,9 @@ describe('when on the policies page', () => { expect(table).not.toBeNull(); }); - it('should display the onboarding steps', async () => { + it('should display the instructions', async () => { const renderResult = render(); - const onboardingSteps = await renderResult.findByTestId('onboardingSteps'); + const onboardingSteps = await renderResult.findByTestId('policyOnboardingInstructions'); expect(onboardingSteps).not.toBeNull(); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx index fc120d9782e67..8a77264c354ad 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx @@ -23,6 +23,7 @@ import { EuiConfirmModal, EuiCallOut, EuiButton, + EuiBetaBadge, EuiHorizontalRule, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -395,14 +396,25 @@ export const PolicyList = React.memo(() => { data-test-subj="policyListPage" headerLeft={ <> - -

- + + +

+ +

+
+
+ + -

-
+ +

diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx index 6f13f64ca1bff..43d8fb10508b7 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.test.tsx @@ -104,6 +104,7 @@ describe('Overview', () => { const mockuseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; mockuseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false)); + (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: true }); const wrapper = mount( @@ -128,6 +129,7 @@ describe('Overview', () => { const mockuseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; mockuseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false)); + (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: true }); const wrapper = mount( @@ -152,6 +154,7 @@ describe('Overview', () => { const mockuseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; mockuseMessagesStorage.mockImplementation(() => endpointNoticeMessage(true)); + (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: true }); const wrapper = mount( @@ -171,6 +174,7 @@ describe('Overview', () => { const mockuseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; mockuseMessagesStorage.mockImplementation(() => endpointNoticeMessage(true)); + (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: true }); const wrapper = mount( @@ -190,6 +194,27 @@ describe('Overview', () => { const mockuseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; mockuseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false)); + (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: true }); + + const wrapper = mount( + + + + + + ); + expect(wrapper.find('[data-test-subj="endpoint-prompt-banner"]').exists()).toBe(false); + }); + + test('it does NOT render the Endpoint banner when Ingest is NOT available', () => { + (useWithSource as jest.Mock).mockReturnValue({ + indicesExist: true, + indexPattern: {}, + }); + + const mockuseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; + mockuseMessagesStorage.mockImplementation(() => endpointNoticeMessage(true)); + (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: false }); const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx index 2a522d3ea8fde..6563f3c2b824d 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx @@ -29,6 +29,7 @@ import { SecurityPageName } from '../../app/types'; import { EndpointNotice } from '../components/endpoint_notice'; import { useMessagesStorage } from '../../common/containers/local_storage/use_messages_storage'; import { ENDPOINT_METADATA_INDEX } from '../../../common/constants'; +import { useIngestEnabledCheck } from '../../common/hooks/endpoint/ingest_enabled'; const DEFAULT_QUERY: Query = { query: '', language: 'kuery' }; const NO_FILTERS: Filter[] = []; @@ -64,6 +65,7 @@ const OverviewComponent: React.FC = ({ setDismissMessage(true); addMessage('management', 'dismissEndpointNotice'); }, [addMessage]); + const { allEnabled: isIngestEnabled } = useIngestEnabledCheck(); return ( <> @@ -74,7 +76,7 @@ const OverviewComponent: React.FC = ({ - {!dismissMessage && !metadataIndexExists && ( + {!dismissMessage && !metadataIndexExists && isIngestEnabled && ( <>