From f9982f19cf979911ae5d6b6c5970b46549dd5ed3 Mon Sep 17 00:00:00 2001 From: kevinlog Date: Fri, 19 Jun 2020 17:39:18 -0400 Subject: [PATCH 01/19] add policy empty state to Host list, placeholder for add endpoint prompt --- .../components/management_empty_state.tsx | 156 ++++++++++++++++++ .../pages/endpoint_hosts/store/middleware.ts | 48 +++++- .../pages/endpoint_hosts/view/index.tsx | 84 ++++++++-- .../pages/policy/view/policy_list.tsx | 114 +------------ 4 files changed, 277 insertions(+), 125 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx 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 new file mode 100644 index 00000000000000..0714df363b6a04 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx @@ -0,0 +1,156 @@ +/* + * 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, { useMemo, MouseEvent, CSSProperties } from 'react'; +import { + EuiText, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiButton, + EuiSteps, + EuiTitle, + EuiProgress, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +const TEXT_ALIGN_CENTER: CSSProperties = Object.freeze({ + textAlign: 'center', +}); + +interface ManagementStep { + title: string; + children: JSX.Element; +} +const PolicyEmptyState = React.memo<{ + loading: boolean; + 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 ( + + ); +}); + +const ManagementEmptyState = React.memo<{ + loading: boolean; + onActionClick?: (event: MouseEvent) => void; + actionDisabled?: boolean; + actionButton?: JSX.Element; + dataTestSubj: string; + steps: ManagementStep[]; +}>(({ loading, onActionClick, actionDisabled, dataTestSubj, steps, actionButton }) => { + return ( +
+ {loading ? ( + + ) : ( + <> + + +

+ +

+
+ + + + + + + + + + + + + <> + {actionButton ? ( + actionButton + ) : ( + + + + )} + + + + + )} +
+ ); +}); + +PolicyEmptyState.displayName = 'PolicyEmptyState'; +ManagementEmptyState.displayName = 'ManagementEmptyState'; + +export { PolicyEmptyState, ManagementEmptyState }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index 85667c9f9fc376..ad52573d97d5e7 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -5,10 +5,13 @@ */ import { HostResultList } from '../../../../../common/endpoint/types'; +import { GetPolicyListResponse } from '../../policy/types'; import { ImmutableMiddlewareFactory } from '../../../../common/store'; import { isOnHostPage, hasSelectedHost, uiQueryParams, listData } from './selectors'; import { HostState } from '../types'; +import { sendGetEndpointSpecificDatasources } from '../../policy/store/policy_list/services/ingest'; +// TODO: I think the problem with the type is the state in this factory export const hostMiddlewareFactory: ImmutableMiddlewareFactory = (coreStart) => { return ({ getState, dispatch }) => (next) => async (action) => { next(action); @@ -26,10 +29,47 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = (cor }), }); response.request_page_index = Number(pageIndex); - dispatch({ - type: 'serverReturnedHostList', - payload: response, - }); + + if (response.hosts.length > 0) { + dispatch({ + type: 'serverReturnedHostList', + payload: response, + }); + } else { + const http = coreStart.http; + try { + const policyDataResponse: GetPolicyListResponse = await sendGetEndpointSpecificDatasources( + http, + { + query: { + perPage: 50, + page: 1, + }, + } + ); + + dispatch({ + type: 'serverReturnedHostList', + payload: response, + }); + + dispatch({ + type: 'serverReturnedPolicyListData', + payload: { + policyItems: policyDataResponse.items, + pageIndex, + pageSize, + total: policyDataResponse.total, + }, + }); + } catch (error) { + dispatch({ + type: 'serverFailedToReturnPolicyListData', + payload: error.body ?? error, + }); + return; + } + } } catch (error) { dispatch({ type: 'serverFailedToReturnHostList', 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 149819480855bd..238504240245d2 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 @@ -18,10 +18,11 @@ import { useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { createStructuredSelector } from 'reselect'; - import { HostDetailsFlyout } from './details'; import * as selectors from '../store/selectors'; +import * as policySelectors from '../../policy/store//policy_list/selectors'; import { useHostSelector } from './hooks'; +import { usePolicyListSelector } from '../../policy/view/policy_hooks'; import { HOST_STATUS_TO_HEALTH_COLOR, POLICY_STATUS_TO_HEALTH_COLOR, @@ -32,8 +33,13 @@ import { CreateStructuredSelector } from '../../../../common/store'; import { Immutable, HostInfo } from '../../../../../common/endpoint/types'; import { SpyRoute } from '../../../../common/utils/route/spy_routes'; import { ManagementPageView } from '../../../components/management_page_view'; +import { PolicyEmptyState } from '../../../components/management_empty_state'; import { getManagementUrl } from '../../..'; import { FormattedDate } from '../../../../common/components/formatted_date'; +import { useEndpointPackageInfo } from '../../policy/view/ingest_hooks'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; +import { CreateDatasourceRouteState } from '../../../../../../ingest_manager/public'; const HostListNavLink = memo<{ name: string; @@ -58,6 +64,7 @@ const HostListNavLink = memo<{ HostListNavLink.displayName = 'HostListNavLink'; const selector = (createStructuredSelector as CreateStructuredSelector)(selectors); +const policySelector = (createStructuredSelector as CreateStructuredSelector)(policySelectors); export const HostList = () => { const history = useHistory(); const { @@ -71,6 +78,8 @@ export const HostList = () => { hasSelectedHost, } = useHostSelector(selector); + const { selectPolicyItems: policyItems } = usePolicyListSelector(policySelector); + const paginationSetup = useMemo(() => { return { pageIndex, @@ -80,6 +89,8 @@ export const HostList = () => { hidePerPageOptions: false, }; }, [pageIndex, pageSize, totalItemCount]); + const [packageInfo, isFetchingPackageInfo] = useEndpointPackageInfo(); + const { services } = useKibana(); const onTableChange = useCallback( ({ page }: { page: { index: number; size: number } }) => { @@ -98,6 +109,26 @@ export const HostList = () => { [history, queryParams] ); + const handleCreatePolicyClick = useNavigateToAppEventHandler( + 'ingestManager', + { + path: `#/integrations${packageInfo ? `/endpoint-${packageInfo.version}/add-datasource` : ''}`, + state: { + onCancelNavigateTo: [ + 'securitySolution', + { path: getManagementUrl({ name: 'endpointList' }) }, + ], + onCancelUrl: services.application?.getUrlForApp('securitySolution', { + path: getManagementUrl({ name: 'endpointList' }), + }), + onSaveNavigateTo: [ + 'securitySolution', + { path: getManagementUrl({ name: 'endpointList' }) }, + ], + }, + } + ); + const columns: Array>> = useMemo(() => { const lastActiveColumnName = i18n.translate('xpack.securitySolution.endpointList.lastActive', { defaultMessage: 'Last Active', @@ -259,6 +290,47 @@ export const HostList = () => { ]; }, [queryParams]); + const renderTableOrEmptyState = useMemo(() => { + if (listData && listData.length > 0) { + return ( + + ); + } else if (policyItems && policyItems.length > 0) { + return ( + + ); + } else { + return ( + + ); + } + }, [ + listData, + policyItems, + columns, + loading, + paginationSetup, + onTableChange, + listError?.message, + handleCreatePolicyClick, + isFetchingPackageInfo, + ]); + return ( { /> - [...listData], [listData])} - columns={columns} - loading={loading} - error={listError?.message} - pagination={paginationSetup} - onChange={onTableChange} - /> + {renderTableOrEmptyState} ); 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 2d4abd6de0e420..0fb5757cf62b59 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 @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useEffect, useMemo, CSSProperties, useState, MouseEvent } from 'react'; +import React, { useCallback, useEffect, useMemo, CSSProperties, useState } from 'react'; import { EuiBasicTable, EuiText, @@ -22,9 +22,6 @@ import { EuiCallOut, EuiSpacer, EuiButton, - EuiSteps, - EuiTitle, - EuiProgress, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -41,6 +38,7 @@ import { Immutable, PolicyData } from '../../../../../common/endpoint/types'; import { useNavigateByRouterEventHandler } from '../../../../common/hooks/endpoint/use_navigate_by_router_event_handler'; import { LinkToApp } from '../../../../common/components/endpoint/link_to_app'; import { ManagementPageView } from '../../../components/management_page_view'; +import { PolicyEmptyState } from '../../../components/management_empty_state'; import { SpyRoute } from '../../../../common/utils/route/spy_routes'; import { getManagementUrl } from '../../../common/routing'; import { FormattedDateAndTime } from '../../../../common/components/endpoint/formatted_date_time'; @@ -64,10 +62,6 @@ const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({ whiteSpace: 'nowrap', }); -const TEXT_ALIGN_CENTER: CSSProperties = Object.freeze({ - textAlign: 'center', -}); - const DangerEuiContextMenuItem = styled(EuiContextMenuItem)` color: ${(props) => props.theme.eui.textColors.danger}; `; @@ -443,11 +437,10 @@ export const PolicyList = React.memo(() => { hasActions={false} /> ) : ( - )} @@ -469,107 +462,6 @@ export const PolicyList = React.memo(() => { PolicyList.displayName = 'PolicyList'; -const EmptyPolicyTable = React.memo<{ - loading: boolean; - onActionClick: (event: MouseEvent) => void; - actionDisabled: boolean; - dataTestSubj: string; -}>(({ loading, onActionClick, actionDisabled, dataTestSubj }) => { - 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 ( -
- {loading ? ( - - ) : ( - <> - - -

- -

-
- - - - - - - - - - - - - - - - - - - )} -
- ); -}); - -EmptyPolicyTable.displayName = 'EmptyPolicyTable'; - const ConfirmDelete = React.memo<{ hostCount: number; isDeleting: boolean; From a319fc18b6ce8cfda46ab4b1868ded6c5b8a9a10 Mon Sep 17 00:00:00 2001 From: kevinlog Date: Mon, 22 Jun 2020 09:12:37 -0400 Subject: [PATCH 02/19] add empty endpoint state --- .../components/management_empty_state.tsx | 208 +++++++++++++----- .../pages/endpoint_hosts/view/index.tsx | 14 +- 2 files changed, 161 insertions(+), 61 deletions(-) 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 0714df363b6a04..9e4ba185cb70fb 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 @@ -26,6 +26,7 @@ interface ManagementStep { title: string; children: JSX.Element; } + const PolicyEmptyState = React.memo<{ loading: boolean; onActionClick: (event: MouseEvent) => void; @@ -83,74 +84,167 @@ const PolicyEmptyState = React.memo<{ actionDisabled={actionDisabled} dataTestSubj="emptyPolicyTable" steps={policySteps} + headerComponent={ + + } + bodyComponent={ + + } /> ); }); -const ManagementEmptyState = React.memo<{ +const EndpointsEmptyState = React.memo<{ loading: boolean; - onActionClick?: (event: MouseEvent) => void; - actionDisabled?: boolean; - actionButton?: JSX.Element; - dataTestSubj: string; - steps: ManagementStep[]; -}>(({ loading, onActionClick, actionDisabled, dataTestSubj, steps, actionButton }) => { - return ( -
- {loading ? ( - - ) : ( - <> - - -

- -

-
- - + onActionClick: (event: MouseEvent) => void; + actionDisabled: boolean; +}>(({ loading, onActionClick, actionDisabled }) => { + const policySteps = useMemo( + () => [ + { + title: i18n.translate('xpack.securitySolution.endpoint.endpointList.stepOneTitle', { + defaultMessage: 'Head over to Ingest Manager.', + }), + children: ( + + + + ), + }, + { + title: i18n.translate('xpack.securitySolution.endpoint.endpointList.stepTwoTitle', { + defaultMessage: 'We’ll create a recommended security policy for you.', + }), + children: ( + + + + ), + }, + { + title: i18n.translate('xpack.securitySolution.endpoint.endpointList.stepThreeTitle', { + defaultMessage: 'Enroll your agents through Fleet.', + }), + children: ( + - - - - - - - - - <> - {actionButton ? ( - actionButton - ) : ( - - - - )} - - - - - )} -
+ ), + }, + ], + [] + ); + + return ( + + } + bodyComponent={ + + } + /> ); }); +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 && ( + + + + + + )} + + + <> + {actionButton ? ( + actionButton + ) : ( + + + + )} + + + + + )} +
+ ); + } +); + PolicyEmptyState.displayName = 'PolicyEmptyState'; +EndpointsEmptyState.displayName = 'EndpointsEmptyState'; ManagementEmptyState.displayName = 'ManagementEmptyState'; -export { PolicyEmptyState, ManagementEmptyState }; +export { PolicyEmptyState, EndpointsEmptyState, ManagementEmptyState }; 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 238504240245d2..6aecd7936e01d5 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 @@ -33,7 +33,7 @@ import { CreateStructuredSelector } from '../../../../common/store'; import { Immutable, HostInfo } from '../../../../../common/endpoint/types'; import { SpyRoute } from '../../../../common/utils/route/spy_routes'; import { ManagementPageView } from '../../../components/management_page_view'; -import { PolicyEmptyState } from '../../../components/management_empty_state'; +import { PolicyEmptyState, EndpointsEmptyState } from '../../../components/management_empty_state'; import { getManagementUrl } from '../../..'; import { FormattedDate } from '../../../../common/components/formatted_date'; import { useEndpointPackageInfo } from '../../policy/view/ingest_hooks'; @@ -129,6 +129,10 @@ export const HostList = () => { } ); + const handleDeployEndpointsClick = useNavigateToAppEventHandler('ingestManager', { + path: `#/fleet`, + }); + const columns: Array>> = useMemo(() => { const lastActiveColumnName = i18n.translate('xpack.securitySolution.endpointList.lastActive', { defaultMessage: 'Last Active', @@ -305,9 +309,10 @@ export const HostList = () => { ); } else if (policyItems && policyItems.length > 0) { return ( - ); } else { @@ -329,6 +334,7 @@ export const HostList = () => { listError?.message, handleCreatePolicyClick, isFetchingPackageInfo, + handleDeployEndpointsClick, ]); return ( From d203642eb8f87ec774d1eecb55f918f1b7f84921 Mon Sep 17 00:00:00 2001 From: kevinlog Date: Mon, 22 Jun 2020 10:59:18 -0400 Subject: [PATCH 03/19] fix types --- .../pages/endpoint_hosts/store/action.ts | 17 ++++++++++++++++- .../pages/endpoint_hosts/store/middleware.ts | 8 ++------ .../pages/endpoint_hosts/store/reducer.ts | 14 ++++++++++---- .../pages/endpoint_hosts/store/selectors.ts | 2 ++ .../management/pages/endpoint_hosts/types.ts | 3 +++ .../pages/endpoint_hosts/view/index.tsx | 6 +----- 6 files changed, 34 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts index 62a2d9e3205c26..324b30a483c6cd 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts @@ -10,6 +10,7 @@ import { GetHostPolicyResponse, } from '../../../../../common/endpoint/types'; import { ServerApiError } from '../../../../common/types'; +import { GetPolicyListResponse } from '../../policy/types'; interface ServerReturnedHostList { type: 'serverReturnedHostList'; @@ -41,10 +42,24 @@ interface ServerFailedToReturnHostPolicyResponse { payload: ServerApiError; } +interface ServerReturnEndpointPolicies { + type: 'serverReturnEndpointPolicies'; + payload: { + policyItems: GetPolicyListResponse['items']; + }; +} + +interface ServerFailedToReturnEndpointPolicies { + type: 'serverFailedToReturnEndpointPolicies'; + payload: ServerApiError; +} + export type HostAction = | ServerReturnedHostList | ServerFailedToReturnHostList | ServerReturnedHostDetails | ServerFailedToReturnHostDetails | ServerReturnedHostPolicyResponse - | ServerFailedToReturnHostPolicyResponse; + | ServerFailedToReturnHostPolicyResponse + | ServerReturnEndpointPolicies + | ServerFailedToReturnEndpointPolicies; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index ad52573d97d5e7..e7e1ffdd471a13 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -11,7 +11,6 @@ import { isOnHostPage, hasSelectedHost, uiQueryParams, listData } from './select import { HostState } from '../types'; import { sendGetEndpointSpecificDatasources } from '../../policy/store/policy_list/services/ingest'; -// TODO: I think the problem with the type is the state in this factory export const hostMiddlewareFactory: ImmutableMiddlewareFactory = (coreStart) => { return ({ getState, dispatch }) => (next) => async (action) => { next(action); @@ -54,17 +53,14 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = (cor }); dispatch({ - type: 'serverReturnedPolicyListData', + type: 'serverReturnEndpointPolicies', payload: { policyItems: policyDataResponse.items, - pageIndex, - pageSize, - total: policyDataResponse.total, }, }); } catch (error) { dispatch({ - type: 'serverFailedToReturnPolicyListData', + type: 'serverFailedToReturnEndpointPolicies', payload: error.body ?? error, }); return; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts index 23682544ec423e..e48cdf2014331d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts @@ -24,6 +24,7 @@ export const initialHostListState: Immutable = { policyResponseLoading: false, policyResponseError: undefined, location: undefined, + policyItems: [], }; export const hostListReducer: ImmutableReducer = ( @@ -52,12 +53,17 @@ export const hostListReducer: ImmutableReducer = ( error: action.payload, loading: false, }; - } else if (action.type === 'serverReturnedHostDetails') { + } else if (action.type === 'serverReturnEndpointPolicies') { return { ...state, - details: action.payload.metadata, - detailsLoading: false, - detailsError: undefined, + policyItems: action.payload.policyItems, + loading: false, + }; + } else if (action.type === 'serverFailedToReturnEndpointPolicies') { + return { + ...state, + error: action.payload, + loading: false, }; } else if (action.type === 'serverFailedToReturnHostDetails') { return { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts index 20365b3fe100b9..b326fcc3f7c094 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts @@ -37,6 +37,8 @@ export const detailsLoading = (state: Immutable): boolean => state.de export const detailsError = (state: Immutable) => state.detailsError; +export const policyItems = (state: Immutable) => state.policyItems; + /** * Returns the full policy response from the endpoint after a user modifies a policy. */ diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts index 4881342c065737..83fb024a6f0755 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts @@ -10,6 +10,7 @@ import { HostMetadata, HostPolicyResponse, AppLocation, + PolicyData, } from '../../../../common/endpoint/types'; import { ServerApiError } from '../../../common/types'; @@ -40,6 +41,8 @@ export interface HostState { policyResponseError?: ServerApiError; /** current location info */ location?: Immutable; + /** policies */ + policyItems: PolicyData[]; } /** 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 6aecd7936e01d5..83c83e4914a2c9 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 @@ -20,9 +20,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { createStructuredSelector } from 'reselect'; import { HostDetailsFlyout } from './details'; import * as selectors from '../store/selectors'; -import * as policySelectors from '../../policy/store//policy_list/selectors'; import { useHostSelector } from './hooks'; -import { usePolicyListSelector } from '../../policy/view/policy_hooks'; import { HOST_STATUS_TO_HEALTH_COLOR, POLICY_STATUS_TO_HEALTH_COLOR, @@ -64,7 +62,6 @@ const HostListNavLink = memo<{ HostListNavLink.displayName = 'HostListNavLink'; const selector = (createStructuredSelector as CreateStructuredSelector)(selectors); -const policySelector = (createStructuredSelector as CreateStructuredSelector)(policySelectors); export const HostList = () => { const history = useHistory(); const { @@ -76,10 +73,9 @@ export const HostList = () => { listError, uiQueryParams: queryParams, hasSelectedHost, + policyItems, } = useHostSelector(selector); - const { selectPolicyItems: policyItems } = usePolicyListSelector(policySelector); - const paginationSetup = useMemo(() => { return { pageIndex, From 08afb5a901f2a1337b6b433eeb605ee13e8f332a Mon Sep 17 00:00:00 2001 From: kevinlog Date: Mon, 22 Jun 2020 11:46:25 -0400 Subject: [PATCH 04/19] fix test, hide total when empty state --- .../pages/endpoint_hosts/store/reducer.ts | 19 ++++++++++++------ .../pages/endpoint_hosts/view/index.test.tsx | 5 +++-- .../pages/endpoint_hosts/view/index.tsx | 20 +++++++++++-------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts index e48cdf2014331d..7b2954b816d61d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts @@ -53,6 +53,19 @@ export const hostListReducer: ImmutableReducer = ( error: action.payload, loading: false, }; + } else if (action.type === 'serverReturnedHostDetails') { + return { + ...state, + details: action.payload.metadata, + detailsLoading: false, + detailsError: undefined, + }; + } else if (action.type === 'serverFailedToReturnHostDetails') { + return { + ...state, + detailsError: action.payload, + detailsLoading: false, + }; } else if (action.type === 'serverReturnEndpointPolicies') { return { ...state, @@ -65,12 +78,6 @@ export const hostListReducer: ImmutableReducer = ( error: action.payload, loading: false, }; - } else if (action.type === 'serverFailedToReturnHostDetails') { - return { - ...state, - detailsError: action.payload, - detailsLoading: false, - }; } else if (action.type === 'serverReturnedHostPolicyResponse') { return { ...state, 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 68c4e960741819..626f879897f970 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 @@ -34,9 +34,10 @@ describe('when on the hosts page', () => { render = () => mockedContext.render(); }); - it('should show a table', async () => { + it('should show the empty state', async () => { const renderResult = render(); - const table = await renderResult.findByTestId('hostListTable'); + // Initially, there are no endpoints or policies, so we prompt to add policies first. + const table = await renderResult.findByTestId('emptyPolicyTable'); expect(table).not.toBeNull(); }); 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 83c83e4914a2c9..578161ab1c95aa 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 @@ -342,14 +342,18 @@ export const HostList = () => { })} > {hasSelectedHost && } - - - - + {listData && listData.length > 0 && ( + <> + + + + + + )} {renderTableOrEmptyState} From 4316454de586483e6a16d1bd6040259ff99c3161 Mon Sep 17 00:00:00 2001 From: kevinlog Date: Mon, 22 Jun 2020 12:53:49 -0400 Subject: [PATCH 05/19] fix types --- .../public/management/pages/endpoint_hosts/view/index.tsx | 2 -- 1 file changed, 2 deletions(-) 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 efa690adbbf8fd..334ccc535aca75 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 @@ -34,7 +34,6 @@ import { ManagementPageView } from '../../../components/management_page_view'; import { PolicyEmptyState, EndpointsEmptyState } from '../../../components/management_empty_state'; import { FormattedDate } from '../../../../common/components/formatted_date'; import { useEndpointPackageInfo } from '../../policy/view/ingest_hooks'; -import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; import { CreateDatasourceRouteState } from '../../../../../../ingest_manager/public'; import { SecurityPageName } from '../../../../app/types'; @@ -93,7 +92,6 @@ export const HostList = () => { }; }, [pageIndex, pageSize, totalItemCount]); const [packageInfo, isFetchingPackageInfo] = useEndpointPackageInfo(); - const { services } = useKibana(); const onTableChange = useCallback( ({ page }: { page: { index: number; size: number } }) => { From 08bbc887dba2a3c3c7ff189ccb7407b839628637 Mon Sep 17 00:00:00 2001 From: kevinlog Date: Mon, 22 Jun 2020 13:53:04 -0400 Subject: [PATCH 06/19] fix test --- .../public/management/pages/endpoint_hosts/store/index.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts index 71452993abf07e..47a5bfafe6c7fa 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts @@ -46,6 +46,7 @@ describe('HostList store concerns', () => { policyResponseLoading: false, policyResponseError: undefined, location: undefined, + policyItems: [], }); }); From 6260e5e5ee03bbdd1a6e1cd400c1cd94103ab032 Mon Sep 17 00:00:00 2001 From: kevinlog Date: Tue, 23 Jun 2020 17:13:15 -0400 Subject: [PATCH 07/19] add policy selection to onboarding --- .../components/management_empty_state.tsx | 70 ++++++++++++------- .../pages/endpoint_hosts/store/action.ts | 10 ++- .../pages/endpoint_hosts/store/reducer.ts | 7 ++ .../pages/endpoint_hosts/store/selectors.ts | 2 + .../management/pages/endpoint_hosts/types.ts | 2 + .../pages/endpoint_hosts/view/index.tsx | 44 +++++++++++- 6 files changed, 108 insertions(+), 27 deletions(-) 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 9e4ba185cb70fb..0a011d1c1a9b66 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 @@ -13,7 +13,10 @@ import { EuiButton, EuiSteps, EuiTitle, + EuiSelectable, EuiProgress, + EuiSelectableMessage, + EuiSelectableProps, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -104,50 +107,69 @@ const EndpointsEmptyState = React.memo<{ loading: boolean; onActionClick: (event: MouseEvent) => void; actionDisabled: boolean; -}>(({ loading, onActionClick, actionDisabled }) => { + handleSelectableOnChange: (o: EuiSelectableProps['options']) => void; + selectionOptions: EuiSelectableProps['options']; +}>(({ loading, onActionClick, actionDisabled, handleSelectableOnChange, selectionOptions }) => { const policySteps = useMemo( () => [ { title: i18n.translate('xpack.securitySolution.endpoint.endpointList.stepOneTitle', { - defaultMessage: 'Head over to Ingest Manager.', + defaultMessage: 'Select a policy you created from the list below.', }), children: ( - - - + <> + + + + + + {(list) => + loading ? ( + + + + ) : selectionOptions.length ? ( + list + ) : ( + + ) + } + + ), }, { title: i18n.translate('xpack.securitySolution.endpoint.endpointList.stepTwoTitle', { - defaultMessage: 'We’ll create a recommended security policy for you.', + defaultMessage: + 'Head over to Ingest to deploy your Agent with Endpoint Security enabled.', }), children: ( - - ), - }, - { - title: i18n.translate('xpack.securitySolution.endpoint.endpointList.stepThreeTitle', { - defaultMessage: 'Enroll your agents through Fleet.', - }), - children: ( - - ), }, ], - [] + [selectionOptions, handleSelectableOnChange, loading] ); return ( diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts index 324b30a483c6cd..3693b62ffcba98 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts @@ -54,6 +54,13 @@ interface ServerFailedToReturnEndpointPolicies { payload: ServerApiError; } +interface UserSelectedEndpointPolicy { + type: 'userSelectedEndpointPolicy'; + payload: { + selectedPolicyId: string; + }; +} + export type HostAction = | ServerReturnedHostList | ServerFailedToReturnHostList @@ -62,4 +69,5 @@ export type HostAction = | ServerReturnedHostPolicyResponse | ServerFailedToReturnHostPolicyResponse | ServerReturnEndpointPolicies - | ServerFailedToReturnEndpointPolicies; + | ServerFailedToReturnEndpointPolicies + | UserSelectedEndpointPolicy; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts index 7b2954b816d61d..7ecd78098d4a96 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts @@ -25,6 +25,7 @@ export const initialHostListState: Immutable = { policyResponseError: undefined, location: undefined, policyItems: [], + selectedPolicyId: undefined, }; export const hostListReducer: ImmutableReducer = ( @@ -91,6 +92,12 @@ export const hostListReducer: ImmutableReducer = ( policyResponseError: action.payload, policyResponseLoading: false, }; + } else if (action.type === 'userSelectedEndpointPolicy') { + return { + ...state, + selectedPolicyId: action.payload.selectedPolicyId, + policyResponseLoading: false, + }; } else if (action.type === 'userChangedUrl') { const newState: Immutable = { ...state, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts index b326fcc3f7c094..61d8f22cd47942 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts @@ -39,6 +39,8 @@ export const detailsError = (state: Immutable) => state.detailsError; export const policyItems = (state: Immutable) => state.policyItems; +export const selectedPolicyId = (state: Immutable) => state.selectedPolicyId; + /** * Returns the full policy response from the endpoint after a user modifies a policy. */ diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts index 83fb024a6f0755..32d34669689465 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts @@ -43,6 +43,8 @@ export interface HostState { location?: Immutable; /** policies */ policyItems: PolicyData[]; + /** the selected policy ID in the onboarding flow */ + selectedPolicyId?: string; } /** 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 334ccc535aca75..2ffc400ebd48a6 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 @@ -13,11 +13,13 @@ import { EuiLink, EuiHealth, EuiToolTip, + EuiSelectableProps, } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { createStructuredSelector } from 'reselect'; +import { useDispatch } from 'react-redux'; import { HostDetailsFlyout } from './details'; import * as selectors from '../store/selectors'; import { useHostSelector } from './hooks'; @@ -43,6 +45,7 @@ import { getPolicyDetailPath, } from '../../../common/routing'; import { useFormatUrl } from '../../../../common/components/link_to'; +import { HostAction } from '../store/action'; const HostListNavLink = memo<{ name: string; @@ -79,9 +82,12 @@ export const HostList = () => { uiQueryParams: queryParams, hasSelectedHost, policyItems, + selectedPolicyId, } = useHostSelector(selector); const { formatUrl, search } = useFormatUrl(SecurityPageName.management); + const dispatch = useDispatch<(a: HostAction) => void>(); + const paginationSetup = useMemo(() => { return { pageIndex, @@ -128,9 +134,38 @@ export const HostList = () => { ); const handleDeployEndpointsClick = useNavigateToAppEventHandler('ingestManager', { - path: `#/fleet`, + path: `#/configs/${selectedPolicyId}`, }); + const selectionOptions = useMemo(() => { + return policyItems.map((item) => { + return { + key: item.config_id, + label: item.name, + checked: selectedPolicyId === item.id ? 'on' : undefined, + }; + }); + }, [policyItems, selectedPolicyId]); + + const handleSelectableOnChange = useCallback<(o: EuiSelectableProps['options']) => void>( + (changedOptions) => { + return changedOptions.some((option) => { + if ('checked' in option && option.checked === 'on') { + dispatch({ + type: 'userSelectedEndpointPolicy', + payload: { + selectedPolicyId: option.key as string, + }, + }); + return true; + } else { + return false; + } + }); + }, + [dispatch] + ); + const columns: Array>> = useMemo(() => { const lastActiveColumnName = i18n.translate('xpack.securitySolution.endpointList.lastActive', { defaultMessage: 'Last Active', @@ -297,7 +332,9 @@ export const HostList = () => { ); } else { @@ -320,6 +357,9 @@ export const HostList = () => { handleCreatePolicyClick, isFetchingPackageInfo, handleDeployEndpointsClick, + handleSelectableOnChange, + selectedPolicyId, + selectionOptions, ]); return ( From 1c55b230b6d9a47b43140bd54fde4225beff952e Mon Sep 17 00:00:00 2001 From: kevinlog Date: Tue, 23 Jun 2020 18:27:43 -0400 Subject: [PATCH 08/19] begin unit tests --- .../components/management_empty_state.tsx | 1 + .../pages/endpoint_hosts/store/action.ts | 12 +++---- .../pages/endpoint_hosts/store/middleware.ts | 4 +-- .../pages/endpoint_hosts/store/reducer.ts | 4 +-- .../pages/endpoint_hosts/view/index.test.tsx | 35 +++++++++++++++++++ 5 files changed, 46 insertions(+), 10 deletions(-) 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 0a011d1c1a9b66..f12975ce252d1d 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 @@ -132,6 +132,7 @@ const EndpointsEmptyState = React.memo<{ height={100} listProps={{ bordered: true, singleSelection: true }} onChange={handleSelectableOnChange} + data-test-sub="onboardingPolicySelect" > {(list) => loading ? ( diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts index 3693b62ffcba98..88255db202ae56 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts @@ -42,15 +42,15 @@ interface ServerFailedToReturnHostPolicyResponse { payload: ServerApiError; } -interface ServerReturnEndpointPolicies { - type: 'serverReturnEndpointPolicies'; +interface ServerReturnedPoliciesForOnboarding { + type: 'serverReturnedPoliciesForOnboarding'; payload: { policyItems: GetPolicyListResponse['items']; }; } -interface ServerFailedToReturnEndpointPolicies { - type: 'serverFailedToReturnEndpointPolicies'; +interface ServerFailedToReturnPoliciesForOnboarding { + type: 'serverFailedToReturnPoliciesForOnboarding'; payload: ServerApiError; } @@ -68,6 +68,6 @@ export type HostAction = | ServerFailedToReturnHostDetails | ServerReturnedHostPolicyResponse | ServerFailedToReturnHostPolicyResponse - | ServerReturnEndpointPolicies - | ServerFailedToReturnEndpointPolicies + | ServerReturnedPoliciesForOnboarding + | ServerFailedToReturnPoliciesForOnboarding | UserSelectedEndpointPolicy; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index e7e1ffdd471a13..b1ed81fc4485cc 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -53,14 +53,14 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = (cor }); dispatch({ - type: 'serverReturnEndpointPolicies', + type: 'serverReturnedPoliciesForOnboarding', payload: { policyItems: policyDataResponse.items, }, }); } catch (error) { dispatch({ - type: 'serverFailedToReturnEndpointPolicies', + type: 'serverFailedToReturnPoliciesForOnboarding', payload: error.body ?? error, }); return; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts index 7ecd78098d4a96..cc13d1573360d2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts @@ -67,13 +67,13 @@ export const hostListReducer: ImmutableReducer = ( detailsError: action.payload, detailsLoading: false, }; - } else if (action.type === 'serverReturnEndpointPolicies') { + } else if (action.type === 'serverReturnedPoliciesForOnboarding') { return { ...state, policyItems: action.payload.policyItems, loading: false, }; - } else if (action.type === 'serverFailedToReturnEndpointPolicies') { + } else if (action.type === 'serverFailedToReturnPoliciesForOnboarding') { return { ...state, error: action.payload, 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 ed6fa1ec4166c6..9cbee960ee59cb 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 @@ -41,6 +41,41 @@ describe('when on the hosts page', () => { expect(table).not.toBeNull(); }); + describe('when there are policies, but no hosts', () => { + beforeEach(() => { + reactTestingLibrary.act(() => { + const hostListData = mockHostResultList({ total: 0 }); + coreStart.http.get.mockReturnValue(Promise.resolve(hostListData)); + const hostAction: AppAction = { + type: 'serverReturnedHostList', + payload: hostListData, + }; + store.dispatch(hostAction); + + jest.clearAllMocks(); + + const policyListData = mockPolicyResultList({ total: 3 }); + coreStart.http.get.mockReturnValue(Promise.resolve(policyListData)); + const policyAction: AppAction = { + type: 'serverReturnedPoliciesForOnboarding', + payload: { + policyItems: policyListData.items, + }, + }; + store.dispatch(policyAction); + }); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should show the no hosts empty state', async () => { + const renderResult = render(); + const emptyEndpointsTable = await renderResult.findByTestId('emptyEndpointsTable'); + expect(emptyEndpointsTable).not.toBeNull(); + }); + }); + describe('when there is no selected host in the url', () => { it('should not show the flyout', () => { const renderResult = render(); From f5e399930e016232ad6c4c2faa7a10f09c94571f Mon Sep 17 00:00:00 2001 From: kevinlog Date: Tue, 23 Jun 2020 21:34:35 -0400 Subject: [PATCH 09/19] additional tests --- .../components/management_empty_state.tsx | 10 +++++----- .../pages/endpoint_hosts/view/index.test.tsx | 14 +++++++++++++- .../apps/endpoint/index.ts | 4 ++-- 3 files changed, 20 insertions(+), 8 deletions(-) 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 f12975ce252d1d..0d1ff45615097c 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 @@ -132,10 +132,10 @@ const EndpointsEmptyState = React.memo<{ height={100} listProps={{ bordered: true, singleSelection: true }} onChange={handleSelectableOnChange} - data-test-sub="onboardingPolicySelect" + data-test-subj="onboardingPolicySelect" > - {(list) => - loading ? ( + {(list) => { + return loading ? ( - ) - } + ); + }} ), 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 9cbee960ee59cb..9a014436c5d525 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 @@ -34,7 +34,7 @@ describe('when on the hosts page', () => { render = () => mockedContext.render(); }); - it('should show the empty state', async () => { + it('should show the empty state when there are no hosts or polices', async () => { const renderResult = render(); // Initially, there are no endpoints or policies, so we prompt to add policies first. const table = await renderResult.findByTestId('emptyPolicyTable'); @@ -74,6 +74,18 @@ describe('when on the hosts page', () => { const emptyEndpointsTable = await renderResult.findByTestId('emptyEndpointsTable'); expect(emptyEndpointsTable).not.toBeNull(); }); + + it('should display the onboarding steps', async () => { + const renderResult = render(); + const onboardingSteps = await renderResult.findByTestId('onboardingSteps'); + expect(onboardingSteps).not.toBeNull(); + }); + + it('should show policy selection', async () => { + const renderResult = render(); + const onboardingPolicySelect = await renderResult.findAllByTestId('onboardingPolicySelect'); + expect(onboardingPolicySelect).not.toBeNull(); + }); }); describe('when there is no selected host in the url', () => { diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index 199d138d1c450a..6af68b1881586f 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -10,8 +10,8 @@ export default function ({ loadTestFile }: FtrProviderContext) { this.tags('ciGroup7'); loadTestFile(require.resolve('./endpoint_list')); - loadTestFile(require.resolve('./policy_list')); - loadTestFile(require.resolve('./policy_details')); + // loadTestFile(require.resolve('./policy_list')); + // loadTestFile(require.resolve('./policy_details')); // loadTestFile(require.resolve('./alerts')); // loadTestFile(require.resolve('./resolver')); From eb4b259c4d0a92496af3047d5fb8a54ff8043134 Mon Sep 17 00:00:00 2001 From: kevinlog Date: Wed, 24 Jun 2020 11:03:05 -0400 Subject: [PATCH 10/19] add URL and roundtrip --- .../hooks/use_intra_app_state.tsx | 8 +- .../agent_config/components/actions_menu.tsx | 184 ++++++++++-------- .../agent_config/details_page/index.tsx | 24 ++- .../pages/endpoint_hosts/view/index.tsx | 20 +- .../apps/endpoint/index.ts | 4 +- 5 files changed, 147 insertions(+), 93 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx index 565c5b364893cc..9c23187c204b23 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx @@ -57,9 +57,13 @@ export function useIntraAppState(): // once so that it does not impact navigation to the page from within the // ingest app. side affect is that the browser back button would not work // consistently either. - if (location.pathname === intraAppState.forRoute && !wasHandled.has(intraAppState)) { + + if ( + location.pathname + location.search === intraAppState.forRoute && + !wasHandled.has(intraAppState) + ) { wasHandled.add(intraAppState); return intraAppState.routeState as S; } - }, [intraAppState, location.pathname]); + }, [intraAppState, location.pathname, location.search]); } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx index 39fe090e5008cf..86d191d4ff9048 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/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 { FormattedMessage } from '@kbn/i18n/react'; import { EuiContextMenuItem, EuiPortal } from '@elastic/eui'; import { AgentConfig } from '../../../types'; @@ -17,86 +17,106 @@ export const AgentConfigActionMenu = memo<{ config: AgentConfig; onCopySuccess?: (newAgentConfig: AgentConfig) => void; fullButton?: boolean; -}>(({ config, onCopySuccess, fullButton = false }) => { - const hasWriteCapabilities = useCapabilities().write; - const [isYamlFlyoutOpen, setIsYamlFlyoutOpen] = useState(false); - const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); + enrollmentFlyoutOpenByDefault?: boolean; + onCancelEnrollment?: () => void; +}>( + ({ + config, + onCopySuccess, + fullButton = false, + enrollmentFlyoutOpenByDefault = false, + onCancelEnrollment, + }) => { + const hasWriteCapabilities = useCapabilities().write; + const [isYamlFlyoutOpen, setIsYamlFlyoutOpen] = useState(false); + const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState( + enrollmentFlyoutOpenByDefault + ); - return ( - - {(copyAgentConfigPrompt) => { - return ( - <> - {isYamlFlyoutOpen ? ( - - setIsYamlFlyoutOpen(false)} /> - - ) : null} - {isEnrollmentFlyoutOpen && ( - - setIsEnrollmentFlyoutOpen(false)} - /> - - )} - - ), - } - : undefined - } - items={[ - setIsEnrollmentFlyoutOpen(true)} - key="enrollAgents" - > - - , - setIsYamlFlyoutOpen(!isYamlFlyoutOpen)} - key="viewConfig" - > - - , - { - copyAgentConfigPrompt(config, onCopySuccess); - }} - key="copyConfig" - > - { + if (onCancelEnrollment) { + return onCancelEnrollment; + } else { + return () => setIsEnrollmentFlyoutOpen(false); + } + }, [onCancelEnrollment, setIsEnrollmentFlyoutOpen]); + + return ( + + {(copyAgentConfigPrompt) => { + return ( + <> + {isYamlFlyoutOpen ? ( + + setIsYamlFlyoutOpen(false)} /> - , - ]} - /> - - ); - }} - - ); -}); + + ) : null} + {isEnrollmentFlyoutOpen && ( + + + + )} + + ), + } + : undefined + } + items={[ + setIsEnrollmentFlyoutOpen(true)} + key="enrollAgents" + > + + , + setIsYamlFlyoutOpen(!isYamlFlyoutOpen)} + key="viewConfig" + > + + , + { + copyAgentConfigPrompt(config, onCopySuccess); + }} + key="copyConfig" + > + + , + ]} + /> + + ); + }} + + ); + } +); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx index 410c0fcb2d140a..b068fd0779e6fe 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx @@ -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, useState } from 'react'; -import { Redirect, useRouteMatch, Switch, Route, useHistory } from 'react-router-dom'; +import React, { useMemo, useState, useCallback } from 'react'; +import { Redirect, useRouteMatch, Switch, Route, useHistory, useLocation } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedDate } from '@kbn/i18n/react'; import { @@ -21,14 +21,16 @@ import { } from '@elastic/eui'; import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; import styled from 'styled-components'; -import { AgentConfig } from '../../../types'; +import queryString from 'query-string'; +import { AgentConfig, CreateDatasourceRouteState } from '../../../types'; import { PAGE_ROUTING_PATHS } from '../../../constants'; -import { useGetOneAgentConfig, useLink, useBreadcrumbs } from '../../../hooks'; +import { useGetOneAgentConfig, useLink, useBreadcrumbs, useCore } from '../../../hooks'; import { Loading } from '../../../components'; import { WithHeaderLayout } from '../../../layouts'; import { ConfigRefreshContext, useGetAgentStatus, AgentStatusRefreshContext } from './hooks'; import { LinkedAgentCount, AgentConfigActionMenu } from '../components'; import { ConfigDatasourcesView, ConfigSettingsView } from './components'; +import { useIntraAppState } from '../../../hooks/use_intra_app_state'; const Divider = styled.div` width: 0; @@ -48,7 +50,13 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { const [redirectToAgentConfigList] = useState(false); const agentStatusRequest = useGetAgentStatus(configId); const { refreshAgentStatus } = agentStatusRequest; + const { + application: { navigateToApp }, + } = useCore(); + const routeState = useIntraAppState(); const agentStatus = agentStatusRequest.data?.results; + const openEnrollmentFlyoutOpenByDefault = + queryString.parse(useLocation().search).openEnrollmentFlyout === 'true'; const headerLeftContent = useMemo( () => ( @@ -95,6 +103,12 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { [getHref, agentConfig, configId] ); + const enrollmentCancelClickHandler = useCallback(() => { + if (routeState && routeState.onCancelNavigateTo) { + navigateToApp(routeState.onCancelNavigateTo[0], routeState.onCancelNavigateTo[1]); + } + }, [routeState, navigateToApp]); + const headerRightContent = useMemo( () => ( @@ -155,6 +169,8 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { onCopySuccess={(newAgentConfig: AgentConfig) => { history.push(getPath('configuration_details', { configId: newAgentConfig.id })); }} + enrollmentFlyoutOpenByDefault={openEnrollmentFlyoutOpenByDefault} + onCancelEnrollment={enrollmentCancelClickHandler} /> ), }, 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 2ffc400ebd48a6..daaf0df3849beb 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 @@ -133,9 +133,23 @@ export const HostList = () => { } ); - const handleDeployEndpointsClick = useNavigateToAppEventHandler('ingestManager', { - path: `#/configs/${selectedPolicyId}`, - }); + // const handleDeployEndpointsClick = useNavigateToAppEventHandler('ingestManager', { + // path: `#/configs/${selectedPolicyId}?openEnrollmentFlyout=true`, + // }); + + const handleDeployEndpointsClick = useNavigateToAppEventHandler( + 'ingestManager', + { + path: `#/configs/${selectedPolicyId}?openEnrollmentFlyout=true`, + state: { + onCancelNavigateTo: [ + 'securitySolution:management', + { path: getEndpointListPath({ name: 'endpointList' }) }, + ], + onCancelUrl: formatUrl(getEndpointListPath({ name: 'endpointList' })), + }, + } + ); const selectionOptions = useMemo(() => { return policyItems.map((item) => { diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index 6af68b1881586f..199d138d1c450a 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -10,8 +10,8 @@ export default function ({ loadTestFile }: FtrProviderContext) { this.tags('ciGroup7'); loadTestFile(require.resolve('./endpoint_list')); - // loadTestFile(require.resolve('./policy_list')); - // loadTestFile(require.resolve('./policy_details')); + loadTestFile(require.resolve('./policy_list')); + loadTestFile(require.resolve('./policy_details')); // loadTestFile(require.resolve('./alerts')); // loadTestFile(require.resolve('./resolver')); From b9129eb1d2a3e905d362c73ed1ec29189a685edb Mon Sep 17 00:00:00 2001 From: kevinlog Date: Wed, 24 Jun 2020 11:32:46 -0400 Subject: [PATCH 11/19] fix bug, add type --- .../agent_config/details_page/index.tsx | 6 +++- .../types/intra_app_route_state.ts | 12 ++++++- .../pages/endpoint_hosts/view/index.tsx | 33 +++++++++---------- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx index b068fd0779e6fe..ed54a42b278d92 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx @@ -170,7 +170,11 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { history.push(getPath('configuration_details', { configId: newAgentConfig.id })); }} enrollmentFlyoutOpenByDefault={openEnrollmentFlyoutOpenByDefault} - onCancelEnrollment={enrollmentCancelClickHandler} + onCancelEnrollment={ + routeState && routeState.onCancelNavigateTo + ? enrollmentCancelClickHandler + : undefined + } /> ), }, 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 6e85d12f718910..2739745111772e 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 @@ -21,7 +21,17 @@ export interface CreateDatasourceRouteState { onCancelUrl?: string; } +/** + * Supported routing state for the agent config details page routes with deploy agents action + */ +export interface AgentConfigDetailsDeployAgentAction { + /** On cancel, navigate to the given app */ + onCancelNavigateTo?: Parameters; +} + /** * All possible Route states. */ -export type AnyIntraAppRouteState = CreateDatasourceRouteState; +export type AnyIntraAppRouteState = + | CreateDatasourceRouteState + | AgentConfigDetailsDeployAgentAction; 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 daaf0df3849beb..a65244c57e678f 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 @@ -37,7 +37,10 @@ import { PolicyEmptyState, EndpointsEmptyState } from '../../../components/manag import { FormattedDate } from '../../../../common/components/formatted_date'; import { useEndpointPackageInfo } from '../../policy/view/ingest_hooks'; import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; -import { CreateDatasourceRouteState } from '../../../../../../ingest_manager/public'; +import { + CreateDatasourceRouteState, + AgentConfigDetailsDeployAgentAction, +} from '../../../../../../ingest_manager/public'; import { SecurityPageName } from '../../../../app/types'; import { getEndpointListPath, @@ -133,23 +136,17 @@ export const HostList = () => { } ); - // const handleDeployEndpointsClick = useNavigateToAppEventHandler('ingestManager', { - // path: `#/configs/${selectedPolicyId}?openEnrollmentFlyout=true`, - // }); - - const handleDeployEndpointsClick = useNavigateToAppEventHandler( - 'ingestManager', - { - path: `#/configs/${selectedPolicyId}?openEnrollmentFlyout=true`, - state: { - onCancelNavigateTo: [ - 'securitySolution:management', - { path: getEndpointListPath({ name: 'endpointList' }) }, - ], - onCancelUrl: formatUrl(getEndpointListPath({ name: 'endpointList' })), - }, - } - ); + const handleDeployEndpointsClick = useNavigateToAppEventHandler< + AgentConfigDetailsDeployAgentAction + >('ingestManager', { + path: `#/configs/${selectedPolicyId}?openEnrollmentFlyout=true`, + state: { + onCancelNavigateTo: [ + 'securitySolution:management', + { path: getEndpointListPath({ name: 'endpointList' }) }, + ], + }, + }); const selectionOptions = useMemo(() => { return policyItems.map((item) => { From a93a1fa8ad1da0ea5d7ee1b1f96fb848a1f28e76 Mon Sep 17 00:00:00 2001 From: kevinlog Date: Wed, 24 Jun 2020 11:56:53 -0400 Subject: [PATCH 12/19] refactor middleware --- .../pages/endpoint_hosts/store/middleware.ts | 76 +++++++++---------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index b1ed81fc4485cc..6ebc757c8cbb60 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -21,57 +21,55 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = (cor hasSelectedHost(state) !== true ) { const { page_index: pageIndex, page_size: pageSize } = uiQueryParams(state); + let hostResponse; + try { - const response = await coreStart.http.post('/api/endpoint/metadata', { + hostResponse = await coreStart.http.post('/api/endpoint/metadata', { body: JSON.stringify({ paging_properties: [{ page_index: pageIndex }, { page_size: pageSize }], }), }); - response.request_page_index = Number(pageIndex); - - if (response.hosts.length > 0) { - dispatch({ - type: 'serverReturnedHostList', - payload: response, - }); - } else { - const http = coreStart.http; - try { - const policyDataResponse: GetPolicyListResponse = await sendGetEndpointSpecificDatasources( - http, - { - query: { - perPage: 50, - page: 1, - }, - } - ); + hostResponse.request_page_index = Number(pageIndex); - dispatch({ - type: 'serverReturnedHostList', - payload: response, - }); - - dispatch({ - type: 'serverReturnedPoliciesForOnboarding', - payload: { - policyItems: policyDataResponse.items, - }, - }); - } catch (error) { - dispatch({ - type: 'serverFailedToReturnPoliciesForOnboarding', - payload: error.body ?? error, - }); - return; - } - } + dispatch({ + type: 'serverReturnedHostList', + payload: hostResponse, + }); } catch (error) { dispatch({ type: 'serverFailedToReturnHostList', payload: error, }); } + + // No hosts, so we should check to see if there are policies for onboarding + if (hostResponse && hostResponse.hosts.length === 0) { + const http = coreStart.http; + try { + const policyDataResponse: GetPolicyListResponse = await sendGetEndpointSpecificDatasources( + http, + { + query: { + perPage: 50, // Since this is an oboarding flow, we'll cap at 50 policies. + page: 1, + }, + } + ); + + dispatch({ + type: 'serverReturnedPoliciesForOnboarding', + payload: { + policyItems: policyDataResponse.items, + }, + }); + } catch (error) { + dispatch({ + type: 'serverFailedToReturnPoliciesForOnboarding', + payload: error.body ?? error, + }); + return; + } + } } if (action.type === 'userChangedUrl' && hasSelectedHost(state) === true) { // If user navigated directly to a host details page, load the host list From 3206882b70e4e039d1fae1f8b2ded07dcd90bac7 Mon Sep 17 00:00:00 2001 From: kevinlog Date: Wed, 24 Jun 2020 12:44:46 -0400 Subject: [PATCH 13/19] fix selection bug --- .../public/management/pages/endpoint_hosts/view/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 a65244c57e678f..d694be2b34ed32 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 @@ -153,7 +153,7 @@ export const HostList = () => { return { key: item.config_id, label: item.name, - checked: selectedPolicyId === item.id ? 'on' : undefined, + checked: selectedPolicyId === item.config_id ? 'on' : undefined, }; }); }, [policyItems, selectedPolicyId]); @@ -343,7 +343,7 @@ export const HostList = () => { From e6835086c7241e19c4f2c32278c8ecbf0ffba070 Mon Sep 17 00:00:00 2001 From: kevinlog Date: Thu, 25 Jun 2020 08:19:03 -0400 Subject: [PATCH 14/19] refactor route path check --- .../ingest_manager/hooks/use_intra_app_state.tsx | 10 +++++----- .../sections/agent_config/details_page/index.tsx | 6 +++--- .../ingest_manager/types/intra_app_route_state.ts | 2 +- .../management/pages/endpoint_hosts/view/index.tsx | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx index 9c23187c204b23..ca4f196c713e04 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx @@ -58,12 +58,12 @@ export function useIntraAppState(): // ingest app. side affect is that the browser back button would not work // consistently either. - if ( - location.pathname + location.search === intraAppState.forRoute && - !wasHandled.has(intraAppState) - ) { + // Compare the routes only, strip any query params + const forBaseRoute = intraAppState.forRoute.split('?')[0]; + + if (location.pathname === forBaseRoute && !wasHandled.has(intraAppState)) { wasHandled.add(intraAppState); return intraAppState.routeState as S; } - }, [intraAppState, location.pathname, location.search]); + }, [intraAppState, location.pathname]); } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx index ed54a42b278d92..a1f61a6c1c5a9d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx @@ -104,8 +104,8 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { ); const enrollmentCancelClickHandler = useCallback(() => { - if (routeState && routeState.onCancelNavigateTo) { - navigateToApp(routeState.onCancelNavigateTo[0], routeState.onCancelNavigateTo[1]); + if (routeState && routeState.onDoneNavigateTo) { + navigateToApp(routeState.onDoneNavigateTo[0], routeState.onDoneNavigateTo[1]); } }, [routeState, navigateToApp]); @@ -171,7 +171,7 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { }} enrollmentFlyoutOpenByDefault={openEnrollmentFlyoutOpenByDefault} onCancelEnrollment={ - routeState && routeState.onCancelNavigateTo + routeState && routeState.onDoneNavigateTo ? enrollmentCancelClickHandler : undefined } 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 2739745111772e..63e9d71873074a 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 @@ -26,7 +26,7 @@ export interface CreateDatasourceRouteState { */ export interface AgentConfigDetailsDeployAgentAction { /** On cancel, navigate to the given app */ - onCancelNavigateTo?: Parameters; + onDoneNavigateTo?: Parameters; } /** 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 d694be2b34ed32..53cd3991878982 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 @@ -141,7 +141,7 @@ export const HostList = () => { >('ingestManager', { path: `#/configs/${selectedPolicyId}?openEnrollmentFlyout=true`, state: { - onCancelNavigateTo: [ + onDoneNavigateTo: [ 'securitySolution:management', { path: getEndpointListPath({ name: 'endpointList' }) }, ], From 7f656f84398b4ec83720bbc20df2e0566be171bc Mon Sep 17 00:00:00 2001 From: kevinlog Date: Thu, 25 Jun 2020 08:52:05 -0400 Subject: [PATCH 15/19] make loading smoother, fix types --- .../agent_config/details_page/index.tsx | 4 ++-- .../pages/endpoint_hosts/store/action.ts | 12 +++++++++++- .../pages/endpoint_hosts/store/middleware.ts | 12 ++++++++++++ .../pages/endpoint_hosts/store/reducer.ts | 19 +++++++++++++++++-- .../pages/endpoint_hosts/store/selectors.ts | 2 ++ .../management/pages/endpoint_hosts/types.ts | 2 ++ .../pages/endpoint_hosts/view/index.test.tsx | 2 +- .../pages/endpoint_hosts/view/index.tsx | 9 +++++---- 8 files changed, 52 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx index a1f61a6c1c5a9d..58bf7492c242db 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx @@ -22,7 +22,7 @@ import { import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; import styled from 'styled-components'; import queryString from 'query-string'; -import { AgentConfig, CreateDatasourceRouteState } from '../../../types'; +import { AgentConfig, AgentConfigDetailsDeployAgentAction } from '../../../types'; import { PAGE_ROUTING_PATHS } from '../../../constants'; import { useGetOneAgentConfig, useLink, useBreadcrumbs, useCore } from '../../../hooks'; import { Loading } from '../../../components'; @@ -53,7 +53,7 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { const { application: { navigateToApp }, } = useCore(); - const routeState = useIntraAppState(); + const routeState = useIntraAppState(); const agentStatus = agentStatusRequest.data?.results; const openEnrollmentFlyoutOpenByDefault = queryString.parse(useLocation().search).openEnrollmentFlyout === 'true'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts index 88255db202ae56..e44ef9783b3549 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts @@ -61,6 +61,14 @@ interface UserSelectedEndpointPolicy { }; } +interface ServerCancelledHostListLoading { + type: 'serverCancelledHostListLoading'; +} + +interface ServerCancelledPolicyItemsLoading { + type: 'serverCancelledPolicyItemsLoading'; +} + export type HostAction = | ServerReturnedHostList | ServerFailedToReturnHostList @@ -70,4 +78,6 @@ export type HostAction = | ServerFailedToReturnHostPolicyResponse | ServerReturnedPoliciesForOnboarding | ServerFailedToReturnPoliciesForOnboarding - | UserSelectedEndpointPolicy; + | UserSelectedEndpointPolicy + | ServerCancelledHostListLoading + | ServerCancelledPolicyItemsLoading; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index 6ebc757c8cbb60..c6c252abcdbaa2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -69,9 +69,17 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = (cor }); return; } + } else { + dispatch({ + type: 'serverCancelledPolicyItemsLoading', + }); } } if (action.type === 'userChangedUrl' && hasSelectedHost(state) === true) { + dispatch({ + type: 'serverCancelledPolicyItemsLoading', + }); + // If user navigated directly to a host details page, load the host list if (listData(state).length === 0) { const { page_index: pageIndex, page_size: pageSize } = uiQueryParams(state); @@ -93,6 +101,10 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = (cor }); return; } + } else { + dispatch({ + type: 'serverCancelledHostListLoading', + }); } // call the host details api diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts index cc13d1573360d2..e3c0affa0d1bcc 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts @@ -26,8 +26,10 @@ export const initialHostListState: Immutable = { location: undefined, policyItems: [], selectedPolicyId: undefined, + policyItemsLoading: false, }; +/* eslint-disable-next-line complexity */ export const hostListReducer: ImmutableReducer = ( state = initialHostListState, action @@ -71,13 +73,13 @@ export const hostListReducer: ImmutableReducer = ( return { ...state, policyItems: action.payload.policyItems, - loading: false, + policyItemsLoading: false, }; } else if (action.type === 'serverFailedToReturnPoliciesForOnboarding') { return { ...state, error: action.payload, - loading: false, + policyItemsLoading: false, }; } else if (action.type === 'serverReturnedHostPolicyResponse') { return { @@ -98,6 +100,16 @@ export const hostListReducer: ImmutableReducer = ( selectedPolicyId: action.payload.selectedPolicyId, policyResponseLoading: false, }; + } else if (action.type === 'serverCancelledHostListLoading') { + return { + ...state, + loading: false, + }; + } else if (action.type === 'serverCancelledPolicyItemsLoading') { + return { + ...state, + policyItemsLoading: false, + }; } else if (action.type === 'userChangedUrl') { const newState: Immutable = { ...state, @@ -115,6 +127,7 @@ export const hostListReducer: ImmutableReducer = ( ...state, location: action.payload, loading: true, + policyItemsLoading: true, error: undefined, detailsError: undefined, }; @@ -152,6 +165,8 @@ export const hostListReducer: ImmutableReducer = ( error: undefined, detailsError: undefined, policyResponseError: undefined, + loading: true, + policyItemsLoading: true, }; } return state; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts index 61d8f22cd47942..d083ab2930c847 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts @@ -39,6 +39,8 @@ export const detailsError = (state: Immutable) => state.detailsError; export const policyItems = (state: Immutable) => state.policyItems; +export const policyItemsLoading = (state: Immutable) => state.policyItemsLoading; + export const selectedPolicyId = (state: Immutable) => state.selectedPolicyId; /** diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts index 32d34669689465..f8d1e9a639798f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts @@ -43,6 +43,8 @@ export interface HostState { location?: Immutable; /** policies */ policyItems: PolicyData[]; + /** policies are loading */ + policyItemsLoading: boolean; /** the selected policy ID in the onboarding flow */ selectedPolicyId?: string; } 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 9a014436c5d525..8461de54a011ec 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 @@ -83,7 +83,7 @@ describe('when on the hosts page', () => { it('should show policy selection', async () => { const renderResult = render(); - const onboardingPolicySelect = await renderResult.findAllByTestId('onboardingPolicySelect'); + const onboardingPolicySelect = await renderResult.findByTestId('onboardingPolicySelect'); expect(onboardingPolicySelect).not.toBeNull(); }); }); 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 53cd3991878982..52348d51996f08 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 @@ -86,6 +86,7 @@ export const HostList = () => { hasSelectedHost, policyItems, selectedPolicyId, + policyItemsLoading, } = useHostSelector(selector); const { formatUrl, search } = useFormatUrl(SecurityPageName.management); @@ -326,19 +327,18 @@ export const HostList = () => { }, [formatUrl, queryParams, search]); const renderTableOrEmptyState = useMemo(() => { - if (listData && listData.length > 0) { + if (!loading && listData && listData.length > 0) { return ( ); - } else if (policyItems && policyItems.length > 0) { + } else if (!policyItemsLoading && policyItems && policyItems.length > 0) { return ( { } else { return ( @@ -371,6 +371,7 @@ export const HostList = () => { handleSelectableOnChange, selectedPolicyId, selectionOptions, + policyItemsLoading, ]); return ( From 310b8efffee38b90dc8ff40ef47b1430bdb31152 Mon Sep 17 00:00:00 2001 From: kevinlog Date: Thu, 25 Jun 2020 10:22:57 -0400 Subject: [PATCH 16/19] address comments, fix tests --- .../ingest_manager/hooks/use_intra_app_state.tsx | 7 ++----- .../ingest_manager/types/intra_app_route_state.ts | 2 +- .../management/components/management_empty_state.tsx | 8 ++++++-- .../management/pages/endpoint_hosts/store/reducer.ts | 3 +-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx index ca4f196c713e04..3d284c7352af2a 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx @@ -28,7 +28,7 @@ export const IntraAppStateProvider = memo<{ }>(({ kibanaScopedHistory, children }) => { const internalAppToAppState = useMemo(() => { return { - forRoute: kibanaScopedHistory.location.hash.substr(1), + forRoute: kibanaScopedHistory.location.hash.substr(1).split('?')[0], routeState: kibanaScopedHistory.location.state as AnyIntraAppRouteState, }; }, [kibanaScopedHistory.location.hash, kibanaScopedHistory.location.state]); @@ -58,10 +58,7 @@ export function useIntraAppState(): // ingest app. side affect is that the browser back button would not work // consistently either. - // Compare the routes only, strip any query params - const forBaseRoute = intraAppState.forRoute.split('?')[0]; - - if (location.pathname === forBaseRoute && !wasHandled.has(intraAppState)) { + if (location.pathname === intraAppState.forRoute && !wasHandled.has(intraAppState)) { wasHandled.add(intraAppState); return intraAppState.routeState as S; } 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 63e9d71873074a..b2948686ff6e57 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 @@ -25,7 +25,7 @@ export interface CreateDatasourceRouteState { * Supported routing state for the agent config details page routes with deploy agents action */ export interface AgentConfigDetailsDeployAgentAction { - /** On cancel, navigate to the given app */ + /** On done, navigate to the given app */ onDoneNavigateTo?: Parameters; } 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 0d1ff45615097c..11449b3dfbad32 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 @@ -14,9 +14,9 @@ import { EuiSteps, EuiTitle, EuiSelectable, - EuiProgress, EuiSelectableMessage, EuiSelectableProps, + EuiLoadingSpinner, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -219,7 +219,11 @@ const ManagementEmptyState = React.memo<{ return (
{loading ? ( - + + + + + ) : ( <> diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts index e3c0affa0d1bcc..c451383858e118 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts @@ -155,6 +155,7 @@ export const hostListReducer: ImmutableReducer = ( error: undefined, detailsError: undefined, policyResponseError: undefined, + policyItemsLoading: true, }; } } @@ -165,8 +166,6 @@ export const hostListReducer: ImmutableReducer = ( error: undefined, detailsError: undefined, policyResponseError: undefined, - loading: true, - policyItemsLoading: true, }; } return state; From f364e7422a955abdbd0875953b441de76c358334 Mon Sep 17 00:00:00 2001 From: kevinlog Date: Thu, 25 Jun 2020 17:14:24 -0400 Subject: [PATCH 17/19] address comments --- .../ingest_manager/hooks/use_intra_app_state.tsx | 9 ++++----- .../sections/agent_config/details_page/index.tsx | 5 ++--- .../ingest_manager/types/intra_app_route_state.ts | 2 ++ .../management/components/management_empty_state.tsx | 2 +- .../pages/endpoint_hosts/store/index.test.ts | 3 +++ .../management/pages/endpoint_hosts/store/reducer.ts | 6 ++++++ .../public/management/pages/endpoint_hosts/types.ts | 4 +++- .../management/pages/endpoint_hosts/view/index.tsx | 5 ++++- .../management/pages/policy/view/policy_list.tsx | 10 ++++------ 9 files changed, 29 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx index f6a8656cad8246..0bae8e935f3467 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx @@ -27,13 +27,12 @@ export const IntraAppStateProvider = memo<{ children: React.ReactNode; }>(({ kibanaScopedHistory, children }) => { const internalAppToAppState = useMemo(() => { + const intraAppState = kibanaScopedHistory.location.state as AnyIntraAppRouteState; return { - forRoute: kibanaScopedHistory.location.state.baseRoute - ? kibanaScopedHistory.location.state.baseRoute.substr(1) - : kibanaScopedHistory.location.hash.substr(1), - routeState: kibanaScopedHistory.location.state as AnyIntraAppRouteState, + forRoute: intraAppState.baseRoute ? intraAppState.baseRoute : '', + routeState: intraAppState, }; - }, [kibanaScopedHistory.location.hash, kibanaScopedHistory.location.state]); + }, [kibanaScopedHistory.location.state]); return ( {children} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx index 58bf7492c242db..eaa161d57bbe41 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx @@ -21,7 +21,6 @@ import { } from '@elastic/eui'; import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; import styled from 'styled-components'; -import queryString from 'query-string'; import { AgentConfig, AgentConfigDetailsDeployAgentAction } from '../../../types'; import { PAGE_ROUTING_PATHS } from '../../../constants'; import { useGetOneAgentConfig, useLink, useBreadcrumbs, useCore } from '../../../hooks'; @@ -55,8 +54,8 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { } = useCore(); const routeState = useIntraAppState(); const agentStatus = agentStatusRequest.data?.results; - const openEnrollmentFlyoutOpenByDefault = - queryString.parse(useLocation().search).openEnrollmentFlyout === 'true'; + const queryParams = new URLSearchParams(useLocation().search); + const openEnrollmentFlyoutOpenByDefault = queryParams.get('openEnrollmentFlyout') === 'true'; const headerLeftContent = useMemo( () => ( 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 ac92ddb60e547a..36c10ffc1efcba 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 @@ -19,6 +19,8 @@ export interface CreateDatasourceRouteState { onCancelNavigateTo?: Parameters; /** Url to be used on cancel links */ onCancelUrl?: string; + /** The base route */ + baseRoute?: string; } /** 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 11449b3dfbad32..5dd47d4e880287 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 @@ -33,7 +33,7 @@ interface ManagementStep { const PolicyEmptyState = React.memo<{ loading: boolean; onActionClick: (event: MouseEvent) => void; - actionDisabled: boolean; + actionDisabled?: boolean; }>(({ loading, onActionClick, actionDisabled }) => { const policySteps = useMemo( () => [ diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts index 47a5bfafe6c7fa..f2c205661b32ca 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts @@ -47,6 +47,9 @@ describe('HostList store concerns', () => { policyResponseError: undefined, location: undefined, policyItems: [], + selectedPolicyId: undefined, + policyItemsLoading: false, + endpointPackageInfo: undefined, }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts index c451383858e118..993267cf1a7041 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts @@ -27,6 +27,7 @@ export const initialHostListState: Immutable = { policyItems: [], selectedPolicyId: undefined, policyItemsLoading: false, + endpointPackageInfo: undefined, }; /* eslint-disable-next-line complexity */ @@ -110,6 +111,11 @@ export const hostListReducer: ImmutableReducer = ( ...state, policyItemsLoading: false, }; + } else if (action.type === 'serverReturnedEndpointPackageInfo') { + return { + ...state, + endpointPackageInfo: action.payload, + }; } else if (action.type === 'userChangedUrl') { const newState: Immutable = { ...state, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts index 4775033280e63c..a5f37a0b49e8f7 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts @@ -13,6 +13,7 @@ import { PolicyData, } from '../../../../common/endpoint/types'; import { ServerApiError } from '../../../common/types'; +import { GetPackagesResponse } from '../../../../../ingest_manager/common'; export interface HostState { /** list of host **/ @@ -47,7 +48,8 @@ export interface HostState { policyItemsLoading: boolean; /** the selected policy ID in the onboarding flow */ selectedPolicyId?: string; - endpointPackageInfo?: sring; + /** Endpoint package info */ + endpointPackageInfo?: GetPackagesResponse['response'][0]; } /** 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 c7d055173c7030..454c20f3364a01 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 @@ -134,6 +134,9 @@ export const HostList = () => { 'securitySolution:management', { path: getEndpointListPath({ name: 'endpointList' }) }, ], + baseRoute: `/integrations${ + endpointPackageVersion ? `/endpoint-${endpointPackageVersion}/add-datasource` : '' + }`, }, } ); @@ -147,7 +150,7 @@ export const HostList = () => { 'securitySolution:management', { path: getEndpointListPath({ name: 'endpointList' }) }, ], - baseRoute: `#/configs/${selectedPolicyId}`, + baseRoute: `/configs/${selectedPolicyId}`, }, }); 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 a0ae367ac773a2..74a29eefd16af6 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 @@ -156,6 +156,9 @@ export const PolicyList = React.memo(() => { onCancelNavigateTo: ['securitySolution:management', { path: getPoliciesPath() }], onCancelUrl: formatUrl(getPoliciesPath()), onSaveNavigateTo: ['securitySolution:management', { path: getPoliciesPath() }], + baseRoute: `/integrations${ + endpointPackageVersion ? `/endpoint-${endpointPackageVersion}/add-datasource` : '' + }`, }, } ); @@ -431,12 +434,7 @@ export const PolicyList = React.memo(() => { hasActions={false} /> ) : ( - + )} ); From 8399f0e935de042358d62c078ea7befee3dcc25a Mon Sep 17 00:00:00 2001 From: kevinlog Date: Thu, 25 Jun 2020 19:51:01 -0400 Subject: [PATCH 18/19] fix test --- .../applications/ingest_manager/hooks/use_intra_app_state.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx index 0bae8e935f3467..88ae03fabaf015 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx @@ -29,7 +29,7 @@ export const IntraAppStateProvider = memo<{ const internalAppToAppState = useMemo(() => { const intraAppState = kibanaScopedHistory.location.state as AnyIntraAppRouteState; return { - forRoute: intraAppState.baseRoute ? intraAppState.baseRoute : '', + forRoute: intraAppState && intraAppState.baseRoute ? intraAppState.baseRoute : '', routeState: intraAppState, }; }, [kibanaScopedHistory.location.state]); From 18dd9548fe4f9507ca41c3e1ae9c347b640590f8 Mon Sep 17 00:00:00 2001 From: kevinlog Date: Fri, 26 Jun 2020 13:55:06 -0400 Subject: [PATCH 19/19] use URL, take out baseRoute --- .../ingest_manager/hooks/use_intra_app_state.tsx | 8 ++++---- .../ingest_manager/types/intra_app_route_state.ts | 4 ---- .../public/management/pages/endpoint_hosts/view/index.tsx | 4 ---- .../public/management/pages/policy/view/policy_list.tsx | 3 --- 4 files changed, 4 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx index 88ae03fabaf015..7bccd3a4b1f584 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_intra_app_state.tsx @@ -27,12 +27,12 @@ export const IntraAppStateProvider = memo<{ children: React.ReactNode; }>(({ kibanaScopedHistory, children }) => { const internalAppToAppState = useMemo(() => { - const intraAppState = kibanaScopedHistory.location.state as AnyIntraAppRouteState; return { - forRoute: intraAppState && intraAppState.baseRoute ? intraAppState.baseRoute : '', - routeState: intraAppState, + forRoute: new URL(`${kibanaScopedHistory.location.hash.substr(1)}`, 'http://localhost') + .pathname, + routeState: kibanaScopedHistory.location.state as AnyIntraAppRouteState, }; - }, [kibanaScopedHistory.location.state]); + }, [kibanaScopedHistory.location.state, kibanaScopedHistory.location.hash]); return ( {children} 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 36c10ffc1efcba..b2948686ff6e57 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 @@ -19,8 +19,6 @@ export interface CreateDatasourceRouteState { onCancelNavigateTo?: Parameters; /** Url to be used on cancel links */ onCancelUrl?: string; - /** The base route */ - baseRoute?: string; } /** @@ -29,8 +27,6 @@ export interface CreateDatasourceRouteState { export interface AgentConfigDetailsDeployAgentAction { /** On done, navigate to the given app */ onDoneNavigateTo?: Parameters; - /** The base route */ - baseRoute?: string; } /** 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 454c20f3364a01..3601b8db5ee592 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 @@ -134,9 +134,6 @@ export const HostList = () => { 'securitySolution:management', { path: getEndpointListPath({ name: 'endpointList' }) }, ], - baseRoute: `/integrations${ - endpointPackageVersion ? `/endpoint-${endpointPackageVersion}/add-datasource` : '' - }`, }, } ); @@ -150,7 +147,6 @@ export const HostList = () => { 'securitySolution:management', { path: getEndpointListPath({ name: 'endpointList' }) }, ], - baseRoute: `/configs/${selectedPolicyId}`, }, }); 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 74a29eefd16af6..8a760334c53af4 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 @@ -156,9 +156,6 @@ export const PolicyList = React.memo(() => { onCancelNavigateTo: ['securitySolution:management', { path: getPoliciesPath() }], onCancelUrl: formatUrl(getPoliciesPath()), onSaveNavigateTo: ['securitySolution:management', { path: getPoliciesPath() }], - baseRoute: `/integrations${ - endpointPackageVersion ? `/endpoint-${endpointPackageVersion}/add-datasource` : '' - }`, }, } );