[Endpoint] Show Host Status on List view (#64455)

* show host status on list

* Adjust type for HostStatus-to-HealthColor

* Fixed unit tests

* Tests

* removed unused translation keys

* clarify test case description

* remove `ts-ignore`
This commit is contained in:
Paul Tavares 2020-04-27 16:19:44 -04:00 committed by GitHub
parent 231de27026
commit 97d7620fcc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 82 additions and 27 deletions

View file

@ -57,7 +57,7 @@ describe('HostList store concerns', () => {
});
const currentState = store.getState();
expect(currentState.hosts).toEqual(payload.hosts.map(hostInfo => hostInfo.metadata));
expect(currentState.hosts).toEqual(payload.hosts);
expect(currentState.pageSize).toEqual(payload.request_page_size);
expect(currentState.pageIndex).toEqual(payload.request_page_index);
expect(currentState.total).toEqual(payload.total);

View file

@ -62,6 +62,6 @@ describe('host list middleware', () => {
paging_properties: [{ page_index: '0' }, { page_size: '10' }],
}),
});
expect(listData(getState())).toEqual(apiResponse.hosts.map(hostInfo => hostInfo.metadata));
expect(listData(getState())).toEqual(apiResponse.hosts);
});
});

View file

@ -38,7 +38,7 @@ export const hostListReducer: ImmutableReducer<HostState, AppAction> = (
} = action.payload;
return {
...state,
hosts: hosts.map(hostInfo => hostInfo.metadata),
hosts,
total,
pageSize,
pageIndex,

View file

@ -23,6 +23,7 @@ import {
UIPolicyConfig,
PolicyData,
HostPolicyResponse,
HostInfo,
} from '../../../common/types';
import { EndpointPluginStartDependencies } from '../../plugin';
import { AppAction } from './store/action';
@ -91,7 +92,7 @@ export type SubstateMiddlewareFactory = <Substate>(
export interface HostState {
/** list of host **/
hosts: HostMetadata[];
hosts: HostInfo[];
/** number of items per page */
pageSize: number;
/** which page to show */

View file

@ -14,7 +14,7 @@ import {
mockHostResultList,
} from '../../store/hosts/mock_host_result_list';
import { AppContextTestRender, createAppRootMockRenderer } from '../../mocks';
import { HostInfo, HostPolicyResponseActionStatus } from '../../../../../common/types';
import { HostInfo, HostStatus, HostPolicyResponseActionStatus } from '../../../../../common/types';
import { EndpointDocGenerator } from '../../../../../common/generate_data';
describe('when on the hosts page', () => {
@ -48,21 +48,50 @@ describe('when on the hosts page', () => {
describe('when list data loads', () => {
beforeEach(() => {
reactTestingLibrary.act(() => {
const hostListData = mockHostResultList({ total: 3 });
[HostStatus.ERROR, HostStatus.ONLINE, HostStatus.OFFLINE].forEach((status, index) => {
hostListData.hosts[index] = {
metadata: hostListData.hosts[index].metadata,
host_status: status,
};
});
const action: AppAction = {
type: 'serverReturnedHostList',
payload: mockHostResultList(),
payload: hostListData,
};
store.dispatch(action);
});
});
it('should render the host summary row in the table', async () => {
it('should display rows in the table', async () => {
const renderResult = render();
const rows = await renderResult.findAllByRole('row');
expect(rows).toHaveLength(2);
expect(rows).toHaveLength(4);
});
it('should show total', async () => {
const renderResult = render();
const total = await renderResult.findByTestId('hostListTableTotal');
expect(total.textContent).toEqual('3 Hosts');
});
it('should display correct status', async () => {
const renderResult = render();
const hostStatuses = await renderResult.findAllByTestId('rowHostStatus');
expect(hostStatuses[0].textContent).toEqual('Error');
expect(hostStatuses[0].querySelector('[data-euiicon-type][color="danger"]')).not.toBeNull();
expect(hostStatuses[1].textContent).toEqual('Online');
expect(
hostStatuses[1].querySelector('[data-euiicon-type][color="success"]')
).not.toBeNull();
expect(hostStatuses[2].textContent).toEqual('Offline');
expect(
hostStatuses[2].querySelector('[data-euiicon-type][color="subdued"]')
).not.toBeNull();
});
describe('when the user clicks the hostname in the table', () => {
describe('when the user clicks the first hostname in the table', () => {
let renderResult: reactTestingLibrary.RenderResult;
beforeEach(async () => {
const hostDetailsApiResponse = mockHostDetailsApiResult();
@ -76,9 +105,9 @@ describe('when on the hosts page', () => {
});
renderResult = render();
const detailsLink = await renderResult.findByTestId('hostnameCellLink');
if (detailsLink) {
reactTestingLibrary.fireEvent.click(detailsLink);
const hostNameLinks = await renderResult.findAllByTestId('hostnameCellLink');
if (hostNameLinks.length) {
reactTestingLibrary.fireEvent.click(hostNameLinks[0]);
}
});

View file

@ -16,10 +16,20 @@ import * as selectors from '../../store/hosts/selectors';
import { useHostSelector } from './hooks';
import { CreateStructuredSelector } from '../../types';
import { urlFromQueryParams } from './url_from_query_params';
import { HostMetadata, Immutable } from '../../../../../common/types';
import { HostInfo, HostStatus, Immutable } from '../../../../../common/types';
import { PageView } from '../components/page_view';
import { useNavigateByRouterEventHandler } from '../hooks/use_navigate_by_router_event_handler';
const HOST_STATUS_TO_HEALTH_COLOR = Object.freeze<
{
[key in HostStatus]: string;
}
>({
[HostStatus.ERROR]: 'danger',
[HostStatus.ONLINE]: 'success',
[HostStatus.OFFLINE]: 'subdued',
});
const HostLink = memo<{
name: string;
href: string;
@ -73,20 +83,40 @@ export const HostList = () => {
[history, queryParams]
);
const columns: Array<EuiBasicTableColumn<Immutable<HostMetadata>>> = useMemo(() => {
const columns: Array<EuiBasicTableColumn<Immutable<HostInfo>>> = useMemo(() => {
return [
{
field: '',
field: 'metadata.host',
name: i18n.translate('xpack.endpoint.host.list.hostname', {
defaultMessage: 'Hostname',
}),
render: ({ host: { hostname, id } }: { host: { hostname: string; id: string } }) => {
render: ({ hostname, id }: HostInfo['metadata']['host']) => {
const newQueryParams = urlFromQueryParams({ ...queryParams, selected_host: id });
return (
<HostLink name={hostname} href={'?' + newQueryParams.search} route={newQueryParams} />
);
},
},
{
field: 'host_status',
name: i18n.translate('xpack.endpoint.host.list.hostStatus', {
defaultMessage: 'Host Status',
}),
render: (hostStatus: HostInfo['host_status']) => {
return (
<EuiHealth
color={HOST_STATUS_TO_HEALTH_COLOR[hostStatus]}
data-test-subj="rowHostStatus"
>
<FormattedMessage
id="xpack.endpoint.host.list.hostStatusValue"
defaultMessage="{hostStatus, select, online {Online} error {Error} other {Offline}}"
values={{ hostStatus }}
/>
</EuiHealth>
);
},
},
{
field: '',
name: i18n.translate('xpack.endpoint.host.list.policy', {
@ -117,26 +147,23 @@ export const HostList = () => {
},
},
{
field: 'host.os.name',
field: 'metadata.host.os.name',
name: i18n.translate('xpack.endpoint.host.list.os', {
defaultMessage: 'Operating System',
}),
},
{
field: 'host.ip',
field: 'metadata.host.ip',
name: i18n.translate('xpack.endpoint.host.list.ip', {
defaultMessage: 'IP Address',
}),
truncateText: true,
},
{
field: '',
name: i18n.translate('xpack.endpoint.host.list.sensorVersion', {
defaultMessage: 'Sensor Version',
field: 'metadata.agent.version',
name: i18n.translate('xpack.endpoint.host.list.endpointVersion', {
defaultMessage: 'Version',
}),
render: () => {
return 'version';
},
},
{
field: '',
@ -158,7 +185,7 @@ export const HostList = () => {
headerLeft={i18n.translate('xpack.endpoint.host.hosts', { defaultMessage: 'Hosts' })}
>
{hasSelectedHost && <HostDetailsFlyout />}
<EuiText color="subdued" size="xs">
<EuiText color="subdued" size="xs" data-test-subj="hostListTableTotal">
<FormattedMessage
id="xpack.endpoint.host.list.totalCount"
defaultMessage="{totalItemCount, plural, one {# Host} other {# Hosts}}"

View file

@ -6370,7 +6370,6 @@
"xpack.endpoint.host.list.os": "オペレーティングシステム",
"xpack.endpoint.host.list.policy": "ポリシー",
"xpack.endpoint.host.list.policyStatus": "ポリシーステータス",
"xpack.endpoint.host.list.sensorVersion": "サーバーバージョン",
"xpack.endpoint.host.list.totalCount": "表示中: {totalItemCount, plural, one {# ホスト} other {# ホスト}}",
"xpack.endpoint.notFound": "ページが見つかりません",
"xpack.endpoint.pluginTitle": "エンドポイント",

View file

@ -6372,7 +6372,6 @@
"xpack.endpoint.host.list.os": "操作系统",
"xpack.endpoint.host.list.policy": "政策",
"xpack.endpoint.host.list.policyStatus": "策略状态",
"xpack.endpoint.host.list.sensorVersion": "感应器版本",
"xpack.endpoint.host.list.totalCount": "正在显示:{totalItemCount, plural, one {# 个主机} other {# 个主机}}",
"xpack.endpoint.notFound": "未找到页面",
"xpack.endpoint.pluginTitle": "终端",