mirror of
https://github.com/ansible/awx.git
synced 2026-03-05 10:41:05 -03:30
Add smart inventory host list view
This commit is contained in:
@@ -8,7 +8,7 @@ import ErrorDetail from '../ErrorDetail';
|
|||||||
import useRequest from '../../util/useRequest';
|
import useRequest from '../../util/useRequest';
|
||||||
import { HostsAPI } from '../../api';
|
import { HostsAPI } from '../../api';
|
||||||
|
|
||||||
function HostToggle({ host, onToggle, className, i18n }) {
|
function HostToggle({ host, isDisabled = false, onToggle, className, i18n }) {
|
||||||
const [isEnabled, setIsEnabled] = useState(host.enabled);
|
const [isEnabled, setIsEnabled] = useState(host.enabled);
|
||||||
const [showError, setShowError] = useState(false);
|
const [showError, setShowError] = useState(false);
|
||||||
|
|
||||||
@@ -54,7 +54,11 @@ function HostToggle({ host, onToggle, className, i18n }) {
|
|||||||
label={i18n._(t`On`)}
|
label={i18n._(t`On`)}
|
||||||
labelOff={i18n._(t`Off`)}
|
labelOff={i18n._(t`Off`)}
|
||||||
isChecked={isEnabled}
|
isChecked={isEnabled}
|
||||||
isDisabled={isLoading || !host.summary_fields.user_capabilities.edit}
|
isDisabled={
|
||||||
|
isLoading ||
|
||||||
|
isDisabled ||
|
||||||
|
!host.summary_fields.user_capabilities.edit
|
||||||
|
}
|
||||||
onChange={toggleHost}
|
onChange={toggleHost}
|
||||||
aria-label={i18n._(t`Toggle host`)}
|
aria-label={i18n._(t`Toggle host`)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const mockHost = {
|
|||||||
},
|
},
|
||||||
user_capabilities: {
|
user_capabilities: {
|
||||||
delete: true,
|
delete: true,
|
||||||
update: true,
|
edit: true,
|
||||||
},
|
},
|
||||||
recent_jobs: [],
|
recent_jobs: [],
|
||||||
},
|
},
|
||||||
@@ -68,6 +68,18 @@ describe('<HostToggle>', () => {
|
|||||||
expect(onToggle).toHaveBeenCalledWith(true);
|
expect(onToggle).toHaveBeenCalledWith(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should be enabled', async () => {
|
||||||
|
const wrapper = mountWithContexts(<HostToggle host={mockHost} />);
|
||||||
|
expect(wrapper.find('Switch').prop('isDisabled')).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be disabled', async () => {
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<HostToggle isDisabled host={mockHost} />
|
||||||
|
);
|
||||||
|
expect(wrapper.find('Switch').prop('isDisabled')).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
test('should show error modal', async () => {
|
test('should show error modal', async () => {
|
||||||
HostsAPI.update.mockImplementation(() => {
|
HostsAPI.update.mockImplementation(() => {
|
||||||
throw new Error('nope');
|
throw new Error('nope');
|
||||||
|
|||||||
@@ -54,11 +54,7 @@ function HostListItem({ i18n, host, isSelected, onSelect, detailUrl }) {
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
<b css="margin-right: 24px">{i18n._(t`Inventory`)}</b>
|
<b css="margin-right: 24px">{i18n._(t`Inventory`)}</b>
|
||||||
<Link
|
<Link
|
||||||
to={`/inventories/${
|
to={`/inventories/inventory/${host.summary_fields.inventory.id}/details`}
|
||||||
host.summary_fields.inventory.kind === 'smart'
|
|
||||||
? 'smart_inventory'
|
|
||||||
: 'inventory'
|
|
||||||
}/${host.summary_fields.inventory.id}/details`}
|
|
||||||
>
|
>
|
||||||
{host.summary_fields.inventory.name}
|
{host.summary_fields.inventory.name}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -105,14 +105,7 @@ function Inventories({ i18n }) {
|
|||||||
</Config>
|
</Config>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/inventories/smart_inventory/:id">
|
<Route path="/inventories/smart_inventory/:id">
|
||||||
<Config>
|
<SmartInventory setBreadcrumb={buildBreadcrumbConfig} />
|
||||||
{({ me }) => (
|
|
||||||
<SmartInventory
|
|
||||||
setBreadcrumb={buildBreadcrumbConfig}
|
|
||||||
me={me || {}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Config>
|
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/inventories">
|
<Route path="/inventories">
|
||||||
<InventoryList />
|
<InventoryList />
|
||||||
|
|||||||
@@ -77,11 +77,18 @@ describe('<InventoryHostDetail />', () => {
|
|||||||
|
|
||||||
describe('User has read-only permissions', () => {
|
describe('User has read-only permissions', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
const readOnlyHost = { ...mockHost };
|
const readOnlyHost = {
|
||||||
|
...mockHost,
|
||||||
|
summary_fields: {
|
||||||
|
...mockHost.summary_fields,
|
||||||
|
user_capabilities: {
|
||||||
|
...mockHost.summary_fields.user_capabilities,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
readOnlyHost.summary_fields.user_capabilities.edit = false;
|
readOnlyHost.summary_fields.user_capabilities.edit = false;
|
||||||
readOnlyHost.summary_fields.recent_jobs = [];
|
readOnlyHost.summary_fields.recent_jobs = [];
|
||||||
|
wrapper = mountWithContexts(<InventoryHostDetail host={readOnlyHost} />);
|
||||||
wrapper = mountWithContexts(<InventoryHostDetail host={mockHost} />);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ function SmartInventory({ i18n, setBreadcrumb }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchInventory();
|
fetchInventory();
|
||||||
}, [fetchInventory, location.pathname]);
|
}, [fetchInventory]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inventory) {
|
if (inventory) {
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import React, { useEffect, useCallback } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { Button } from '@patternfly/react-core';
|
||||||
|
import DataListToolbar from '../../../components/DataListToolbar';
|
||||||
|
import PaginatedDataList from '../../../components/PaginatedDataList';
|
||||||
|
import SmartInventoryHostListItem from './SmartInventoryHostListItem';
|
||||||
|
import useRequest from '../../../util/useRequest';
|
||||||
|
import useSelected from '../../../util/useSelected';
|
||||||
|
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
||||||
|
import { InventoriesAPI } from '../../../api';
|
||||||
|
import { Inventory } from '../../../types';
|
||||||
|
|
||||||
|
const QS_CONFIG = getQSConfig('host', {
|
||||||
|
page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
order_by: 'name',
|
||||||
|
});
|
||||||
|
|
||||||
|
function SmartInventoryHostList({ i18n, inventory }) {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const {
|
||||||
|
result: { hosts, count },
|
||||||
|
error: contentError,
|
||||||
|
isLoading,
|
||||||
|
request: fetchHosts,
|
||||||
|
} = useRequest(
|
||||||
|
useCallback(async () => {
|
||||||
|
const params = parseQueryString(QS_CONFIG, location.search);
|
||||||
|
const { data } = await InventoriesAPI.readHosts(inventory.id, params);
|
||||||
|
return {
|
||||||
|
hosts: data.results,
|
||||||
|
count: data.count,
|
||||||
|
};
|
||||||
|
}, [location.search, inventory.id]),
|
||||||
|
{
|
||||||
|
hosts: [],
|
||||||
|
count: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
|
||||||
|
hosts
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchHosts();
|
||||||
|
}, [fetchHosts]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PaginatedDataList
|
||||||
|
contentError={contentError}
|
||||||
|
hasContentLoading={isLoading}
|
||||||
|
items={hosts}
|
||||||
|
itemCount={count}
|
||||||
|
pluralizedItemName={i18n._(t`Hosts`)}
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
onRowClick={handleSelect}
|
||||||
|
toolbarSearchColumns={[
|
||||||
|
{
|
||||||
|
name: i18n._(t`Name`),
|
||||||
|
key: 'name',
|
||||||
|
isDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Created by (username)`),
|
||||||
|
key: 'created_by__username',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Modified by (username)`),
|
||||||
|
key: 'modified_by__username',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
toolbarSortColumns={[
|
||||||
|
{
|
||||||
|
name: i18n._(t`Name`),
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
renderToolbar={props => (
|
||||||
|
<DataListToolbar
|
||||||
|
{...props}
|
||||||
|
showSelectAll
|
||||||
|
isAllSelected={isAllSelected}
|
||||||
|
onSelectAll={isSelected => setSelected(isSelected ? [...hosts] : [])}
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
additionalControls={
|
||||||
|
inventory?.summary_fields?.user_capabilities?.adhoc
|
||||||
|
? [
|
||||||
|
<Button
|
||||||
|
aria-label={i18n._(t`Run commands`)}
|
||||||
|
isDisabled={selected.length === 0}
|
||||||
|
>
|
||||||
|
{i18n._(t`Run commands`)}
|
||||||
|
</Button>,
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
renderItem={host => (
|
||||||
|
<SmartInventoryHostListItem
|
||||||
|
key={host.id}
|
||||||
|
host={host}
|
||||||
|
detailUrl={`/inventories/smart_inventory/${inventory.id}/hosts/${host.id}/details`}
|
||||||
|
isSelected={selected.some(row => row.id === host.id)}
|
||||||
|
onSelect={() => handleSelect(host)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SmartInventoryHostList.propTypes = {
|
||||||
|
inventory: Inventory.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withI18n()(SmartInventoryHostList);
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { InventoriesAPI } from '../../../api';
|
||||||
|
import {
|
||||||
|
mountWithContexts,
|
||||||
|
waitForElement,
|
||||||
|
} from '../../../../testUtils/enzymeHelpers';
|
||||||
|
import SmartInventoryHostList from './SmartInventoryHostList';
|
||||||
|
import mockInventory from '../shared/data.inventory.json';
|
||||||
|
import mockHosts from '../shared/data.hosts.json';
|
||||||
|
|
||||||
|
jest.mock('../../../api');
|
||||||
|
|
||||||
|
describe('<SmartInventoryHostList />', () => {
|
||||||
|
describe('User has adhoc permissions', () => {
|
||||||
|
let wrapper;
|
||||||
|
const clonedInventory = {
|
||||||
|
...mockInventory,
|
||||||
|
summary_fields: {
|
||||||
|
...mockInventory.summary_fields,
|
||||||
|
user_capabilities: {
|
||||||
|
...mockInventory.summary_fields.user_capabilities,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
InventoriesAPI.readHosts.mockResolvedValue({
|
||||||
|
data: mockHosts,
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<SmartInventoryHostList inventory={clonedInventory} />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('initially renders successfully', () => {
|
||||||
|
expect(wrapper.find('SmartInventoryHostList').length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fetch hosts from api and render them in the list', () => {
|
||||||
|
expect(InventoriesAPI.readHosts).toHaveBeenCalled();
|
||||||
|
expect(wrapper.find('SmartInventoryHostListItem').length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should disable run commands button when no hosts are selected', () => {
|
||||||
|
wrapper.find('DataListCheck').forEach(el => {
|
||||||
|
expect(el.props().checked).toBe(false);
|
||||||
|
});
|
||||||
|
const runCommandsButton = wrapper.find(
|
||||||
|
'button[aria-label="Run commands"]'
|
||||||
|
);
|
||||||
|
expect(runCommandsButton.length).toBe(1);
|
||||||
|
expect(runCommandsButton.prop('disabled')).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should enable run commands button when at least one host is selected', () => {
|
||||||
|
act(() => {
|
||||||
|
wrapper.find('DataListCheck[id="select-host-2"]').invoke('onChange')(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
const runCommandsButton = wrapper.find(
|
||||||
|
'button[aria-label="Run commands"]'
|
||||||
|
);
|
||||||
|
expect(runCommandsButton.prop('disabled')).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should select and deselect all items', async () => {
|
||||||
|
act(() => {
|
||||||
|
wrapper.find('DataListToolbar').invoke('onSelectAll')(true);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
wrapper.find('DataListCheck').forEach(el => {
|
||||||
|
expect(el.props().checked).toEqual(true);
|
||||||
|
});
|
||||||
|
act(() => {
|
||||||
|
wrapper.find('DataListToolbar').invoke('onSelectAll')(false);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
wrapper.find('DataListCheck').forEach(el => {
|
||||||
|
expect(el.props().checked).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show content error when api throws an error', async () => {
|
||||||
|
InventoriesAPI.readHosts.mockImplementation(() =>
|
||||||
|
Promise.reject(new Error())
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<SmartInventoryHostList inventory={mockInventory} />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('User does not have adhoc permissions', () => {
|
||||||
|
let wrapper;
|
||||||
|
const clonedInventory = {
|
||||||
|
...mockInventory,
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
adhoc: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
test('should hide run commands button', async () => {
|
||||||
|
InventoriesAPI.readHosts.mockResolvedValue({
|
||||||
|
data: { results: [], count: 0 },
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<SmartInventoryHostList inventory={clonedInventory} />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
|
const runCommandsButton = wrapper.find(
|
||||||
|
'button[aria-label="Run commands"]'
|
||||||
|
);
|
||||||
|
expect(runCommandsButton.length).toBe(0);
|
||||||
|
jest.clearAllMocks();
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { string, bool, func } from 'prop-types';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import 'styled-components/macro';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DataListAction,
|
||||||
|
DataListCheck,
|
||||||
|
DataListItem,
|
||||||
|
DataListItemCells,
|
||||||
|
DataListItemRow,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import DataListCell from '../../../components/DataListCell';
|
||||||
|
import HostToggle from '../../../components/HostToggle';
|
||||||
|
import Sparkline from '../../../components/Sparkline';
|
||||||
|
import { Host } from '../../../types';
|
||||||
|
|
||||||
|
function SmartInventoryHostListItem({
|
||||||
|
i18n,
|
||||||
|
detailUrl,
|
||||||
|
host,
|
||||||
|
isSelected,
|
||||||
|
onSelect,
|
||||||
|
}) {
|
||||||
|
const recentPlaybookJobs = host.summary_fields.recent_jobs.map(job => ({
|
||||||
|
...job,
|
||||||
|
type: 'job',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const labelId = `check-action-${host.id}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataListItem key={host.id} aria-labelledby={labelId} id={`${host.id}`}>
|
||||||
|
<DataListItemRow>
|
||||||
|
<DataListCheck
|
||||||
|
id={`select-host-${host.id}`}
|
||||||
|
checked={isSelected}
|
||||||
|
onChange={onSelect}
|
||||||
|
aria-labelledby={labelId}
|
||||||
|
/>
|
||||||
|
<DataListItemCells
|
||||||
|
dataListCells={[
|
||||||
|
<DataListCell key="name">
|
||||||
|
<Link to={`${detailUrl}`}>
|
||||||
|
<b>{host.name}</b>
|
||||||
|
</Link>
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell key="recentJobs">
|
||||||
|
<Sparkline jobs={recentPlaybookJobs} />
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell key="inventory">
|
||||||
|
<>
|
||||||
|
<b css="margin-right: 24px">{i18n._(t`Inventory`)}</b>
|
||||||
|
<Link
|
||||||
|
to={`/inventories/inventory/${host.summary_fields.inventory.id}/details`}
|
||||||
|
>
|
||||||
|
{host.summary_fields.inventory.name}
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
</DataListCell>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<DataListAction
|
||||||
|
aria-label="actions"
|
||||||
|
aria-labelledby={labelId}
|
||||||
|
id={labelId}
|
||||||
|
>
|
||||||
|
<HostToggle isDisabled host={host} />
|
||||||
|
</DataListAction>
|
||||||
|
</DataListItemRow>
|
||||||
|
</DataListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SmartInventoryHostListItem.propTypes = {
|
||||||
|
detailUrl: string.isRequired,
|
||||||
|
host: Host.isRequired,
|
||||||
|
isSelected: bool.isRequired,
|
||||||
|
onSelect: func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withI18n()(SmartInventoryHostListItem);
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||||
|
import SmartInventoryHostListItem from './SmartInventoryHostListItem';
|
||||||
|
|
||||||
|
const mockHost = {
|
||||||
|
id: 2,
|
||||||
|
name: 'Host Two',
|
||||||
|
url: '/api/v2/hosts/2',
|
||||||
|
inventory: 1,
|
||||||
|
summary_fields: {
|
||||||
|
inventory: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Inv 1',
|
||||||
|
},
|
||||||
|
user_capabilities: {
|
||||||
|
edit: true,
|
||||||
|
},
|
||||||
|
recent_jobs: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('<SmartInventoryHostListItem />', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<SmartInventoryHostListItem
|
||||||
|
detailUrl="/inventories/smart_inventory/1/hosts/2"
|
||||||
|
host={mockHost}
|
||||||
|
isSelected={false}
|
||||||
|
onSelect={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render expected row cells', () => {
|
||||||
|
const cells = wrapper.find('DataListCell');
|
||||||
|
expect(cells).toHaveLength(3);
|
||||||
|
expect(cells.at(0).text()).toEqual('Host Two');
|
||||||
|
expect(cells.at(1).find('Sparkline').length).toEqual(1);
|
||||||
|
expect(cells.at(2).text()).toContain('Inv 1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should display disabled host toggle', () => {
|
||||||
|
expect(wrapper.find('HostToggle').length).toBe(1);
|
||||||
|
expect(wrapper.find('HostToggle Switch').prop('isDisabled')).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,10 +1,18 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { CardBody } from '../../../components/Card';
|
import { Route } from 'react-router-dom';
|
||||||
|
import SmartInventoryHostList from './SmartInventoryHostList';
|
||||||
|
import { Inventory } from '../../../types';
|
||||||
|
|
||||||
class SmartInventoryHosts extends Component {
|
function SmartInventoryHosts({ inventory }) {
|
||||||
render() {
|
return (
|
||||||
return <CardBody>Coming soon :)</CardBody>;
|
<Route key="host-list" path="/inventories/smart_inventory/:id/hosts">
|
||||||
}
|
<SmartInventoryHostList inventory={inventory} />
|
||||||
|
</Route>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SmartInventoryHosts.propTypes = {
|
||||||
|
inventory: Inventory.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
export default SmartInventoryHosts;
|
export default SmartInventoryHosts;
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
|
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||||
|
import SmartInventoryHosts from './SmartInventoryHosts';
|
||||||
|
|
||||||
|
jest.mock('../../../api');
|
||||||
|
|
||||||
|
describe('<SmartInventoryHosts />', () => {
|
||||||
|
test('should render smart inventory host list', () => {
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/inventories/smart_inventory/1/hosts'],
|
||||||
|
});
|
||||||
|
const match = {
|
||||||
|
path: '/inventories/smart_inventory/:id/hosts',
|
||||||
|
url: '/inventories/smart_inventory/1/hosts',
|
||||||
|
isExact: true,
|
||||||
|
};
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<SmartInventoryHosts inventory={{ id: 1 }} />,
|
||||||
|
{
|
||||||
|
context: { router: { history, route: { match } } },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(wrapper.find('SmartInventoryHostList').length).toBe(1);
|
||||||
|
jest.clearAllMocks();
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user