From ab8fe7139b4f7cade58a7ba54f4a3bdc34fceb50 Mon Sep 17 00:00:00 2001 From: Candace Park Date: Tue, 14 Apr 2020 15:35:15 -0400 Subject: [PATCH 01/18] truncation/wrapping for hostname --- .../public/applications/endpoint/view/hosts/details.tsx | 5 +++-- .../public/applications/endpoint/view/hosts/index.tsx | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx index f51349b24933a7..8f1a2f1d47b11d 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx @@ -22,6 +22,7 @@ import { useHistory } from 'react-router-dom'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiText } from '@elastic/eui'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { HostMetadata } from '../../../../../common/types'; import { useHostListSelector } from './hooks'; @@ -92,7 +93,7 @@ const HostDetails = memo(({ details }: { details: HostMetadata }) => { title: i18n.translate('xpack.endpoint.host.details.hostname', { defaultMessage: 'Hostname', }), - description: details.host.hostname, + description: {details.host.hostname}, }, { title: i18n.translate('xpack.endpoint.host.details.sensorVersion', { @@ -168,7 +169,7 @@ export const HostDetailsFlyout = () => { return ( - +

{details === undefined ? : details.host.hostname}

diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx index 1d81d6e8a16dbe..068df791349666 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx @@ -79,6 +79,7 @@ export const HostList = () => { // eslint-disable-next-line @elastic/eui/href-or-on-click { ev.preventDefault(); @@ -95,6 +96,7 @@ export const HostList = () => { name: i18n.translate('xpack.endpoint.host.list.policy', { defaultMessage: 'Policy', }), + truncateText: true, render: () => { return 'Policy Name'; }, @@ -129,6 +131,7 @@ export const HostList = () => { name: i18n.translate('xpack.endpoint.host.list.ip', { defaultMessage: 'IP Address', }), + truncateText: true, }, { field: '', From 9b0e3dc9d8e9f1d746567d10bddc811e80a13576 Mon Sep 17 00:00:00 2001 From: Candace Park Date: Wed, 15 Apr 2020 18:07:41 -0400 Subject: [PATCH 02/18] url pagination... still something wonky tho --- .../endpoint/store/hosts/action.ts | 10 ++-------- .../endpoint/store/hosts/middleware.ts | 16 +++++++--------- .../endpoint/store/hosts/reducer.ts | 6 ------ .../endpoint/store/hosts/selectors.ts | 6 +++++- .../public/applications/endpoint/types.ts | 18 ++++++++++++++---- .../applications/endpoint/view/hosts/index.tsx | 16 ++++++++-------- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts index 21871ec8ca8490..4aea9eb95ce42d 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HostListPagination, ServerApiError } from '../../types'; +import { ServerApiError } from '../../types'; import { HostResultList, HostInfo } from '../../../../../common/types'; interface ServerReturnedHostList { @@ -22,13 +22,7 @@ interface ServerFailedToReturnHostDetails { payload: ServerApiError; } -interface UserPaginatedHostList { - type: 'userPaginatedHostList'; - payload: HostListPagination; -} - export type HostAction = | ServerReturnedHostList | ServerReturnedHostDetails - | ServerFailedToReturnHostDetails - | UserPaginatedHostList; + | ServerFailedToReturnHostDetails; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts index 9481b6633f12e5..47e2fc8b5b0526 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts @@ -5,7 +5,7 @@ */ import { MiddlewareFactory } from '../../types'; -import { pageIndex, pageSize, isOnHostPage, hasSelectedHost, uiQueryParams } from './selectors'; +import { isOnHostPage, hasSelectedHost, uiQueryParams } from './selectors'; import { HostListState } from '../../types'; import { AppAction } from '../action'; @@ -14,19 +14,17 @@ export const hostMiddlewareFactory: MiddlewareFactory = coreStart next(action); const state = getState(); if ( - (action.type === 'userChangedUrl' && - isOnHostPage(state) && - hasSelectedHost(state) !== true) || - action.type === 'userPaginatedHostList' + action.type === 'userChangedUrl' && + isOnHostPage(state) && + hasSelectedHost(state) !== true ) { - const hostPageIndex = pageIndex(state); - const hostPageSize = pageSize(state); + const { page_index: pageIndex, page_size: pageSize } = uiQueryParams(state); const response = await coreStart.http.post('/api/endpoint/metadata', { body: JSON.stringify({ - paging_properties: [{ page_index: hostPageIndex }, { page_size: hostPageSize }], + paging_properties: [{ page_index: pageIndex }, { page_size: pageSize }], }), }); - response.request_page_index = hostPageIndex; + response.request_page_index = pageIndex; dispatch({ type: 'serverReturnedHostList', payload: response, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts index 298e819645dbe8..bcc20de7fd6612 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts @@ -49,12 +49,6 @@ export const hostListReducer: ImmutableReducer = ( ...state, detailsError: action.payload, }; - } else if (action.type === 'userPaginatedHostList') { - return { - ...state, - ...action.payload, - loading: true, - }; } else if (action.type === 'userChangedUrl') { return { ...state, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts index 35bf5d06168789..c6a8d22b419771 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts @@ -37,7 +37,11 @@ export const uiQueryParams: ( // Removes the `?` from the beginning of query string if it exists const query = querystring.parse(location.search.slice(1)); - const keys: Array = ['selected_host']; + const keys: Array = [ + 'selected_host', + 'page_size', + 'page_index', + ]; for (const key of keys) { const value = query[key]; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index 7aca94d3e9c7c2..07a273a839a4ef 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -46,12 +46,22 @@ export interface HostListState { location?: Immutable; } -export interface HostListPagination { - pageIndex: number; - pageSize: number; -} +/** + * Query params on the host page parsed from the URL + */ export interface HostIndexUIQueryParams { + /** + * If host id is present, show the host detail flyout for the selected id + */ selected_host?: string; + /** + * How many items to show in list + */ + page_size?: string; + /** + * Which page to show + */ + page_index?: string; } export interface ServerApiError { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx index 068df791349666..2f1c563b0f7903 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx @@ -5,7 +5,6 @@ */ import React, { useMemo, useCallback } from 'react'; -import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { EuiPage, @@ -26,7 +25,6 @@ import { createStructuredSelector } from 'reselect'; import { EuiBasicTableColumn } from '@elastic/eui'; import { HostDetailsFlyout } from './details'; import * as selectors from '../../store/hosts/selectors'; -import { HostAction } from '../../store/hosts/action'; import { useHostListSelector } from './hooks'; import { CreateStructuredSelector } from '../../types'; import { urlFromQueryParams } from './url_from_query_params'; @@ -34,7 +32,6 @@ import { HostMetadata, Immutable } from '../../../../../common/types'; const selector = (createStructuredSelector as CreateStructuredSelector)(selectors); export const HostList = () => { - const dispatch = useDispatch<(a: HostAction) => void>(); const history = useHistory(); const { listData, @@ -59,12 +56,15 @@ export const HostList = () => { const onTableChange = useCallback( ({ page }: { page: { index: number; size: number } }) => { const { index, size } = page; - dispatch({ - type: 'userPaginatedHostList', - payload: { pageIndex: index, pageSize: size }, - }); + history.push( + urlFromQueryParams({ + ...queryParams, + page_index: JSON.stringify(index), + page_size: JSON.stringify(size), + }) + ); }, - [dispatch] + [history, queryParams] ); const columns: Array>> = useMemo(() => { From fd95269c865c8f54c2b456a8edbccb44e20b2b98 Mon Sep 17 00:00:00 2001 From: Candace Park Date: Fri, 17 Apr 2020 10:30:03 -0400 Subject: [PATCH 03/18] page index and size checks, fixed error --- .../endpoint/store/hosts/middleware.ts | 2 +- .../endpoint/store/hosts/selectors.ts | 23 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts index 47e2fc8b5b0526..a2cc0f44adaa89 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts @@ -24,7 +24,7 @@ export const hostMiddlewareFactory: MiddlewareFactory = coreStart paging_properties: [{ page_index: pageIndex }, { page_size: pageSize }], }), }); - response.request_page_index = pageIndex; + response.request_page_index = Number(pageIndex); dispatch({ type: 'serverReturnedHostList', payload: response, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts index c6a8d22b419771..db08f1edc630a7 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts @@ -8,15 +8,17 @@ import { createSelector } from 'reselect'; import { Immutable } from '../../../../../common/types'; import { HostListState, HostIndexUIQueryParams } from '../../types'; +const PAGE_SIZES = Object.freeze([10, 20, 50]); + export const listData = (state: Immutable) => state.hosts; -export const pageIndex = (state: Immutable) => state.pageIndex; +export const pageIndex = (state: Immutable): number => state.pageIndex; -export const pageSize = (state: Immutable) => state.pageSize; +export const pageSize = (state: Immutable): number => state.pageSize; -export const totalHits = (state: Immutable) => state.total; +export const totalHits = (state: Immutable): number => state.total; -export const isLoading = (state: Immutable) => state.loading; +export const isLoading = (state: Immutable): boolean => state.loading; export const detailsError = (state: Immutable) => state.detailsError; @@ -32,7 +34,7 @@ export const uiQueryParams: ( ) => Immutable = createSelector( (state: Immutable) => state.location, (location: Immutable['location']) => { - const data: HostIndexUIQueryParams = {}; + const data: HostIndexUIQueryParams = { page_index: '0', page_size: '10' }; if (location) { // Removes the `?` from the beginning of query string if it exists const query = querystring.parse(location.search.slice(1)); @@ -51,6 +53,17 @@ export const uiQueryParams: ( data[key] = value[value.length - 1]; } } + + // Check if page size is an expected size, otherwise default to 10 + if (!PAGE_SIZES.includes(Number(data.page_size))) { + data.page_size = '10'; + } + + // Check if page index is a valid positive integer, otherwise default to 0 + const pageIndexAsNumber = Number(data.page_index); + if (!Number.isFinite(pageIndexAsNumber) || pageIndexAsNumber < 0) { + data.page_index = '0'; + } } return data; } From 2fb8c4088f9adf563a2e5a60a8d3283f3e76f70e Mon Sep 17 00:00:00 2001 From: Candace Park Date: Fri, 17 Apr 2020 16:10:01 -0400 Subject: [PATCH 04/18] list api called when user directly navigates to details --- .../endpoint/store/hosts/action.ts | 6 +++ .../endpoint/store/hosts/middleware.test.ts | 2 +- .../endpoint/store/hosts/middleware.ts | 52 +++++++++++++++---- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts index 4aea9eb95ce42d..56a49df3bdab48 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts @@ -12,6 +12,11 @@ interface ServerReturnedHostList { payload: HostResultList; } +interface ServerFailedToReturnHostList { + type: 'serverFailedToReturnHostList'; + payload: ServerApiError; +} + interface ServerReturnedHostDetails { type: 'serverReturnedHostDetails'; payload: HostInfo; @@ -24,5 +29,6 @@ interface ServerFailedToReturnHostDetails { export type HostAction = | ServerReturnedHostList + | ServerFailedToReturnHostList | ServerReturnedHostDetails | ServerFailedToReturnHostDetails; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts index 8f39baddda00e4..179a7268b1c398 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts @@ -56,7 +56,7 @@ describe('host list middleware', () => { await sleep(); expect(fakeHttpServices.post).toHaveBeenCalledWith('/api/endpoint/metadata', { body: JSON.stringify({ - paging_properties: [{ page_index: 0 }, { page_size: 10 }], + paging_properties: [{ page_index: '0' }, { page_size: '10' }], }), }); expect(listData(getState())).toEqual(apiResponse.hosts.map(hostInfo => hostInfo.metadata)); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts index a2cc0f44adaa89..ca7220d9f576ba 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts @@ -5,7 +5,7 @@ */ import { MiddlewareFactory } from '../../types'; -import { isOnHostPage, hasSelectedHost, uiQueryParams } from './selectors'; +import { isOnHostPage, hasSelectedHost, uiQueryParams, listData } from './selectors'; import { HostListState } from '../../types'; import { AppAction } from '../action'; @@ -19,18 +19,48 @@ export const hostMiddlewareFactory: MiddlewareFactory = coreStart hasSelectedHost(state) !== true ) { const { page_index: pageIndex, page_size: pageSize } = uiQueryParams(state); - const response = await coreStart.http.post('/api/endpoint/metadata', { - body: JSON.stringify({ - paging_properties: [{ page_index: pageIndex }, { page_size: pageSize }], - }), - }); - response.request_page_index = Number(pageIndex); - dispatch({ - type: 'serverReturnedHostList', - payload: response, - }); + try { + const response = await coreStart.http.post('/api/endpoint/metadata', { + body: JSON.stringify({ + paging_properties: [{ page_index: pageIndex }, { page_size: pageSize }], + }), + }); + response.request_page_index = Number(pageIndex); + dispatch({ + type: 'serverReturnedHostList', + payload: response, + }); + } catch (error) { + dispatch({ + type: 'serverFailedToReturnHostList', + payload: error, + }); + } } if (action.type === 'userChangedUrl' && hasSelectedHost(state) !== false) { + // 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); + try { + const response = await coreStart.http.post('/api/endpoint/metadata', { + body: JSON.stringify({ + paging_properties: [{ page_index: pageIndex }, { page_size: pageSize }], + }), + }); + response.request_page_index = Number(pageIndex); + dispatch({ + type: 'serverReturnedHostList', + payload: response, + }); + } catch (error) { + dispatch({ + type: 'serverFailedToReturnHostList', + payload: error, + }); + } + } + + // call the host details api const { selected_host: selectedHost } = uiQueryParams(state); try { const response = await coreStart.http.get(`/api/endpoint/metadata/${selectedHost}`); From 8a5f7a7254d54f85e4e97cd686c2147bd80d092e Mon Sep 17 00:00:00 2001 From: Candace Park Date: Mon, 20 Apr 2020 14:49:36 -0400 Subject: [PATCH 05/18] new action, list loading and error, hostlist --> host --- .../endpoint/store/hosts/index.test.ts | 4 +-- .../endpoint/store/hosts/middleware.test.ts | 4 +-- .../endpoint/store/hosts/middleware.ts | 4 +-- .../endpoint/store/hosts/reducer.ts | 22 ++++++++++--- .../endpoint/store/hosts/selectors.ts | 32 ++++++++++--------- .../public/applications/endpoint/types.ts | 18 +++++++++-- .../endpoint/view/hosts/details.tsx | 8 ++--- .../applications/endpoint/view/hosts/hooks.ts | 4 +-- .../endpoint/view/hosts/index.tsx | 8 ++--- 9 files changed, 66 insertions(+), 38 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts index 6148934343635a..75137157c8c380 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts @@ -6,12 +6,12 @@ import { createStore, Dispatch, Store } from 'redux'; import { HostAction, hostListReducer } from './index'; -import { HostListState } from '../../types'; +import { HostState } from '../../types'; import { listData } from './selectors'; import { mockHostResultList } from './mock_host_result_list'; describe('HostList store concerns', () => { - let store: Store; + let store: Store; let dispatch: Dispatch; const createTestStore = () => { store = createStore(hostListReducer); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts index 179a7268b1c398..bd0bda32e88f97 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts @@ -9,7 +9,7 @@ import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { History, createBrowserHistory } from 'history'; import { hostListReducer, hostMiddlewareFactory } from './index'; import { HostResultList, Immutable } from '../../../../../common/types'; -import { HostListState } from '../../types'; +import { HostState } from '../../types'; import { AppAction } from '../action'; import { listData } from './selectors'; import { DepsStartMock, depsStartMock } from '../../mocks'; @@ -20,7 +20,7 @@ describe('host list middleware', () => { let fakeCoreStart: jest.Mocked; let depsStart: DepsStartMock; let fakeHttpServices: jest.Mocked; - type HostListStore = Store, Immutable>; + type HostListStore = Store, Immutable>; let store: HostListStore; let getState: HostListStore['getState']; let dispatch: HostListStore['dispatch']; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts index ca7220d9f576ba..b74107983422dd 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts @@ -6,10 +6,10 @@ import { MiddlewareFactory } from '../../types'; import { isOnHostPage, hasSelectedHost, uiQueryParams, listData } from './selectors'; -import { HostListState } from '../../types'; +import { HostState } from '../../types'; import { AppAction } from '../action'; -export const hostMiddlewareFactory: MiddlewareFactory = coreStart => { +export const hostMiddlewareFactory: MiddlewareFactory = coreStart => { return ({ getState, dispatch }) => next => async (action: AppAction) => { next(action); const state = getState(); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts index bcc20de7fd6612..6cf64331979c2f 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts @@ -4,23 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HostListState, ImmutableReducer } from '../../types'; +import { HostState, ImmutableReducer } from '../../types'; import { AppAction } from '../action'; -const initialState = (): HostListState => { +const initialState = (): HostState => { return { hosts: [], pageSize: 10, pageIndex: 0, total: 0, loading: false, - detailsError: undefined, + error: undefined, details: undefined, + detailsLoading: undefined, + detailsError: undefined, location: undefined, }; }; -export const hostListReducer: ImmutableReducer = ( +export const hostListReducer: ImmutableReducer = ( state = initialState(), action ) => { @@ -38,21 +40,33 @@ export const hostListReducer: ImmutableReducer = ( pageSize, pageIndex, loading: false, + error: undefined, + }; + } else if (action.type === 'serverFailedToReturnHostList') { + return { + ...state, + error: action.payload, + loading: false, }; } else if (action.type === 'serverReturnedHostDetails') { return { ...state, details: action.payload.metadata, + detailsLoading: false, + loading: false, }; } else if (action.type === 'serverFailedToReturnHostDetails') { return { ...state, detailsError: action.payload, + detailsLoading: false, }; } else if (action.type === 'userChangedUrl') { return { ...state, location: action.payload, + loading: true, + detailsLoading: true, detailsError: undefined, }; } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts index db08f1edc630a7..108939bef7ef6e 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts @@ -6,34 +6,36 @@ import querystring from 'querystring'; import { createSelector } from 'reselect'; import { Immutable } from '../../../../../common/types'; -import { HostListState, HostIndexUIQueryParams } from '../../types'; +import { HostState, HostIndexUIQueryParams } from '../../types'; const PAGE_SIZES = Object.freeze([10, 20, 50]); -export const listData = (state: Immutable) => state.hosts; +export const listData = (state: Immutable) => state.hosts; -export const pageIndex = (state: Immutable): number => state.pageIndex; +export const pageIndex = (state: Immutable): number => state.pageIndex; -export const pageSize = (state: Immutable): number => state.pageSize; +export const pageSize = (state: Immutable): number => state.pageSize; -export const totalHits = (state: Immutable): number => state.total; +export const totalHits = (state: Immutable): number => state.total; -export const isLoading = (state: Immutable): boolean => state.loading; +export const listLoading = (state: Immutable): boolean => state.loading; -export const detailsError = (state: Immutable) => state.detailsError; +export const listError = (state: Immutable) => state.error; -export const detailsData = (state: Immutable) => { - return state.details; -}; +export const detailsData = (state: Immutable) => state.details; -export const isOnHostPage = (state: Immutable) => +export const detailsLoading = (state: Immutable): boolean => state.detailsLoading; + +export const detailsError = (state: Immutable) => state.detailsError; + +export const isOnHostPage = (state: Immutable) => state.location ? state.location.pathname === '/hosts' : false; export const uiQueryParams: ( - state: Immutable + state: Immutable ) => Immutable = createSelector( - (state: Immutable) => state.location, - (location: Immutable['location']) => { + (state: Immutable) => state.location, + (location: Immutable['location']) => { const data: HostIndexUIQueryParams = { page_index: '0', page_size: '10' }; if (location) { // Removes the `?` from the beginning of query string if it exists @@ -69,7 +71,7 @@ export const uiQueryParams: ( } ); -export const hasSelectedHost: (state: Immutable) => boolean = createSelector( +export const hasSelectedHost: (state: Immutable) => boolean = createSelector( uiQueryParams, ({ selected_host: selectedHost }) => { return selectedHost !== undefined; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index 07a273a839a4ef..5335a871ed07aa 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -35,14 +35,26 @@ export type MiddlewareFactory = ( api: MiddlewareAPI, S> ) => (next: Dispatch) => (action: AppAction) => unknown; -export interface HostListState { +export interface HostState { + /** list of host **/ hosts: HostMetadata[]; + /** number of items per page */ pageSize: number; + /** which page to show */ pageIndex: number; + /** total number of hosts returned */ total: number; + /** list page is retrieving data */ loading: boolean; - detailsError?: ServerApiError; + /** api error from retrieving host list */ + error?: ServerApiError; + /** details data for a specific host */ details?: Immutable; + /** details page is retrieving data */ + detailsLoading: boolean; + /** api error from retrieving host details */ + detailsError?: ServerApiError; + /** current location info */ location?: Immutable; } @@ -213,7 +225,7 @@ export type KeysByValueCriteria = { export type MalwareProtectionOSes = KeysByValueCriteria; export interface GlobalState { - readonly hostList: HostListState; + readonly hostList: HostState; readonly alertList: AlertListState; readonly policyList: PolicyListState; readonly policyDetails: PolicyDetailsState; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx index 8f1a2f1d47b11d..7a6ca8ed2b0f22 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx @@ -25,7 +25,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiText } from '@elastic/eui'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { HostMetadata } from '../../../../../common/types'; -import { useHostListSelector } from './hooks'; +import { useHostSelector } from './hooks'; import { urlFromQueryParams } from './url_from_query_params'; import { FormattedDateAndTime } from '../formatted_date_time'; import { uiQueryParams, detailsData, detailsError } from './../../store/hosts/selectors'; @@ -137,10 +137,10 @@ const HostDetails = memo(({ details }: { details: HostMetadata }) => { export const HostDetailsFlyout = () => { const history = useHistory(); const { notifications } = useKibana(); - const queryParams = useHostListSelector(uiQueryParams); + const queryParams = useHostSelector(uiQueryParams); const { selected_host: selectedHost, ...queryParamsWithoutSelectedHost } = queryParams; - const details = useHostListSelector(detailsData); - const error = useHostListSelector(detailsError); + const details = useHostSelector(detailsData); + const error = useHostSelector(detailsError); const handleFlyoutClose = useCallback(() => { history.push(urlFromQueryParams(queryParamsWithoutSelectedHost)); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/hooks.ts b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/hooks.ts index 99a0073f46c743..b39176b18fb04d 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/hooks.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/hooks.ts @@ -5,9 +5,9 @@ */ import { useSelector } from 'react-redux'; -import { GlobalState, HostListState } from '../../types'; +import { GlobalState, HostState } from '../../types'; -export function useHostListSelector(selector: (state: HostListState) => TSelected) { +export function useHostSelector(selector: (state: HostState) => TSelected) { return useSelector(function(state: GlobalState) { return selector(state.hostList); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx index 2f1c563b0f7903..6852c9445d1825 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx @@ -25,7 +25,7 @@ import { createStructuredSelector } from 'reselect'; import { EuiBasicTableColumn } from '@elastic/eui'; import { HostDetailsFlyout } from './details'; import * as selectors from '../../store/hosts/selectors'; -import { useHostListSelector } from './hooks'; +import { useHostSelector } from './hooks'; import { CreateStructuredSelector } from '../../types'; import { urlFromQueryParams } from './url_from_query_params'; import { HostMetadata, Immutable } from '../../../../../common/types'; @@ -38,10 +38,10 @@ export const HostList = () => { pageIndex, pageSize, totalHits: totalItemCount, - isLoading, + listLoading: loading, uiQueryParams: queryParams, hasSelectedHost, - } = useHostListSelector(selector); + } = useHostSelector(selector); const paginationSetup = useMemo(() => { return { @@ -181,7 +181,7 @@ export const HostList = () => { data-test-subj="hostListTable" items={useMemo(() => [...listData], [listData])} columns={columns} - loading={isLoading} + loading={loading} pagination={paginationSetup} onChange={onTableChange} /> From f17ecfa9aabe6fe61f812257c57cca777073edf5 Mon Sep 17 00:00:00 2001 From: Candace Park Date: Mon, 20 Apr 2020 15:46:11 -0400 Subject: [PATCH 06/18] fixed list error --- .../public/applications/endpoint/store/hosts/middleware.ts | 2 +- .../endpoint/public/applications/endpoint/view/hosts/index.tsx | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts index b74107983422dd..364625c37dcf76 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts @@ -33,7 +33,7 @@ export const hostMiddlewareFactory: MiddlewareFactory = coreStart => } catch (error) { dispatch({ type: 'serverFailedToReturnHostList', - payload: error, + payload: error.message, }); } } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx index 6852c9445d1825..57553b8c6933dc 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx @@ -39,6 +39,7 @@ export const HostList = () => { pageSize, totalHits: totalItemCount, listLoading: loading, + listError, uiQueryParams: queryParams, hasSelectedHost, } = useHostSelector(selector); @@ -182,6 +183,7 @@ export const HostList = () => { items={useMemo(() => [...listData], [listData])} columns={columns} loading={loading} + error={listError} pagination={paginationSetup} onChange={onTableChange} /> From 7e86f16a992993b9aedf5036a4a010af07cf9fb3 Mon Sep 17 00:00:00 2001 From: Candace Park Date: Mon, 20 Apr 2020 17:36:19 -0400 Subject: [PATCH 07/18] host list vs details loading logic --- .../endpoint/store/hosts/reducer.ts | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts index 6cf64331979c2f..0ea2df48e1277f 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Immutable } from '../../../../../common/types'; import { HostState, ImmutableReducer } from '../../types'; import { AppAction } from '../action'; +import { isOnHostPage, hasSelectedHost } from './selectors'; const initialState = (): HostState => { return { @@ -16,7 +18,7 @@ const initialState = (): HostState => { loading: false, error: undefined, details: undefined, - detailsLoading: undefined, + detailsLoading: false, detailsError: undefined, location: undefined, }; @@ -53,7 +55,7 @@ export const hostListReducer: ImmutableReducer = ( ...state, details: action.payload.metadata, detailsLoading: false, - loading: false, + detailsError: undefined, }; } else if (action.type === 'serverFailedToReturnHostDetails') { return { @@ -62,14 +64,47 @@ export const hostListReducer: ImmutableReducer = ( detailsLoading: false, }; } else if (action.type === 'userChangedUrl') { + const newState: Immutable = { + ...state, + location: action.payload, + }; + const isCurrentlyOnListPage = isOnHostPage(newState) && !hasSelectedHost(newState); + const wasPreviouslyOnListPage = isOnHostPage(state) && !hasSelectedHost(state); + const isCurrentlyOnDetailsPage = isOnHostPage(newState) && hasSelectedHost(newState); + const wasPreviouslyOnDetailsPage = isOnHostPage(state) && hasSelectedHost(state); + + // if on the host list page for the first time, return new location and load list + if (isCurrentlyOnListPage) { + if (!wasPreviouslyOnListPage) { + return { + ...state, + location: action.payload, + loading: true, + }; + } + } else if (isCurrentlyOnDetailsPage) { + // if previous page was the list or another host details page, load host details only + if (wasPreviouslyOnDetailsPage || wasPreviouslyOnListPage) { + return { + ...state, + location: action.payload, + detailsLoading: true, + }; + } else { + // if previous page was not host list or host details, load both list and details + return { + ...state, + location: action.payload, + loading: true, + detailsLoading: true, + }; + } + } + // otherwise we are not on a host list or details page return { ...state, location: action.payload, - loading: true, - detailsLoading: true, - detailsError: undefined, }; } - return state; }; From ef20c608c1f382592d853bf9be8af826d641437d Mon Sep 17 00:00:00 2001 From: Candace Park Date: Mon, 20 Apr 2020 18:08:28 -0400 Subject: [PATCH 08/18] fixing list error types --- .../public/applications/endpoint/store/hosts/middleware.ts | 2 +- .../endpoint/public/applications/endpoint/view/hosts/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts index 364625c37dcf76..b74107983422dd 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts @@ -33,7 +33,7 @@ export const hostMiddlewareFactory: MiddlewareFactory = coreStart => } catch (error) { dispatch({ type: 'serverFailedToReturnHostList', - payload: error.message, + payload: error, }); } } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx index 57553b8c6933dc..c6ed35493088d2 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx @@ -183,7 +183,7 @@ export const HostList = () => { items={useMemo(() => [...listData], [listData])} columns={columns} loading={loading} - error={listError} + error={listError?.message} pagination={paginationSetup} onChange={onTableChange} /> From 2ebf7b56063f3805a0570ee6f1ca57616feb253c Mon Sep 17 00:00:00 2001 From: Candace Park Date: Tue, 21 Apr 2020 00:07:06 -0400 Subject: [PATCH 09/18] reset errors when url changes --- .../public/applications/endpoint/store/hosts/reducer.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts index 0ea2df48e1277f..adf18fa50c24ff 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts @@ -80,6 +80,8 @@ export const hostListReducer: ImmutableReducer = ( ...state, location: action.payload, loading: true, + error: undefined, + detailsError: undefined, }; } } else if (isCurrentlyOnDetailsPage) { @@ -89,6 +91,8 @@ export const hostListReducer: ImmutableReducer = ( ...state, location: action.payload, detailsLoading: true, + error: undefined, + detailsError: undefined, }; } else { // if previous page was not host list or host details, load both list and details @@ -97,6 +101,8 @@ export const hostListReducer: ImmutableReducer = ( location: action.payload, loading: true, detailsLoading: true, + error: undefined, + detailsError: undefined, }; } } @@ -104,6 +110,8 @@ export const hostListReducer: ImmutableReducer = ( return { ...state, location: action.payload, + error: undefined, + detailsError: undefined, }; } return state; From 3b2d2a3f3b1c4c080b6a3eb42d0baaac91e10631 Mon Sep 17 00:00:00 2001 From: Candace Park Date: Tue, 21 Apr 2020 00:31:30 -0400 Subject: [PATCH 10/18] details loading stuff --- .../applications/endpoint/view/hosts/details.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx index 7a6ca8ed2b0f22..7df5c25081566a 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx @@ -28,7 +28,12 @@ import { HostMetadata } from '../../../../../common/types'; import { useHostSelector } from './hooks'; import { urlFromQueryParams } from './url_from_query_params'; import { FormattedDateAndTime } from '../formatted_date_time'; -import { uiQueryParams, detailsData, detailsError } from './../../store/hosts/selectors'; +import { + uiQueryParams, + detailsData, + detailsError, + detailsLoading, +} from './../../store/hosts/selectors'; import { LinkToApp } from '../components/link_to_app'; const HostIds = styled(EuiListGroupItem)` @@ -140,6 +145,7 @@ export const HostDetailsFlyout = () => { const queryParams = useHostSelector(uiQueryParams); const { selected_host: selectedHost, ...queryParamsWithoutSelectedHost } = queryParams; const details = useHostSelector(detailsData); + const loading = useHostSelector(detailsLoading); const error = useHostSelector(detailsError); const handleFlyoutClose = useCallback(() => { @@ -171,7 +177,7 @@ export const HostDetailsFlyout = () => {

- {details === undefined ? : details.host.hostname} + {loading ? : details?.host?.hostname}

From ff737a75edf3f0460469ddf433920d73a018fea7 Mon Sep 17 00:00:00 2001 From: Candace Park Date: Tue, 21 Apr 2020 00:43:53 -0400 Subject: [PATCH 11/18] uses pageview, fixes index test --- .../endpoint/store/hosts/index.test.ts | 5 + .../endpoint/view/hosts/index.tsx | 92 +++++-------------- 2 files changed, 30 insertions(+), 67 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts index 75137157c8c380..515c54eab32806 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts @@ -37,6 +37,11 @@ describe('HostList store concerns', () => { pageIndex: 0, total: 0, loading: false, + error: undefined, + details: undefined, + detailsLoading: false, + detailsError: undefined, + location: undefined, }); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx index c6ed35493088d2..a3161b7a645b4c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx @@ -6,20 +6,8 @@ import React, { useMemo, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; -import { - EuiPage, - EuiPageBody, - EuiPageHeader, - EuiPageContent, - EuiHorizontalRule, - EuiTitle, - EuiBasicTable, - EuiText, - EuiLink, - EuiHealth, -} from '@elastic/eui'; +import { EuiHorizontalRule, EuiBasicTable, EuiText, EuiLink, EuiHealth } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; import { createStructuredSelector } from 'reselect'; import { EuiBasicTableColumn } from '@elastic/eui'; @@ -29,6 +17,7 @@ import { useHostSelector } from './hooks'; import { CreateStructuredSelector } from '../../types'; import { urlFromQueryParams } from './url_from_query_params'; import { HostMetadata, Immutable } from '../../../../../common/types'; +import { PageView } from '../components/page_view'; const selector = (createStructuredSelector as CreateStructuredSelector)(selectors); export const HostList = () => { @@ -157,60 +146,29 @@ export const HostList = () => { }, [queryParams, history]); return ( - + {hasSelectedHost && } - - - - -

- -

-
-
- - - - - - - [...listData], [listData])} - columns={columns} - loading={loading} - error={listError?.message} - pagination={paginationSetup} - onChange={onTableChange} - /> - -
-
-
+ + + + + [...listData], [listData])} + columns={columns} + loading={loading} + error={listError?.message} + pagination={paginationSetup} + onChange={onTableChange} + /> + ); }; - -const HostPage = styled.div` - .hostPage { - padding: 0; - } - .hostHeader { - background-color: ${props => props.theme.eui.euiColorLightestShade}; - border-bottom: ${props => props.theme.eui.euiBorderThin}; - padding: ${props => - props.theme.eui.euiSizeXL + - ' ' + - 0 + - props.theme.eui.euiSizeXL + - ' ' + - props.theme.eui.euiSizeL}; - margin-bottom: 0; - } - .hostPageContent { - border: none; - } -`; From 0f69d0c3f7c8d0164107463ffaee0354fb74115d Mon Sep 17 00:00:00 2001 From: Candace Park Date: Tue, 21 Apr 2020 01:12:53 -0400 Subject: [PATCH 12/18] cleanup merge --- .../public/applications/endpoint/store/hosts/selectors.ts | 2 +- .../applications/endpoint/view/hosts/details/host_details.tsx | 2 +- .../public/applications/endpoint/view/hosts/details/index.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts index 16d8455e4ff07b..b0f949ebbe757a 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts @@ -80,7 +80,7 @@ export const hasSelectedHost: (state: Immutable) => boolean = createS ); /** What policy details panel view to show */ -export const showView: (state: HostListState) => 'policy_response' | 'details' = createSelector( +export const showView: (state: HostState) => 'policy_response' | 'details' = createSelector( uiQueryParams, searchParams => { return searchParams.show === 'policy_response' ? 'policy_response' : 'details'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx index f59b1fb3e4de3b..fb3aefcd46eec5 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx @@ -33,7 +33,7 @@ const HostIds = styled(EuiListGroupItem)` export const HostDetails = memo(({ details }: { details: HostMetadata }) => { const { appId, appPath, url } = useHostLogsUrl(details.host.id); - const queryParams = useHostListSelector(uiQueryParams); + const queryParams = useHostSelector(uiQueryParams); const history = useHistory(); const detailsResultsUpper = useMemo(() => { return [ diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx index 77850cf969252a..bbbddbdbb2023f 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx @@ -70,7 +70,7 @@ export const HostDetailsFlyout = memo(() => {

- {loading ? : details.host.hostname} + {loading ? : details?.host?.hostname}

From 58e637e71ac7e4df25e43703663cb893194a64ed Mon Sep 17 00:00:00 2001 From: Candace Park Date: Tue, 21 Apr 2020 10:44:53 -0400 Subject: [PATCH 13/18] fixing up broken tests --- .../endpoint/view/hosts/index.test.tsx | 16 +++++++++++----- .../applications/endpoint/view/hosts/index.tsx | 2 +- .../endpoint/feature_controls/endpoint_spaces.ts | 4 ++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx index 88416b577ed0c2..11dbed716c5277 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx @@ -139,7 +139,7 @@ describe('when on the hosts page', () => { expect(policyStatusLink).not.toBeNull(); expect(policyStatusLink.textContent).toEqual('Successful'); expect(policyStatusLink.getAttribute('href')).toEqual( - '?selected_host=1&show=policy_response' + '?page_index=0&page_size=10&selected_host=1&show=policy_response' ); }); it('should update the URL when policy status link is clicked', async () => { @@ -150,7 +150,9 @@ describe('when on the hosts page', () => { fireEvent.click(policyStatusLink); }); const changedUrlAction = await userChangedUrlChecker; - expect(changedUrlAction.payload.search).toEqual('?selected_host=1&show=policy_response'); + expect(changedUrlAction.payload.search).toEqual( + '?page_index=0&page_size=10&selected_host=1&show=policy_response' + ); }); it('should include the link to logs', async () => { const renderResult = render(); @@ -170,7 +172,7 @@ describe('when on the hosts page', () => { }); }); - it('should navigate to logs without full page refresh', async () => { + it('should navigate to logs without full page refresh', () => { expect(coreStart.application.navigateToApp.mock.calls).toHaveLength(1); }); }); @@ -205,7 +207,9 @@ describe('when on the hosts page', () => { it('should include the back to details link', async () => { const subHeaderBackLink = await renderResult.findByTestId('flyoutSubHeaderBackButton'); expect(subHeaderBackLink.textContent).toBe('Endpoint Details'); - expect(subHeaderBackLink.getAttribute('href')).toBe('?selected_host=1'); + expect(subHeaderBackLink.getAttribute('href')).toBe( + '?page_index=0&page_size=10&selected_host=1' + ); }); it('should update URL when back to details link is clicked', async () => { const subHeaderBackLink = await renderResult.findByTestId('flyoutSubHeaderBackButton'); @@ -214,7 +218,9 @@ describe('when on the hosts page', () => { fireEvent.click(subHeaderBackLink); }); const changedUrlAction = await userChangedUrlChecker; - expect(changedUrlAction.payload.search).toEqual('?selected_host=1'); + expect(changedUrlAction.payload.search).toEqual( + '?page_index=0&page_size=10&selected_host=1' + ); }); }); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx index a3161b7a645b4c..e250d73b0bee4f 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx @@ -149,7 +149,7 @@ export const HostList = () => { {hasSelectedHost && } diff --git a/x-pack/test/functional_endpoint/apps/endpoint/feature_controls/endpoint_spaces.ts b/x-pack/test/functional_endpoint/apps/endpoint/feature_controls/endpoint_spaces.ts index c543046031e9f4..fdebdae9e5d0e0 100644 --- a/x-pack/test/functional_endpoint/apps/endpoint/feature_controls/endpoint_spaces.ts +++ b/x-pack/test/functional_endpoint/apps/endpoint/feature_controls/endpoint_spaces.ts @@ -41,13 +41,13 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.existOrFail('welcomeTitle'); }); - it(`endpoint management shows 'Hosts'`, async () => { + it(`endpoint hosts shows hosts lists page`, async () => { await pageObjects.common.navigateToUrlWithBrowserHistory('endpoint', '/hosts', undefined, { basePath: '/s/custom_space', ensureCurrentUrl: false, shouldLoginIfPrompted: false, }); - await testSubjects.existOrFail('hostListTitle'); + await testSubjects.existOrFail('hostPage'); }); }); From 05f928f2d88cbe7ee7ca6a78ac8b282c0bf95cd9 Mon Sep 17 00:00:00 2001 From: Candace Park Date: Tue, 21 Apr 2020 15:29:36 -0400 Subject: [PATCH 14/18] another test to fix --- x-pack/test/functional_endpoint/apps/endpoint/header_nav.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/test/functional_endpoint/apps/endpoint/header_nav.ts b/x-pack/test/functional_endpoint/apps/endpoint/header_nav.ts index c2c40682124846..b944056e009117 100644 --- a/x-pack/test/functional_endpoint/apps/endpoint/header_nav.ts +++ b/x-pack/test/functional_endpoint/apps/endpoint/header_nav.ts @@ -31,7 +31,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('renders the hosts page when the Hosts tab is selected', async () => { await (await testSubjects.find('hostsEndpointTab')).click(); - await testSubjects.existOrFail('hostListTitle'); + await testSubjects.existOrFail('hostPage'); }); it('renders the alerts page when the Alerts tab is selected', async () => { @@ -46,7 +46,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('renders the home page when Home tab is selected after selecting another tab', async () => { await (await testSubjects.find('hostsEndpointTab')).click(); - await testSubjects.existOrFail('hostListTitle'); + await testSubjects.existOrFail('hostPage'); await (await testSubjects.find('homeEndpointTab')).click(); await testSubjects.existOrFail('welcomeTitle'); From 913c428958baecb7a3612c9a3aa2d7f49fd63e6a Mon Sep 17 00:00:00 2001 From: Candace Park Date: Tue, 21 Apr 2020 17:37:14 -0400 Subject: [PATCH 15/18] some pagination tests --- .../store/hosts/host_pagination.test.ts | 76 +++++++++++++++++++ .../endpoint/store/hosts/middleware.test.ts | 2 +- 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts new file mode 100644 index 00000000000000..a0638df3d9ff83 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts @@ -0,0 +1,76 @@ +/* + * 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 { CoreStart } from 'kibana/public'; +import { DepsStartMock, depsStartMock } from '../../mocks'; +import { AppAction, HostState, HostIndexUIQueryParams } from '../../types'; +import { Immutable } from '../../../../../common/types'; +import { History, createBrowserHistory } from 'history'; +import { hostMiddlewareFactory } from './middleware'; +import { applyMiddleware, Store, createStore } from 'redux'; +import { hostListReducer } from './reducer'; +import { coreMock } from 'src/core/public/mocks'; +import { urlFromQueryParams } from '../../view/hosts/url_from_query_params'; +import { uiQueryParams } from './selectors'; + +describe('host list pagination: ', () => { + let store: Store, Immutable>; + let fakeCoreStart: jest.Mocked; + let depsStart: DepsStartMock; + let history: History; + let queryParams: () => HostIndexUIQueryParams; + + let historyPush: (params: HostIndexUIQueryParams) => void; + beforeEach(() => { + fakeCoreStart = coreMock.createStart(); + depsStart = depsStartMock(); + history = createBrowserHistory(); + + const middleware = hostMiddlewareFactory(fakeCoreStart, depsStart); + store = createStore(hostListReducer, applyMiddleware(middleware)); + + history.listen(location => { + store.dispatch({ type: 'userChangedUrl', payload: location }); + }); + + queryParams = () => uiQueryParams(store.getState()); + + historyPush = (nextQueryParams: HostIndexUIQueryParams): void => { + return history.push(urlFromQueryParams(nextQueryParams)); + }; + }); + + describe('when a new page size is passed', () => { + beforeEach(() => { + historyPush({ ...queryParams(), page_size: '20' }); + }); + it('should modify the url correctly', () => { + expect(queryParams()).toMatchInlineSnapshot(` + Object { + "page_index": "0", + "page_size": "20", + } + `); + }); + }); + describe('when an invalid page size is passed', () => { + beforeEach(() => { + historyPush({ ...queryParams(), page_size: '1' }); + }); + it('should modify the page size in the url to the default page size', () => { + expect(queryParams()).toEqual({ page_index: '0', page_size: '10' }); + }); + }); + + describe('when a new page index is passed', () => { + beforeEach(() => { + historyPush({ ...queryParams(), page_index: '2' }); + }); + it('should modify the page index in the url correctly', () => { + expect(queryParams()).toEqual({ page_index: '2', page_size: '10' }); + }); + }); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts index bd0bda32e88f97..69fb3d191912b0 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts @@ -41,7 +41,7 @@ describe('host list middleware', () => { dispatch = store.dispatch; history = createBrowserHistory(); }); - test('handles `userChangedUrl`', async () => { + it('handles `userChangedUrl`', async () => { const apiResponse = getEndpointListApiResponse(); fakeHttpServices.post.mockResolvedValue(apiResponse); expect(fakeHttpServices.post).not.toHaveBeenCalled(); From 971352a6484a0ee1e23b7aff4cbbb95bc1757874 Mon Sep 17 00:00:00 2001 From: Candace Park Date: Wed, 22 Apr 2020 11:45:43 -0400 Subject: [PATCH 16/18] add one more test using spy middleware --- .../store/hosts/host_pagination.test.ts | 42 ++++++++++++++++--- .../public/applications/endpoint/types.ts | 13 ++---- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts index a0638df3d9ff83..b1b3cee03f4f53 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreStart } from 'kibana/public'; +import { CoreStart, HttpSetup } from 'kibana/public'; import { DepsStartMock, depsStartMock } from '../../mocks'; import { AppAction, HostState, HostIndexUIQueryParams } from '../../types'; -import { Immutable } from '../../../../../common/types'; +import { Immutable, HostResultList } from '../../../../../common/types'; import { History, createBrowserHistory } from 'history'; import { hostMiddlewareFactory } from './middleware'; import { applyMiddleware, Store, createStore } from 'redux'; @@ -15,22 +15,31 @@ import { hostListReducer } from './reducer'; import { coreMock } from 'src/core/public/mocks'; import { urlFromQueryParams } from '../../view/hosts/url_from_query_params'; import { uiQueryParams } from './selectors'; +import { mockHostResultList } from './mock_host_result_list'; +import { MiddlewareActionSpyHelper, createSpyMiddleware } from '../test_utils'; describe('host list pagination: ', () => { - let store: Store, Immutable>; let fakeCoreStart: jest.Mocked; let depsStart: DepsStartMock; + let fakeHttpServices: jest.Mocked; let history: History; + let store: Store, Immutable>; let queryParams: () => HostIndexUIQueryParams; + let waitForAction: MiddlewareActionSpyHelper['waitForAction']; + let actionSpyMiddleware; + const getEndpointListApiResponse = (): HostResultList => { + return mockHostResultList({ request_page_size: 1, request_page_index: 1, total: 10 }); + }; let historyPush: (params: HostIndexUIQueryParams) => void; beforeEach(() => { fakeCoreStart = coreMock.createStart(); depsStart = depsStartMock(); + fakeHttpServices = fakeCoreStart.http as jest.Mocked; history = createBrowserHistory(); - const middleware = hostMiddlewareFactory(fakeCoreStart, depsStart); - store = createStore(hostListReducer, applyMiddleware(middleware)); + ({ actionSpyMiddleware, waitForAction } = createSpyMiddleware()); + store = createStore(hostListReducer, applyMiddleware(middleware, actionSpyMiddleware)); history.listen(location => { store.dispatch({ type: 'userChangedUrl', payload: location }); @@ -73,4 +82,27 @@ describe('host list pagination: ', () => { expect(queryParams()).toEqual({ page_index: '2', page_size: '10' }); }); }); + + describe('when a negative page index is passed', () => { + it('should modify the page index in the url to the default page index', async () => { + const apiResponse = getEndpointListApiResponse(); + fakeHttpServices.post.mockResolvedValue(apiResponse); + expect(fakeHttpServices.post).not.toHaveBeenCalled(); + + store.dispatch({ + type: 'userChangedUrl', + payload: { + ...history.location, + pathname: '/hosts', + search: '?page_index=-2', + }, + }); + await waitForAction('serverReturnedHostList'); + expect(fakeHttpServices.post).toHaveBeenCalledWith('/api/endpoint/metadata', { + body: JSON.stringify({ + paging_properties: [{ page_index: '0' }, { page_size: '10' }], + }), + }); + }); + }); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index ecc5e8179e21c4..e5e600f6c62884 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -115,18 +115,13 @@ export interface HostState { * Query params on the host page parsed from the URL */ export interface HostIndexUIQueryParams { - /** - * If host id is present, show the host detail flyout for the selected id - */ + /** Selected host id shows host details flyout */ selected_host?: string; - /** - * How many items to show in list - */ + /** How many items to show in list */ page_size?: string; - /** - * Which page to show - */ + /** Which page to show */ page_index?: string; + /** show the policy response or host details */ show?: string; } From 16a111385a2d694a717ca36e62482be054ab5208 Mon Sep 17 00:00:00 2001 From: Candace Park Date: Wed, 22 Apr 2020 15:50:30 -0400 Subject: [PATCH 17/18] add and fix tests --- .../store/hosts/host_pagination.test.ts | 71 ++++++++++++++----- .../endpoint/store/hosts/middleware.test.ts | 9 ++- .../endpoint/store/hosts/middleware.ts | 4 +- 3 files changed, 63 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts index b1b3cee03f4f53..d2e1985d055c6d 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/host_pagination.test.ts @@ -52,11 +52,30 @@ describe('host list pagination: ', () => { }; }); - describe('when a new page size is passed', () => { - beforeEach(() => { - historyPush({ ...queryParams(), page_size: '20' }); + describe('when the user enteres the host list for the first time', () => { + it('the api is called with page_index and page_size defaulting to 0 and 10 respectively', async () => { + const apiResponse = getEndpointListApiResponse(); + fakeHttpServices.post.mockResolvedValue(apiResponse); + expect(fakeHttpServices.post).not.toHaveBeenCalled(); + + store.dispatch({ + type: 'userChangedUrl', + payload: { + ...history.location, + pathname: '/hosts', + }, + }); + await waitForAction('serverReturnedHostList'); + expect(fakeHttpServices.post).toHaveBeenCalledWith('/api/endpoint/metadata', { + body: JSON.stringify({ + paging_properties: [{ page_index: '0' }, { page_size: '10' }], + }), + }); }); + }); + describe('when a new page size is passed', () => { it('should modify the url correctly', () => { + historyPush({ ...queryParams(), page_size: '20' }); expect(queryParams()).toMatchInlineSnapshot(` Object { "page_index": "0", @@ -66,43 +85,61 @@ describe('host list pagination: ', () => { }); }); describe('when an invalid page size is passed', () => { - beforeEach(() => { + it('should modify the page size in the url to the default page size', () => { historyPush({ ...queryParams(), page_size: '1' }); + expect(queryParams()).toEqual({ page_index: '0', page_size: '10' }); }); + }); + + describe('when a negative page size is passed', () => { it('should modify the page size in the url to the default page size', () => { + historyPush({ ...queryParams(), page_size: '-1' }); expect(queryParams()).toEqual({ page_index: '0', page_size: '10' }); }); }); describe('when a new page index is passed', () => { - beforeEach(() => { - historyPush({ ...queryParams(), page_index: '2' }); - }); it('should modify the page index in the url correctly', () => { + historyPush({ ...queryParams(), page_index: '2' }); expect(queryParams()).toEqual({ page_index: '2', page_size: '10' }); }); }); describe('when a negative page index is passed', () => { - it('should modify the page index in the url to the default page index', async () => { - const apiResponse = getEndpointListApiResponse(); - fakeHttpServices.post.mockResolvedValue(apiResponse); - expect(fakeHttpServices.post).not.toHaveBeenCalled(); + it('should modify the page index in the url to the default page index', () => { + historyPush({ ...queryParams(), page_index: '-2' }); + expect(queryParams()).toEqual({ page_index: '0', page_size: '10' }); + }); + }); + + describe('when invalid params are passed in the url', () => { + it('ignores non-numeric values for page_index and page_size', () => { + historyPush({ ...queryParams, page_index: 'one', page_size: 'fifty' }); + expect(queryParams()).toEqual({ page_index: '0', page_size: '10' }); + }); + it('ignores unknown url search params', () => { store.dispatch({ type: 'userChangedUrl', payload: { ...history.location, pathname: '/hosts', - search: '?page_index=-2', + search: '?foo=bar', }, }); - await waitForAction('serverReturnedHostList'); - expect(fakeHttpServices.post).toHaveBeenCalledWith('/api/endpoint/metadata', { - body: JSON.stringify({ - paging_properties: [{ page_index: '0' }, { page_size: '10' }], - }), + expect(queryParams()).toEqual({ page_index: '0', page_size: '10' }); + }); + + it('ignores multiple values of the same query params except the last value', () => { + store.dispatch({ + type: 'userChangedUrl', + payload: { + ...history.location, + pathname: '/hosts', + search: '?page_index=2&page_index=3&page_size=20&page_size=50', + }, }); + expect(queryParams()).toEqual({ page_index: '3', page_size: '50' }); }); }); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts index 69fb3d191912b0..1af83a975d1d87 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts @@ -14,9 +14,9 @@ import { AppAction } from '../action'; import { listData } from './selectors'; import { DepsStartMock, depsStartMock } from '../../mocks'; import { mockHostResultList } from './mock_host_result_list'; +import { createSpyMiddleware, MiddlewareActionSpyHelper } from '../test_utils'; describe('host list middleware', () => { - const sleep = (ms = 100) => new Promise(wakeup => setTimeout(wakeup, ms)); let fakeCoreStart: jest.Mocked; let depsStart: DepsStartMock; let fakeHttpServices: jest.Mocked; @@ -24,6 +24,8 @@ describe('host list middleware', () => { let store: HostListStore; let getState: HostListStore['getState']; let dispatch: HostListStore['dispatch']; + let waitForAction: MiddlewareActionSpyHelper['waitForAction']; + let actionSpyMiddleware; let history: History; const getEndpointListApiResponse = (): HostResultList => { @@ -33,9 +35,10 @@ describe('host list middleware', () => { fakeCoreStart = coreMock.createStart({ basePath: '/mock' }); depsStart = depsStartMock(); fakeHttpServices = fakeCoreStart.http as jest.Mocked; + ({ actionSpyMiddleware, waitForAction } = createSpyMiddleware()); store = createStore( hostListReducer, - applyMiddleware(hostMiddlewareFactory(fakeCoreStart, depsStart)) + applyMiddleware(hostMiddlewareFactory(fakeCoreStart, depsStart), actionSpyMiddleware) ); getState = store.getState; dispatch = store.dispatch; @@ -53,7 +56,7 @@ describe('host list middleware', () => { pathname: '/hosts', }, }); - await sleep(); + await waitForAction('serverReturnedHostList'); expect(fakeHttpServices.post).toHaveBeenCalledWith('/api/endpoint/metadata', { body: JSON.stringify({ paging_properties: [{ page_index: '0' }, { page_size: '10' }], diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts index e656d6777b688b..bb1cfc4dd10af1 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { HostResultList } from '../../../../../common/types'; import { isOnHostPage, hasSelectedHost, uiQueryParams, listData } from './selectors'; import { HostState } from '../../types'; import { ImmutableMiddlewareFactory } from '../../types'; @@ -19,7 +20,7 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = core ) { const { page_index: pageIndex, page_size: pageSize } = uiQueryParams(state); try { - const response = await coreStart.http.post('/api/endpoint/metadata', { + const response = await coreStart.http.post('/api/endpoint/metadata', { body: JSON.stringify({ paging_properties: [{ page_index: pageIndex }, { page_size: pageSize }], }), @@ -56,6 +57,7 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = core type: 'serverFailedToReturnHostList', payload: error, }); + return; } } From 58cc53db8fed6ae397c0fa521caba79858e09ce1 Mon Sep 17 00:00:00 2001 From: Candace Park Date: Wed, 22 Apr 2020 15:53:36 -0400 Subject: [PATCH 18/18] removing incorrect wording showing --- .../endpoint/public/applications/endpoint/view/hosts/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx index 8d46edc722c38a..5c2922162ce0ca 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx @@ -161,7 +161,7 @@ export const HostList = () => {