Skip to content

Commit

Permalink
[Ingest Manager][SECURITY SOLUTION] adjust config reassign link and a…
Browse files Browse the repository at this point in the history
…dd roundtrip to Reassignment flow (#70208)
  • Loading branch information
kevinlog committed Jun 30, 2020
1 parent 590fc8d commit b3f19da
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 && (
<EuiPortal>
<AgentReassignConfigFlyout agent={agent} onClose={() => setIsReassignFlyoutOpen(false)} />
<AgentReassignConfigFlyout agent={agent} onClose={onClose} />
</EuiPortal>
)}
<ContextMenuActions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* 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, { useMemo } from 'react';
import { useRouteMatch, Switch, Route } from 'react-router-dom';
import React, { useMemo, useCallback } from 'react';
import { useRouteMatch, Switch, Route, useLocation } from 'react-router-dom';
import styled from 'styled-components';
import {
EuiFlexGroup,
Expand All @@ -19,14 +19,21 @@ import {
import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { Agent, AgentConfig } from '../../../types';
import { Agent, AgentConfig, AgentDetailsReassignConfigAction } from '../../../types';
import { PAGE_ROUTING_PATHS } from '../../../constants';
import { Loading, Error } from '../../../components';
import { useGetOneAgent, useGetOneAgentConfig, useLink, useBreadcrumbs } from '../../../hooks';
import {
useGetOneAgent,
useGetOneAgentConfig,
useLink,
useBreadcrumbs,
useCore,
} from '../../../hooks';
import { WithHeaderLayout } from '../../../layouts';
import { AgentHealth } from '../components';
import { AgentRefreshContext } from './hooks';
import { AgentEventsTable, AgentDetailsActionMenu, AgentDetailsContent } from './components';
import { useIntraAppState } from '../../../hooks/use_intra_app_state';

const Divider = styled.div`
width: 0;
Expand Down Expand Up @@ -54,6 +61,19 @@ export const AgentDetailsPage: React.FunctionComponent = () => {
sendRequest: sendAgentConfigRequest,
} = useGetOneAgentConfig(agentData?.item?.config_id);

const {
application: { navigateToApp },
} = useCore();
const routeState = useIntraAppState<AgentDetailsReassignConfigAction>();
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(
() => (
<EuiFlexGroup direction="column" gutterSize="s" alignItems="flexStart">
Expand Down Expand Up @@ -124,7 +144,17 @@ export const AgentDetailsPage: React.FunctionComponent = () => {
},
{ isDivider: true },
{
content: <AgentDetailsActionMenu agent={agentData.item} />,
content: (
<AgentDetailsActionMenu
agent={agentData.item}
assignFlyoutOpenByDefault={openReassignFlyoutOpenByDefault}
onCancelReassign={
routeState && routeState.onDoneNavigateTo
? reassignCancelClickHandler
: undefined
}
/>
),
},
].map((item, index) => (
<EuiFlexItem grow={false} key={index}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,18 @@ export interface AgentConfigDetailsDeployAgentAction {
onDoneNavigateTo?: Parameters<ApplicationStart['navigateToApp']>;
}

/**
* 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<ApplicationStart['navigateToApp']>;
}

/**
* All possible Route states.
*/
export type AnyIntraAppRouteState =
| CreateDatasourceRouteState
| AgentConfigDetailsDeployAgentAction;
| AgentConfigDetailsDeployAgentAction
| AgentDetailsReassignConfigAction;
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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(() => {
Expand Down Expand Up @@ -207,8 +232,9 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => {
<LinkToExternalApp>
<LinkToApp
appId={ingestAppId}
appPath={ingestAppPath}
href={ingestUrl}
appPath={agentDetailsWithFlyoutPath}
href={agentDetailsWithFlyoutUrl}
onClick={handleReassignEndpointsClick}
data-test-subj="hostDetailsLinkToIngest"
>
<EuiIcon type="savedObjectsApp" className="linkToAppIcon" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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
) => {
Expand Down Expand Up @@ -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');

Expand Down Expand Up @@ -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');
Expand Down

0 comments on commit b3f19da

Please sign in to comment.