From 7b812276765fda80424bc0b7dd155a943393cdf4 Mon Sep 17 00:00:00 2001 From: Candace Park Date: Mon, 24 Aug 2020 15:51:54 -0400 Subject: [PATCH 01/10] all three linkies --- .../scripts/dev_agent/script.ts | 1 + .../pages/endpoint_hosts/view/index.tsx | 105 +++++++++++++++++- 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts b/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts index 65375a076e9a48..f702da304e46b7 100644 --- a/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts +++ b/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts @@ -104,6 +104,7 @@ async function enroll(kibanaURL: string, apiKey: string, log: ToolingLog): Promi ip: '127.0.0.1', system: `${os.type()} ${os.release()}`, memory: os.totalmem(), + elastic: { agent: { version: '8.0.0' } }, }, user_provided: { dev_agent_version: '0.0.1', 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 8d08ac4e59a870..5b812eee6c24ef 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 @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo, useCallback, memo } from 'react'; +import React, { useMemo, useCallback, memo, useState } from 'react'; import { EuiHorizontalRule, EuiBasicTable, @@ -16,6 +16,11 @@ import { EuiSelectableProps, EuiSuperDatePicker, EuiSpacer, + EuiPopover, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiContextMenuPanelProps, + EuiButtonIcon, } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; @@ -47,6 +52,8 @@ import { useFormatUrl } from '../../../../common/components/link_to'; import { EndpointAction } from '../store/action'; import { EndpointPolicyLink } from './components/endpoint_policy_link'; import { AdministrationListPage } from '../../../components/administration_list_page'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { LinkToApp } from '../../../../common/components/endpoint/link_to_app'; const EndpointListNavLink = memo<{ name: string; @@ -70,9 +77,39 @@ const EndpointListNavLink = memo<{ }); EndpointListNavLink.displayName = 'EndpointListNavLink'; +const TableRowActions = memo<{ + items: EuiContextMenuPanelProps['items']; +}>(({ items }) => { + const [isOpen, setIsOpen] = useState(false); + const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); + const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); + + return ( + + } + isOpen={isOpen} + closePopover={handleCloseMenu} + > + + + ); +}); +TableRowActions.displayName = 'EndpointTableRowActions'; + const selector = (createStructuredSelector as CreateStructuredSelector)(selectors); export const EndpointList = () => { const history = useHistory(); + const { services } = useKibana(); const { listData, pageIndex, @@ -346,8 +383,72 @@ export const EndpointList = () => { ); }, }, + { + field: '', + name: i18n.translate('xpack.securitySolution.endpoint.list.actions', { + defaultMessage: 'Actions', + }), + actions: [ + { + // eslint-disable-next-line react/display-name + render: (item: HostInfo) => { + return ( + + + + + , + + + + + , + + + + + , + ]} + /> + ); + }, + }, + ], + }, ]; - }, [formatUrl, queryParams, search]); + }, [formatUrl, queryParams, search, services.application]); const renderTableOrEmptyState = useMemo(() => { if (endpointsExist) { From 0001d299fa2b679c9c5c92872e153e985c5481b7 Mon Sep 17 00:00:00 2001 From: Candace Park Date: Wed, 26 Aug 2020 22:55:08 -0400 Subject: [PATCH 02/10] wip grabbing agent policy id --- .../pages/endpoint_hosts/store/action.ts | 5 +++ .../pages/endpoint_hosts/store/middleware.ts | 39 +++++++++++++------ .../management/pages/endpoint_hosts/types.ts | 7 +++- .../view/components/endpoint_policy_link.tsx | 2 +- 4 files changed, 39 insertions(+), 14 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 5f36af2a2d8eab..cd98edd582d14b 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 @@ -81,6 +81,11 @@ interface ServerReturnedEndpointNonExistingPolicies { payload: EndpointState['nonExistingPolicies']; } +interface ServerReturnedEndpointAgentPolicies { + type: 'serverReturnedEndpointAgentPolicies'; + payload: EndpointState['nonExistingPolicies']; +} + interface ServerReturnedEndpointExistValue { type: 'serverReturnedEndpointExistValue'; payload: boolean; 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 2650aa4865228e..635cbf7d2f0788 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 @@ -71,10 +71,16 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory !currentNonExistingPolicies[host.metadata.Endpoint.policy.applied.id]) + .filter( + (host) => + !currentNonExistingPolicies.packagePolicy[host.metadata.Endpoint.policy.applied.id] + ) .map((host) => host.metadata.Endpoint.policy.applied.id) ) ); @@ -279,22 +288,28 @@ const getNonExistingPoliciesForEndpointsList = async ( )})`, }, }) - ).items.reduce((list, agentPolicy) => { - (agentPolicy.package_policies as string[]).forEach((packagePolicy) => { - list[packagePolicy as string] = true; - }); - return list; - }, {}); + ).items.reduce( + (list, agentPolicy) => { + (agentPolicy.package_policies as string[]).forEach((packagePolicy) => { + list.packagePolicy[packagePolicy as string] = true; + list.agentPolicy[packagePolicy as string] = agentPolicy.id; + }); + return list; + }, + { packagePolicy: {}, agentPolicy: {} } + ); + // packagePolicy contains non-existing packagePolicy ids whereas agentPolicy contains existing agentPolicy ids const nonExisting = policyIdsToCheck.reduce( (list, policyId) => { - if (policiesFound[policyId]) { + if (policiesFound.packagePolicy[policyId]) { + list.agentPolicy[policyId] = policiesFound.agentPolicy[policyId]; return list; } - list[policyId] = true; + list.packagePolicy[policyId] = true; return list; }, - {} + { packagePolicy: {}, agentPolicy: {} } ); if (Object.keys(nonExisting).length === 0) { 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 5a6a1af7bd7e84..7287287cb47b26 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 @@ -51,7 +51,7 @@ export interface EndpointState { /** Endpoint package info */ endpointPackageInfo?: GetPackagesResponse['response'][0]; /** tracks the list of policies IDs used in Host metadata that may no longer exist */ - nonExistingPolicies: Record; + nonExistingPolicies: PolicyIds; /** Tracks whether hosts exist and helps control if onboarding should be visible */ endpointsExist: boolean; /** Is auto-refresh enabled */ @@ -60,6 +60,11 @@ export interface EndpointState { autoRefreshInterval: number; } +interface PolicyIds { + packagePolicy: Record; + agentPolicy: Record; +} + /** * Query params on the host page parsed from the URL */ diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_policy_link.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_policy_link.tsx index 2b6132aca41089..311ff97a9c7b57 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_policy_link.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_policy_link.tsx @@ -34,7 +34,7 @@ export const EndpointPolicyLink = memo< }, [formatUrl, policyId]); const clickHandler = useNavigateByRouterEventHandler(toRoutePath, onClick); - if (missingPolicies[policyId]) { + if (missingPolicies.packagePolicy[policyId]) { return ( {children} From f1ef86a86217aad66f4b404db0d27ce066afbefb Mon Sep 17 00:00:00 2001 From: Candace Park Date: Wed, 2 Sep 2020 13:49:11 -0400 Subject: [PATCH 03/10] all links working --- .../pages/endpoint_hosts/store/action.ts | 3 +- .../pages/endpoint_hosts/store/middleware.ts | 64 +++++++++++-------- .../pages/endpoint_hosts/store/reducer.ts | 9 +++ .../pages/endpoint_hosts/store/selectors.ts | 7 ++ .../management/pages/endpoint_hosts/types.ts | 9 ++- .../view/components/endpoint_policy_link.tsx | 2 +- .../pages/endpoint_hosts/view/index.tsx | 15 +++-- 7 files changed, 73 insertions(+), 36 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 cd98edd582d14b..dfe8817121ecea 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 @@ -83,7 +83,7 @@ interface ServerReturnedEndpointNonExistingPolicies { interface ServerReturnedEndpointAgentPolicies { type: 'serverReturnedEndpointAgentPolicies'; - payload: EndpointState['nonExistingPolicies']; + payload: EndpointState['agentPolicies']; } interface ServerReturnedEndpointExistValue { @@ -119,4 +119,5 @@ export type EndpointAction = | ServerReturnedEndpointPackageInfo | AppRequestedEndpointList | ServerReturnedEndpointNonExistingPolicies + | ServerReturnedEndpointAgentPolicies | UserUpdatedEndpointListRefreshOptions; 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 635cbf7d2f0788..dd65aea132d09b 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 @@ -16,7 +16,7 @@ import { endpointPackageInfo, nonExistingPolicies, } from './selectors'; -import { EndpointState } from '../types'; +import { EndpointState, PolicyIds } from '../types'; import { sendGetEndpointSpecificPackagePolicies, sendGetEndpointSecurityPackage, @@ -66,21 +66,21 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory => { +): Promise => { if (hosts.length === 0) { return; } @@ -264,10 +276,7 @@ const getNonExistingPoliciesForEndpointsList = async ( const policyIdsToCheck = Array.from( new Set( hosts - .filter( - (host) => - !currentNonExistingPolicies.packagePolicy[host.metadata.Endpoint.policy.applied.id] - ) + .filter((host) => !currentNonExistingPolicies[host.metadata.Endpoint.policy.applied.id]) .map((host) => host.metadata.Endpoint.policy.applied.id) ) ); @@ -288,7 +297,7 @@ const getNonExistingPoliciesForEndpointsList = async ( )})`, }, }) - ).items.reduce( + ).items.reduce( (list, agentPolicy) => { (agentPolicy.package_policies as string[]).forEach((packagePolicy) => { list.packagePolicy[packagePolicy as string] = true; @@ -300,23 +309,26 @@ const getNonExistingPoliciesForEndpointsList = async ( ); // packagePolicy contains non-existing packagePolicy ids whereas agentPolicy contains existing agentPolicy ids - const nonExisting = policyIdsToCheck.reduce( - (list, policyId) => { - if (policiesFound.packagePolicy[policyId]) { - list.agentPolicy[policyId] = policiesFound.agentPolicy[policyId]; + const nonExistingPackagePoliciesAndExistingAgentPolicies = policyIdsToCheck.reduce( + (list, policyId: string) => { + if (policiesFound.packagePolicy[policyId as string]) { + list.agentPolicy[policyId as string] = policiesFound.agentPolicy[policyId]; return list; } - list.packagePolicy[policyId] = true; + list.packagePolicy[policyId as string] = true; return list; }, { packagePolicy: {}, agentPolicy: {} } ); - if (Object.keys(nonExisting).length === 0) { + if ( + Object.keys(nonExistingPackagePoliciesAndExistingAgentPolicies.packagePolicy).length === 0 && + Object.keys(nonExistingPackagePoliciesAndExistingAgentPolicies.agentPolicy).length === 0 + ) { return; } - return nonExisting; + return nonExistingPackagePoliciesAndExistingAgentPolicies; }; const doEndpointsExist = async (http: HttpStart): Promise => { 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 060321fa404013..cbdd1cab4ee88e 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 @@ -30,6 +30,7 @@ export const initialEndpointListState: Immutable = { policyItemsLoading: false, endpointPackageInfo: undefined, nonExistingPolicies: {}, + agentPolicies: {}, endpointsExist: true, isAutoRefreshEnabled: true, autoRefreshInterval: DEFAULT_POLL_INTERVAL, @@ -70,6 +71,14 @@ export const endpointListReducer: ImmutableReducer = ( ...action.payload, }, }; + } else if (action.type === 'serverReturnedEndpointAgentPolicies') { + return { + ...state, + agentPolicies: { + ...state.agentPolicies, + ...action.payload, + }, + }; } else if (action.type === 'serverReturnedEndpointDetails') { 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 68ba71b7bbc94a..9a7ebe858cea2e 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 @@ -208,6 +208,13 @@ export const nonExistingPolicies: ( state: Immutable ) => Immutable = (state) => state.nonExistingPolicies; +/** + * returns the list of known existing agent policies + */ +export const agentPolicies: ( + state: Immutable +) => Immutable = (state) => state.agentPolicies; + /** * Return boolean that indicates whether endpoints exist * @param 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 7287287cb47b26..fa6517aee77da7 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 @@ -51,7 +51,9 @@ export interface EndpointState { /** Endpoint package info */ endpointPackageInfo?: GetPackagesResponse['response'][0]; /** tracks the list of policies IDs used in Host metadata that may no longer exist */ - nonExistingPolicies: PolicyIds; + nonExistingPolicies: Record; + /** List of Agent Policies Ids*/ + agentPolicies: Record; /** Tracks whether hosts exist and helps control if onboarding should be visible */ endpointsExist: boolean; /** Is auto-refresh enabled */ @@ -60,7 +62,10 @@ export interface EndpointState { autoRefreshInterval: number; } -interface PolicyIds { +/** + * Policy Ids returned from an API call to ingest + */ +export interface PolicyIds { packagePolicy: Record; agentPolicy: Record; } diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_policy_link.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_policy_link.tsx index 311ff97a9c7b57..2b6132aca41089 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_policy_link.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_policy_link.tsx @@ -34,7 +34,7 @@ export const EndpointPolicyLink = memo< }, [formatUrl, policyId]); const clickHandler = useNavigateByRouterEventHandler(toRoutePath, onClick); - if (missingPolicies.packagePolicy[policyId]) { + if (missingPolicies[policyId]) { return ( {children} 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 5b812eee6c24ef..0a503e1d49f501 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 @@ -124,6 +124,7 @@ export const EndpointList = () => { policyItemsLoading, endpointPackageVersion, endpointsExist, + agentPolicies, autoRefreshInterval, isAutoRefreshEnabled, } = useEndpointSelector(selector); @@ -400,7 +401,7 @@ export const EndpointList = () => { data-test-subj="hostLink" appId="securitySolution" appPath={`hosts/${item.metadata.host.hostname}`} - href={`${services.application.getUrlForApp('securitySolution')}/hosts/${ + href={`${services?.application?.getUrlForApp('securitySolution')}/hosts/${ item.metadata.host.hostname }`} > @@ -414,9 +415,11 @@ export const EndpointList = () => { { data-test-subj="agentDetailsLink" appId="ingestManager" appPath={`#/fleet/agents/${item.metadata.elastic.agent.id}`} - href={`${services.application.getUrlForApp('ingestManager')}#/policies/${ + href={`${services?.application?.getUrlForApp('ingestManager')}#/policies/${ item.metadata.elastic.agent.id }`} > @@ -448,7 +451,7 @@ export const EndpointList = () => { ], }, ]; - }, [formatUrl, queryParams, search, services.application]); + }, [formatUrl, queryParams, search, agentPolicies, services?.application?.getUrlForApp]); const renderTableOrEmptyState = useMemo(() => { if (endpointsExist) { From 7e4e1714c0243a44b6a992414f115bb8ba3519ba Mon Sep 17 00:00:00 2001 From: Candace Park Date: Tue, 8 Sep 2020 10:31:51 -0400 Subject: [PATCH 04/10] comments, use package version, wip tests --- .../scripts/dev_agent/script.ts | 3 +- .../pages/endpoint_hosts/store/index.test.ts | 1 + .../store/mock_endpoint_result_list.ts | 2 +- .../management/pages/endpoint_hosts/types.ts | 7 ++- .../pages/endpoint_hosts/view/index.test.tsx | 60 +++++++++++++++++++ .../pages/endpoint_hosts/view/index.tsx | 3 +- .../apps/endpoint/endpoint_list.ts | 4 ++ 7 files changed, 74 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts b/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts index f702da304e46b7..3028fe397771e4 100644 --- a/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts +++ b/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts @@ -14,6 +14,7 @@ import { PostAgentEnrollRequest, PostAgentEnrollResponse, } from '../../common/types'; +import { version } from '../../package.json'; const CHECKIN_INTERVAL = 3000; // 3 seconds @@ -104,7 +105,7 @@ async function enroll(kibanaURL: string, apiKey: string, log: ToolingLog): Promi ip: '127.0.0.1', system: `${os.type()} ${os.release()}`, memory: os.totalmem(), - elastic: { agent: { version: '8.0.0' } }, + elastic: { agent: { version } }, }, user_provided: { dev_agent_version: '0.0.1', 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 3a095644b3b41a..64169f3ac4e2ce 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 @@ -52,6 +52,7 @@ describe('EndpointList store concerns', () => { policyItemsLoading: false, endpointPackageInfo: undefined, nonExistingPolicies: {}, + agentPolicies: {}, endpointsExist: true, isAutoRefreshEnabled: true, autoRefreshInterval: DEFAULT_POLL_INTERVAL, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts index cfde474c6290dc..cb91895c1fb18e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts @@ -43,7 +43,7 @@ export const mockEndpointResultList: (options?: { // total - numberToSkip is the count of non-skipped ones, but return no more than a pageSize, and no less than 0 const actualCountToReturn = Math.max(Math.min(total - numberToSkip, requestPageSize), 0); - const hosts = []; + const hosts: HostInfo[] = []; for (let index = 0; index < actualCountToReturn; index++) { hosts.push({ metadata: generator.generateHostMetadata(), 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 fa6517aee77da7..dac2d435f2a4b7 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 @@ -51,9 +51,9 @@ export interface EndpointState { /** Endpoint package info */ endpointPackageInfo?: GetPackagesResponse['response'][0]; /** tracks the list of policies IDs used in Host metadata that may no longer exist */ - nonExistingPolicies: Record; + nonExistingPolicies: PolicyIds['packagePolicy']; /** List of Agent Policies Ids*/ - agentPolicies: Record; + agentPolicies: PolicyIds['agentPolicy']; /** Tracks whether hosts exist and helps control if onboarding should be visible */ endpointsExist: boolean; /** Is auto-refresh enabled */ @@ -63,7 +63,8 @@ export interface EndpointState { } /** - * Policy Ids returned from an API call to ingest + * packagePolicy contains a list of Package Policy IDs (received via Endpoint metadata policy response) mapped to a boolean whether they exist or not. + * agentPolicy contains a list of existing Package Policy Ids mapped to an associated Fleet parent Agent Config. */ export interface PolicyIds { packagePolicy: Record; 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 6e373679304668..defdbbc9c37469 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 @@ -6,6 +6,7 @@ import React from 'react'; import * as reactTestingLibrary from '@testing-library/react'; +import { prettyDOM, screen } from '@testing-library/dom'; import { EndpointList } from './index'; import '../../../../common/mock/match_media.ts'; @@ -669,4 +670,63 @@ describe('when on the list page', () => { }); }); }); + + describe('when the more actions column is opened', () => { + let hostInfo: HostInfo; + let agentId: string; + let renderAndWaitForData: () => Promise>; + + const mockEndpointListApi = () => { + const { hosts } = mockEndpointResultList(); + hostInfo = { + host_status: hosts[0].host_status, + metadata: hosts[0].metadata, + }; + const packagePolicy = docGenerator.generatePolicyPackagePolicy(); + packagePolicy.id = hosts[0].metadata.Endpoint.policy.applied.id; + agentId = hosts[0].metadata.elastic.agent.id; + + setEndpointListApiMockImplementation(coreStart.http, { + endpointsResults: [hostInfo], + endpointPackagePolicies: [packagePolicy], + }); + }; + + beforeEach(() => { + mockEndpointListApi(); + + reactTestingLibrary.act(() => { + history.push('/endpoints'); + }); + + renderAndWaitForData = async () => { + const renderResult = render(); + await middlewareSpy.waitForAction('serverReturnedEndpointList'); + await middlewareSpy.waitForAction('serverReturnedEndpointAgentPolicies'); + return renderResult; + }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('navigates to the Security Solution Host Details page', async () => { + const renderResult = await renderAndWaitForData(); + // open the endpoint actions menu + const endpointActionsButton = await renderResult.findByTestId('endpointTableRowActions'); + reactTestingLibrary.act(() => { + reactTestingLibrary.fireEvent.click(endpointActionsButton); + }); + const hostLink = await renderResult.findByTestId('hostLink'); + const userChangedUrlChecker = middlewareSpy.waitForAction('userChangedUrl'); + reactTestingLibrary.act(() => { + reactTestingLibrary.fireEvent.click(hostLink); + }); + const changedUrlAction = await userChangedUrlChecker; + expect(changedUrlAction.payload.pathname).toEqual(`hosts/${hostInfo.metadata.host.hostname}`); + }); + it.skip('navigates to the Ingest Agent Policy page', () => {}); + it.skip('navigates to the Ingest Agent Details page', () => {}); + }); }); 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 0a503e1d49f501..0a2203d8da467d 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 @@ const TableRowActions = memo<{ return ( { agentPolicies[item.metadata.Endpoint.policy.applied.id] }`} href={`${services?.application?.getUrlForApp('ingestManager')}#/policies/${ - agentPolicies[item.metadata.elastic.agent.id] + agentPolicies[item.metadata.Endpoint.policy.applied.id] }`} > { 'IP Address', 'Version', 'Last Active', + 'Actions', ], [ 'cadmann-4.example.com', @@ -32,6 +33,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { '10.192.213.130, 10.70.28.129', '6.6.1', 'Jan 24, 2020 @ 16:06:09.541', + '', ], [ 'thurlow-9.example.com', @@ -42,6 +44,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { '10.46.229.234', '6.0.0', 'Jan 24, 2020 @ 16:06:09.541', + '', ], [ 'rezzani-7.example.com', @@ -52,6 +55,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { '10.101.149.26, 2606:a000:ffc0:39:11ef:37b9:3371:578c', '6.8.0', 'Jan 24, 2020 @ 16:06:09.541', + '', ], ]; From 7030339fd23049782c168fdb7be76fc831f11882 Mon Sep 17 00:00:00 2001 From: Candace Park Date: Tue, 8 Sep 2020 16:57:50 -0400 Subject: [PATCH 05/10] tests are working --- .../scripts/dev_agent/script.ts | 5 +- .../store/mock_endpoint_result_list.ts | 5 +- .../pages/endpoint_hosts/view/index.test.tsx | 49 ++++++++++++++++--- .../pages/endpoint_hosts/view/index.tsx | 8 +-- 4 files changed, 53 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts b/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts index 3028fe397771e4..47108508ec68a0 100644 --- a/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts +++ b/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts @@ -14,8 +14,11 @@ import { PostAgentEnrollRequest, PostAgentEnrollResponse, } from '../../common/types'; -import { version } from '../../package.json'; +import * as kibanaPackage from '../../package.json'; +// @ts-ignore +// Using the ts-ignore because we are importing directly from a json to a script file +const version = kibanaPackage.version; const CHECKIN_INTERVAL = 3000; // 3 seconds type Agent = Pick<_Agent, 'id' | 'access_api_key'>; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts index cb91895c1fb18e..a9911c63c2b33e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts @@ -20,6 +20,7 @@ import { } from '../../policy/store/policy_list/services/ingest'; import { GetAgentPoliciesResponse, + GetAgentPoliciesResponseItem, GetPackagesResponse, } from '../../../../../../ingest_manager/common/types/rest_spec'; import { GetPolicyListResponse } from '../../policy/types'; @@ -78,12 +79,14 @@ const endpointListApiPathHandlerMocks = ({ epmPackages = [generator.generateEpmPackage()], endpointPackagePolicies = [], policyResponse = generator.generatePolicyResponse(), + agentPolicy = generator.generateAgentPolicy(), }: { /** route handlers will be setup for each individual host in this array */ endpointsResults?: HostResultList['hosts']; epmPackages?: GetPackagesResponse['response']; endpointPackagePolicies?: GetPolicyListResponse['items']; policyResponse?: HostPolicyResponse; + agentPolicy?: GetAgentPoliciesResponseItem; } = {}) => { const apiHandlers = { // endpoint package info @@ -106,7 +109,7 @@ const endpointListApiPathHandlerMocks = ({ // Do policies referenced in endpoint list exist // just returns 1 single agent policy that includes all of the packagePolicy IDs provided [INGEST_API_AGENT_POLICIES]: (): GetAgentPoliciesResponse => { - const agentPolicy = generator.generateAgentPolicy(); + // const agentPolicy = generator.generateAgentPolicy(); (agentPolicy.package_policies as string[]).push( ...endpointPackagePolicies.map((packagePolicy) => packagePolicy.id) ); 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 defdbbc9c37469..14167f25d5b906 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 @@ -6,8 +6,6 @@ import React from 'react'; import * as reactTestingLibrary from '@testing-library/react'; -import { prettyDOM, screen } from '@testing-library/dom'; - import { EndpointList } from './index'; import '../../../../common/mock/match_media.ts'; import { @@ -674,6 +672,8 @@ describe('when on the list page', () => { describe('when the more actions column is opened', () => { let hostInfo: HostInfo; let agentId: string; + let agentPolicyId: string; + const generator = new EndpointDocGenerator('seed'); let renderAndWaitForData: () => Promise>; const mockEndpointListApi = () => { @@ -684,11 +684,14 @@ describe('when on the list page', () => { }; const packagePolicy = docGenerator.generatePolicyPackagePolicy(); packagePolicy.id = hosts[0].metadata.Endpoint.policy.applied.id; + const agentPolicy = generator.generateAgentPolicy(); + agentPolicyId = agentPolicy.id; agentId = hosts[0].metadata.elastic.agent.id; setEndpointListApiMockImplementation(coreStart.http, { endpointsResults: [hostInfo], endpointPackagePolicies: [packagePolicy], + agentPolicy, }); }; @@ -705,6 +708,16 @@ describe('when on the list page', () => { await middlewareSpy.waitForAction('serverReturnedEndpointAgentPolicies'); return renderResult; }; + + coreStart.application.getUrlForApp.mockImplementation((appName) => { + switch (appName) { + case 'securitySolution': + return '/app/security'; + case 'ingestManager': + return '/app/ingestManager'; + } + return appName; + }); }); afterEach(() => { @@ -718,15 +731,35 @@ describe('when on the list page', () => { reactTestingLibrary.act(() => { reactTestingLibrary.fireEvent.click(endpointActionsButton); }); + const hostLink = await renderResult.findByTestId('hostLink'); - const userChangedUrlChecker = middlewareSpy.waitForAction('userChangedUrl'); + expect(hostLink.getAttribute('href')).toEqual( + `/app/security/hosts/${hostInfo.metadata.host.hostname}` + ); + }); + it('navigates to the Ingest Agent Policy page', async () => { + const renderResult = await renderAndWaitForData(); + const endpointActionsButton = await renderResult.findByTestId('endpointTableRowActions'); reactTestingLibrary.act(() => { - reactTestingLibrary.fireEvent.click(hostLink); + reactTestingLibrary.fireEvent.click(endpointActionsButton); }); - const changedUrlAction = await userChangedUrlChecker; - expect(changedUrlAction.payload.pathname).toEqual(`hosts/${hostInfo.metadata.host.hostname}`); + + const agentPolicyLink = await renderResult.findByTestId('agentPolicyLink'); + expect(agentPolicyLink.getAttribute('href')).toEqual( + `/app/ingestManager#/policies/${agentPolicyId}` + ); + }); + it('navigates to the Ingest Agent Details page', async () => { + const renderResult = await renderAndWaitForData(); + const endpointActionsButton = await renderResult.findByTestId('endpointTableRowActions'); + reactTestingLibrary.act(() => { + reactTestingLibrary.fireEvent.click(endpointActionsButton); + }); + + const agentDetailsLink = await renderResult.findByTestId('agentDetailsLink'); + expect(agentDetailsLink.getAttribute('href')).toEqual( + `/app/ingestManager#/fleet/agents/${agentId}` + ); }); - it.skip('navigates to the Ingest Agent Policy page', () => {}); - it.skip('navigates to the Ingest Agent Details page', () => {}); }); }); 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 0a2203d8da467d..178bdb676b7e7c 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,11 +86,11 @@ const TableRowActions = memo<{ return ( { data-test-subj="agentDetailsLink" appId="ingestManager" appPath={`#/fleet/agents/${item.metadata.elastic.agent.id}`} - href={`${services?.application?.getUrlForApp('ingestManager')}#/policies/${ - item.metadata.elastic.agent.id - }`} + href={`${services?.application?.getUrlForApp( + 'ingestManager' + )}#/fleet/agents/${item.metadata.elastic.agent.id}`} > Date: Wed, 9 Sep 2020 16:27:34 -0400 Subject: [PATCH 06/10] I did a thing --- .../use_navigate_to_app_event_handler.ts | 2 +- .../pages/endpoint_hosts/view/index.tsx | 49 +++++++++++++------ 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts index 190009440529ca..943b30925a54cc 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts @@ -12,7 +12,7 @@ type NavigateToAppHandlerOptions = NavigateToAppOptions & { state?: S; onClick?: EventHandlerCallback; }; -type EventHandlerCallback = MouseEventHandler; +type EventHandlerCallback = MouseEventHandler; /** * Provides an event handlers that can be used with (for example) `onClick` to prevent the 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 178bdb676b7e7c..f1ba98d0f87913 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 @@ -27,6 +27,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { createStructuredSelector } from 'reselect'; import { useDispatch } from 'react-redux'; +import { EuiContextMenuItemProps } from '@elastic/eui/src/components/context_menu/context_menu_item'; +import { NavigateToAppOptions } from 'kibana/public'; import { EndpointDetailsFlyout } from './details'; import * as selectors from '../store/selectors'; import { useEndpointSelector } from './hooks'; @@ -397,21 +399,21 @@ export const EndpointList = () => { return ( - - - - , + + + , { ); }; + +const EuiContextMenuItemNavByRouter = memo< + Omit & { + navigateAppId: string; + navigateOptions: NavigateToAppOptions; + children: React.ReactNode; + } +>(({ navigateAppId, navigateOptions, children, ...otherMenuItemProps }) => { + const handleOnClick = useNavigateToAppEventHandler(navigateAppId, navigateOptions); + + return ( + + {children} + + ); +}); +EuiContextMenuItemNavByRouter.displayName = 'EuiContextMenuItemNavByRouter'; From d1fbf4126a900427f3774af5234822961d5204bc Mon Sep 17 00:00:00 2001 From: Candace Park Date: Thu, 10 Sep 2020 15:30:32 -0400 Subject: [PATCH 07/10] remove linktoapp --- .../store/mock_endpoint_result_list.ts | 1 - .../management/pages/endpoint_hosts/types.ts | 4 +- .../pages/endpoint_hosts/view/index.tsx | 82 +++++++++++-------- 3 files changed, 52 insertions(+), 35 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts index a9911c63c2b33e..c5363a5ae9522f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts @@ -109,7 +109,6 @@ const endpointListApiPathHandlerMocks = ({ // Do policies referenced in endpoint list exist // just returns 1 single agent policy that includes all of the packagePolicy IDs provided [INGEST_API_AGENT_POLICIES]: (): GetAgentPoliciesResponse => { - // const agentPolicy = generator.generateAgentPolicy(); (agentPolicy.package_policies as string[]).push( ...endpointPackagePolicies.map((packagePolicy) => packagePolicy.id) ); 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 dac2d435f2a4b7..106be34eb498aa 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 @@ -50,9 +50,9 @@ export interface EndpointState { selectedPolicyId?: string; /** Endpoint package info */ endpointPackageInfo?: GetPackagesResponse['response'][0]; - /** tracks the list of policies IDs used in Host metadata that may no longer exist */ + /** Tracks the list of policies IDs used in Host metadata that may no longer exist */ nonExistingPolicies: PolicyIds['packagePolicy']; - /** List of Agent Policies Ids*/ + /** List of Package Policy Ids mapped to an associated Fleet Parent Agent Policy Id*/ agentPolicies: PolicyIds['agentPolicy']; /** Tracks whether hosts exist and helps control if onboarding should be visible */ endpointsExist: boolean; 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 b5dfb9397ee345..c7d67f3235d4cd 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 @@ -47,6 +47,7 @@ import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/ import { CreatePackagePolicyRouteState, AgentPolicyDetailsDeployAgentAction, + pagePathGetters, } from '../../../../../../ingest_manager/public'; import { SecurityPageName } from '../../../../app/types'; import { getEndpointListPath, getEndpointDetailsPath } from '../../../common/routing'; @@ -56,6 +57,7 @@ import { EndpointPolicyLink } from './components/endpoint_policy_link'; import { AdministrationListPage } from '../../../components/administration_list_page'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { LinkToApp } from '../../../../common/components/endpoint/link_to_app'; +import { APP_ID } from '../../../../../common/constants'; const EndpointListNavLink = memo<{ name: string; @@ -403,7 +405,7 @@ export const EndpointList = () => { data-test-subj="hostLink" icon="logoSecurity" key="hostDetailsLink" - navigateAppId="securitySolution" + navigateAppId={APP_ID} navigateOptions={{ path: `hosts/${item.metadata.host.hostname}` }} href={`${services?.application?.getUrlForApp('securitySolution')}/hosts/${ item.metadata.host.hostname @@ -414,38 +416,54 @@ export const EndpointList = () => { defaultMessage="View Host Details" /> , - - - - - , - - - - - , + }`, + }} + href={`${services?.application?.getUrlForApp('ingestManager')}#/policies/${ + agentPolicies[item.metadata.Endpoint.policy.applied.id] + }`} + disabled={ + agentPolicies[item.metadata.Endpoint.policy.applied.id] === undefined + } + // href={`${services?.application?.getUrlForApp( + // 'ingestManager' + // )}#${pagePathGetters.policy_details({ + // policyId: agentPolicies[item.metadata.Endpoint.policy.applied.id], + // })}`} + > + + , + + + , ]} /> ); From 74df4324db445c1abd857987872091b184362a4a Mon Sep 17 00:00:00 2001 From: Candace Park Date: Thu, 10 Sep 2020 17:19:06 -0400 Subject: [PATCH 08/10] use ingest path getter --- .../pages/endpoint_hosts/view/index.tsx | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 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 ffcda85c1a375c..8c4834c6ff28e4 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 @@ -426,21 +426,18 @@ export const EndpointList = () => { data-test-subj="agentPolicyLink" navigateAppId="ingestManager" navigateOptions={{ - path: `#/policies/${ - agentPolicies[item.metadata.Endpoint.policy.applied.id] - }`, + path: `#${pagePathGetters.policy_details({ + policyId: agentPolicies[item.metadata.Endpoint.policy.applied.id], + })}`, }} - href={`${services?.application?.getUrlForApp('ingestManager')}#/policies/${ - agentPolicies[item.metadata.Endpoint.policy.applied.id] - }`} + href={`${services?.application?.getUrlForApp( + 'ingestManager' + )}#${pagePathGetters.policy_details({ + policyId: agentPolicies[item.metadata.Endpoint.policy.applied.id], + })}`} disabled={ agentPolicies[item.metadata.Endpoint.policy.applied.id] === undefined } - // href={`${services?.application?.getUrlForApp( - // 'ingestManager' - // )}#${pagePathGetters.policy_details({ - // policyId: agentPolicies[item.metadata.Endpoint.policy.applied.id], - // })}`} > Date: Thu, 10 Sep 2020 17:23:19 -0400 Subject: [PATCH 09/10] remove unused import --- .../public/management/pages/endpoint_hosts/view/index.tsx | 1 - 1 file changed, 1 deletion(-) 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 8c4834c6ff28e4..166f1660bf3d6f 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 @@ -59,7 +59,6 @@ import { EndpointPolicyLink } from './components/endpoint_policy_link'; import { AdminSearchBar } from './components/search_bar'; import { AdministrationListPage } from '../../../components/administration_list_page'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; -import { LinkToApp } from '../../../../common/components/endpoint/link_to_app'; import { APP_ID } from '../../../../../common/constants'; const EndpointListNavLink = memo<{ From a16b8abcf40a4ec6db734d5c4c8726f410a98e1a Mon Sep 17 00:00:00 2001 From: Candace Park Date: Sat, 12 Sep 2020 00:00:27 -0400 Subject: [PATCH 10/10] fix up functional tests --- .../security_solution_endpoint/apps/endpoint/endpoint_list.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts index 9d44d27799f5d4..b0b8d141080042 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts @@ -242,6 +242,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { 'IP Address', 'Version', 'Last Active', + 'Actions', ], ['No items found'], ]; @@ -272,6 +273,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { 'IP Address', 'Version', 'Last Active', + 'Actions', ], [ 'cadmann-4.example.com', @@ -282,6 +284,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { '10.192.213.130, 10.70.28.129', '6.6.1', 'Jan 24, 2020 @ 16:06:09.541', + '', ], [ 'thurlow-9.example.com', @@ -292,6 +295,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { '10.46.229.234', '6.0.0', 'Jan 24, 2020 @ 16:06:09.541', + '', ], ];