mirror of
https://github.com/ansible/awx.git
synced 2026-05-08 09:57:35 -02:30
Merge pull request #6109 from marshmalien/inventory-host-toggle
Use HostToggle component in InventoryHostList Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
1
awx/ui_next/src/components/HostToggle/index.js
Normal file
1
awx/ui_next/src/components/HostToggle/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './HostToggle';
|
||||||
@@ -12,7 +12,7 @@ import { VariablesDetail } from '@components/CodeMirrorInput';
|
|||||||
import Sparkline from '@components/Sparkline';
|
import Sparkline from '@components/Sparkline';
|
||||||
import DeleteButton from '@components/DeleteButton';
|
import DeleteButton from '@components/DeleteButton';
|
||||||
import { HostsAPI } from '@api';
|
import { HostsAPI } from '@api';
|
||||||
import HostToggle from '../shared/HostToggle';
|
import HostToggle from '@components/HostToggle';
|
||||||
|
|
||||||
function HostDetail({ host, i18n, onUpdateHost }) {
|
function HostDetail({ host, i18n, onUpdateHost }) {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ describe('<HostList />', () => {
|
|||||||
expect(wrapper.find('HostListItem')).toHaveLength(3);
|
expect(wrapper.find('HostListItem')).toHaveLength(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should select single item', async () => {
|
test('should select and deselect a single item', async () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<HostList />);
|
wrapper = mountWithContexts(<HostList />);
|
||||||
@@ -141,6 +141,19 @@ describe('<HostList />', () => {
|
|||||||
.first()
|
.first()
|
||||||
.prop('isSelected')
|
.prop('isSelected')
|
||||||
).toEqual(true);
|
).toEqual(true);
|
||||||
|
act(() => {
|
||||||
|
wrapper
|
||||||
|
.find('input#select-host-1')
|
||||||
|
.closest('DataListCheck')
|
||||||
|
.invoke('onChange')();
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find('HostListItem')
|
||||||
|
.first()
|
||||||
|
.prop('isSelected')
|
||||||
|
).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should select all items', async () => {
|
test('should select all items', async () => {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { PencilAltIcon } from '@patternfly/react-icons';
|
|||||||
import Sparkline from '@components/Sparkline';
|
import Sparkline from '@components/Sparkline';
|
||||||
import { Host } from '@types';
|
import { Host } from '@types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import HostToggle from '../shared/HostToggle';
|
import HostToggle from '@components/HostToggle';
|
||||||
|
|
||||||
const DataListAction = styled(_DataListAction)`
|
const DataListAction = styled(_DataListAction)`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
|||||||
|
|
||||||
import HostsListItem from './HostListItem';
|
import HostsListItem from './HostListItem';
|
||||||
|
|
||||||
const onToggleHost = jest.fn();
|
|
||||||
|
|
||||||
const mockHost = {
|
const mockHost = {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'Host 1',
|
name: 'Host 1',
|
||||||
@@ -32,13 +30,12 @@ describe('<HostsListItem />', () => {
|
|||||||
detailUrl="/host/1"
|
detailUrl="/host/1"
|
||||||
onSelect={() => {}}
|
onSelect={() => {}}
|
||||||
host={mockHost}
|
host={mockHost}
|
||||||
onToggleHost={onToggleHost}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks();
|
wrapper.unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('edit button shown to users with edit capabilities', () => {
|
test('edit button shown to users with edit capabilities', () => {
|
||||||
@@ -54,7 +51,6 @@ describe('<HostsListItem />', () => {
|
|||||||
detailUrl="/host/1"
|
detailUrl="/host/1"
|
||||||
onSelect={() => {}}
|
onSelect={() => {}}
|
||||||
host={copyMockHost}
|
host={copyMockHost}
|
||||||
onToggleHost={onToggleHost}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
|
expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import {
|
|||||||
DataListItem,
|
DataListItem,
|
||||||
DataListItemCells,
|
DataListItemCells,
|
||||||
DataListItemRow,
|
DataListItemRow,
|
||||||
Switch,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { PencilAltIcon } from '@patternfly/react-icons';
|
import { PencilAltIcon } from '@patternfly/react-icons';
|
||||||
|
import HostToggle from '@components/HostToggle';
|
||||||
import Sparkline from '@components/Sparkline';
|
import Sparkline from '@components/Sparkline';
|
||||||
import { Host } from '@types';
|
import { Host } from '@types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -27,16 +27,7 @@ const DataListAction = styled(_DataListAction)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
function InventoryHostItem(props) {
|
function InventoryHostItem(props) {
|
||||||
const {
|
const { detailUrl, editUrl, host, i18n, isSelected, onSelect } = props;
|
||||||
detailUrl,
|
|
||||||
editUrl,
|
|
||||||
host,
|
|
||||||
i18n,
|
|
||||||
isSelected,
|
|
||||||
onSelect,
|
|
||||||
toggleHost,
|
|
||||||
toggleLoading,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const recentPlaybookJobs = host.summary_fields.recent_jobs.map(job => ({
|
const recentPlaybookJobs = host.summary_fields.recent_jobs.map(job => ({
|
||||||
...job,
|
...job,
|
||||||
@@ -71,27 +62,7 @@ function InventoryHostItem(props) {
|
|||||||
aria-labelledby={labelId}
|
aria-labelledby={labelId}
|
||||||
id={labelId}
|
id={labelId}
|
||||||
>
|
>
|
||||||
<Tooltip
|
<HostToggle host={host} />
|
||||||
content={i18n._(
|
|
||||||
t`Indicates if a host is available and should be included
|
|
||||||
in running jobs. For hosts that are part of an external
|
|
||||||
inventory, this may be reset by the inventory sync process.`
|
|
||||||
)}
|
|
||||||
position="top"
|
|
||||||
>
|
|
||||||
<Switch
|
|
||||||
css="display: inline-flex;"
|
|
||||||
id={`host-${host.id}-toggle`}
|
|
||||||
label={i18n._(t`On`)}
|
|
||||||
labelOff={i18n._(t`Off`)}
|
|
||||||
isChecked={host.enabled}
|
|
||||||
isDisabled={
|
|
||||||
toggleLoading || !host.summary_fields.user_capabilities?.edit
|
|
||||||
}
|
|
||||||
onChange={() => toggleHost(host)}
|
|
||||||
aria-label={i18n._(t`Toggle host`)}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
{host.summary_fields.user_capabilities?.edit && (
|
{host.summary_fields.user_capabilities?.edit && (
|
||||||
<Tooltip content={i18n._(t`Edit Host`)} position="top">
|
<Tooltip content={i18n._(t`Edit Host`)} position="top">
|
||||||
<Button variant="plain" component={Link} to={`${editUrl}`}>
|
<Button variant="plain" component={Link} to={`${editUrl}`}>
|
||||||
@@ -110,8 +81,6 @@ InventoryHostItem.propTypes = {
|
|||||||
host: Host.isRequired,
|
host: Host.isRequired,
|
||||||
isSelected: bool.isRequired,
|
isSelected: bool.isRequired,
|
||||||
onSelect: func.isRequired,
|
onSelect: func.isRequired,
|
||||||
toggleHost: func.isRequired,
|
|
||||||
toggleLoading: bool.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withI18n()(InventoryHostItem);
|
export default withI18n()(InventoryHostItem);
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import React from 'react';
|
|||||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||||
import InventoryHostItem from './InventoryHostItem';
|
import InventoryHostItem from './InventoryHostItem';
|
||||||
|
|
||||||
let toggleHost;
|
|
||||||
|
|
||||||
const mockHost = {
|
const mockHost = {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'Host 1',
|
name: 'Host 1',
|
||||||
@@ -17,65 +15,54 @@ const mockHost = {
|
|||||||
user_capabilities: {
|
user_capabilities: {
|
||||||
edit: true,
|
edit: true,
|
||||||
},
|
},
|
||||||
recent_jobs: [],
|
recent_jobs: [
|
||||||
|
{
|
||||||
|
id: 123,
|
||||||
|
name: 'Demo Job Template',
|
||||||
|
status: 'failed',
|
||||||
|
finished: '2020-02-26T22:38:41.037991Z',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('<InventoryHostItem />', () => {
|
describe('<InventoryHostItem />', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
toggleHost = jest.fn();
|
wrapper = mountWithContexts(
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('edit button shown to users with edit capabilities', () => {
|
|
||||||
const wrapper = mountWithContexts(
|
|
||||||
<InventoryHostItem
|
<InventoryHostItem
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
detailUrl="/host/1"
|
detailUrl="/host/1"
|
||||||
onSelect={() => {}}
|
onSelect={() => {}}
|
||||||
host={mockHost}
|
host={mockHost}
|
||||||
toggleHost={toggleHost}
|
|
||||||
toggleLoading={false}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('edit button shown to users with edit capabilities', () => {
|
||||||
expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
|
expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('edit button hidden from users without edit capabilities', () => {
|
test('edit button hidden from users without edit capabilities', () => {
|
||||||
const copyMockHost = Object.assign({}, mockHost);
|
const copyMockHost = Object.assign({}, mockHost);
|
||||||
copyMockHost.summary_fields.user_capabilities.edit = false;
|
copyMockHost.summary_fields.user_capabilities.edit = false;
|
||||||
const wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<InventoryHostItem
|
<InventoryHostItem
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
detailUrl="/host/1"
|
detailUrl="/host/1"
|
||||||
onSelect={() => {}}
|
onSelect={() => {}}
|
||||||
host={copyMockHost}
|
host={copyMockHost}
|
||||||
toggleHost={toggleHost}
|
|
||||||
toggleLoading={false}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
|
expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles toggle click when host is enabled', () => {
|
test('should display host toggle', () => {
|
||||||
const wrapper = mountWithContexts(
|
expect(wrapper.find('HostToggle').length).toBe(1);
|
||||||
<InventoryHostItem
|
|
||||||
isSelected={false}
|
|
||||||
detailUrl="/host/1"
|
|
||||||
onSelect={() => {}}
|
|
||||||
host={mockHost}
|
|
||||||
toggleHost={toggleHost}
|
|
||||||
toggleLoading={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
wrapper
|
|
||||||
.find('Switch')
|
|
||||||
.first()
|
|
||||||
.find('input')
|
|
||||||
.simulate('change');
|
|
||||||
expect(toggleHost).toHaveBeenCalledWith(mockHost);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ function InventoryHostList({ i18n, location, match }) {
|
|||||||
const [hosts, setHosts] = useState([]);
|
const [hosts, setHosts] = useState([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [selected, setSelected] = useState([]);
|
const [selected, setSelected] = useState([]);
|
||||||
const [toggleError, setToggleError] = useState(null);
|
|
||||||
const [toggleLoading, setToggleLoading] = useState(null);
|
|
||||||
|
|
||||||
const fetchHosts = (id, queryString) => {
|
const fetchHosts = (id, queryString) => {
|
||||||
const params = parseQueryString(QS_CONFIG, queryString);
|
const params = parseQueryString(QS_CONFIG, queryString);
|
||||||
@@ -100,24 +98,6 @@ function InventoryHostList({ i18n, location, match }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggle = async hostToToggle => {
|
|
||||||
setToggleLoading(hostToToggle.id);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { data: updatedHost } = await HostsAPI.update(hostToToggle.id, {
|
|
||||||
enabled: !hostToToggle.enabled,
|
|
||||||
});
|
|
||||||
|
|
||||||
setHosts(
|
|
||||||
hosts.map(host => (host.id === updatedHost.id ? updatedHost : host))
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
setToggleError(error);
|
|
||||||
} finally {
|
|
||||||
setToggleLoading(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const canAdd =
|
const canAdd =
|
||||||
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
||||||
const isAllSelected = selected.length > 0 && selected.length === hosts.length;
|
const isAllSelected = selected.length > 0 && selected.length === hosts.length;
|
||||||
@@ -184,8 +164,6 @@ function InventoryHostList({ i18n, location, match }) {
|
|||||||
editUrl={`/inventories/inventory/${match.params.id}/hosts/${o.id}/edit`}
|
editUrl={`/inventories/inventory/${match.params.id}/hosts/${o.id}/edit`}
|
||||||
isSelected={selected.some(row => row.id === o.id)}
|
isSelected={selected.some(row => row.id === o.id)}
|
||||||
onSelect={() => handleSelect(o)}
|
onSelect={() => handleSelect(o)}
|
||||||
toggleHost={handleToggle}
|
|
||||||
toggleLoading={toggleLoading === o.id}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
emptyStateControls={
|
emptyStateControls={
|
||||||
@@ -197,19 +175,6 @@ function InventoryHostList({ i18n, location, match }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{toggleError && !toggleLoading && (
|
|
||||||
<AlertModal
|
|
||||||
variant="error"
|
|
||||||
title={i18n._(t`Error!`)}
|
|
||||||
isOpen={toggleError && !toggleLoading}
|
|
||||||
onClose={() => setToggleError(false)}
|
|
||||||
>
|
|
||||||
{i18n._(t`Failed to toggle host.`)}
|
|
||||||
<ErrorDetail error={toggleError} />
|
|
||||||
</AlertModal>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{deletionError && (
|
{deletionError && (
|
||||||
<AlertModal
|
<AlertModal
|
||||||
isOpen={deletionError}
|
isOpen={deletionError}
|
||||||
|
|||||||
@@ -60,7 +60,14 @@ const mockHosts = [
|
|||||||
delete: false,
|
delete: false,
|
||||||
update: false,
|
update: false,
|
||||||
},
|
},
|
||||||
recent_jobs: [],
|
recent_jobs: [
|
||||||
|
{
|
||||||
|
id: 123,
|
||||||
|
name: 'Recent Job',
|
||||||
|
status: 'success',
|
||||||
|
finished: '2020-01-27T19:40:36.208728Z',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user