From 32a68c57878c159325a32108a1d67cfefcc23640 Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Mon, 29 Jun 2020 21:43:47 -0400 Subject: [PATCH] [Ingest Manager][SECURITY SOLUTION] adjust config reassign link and add roundtrip to Reassignment flow (#70208) --- .../components/actions_menu.tsx | 18 +++++++-- .../fleet/agent_details_page/index.tsx | 40 ++++++++++++++++--- .../types/intra_app_route_state.ts | 11 ++++- .../view/details/host_details.tsx | 34 ++++++++++++++-- .../pages/endpoint_hosts/view/hooks.ts | 17 ++++++++ .../pages/endpoint_hosts/view/index.test.tsx | 29 ++++++++++++++ 6 files changed, 135 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx index 27e17f6b3df610..75a67fb9288e55 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/actions_menu.tsx @@ -3,7 +3,7 @@ * 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, { memo, useState } from 'react'; +import React, { memo, useState, useMemo } from 'react'; import { EuiPortal, EuiContextMenuItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { Agent } from '../../../../types'; @@ -14,16 +14,26 @@ import { useAgentRefresh } from '../hooks'; export const AgentDetailsActionMenu: React.FunctionComponent<{ agent: Agent; -}> = memo(({ agent }) => { + assignFlyoutOpenByDefault?: boolean; + onCancelReassign?: () => void; +}> = memo(({ agent, assignFlyoutOpenByDefault = false, onCancelReassign }) => { const hasWriteCapabilites = useCapabilities().write; const refreshAgent = useAgentRefresh(); - const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(false); + const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(assignFlyoutOpenByDefault); + + const onClose = useMemo(() => { + if (onCancelReassign) { + return onCancelReassign; + } else { + return () => setIsReassignFlyoutOpen(false); + } + }, [onCancelReassign, setIsReassignFlyoutOpen]); return ( <> {isReassignFlyoutOpen && ( - setIsReassignFlyoutOpen(false)} /> + )} { sendRequest: sendAgentConfigRequest, } = useGetOneAgentConfig(agentData?.item?.config_id); + const { + application: { navigateToApp }, + } = useCore(); + const routeState = useIntraAppState(); + const queryParams = new URLSearchParams(useLocation().search); + const openReassignFlyoutOpenByDefault = queryParams.get('openReassignFlyout') === 'true'; + + const reassignCancelClickHandler = useCallback(() => { + if (routeState && routeState.onDoneNavigateTo) { + navigateToApp(routeState.onDoneNavigateTo[0], routeState.onDoneNavigateTo[1]); + } + }, [routeState, navigateToApp]); + const headerLeftContent = useMemo( () => ( @@ -124,7 +144,17 @@ export const AgentDetailsPage: React.FunctionComponent = () => { }, { isDivider: true }, { - content: , + content: ( + + ), }, ].map((item, index) => ( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/intra_app_route_state.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/intra_app_route_state.ts index b2948686ff6e57..c5833adcded5f9 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/intra_app_route_state.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/intra_app_route_state.ts @@ -29,9 +29,18 @@ export interface AgentConfigDetailsDeployAgentAction { onDoneNavigateTo?: Parameters; } +/** + * Supported routing state for the agent config details page routes with deploy agents action + */ +export interface AgentDetailsReassignConfigAction { + /** On done, navigate to the given app */ + onDoneNavigateTo?: Parameters; +} + /** * All possible Route states. */ export type AnyIntraAppRouteState = | CreateDatasourceRouteState - | AgentConfigDetailsDeployAgentAction; + | AgentConfigDetailsDeployAgentAction + | AgentDetailsReassignConfigAction; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx index b7e90c19799c76..80c4e2f379c7c6 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx @@ -19,7 +19,8 @@ import React, { memo, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { HostMetadata } from '../../../../../../common/endpoint/types'; -import { useHostSelector, useHostLogsUrl, useHostIngestUrl } from '../hooks'; +import { useHostSelector, useHostLogsUrl, useAgentDetailsIngestUrl } from '../hooks'; +import { useNavigateToAppEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; import { policyResponseStatus, uiQueryParams } from '../../store/selectors'; import { POLICY_STATUS_TO_HEALTH_COLOR } from '../host_constants'; import { FormattedDateAndTime } from '../../../../../common/components/endpoint/formatted_date_time'; @@ -28,6 +29,7 @@ import { LinkToApp } from '../../../../../common/components/endpoint/link_to_app import { getEndpointDetailsPath, getPolicyDetailPath } from '../../../../common/routing'; import { SecurityPageName } from '../../../../../app/types'; import { useFormatUrl } from '../../../../../common/components/link_to'; +import { AgentDetailsReassignConfigAction } from '../../../../../../../ingest_manager/public'; const HostIds = styled(EuiListGroupItem)` margin-top: 0; @@ -46,9 +48,16 @@ const LinkToExternalApp = styled.div` } `; +const openReassignFlyoutSearch = '?openReassignFlyout=true'; + export const HostDetails = memo(({ details }: { details: HostMetadata }) => { const { url: logsUrl, appId: logsAppId, appPath: logsAppPath } = useHostLogsUrl(details.host.id); - const { url: ingestUrl, appId: ingestAppId, appPath: ingestAppPath } = useHostIngestUrl(); + const agentId = details.elastic.agent.id; + const { + url: agentDetailsUrl, + appId: ingestAppId, + appPath: agentDetailsAppPath, + } = useAgentDetailsIngestUrl(agentId); const queryParams = useHostSelector(uiQueryParams); const policyStatus = useHostSelector( policyResponseStatus @@ -96,6 +105,22 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { ]; }, [details.host.id, formatUrl, queryParams]); + const agentDetailsWithFlyoutPath = `${agentDetailsAppPath}${openReassignFlyoutSearch}`; + const agentDetailsWithFlyoutUrl = `${agentDetailsUrl}${openReassignFlyoutSearch}`; + const handleReassignEndpointsClick = useNavigateToAppEventHandler< + AgentDetailsReassignConfigAction + >(ingestAppId, { + path: agentDetailsWithFlyoutPath, + state: { + onDoneNavigateTo: [ + 'securitySolution:management', + { + path: getEndpointDetailsPath({ name: 'endpointDetails', selected_host: details.host.id }), + }, + ], + }, + }); + const policyStatusClickHandler = useNavigateByRouterEventHandler(policyResponseRoutePath); const [policyDetailsRoutePath, policyDetailsRouteUrl] = useMemo(() => { @@ -207,8 +232,9 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts index 51aaea20df8431..c072c812edbb5d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts @@ -51,3 +51,20 @@ export const useHostIngestUrl = (): { url: string; appId: string; appPath: strin }; }, [services.application]); }; + +/** + * Returns an object that contains Ingest app and URL information + */ +export const useAgentDetailsIngestUrl = ( + agentId: string +): { url: string; appId: string; appPath: string } => { + const { services } = useKibana(); + return useMemo(() => { + const appPath = `#/fleet/agents/${agentId}/activity`; + return { + url: `${services.application.getUrlForApp('ingestManager')}${appPath}`, + appId: 'ingestManager', + appPath, + }; + }, [services.application, agentId]); +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index 9690ac5c1b9bf9..68943797ea07e1 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -210,6 +210,7 @@ describe('when on the hosts page', () => { describe('when there is a selected host in the url', () => { let hostDetails: HostInfo; + let agentId: string; const dispatchServerReturnedHostPolicyResponse = ( overallStatus: HostPolicyResponseActionStatus = HostPolicyResponseActionStatus.success ) => { @@ -274,6 +275,8 @@ describe('when on the hosts page', () => { }, }; + agentId = hostDetails.metadata.elastic.agent.id; + coreStart.http.get.mockReturnValue(Promise.resolve(hostDetails)); coreStart.application.getUrlForApp.mockReturnValue('/app/logs'); @@ -404,6 +407,32 @@ describe('when on the hosts page', () => { ).not.toBeNull(); }); + it('should include the link to reassignment in Ingest', async () => { + coreStart.application.getUrlForApp.mockReturnValue('/app/ingestManager'); + const renderResult = render(); + const linkToReassign = await renderResult.findByTestId('hostDetailsLinkToIngest'); + expect(linkToReassign).not.toBeNull(); + expect(linkToReassign.textContent).toEqual('Reassign Policy'); + expect(linkToReassign.getAttribute('href')).toEqual( + `/app/ingestManager#/fleet/agents/${agentId}/activity?openReassignFlyout=true` + ); + }); + + describe('when link to reassignment in Ingest is clicked', () => { + beforeEach(async () => { + coreStart.application.getUrlForApp.mockReturnValue('/app/ingestManager'); + const renderResult = render(); + const linkToReassign = await renderResult.findByTestId('hostDetailsLinkToIngest'); + reactTestingLibrary.act(() => { + reactTestingLibrary.fireEvent.click(linkToReassign); + }); + }); + + it('should navigate to Ingest without full page refresh', () => { + expect(coreStart.application.navigateToApp.mock.calls).toHaveLength(1); + }); + }); + it('should include the link to logs', async () => { const renderResult = render(); const linkToLogs = await renderResult.findByTestId('hostDetailsLinkToLogs');