mirror of
https://github.com/ansible/awx.git
synced 2026-05-07 17:37:37 -02:30
Adds Ad Hoc Commands
This commit is contained in:
@@ -25,7 +25,7 @@ function AdHocCommands({ children, apiModule, adHocItems, itemId, i18n }) {
|
|||||||
const {
|
const {
|
||||||
error: fetchError,
|
error: fetchError,
|
||||||
request: fetchModuleOptions,
|
request: fetchModuleOptions,
|
||||||
result: { moduleOptions, credentialTypeId },
|
result: { moduleOptions, credentialTypeId, isDisabled },
|
||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const [choices, credId] = await Promise.all([
|
const [choices, credId] = await Promise.all([
|
||||||
@@ -44,13 +44,13 @@ function AdHocCommands({ children, apiModule, adHocItems, itemId, i18n }) {
|
|||||||
const options = choices.data.actions.GET.module_name.choices.map(
|
const options = choices.data.actions.GET.module_name.choices.map(
|
||||||
(choice, index) => itemObject(choice[0], index)
|
(choice, index) => itemObject(choice[0], index)
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
moduleOptions: [itemObject('', -1), ...options],
|
moduleOptions: [itemObject('', -1), ...options],
|
||||||
credentialTypeId: credId.data.results[0].id,
|
credentialTypeId: credId.data.results[0].id,
|
||||||
|
isDisabled: !choices.data.actions.POST,
|
||||||
};
|
};
|
||||||
}, [itemId, apiModule]),
|
}, [itemId, apiModule]),
|
||||||
{ moduleOptions: [] }
|
{ moduleOptions: [], isDisabled: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -118,6 +118,7 @@ function AdHocCommands({ children, apiModule, adHocItems, itemId, i18n }) {
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
{children({
|
{children({
|
||||||
openAdHocCommands: () => setIsWizardOpen(true),
|
openAdHocCommands: () => setIsWizardOpen(true),
|
||||||
|
isDisabled,
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{isWizardOpen && (
|
{isWizardOpen && (
|
||||||
|
|||||||
@@ -25,8 +25,12 @@ const adHocItems = [
|
|||||||
{ name: 'Inventory 2 Org 0' },
|
{ name: 'Inventory 2 Org 0' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const children = ({ openAdHocCommands }) => (
|
const children = ({ openAdHocCommands, isDisabled }) => (
|
||||||
<button type="submit" onClick={() => openAdHocCommands()} />
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={isDisabled}
|
||||||
|
onClick={() => openAdHocCommands()}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
describe('<AdHocCommands />', () => {
|
describe('<AdHocCommands />', () => {
|
||||||
@@ -344,4 +348,26 @@ describe('<AdHocCommands />', () => {
|
|||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(wrapper.find('ErrorDetail').length).toBe(1);
|
expect(wrapper.find('ErrorDetail').length).toBe(1);
|
||||||
});
|
});
|
||||||
|
test('should disable button', async () => {
|
||||||
|
const isDisabled = true;
|
||||||
|
const newChild = ({ openAdHocCommands }) => (
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={isDisabled}
|
||||||
|
onClick={() => openAdHocCommands()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<AdHocCommands
|
||||||
|
apiModule={InventoriesAPI}
|
||||||
|
adHocItems={adHocItems}
|
||||||
|
itemId={1}
|
||||||
|
credentialTypeId={1}
|
||||||
|
>
|
||||||
|
{newChild}
|
||||||
|
</AdHocCommands>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ import React, { useEffect, useCallback, useState } from 'react';
|
|||||||
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Tooltip,
|
||||||
|
DropdownItem,
|
||||||
|
ToolbarItem,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
import { getQSConfig, mergeParams, parseQueryString } from '../../../util/qs';
|
import { getQSConfig, mergeParams, parseQueryString } from '../../../util/qs';
|
||||||
import { GroupsAPI, InventoriesAPI } from '../../../api';
|
import { GroupsAPI, InventoriesAPI } from '../../../api';
|
||||||
|
|
||||||
@@ -16,6 +22,8 @@ import ErrorDetail from '../../../components/ErrorDetail';
|
|||||||
import PaginatedDataList from '../../../components/PaginatedDataList';
|
import PaginatedDataList from '../../../components/PaginatedDataList';
|
||||||
import AssociateModal from '../../../components/AssociateModal';
|
import AssociateModal from '../../../components/AssociateModal';
|
||||||
import DisassociateButton from '../../../components/DisassociateButton';
|
import DisassociateButton from '../../../components/DisassociateButton';
|
||||||
|
import { Kebabified } from '../../../contexts/Kebabified';
|
||||||
|
import AdHocCommandsButton from '../../../components/AdHocCommands/AdHocCommands';
|
||||||
import InventoryGroupHostListItem from './InventoryGroupHostListItem';
|
import InventoryGroupHostListItem from './InventoryGroupHostListItem';
|
||||||
import AddHostDropdown from './AddHostDropdown';
|
import AddHostDropdown from './AddHostDropdown';
|
||||||
|
|
||||||
@@ -195,6 +203,55 @@ function InventoryGroupHostList({ i18n }) {
|
|||||||
/>,
|
/>,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
<Kebabified>
|
||||||
|
{({ isKebabified }) =>
|
||||||
|
isKebabified ? (
|
||||||
|
<AdHocCommandsButton
|
||||||
|
adHocItems={selected}
|
||||||
|
apiModule={InventoriesAPI}
|
||||||
|
itemId={parseInt(inventoryId, 10)}
|
||||||
|
>
|
||||||
|
{({ openAdHocCommands, isDisabled }) => (
|
||||||
|
<DropdownItem
|
||||||
|
key="run command"
|
||||||
|
onClick={openAdHocCommands}
|
||||||
|
isDisabled={hostCount === 0 || isDisabled}
|
||||||
|
>
|
||||||
|
{i18n._(t`Run command`)}
|
||||||
|
</DropdownItem>
|
||||||
|
)}
|
||||||
|
</AdHocCommandsButton>
|
||||||
|
) : (
|
||||||
|
<ToolbarItem>
|
||||||
|
<Tooltip
|
||||||
|
content={i18n._(
|
||||||
|
t`Select an inventory source by clicking the check box beside it. The inventory source can be a single host or a selection of multiple hosts.`
|
||||||
|
)}
|
||||||
|
position="top"
|
||||||
|
key="adhoc"
|
||||||
|
>
|
||||||
|
<AdHocCommandsButton
|
||||||
|
css="margin-right: 20px"
|
||||||
|
adHocItems={selected}
|
||||||
|
apiModule={InventoriesAPI}
|
||||||
|
itemId={parseInt(inventoryId, 10)}
|
||||||
|
>
|
||||||
|
{({ openAdHocCommands, isDisabled }) => (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
aria-label={i18n._(t`Run command`)}
|
||||||
|
onClick={openAdHocCommands}
|
||||||
|
isDisabled={hostCount === 0 || isDisabled}
|
||||||
|
>
|
||||||
|
{i18n._(t`Run command`)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</AdHocCommandsButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ToolbarItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Kebabified>,
|
||||||
<DisassociateButton
|
<DisassociateButton
|
||||||
key="disassociate"
|
key="disassociate"
|
||||||
onDisassociate={handleDisassociate}
|
onDisassociate={handleDisassociate}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { createMemoryHistory } from 'history';
|
import { createMemoryHistory } from 'history';
|
||||||
import { GroupsAPI, InventoriesAPI } from '../../../api';
|
import { GroupsAPI, InventoriesAPI, CredentialTypesAPI } from '../../../api';
|
||||||
import {
|
import {
|
||||||
mountWithContexts,
|
mountWithContexts,
|
||||||
waitForElement,
|
waitForElement,
|
||||||
@@ -11,6 +11,7 @@ import mockHosts from '../shared/data.hosts.json';
|
|||||||
|
|
||||||
jest.mock('../../../api/models/Groups');
|
jest.mock('../../../api/models/Groups');
|
||||||
jest.mock('../../../api/models/Inventories');
|
jest.mock('../../../api/models/Inventories');
|
||||||
|
jest.mock('../../../api/models/CredentialTypes');
|
||||||
jest.mock('react-router-dom', () => ({
|
jest.mock('react-router-dom', () => ({
|
||||||
...jest.requireActual('react-router-dom'),
|
...jest.requireActual('react-router-dom'),
|
||||||
useParams: () => ({
|
useParams: () => ({
|
||||||
@@ -95,6 +96,52 @@ describe('<InventoryGroupHostList />', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should render enabled ad hoc commands button', async () => {
|
||||||
|
GroupsAPI.readAllHosts.mockResolvedValue({
|
||||||
|
data: { ...mockHosts },
|
||||||
|
});
|
||||||
|
InventoriesAPI.readHostsOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: {},
|
||||||
|
POST: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: { module_name: { choices: [['module']] } },
|
||||||
|
POST: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
CredentialTypesAPI.read.mockResolvedValue({
|
||||||
|
data: { count: 1, results: [{ id: 1, name: 'cred' }] },
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<InventoryGroupHostList>
|
||||||
|
{({ openAdHocCommands, isDisabled }) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
variant="secondary"
|
||||||
|
className="run-command"
|
||||||
|
onClick={openAdHocCommands}
|
||||||
|
disabled={isDisabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</InventoryGroupHostList>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForElement(
|
||||||
|
wrapper,
|
||||||
|
'button[aria-label="Run command"]',
|
||||||
|
el => el.prop('disabled') === false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('should show add dropdown button according to permissions', async () => {
|
test('should show add dropdown button according to permissions', async () => {
|
||||||
expect(wrapper.find('AddHostDropdown').length).toBe(1);
|
expect(wrapper.find('AddHostDropdown').length).toBe(1);
|
||||||
InventoriesAPI.readHostsOptions.mockResolvedValueOnce({
|
InventoriesAPI.readHostsOptions.mockResolvedValueOnce({
|
||||||
|
|||||||
@@ -158,11 +158,11 @@ function InventoryGroupsList({ i18n }) {
|
|||||||
apiModule={InventoriesAPI}
|
apiModule={InventoriesAPI}
|
||||||
itemId={parseInt(inventoryId, 10)}
|
itemId={parseInt(inventoryId, 10)}
|
||||||
>
|
>
|
||||||
{({ openAdHocCommands }) => (
|
{({ openAdHocCommands, isDisabled }) => (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
key="run command"
|
key="run command"
|
||||||
onClick={openAdHocCommands}
|
onClick={openAdHocCommands}
|
||||||
isDisabled={groupCount === 0}
|
isDisabled={groupCount === 0 || isDisabled}
|
||||||
>
|
>
|
||||||
{i18n._(t`Run command`)}
|
{i18n._(t`Run command`)}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
@@ -270,12 +270,12 @@ function InventoryGroupsList({ i18n }) {
|
|||||||
apiModule={InventoriesAPI}
|
apiModule={InventoriesAPI}
|
||||||
itemId={parseInt(inventoryId, 10)}
|
itemId={parseInt(inventoryId, 10)}
|
||||||
>
|
>
|
||||||
{({ openAdHocCommands }) => (
|
{({ openAdHocCommands, isDisabled }) => (
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
aria-label={i18n._(t`Run command`)}
|
aria-label={i18n._(t`Run command`)}
|
||||||
onClick={openAdHocCommands}
|
onClick={openAdHocCommands}
|
||||||
isDisabled={groupCount === 0}
|
isDisabled={groupCount === 0 || isDisabled}
|
||||||
>
|
>
|
||||||
{i18n._(t`Run command`)}
|
{i18n._(t`Run command`)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
mountWithContexts,
|
mountWithContexts,
|
||||||
waitForElement,
|
waitForElement,
|
||||||
} from '../../../../testUtils/enzymeHelpers';
|
} from '../../../../testUtils/enzymeHelpers';
|
||||||
import { InventoriesAPI, GroupsAPI } from '../../../api';
|
import { InventoriesAPI, GroupsAPI, CredentialTypesAPI } from '../../../api';
|
||||||
import InventoryGroupsList from './InventoryGroupsList';
|
import InventoryGroupsList from './InventoryGroupsList';
|
||||||
|
|
||||||
jest.mock('../../../api');
|
jest.mock('../../../api');
|
||||||
@@ -71,13 +71,34 @@ describe('<InventoryGroupsList />', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: { module_name: { choices: [['module']] } },
|
||||||
|
POST: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
CredentialTypesAPI.read.mockResolvedValue({
|
||||||
|
data: { count: 1, results: [{ id: 1, name: 'cred' }] },
|
||||||
|
});
|
||||||
const history = createMemoryHistory({
|
const history = createMemoryHistory({
|
||||||
initialEntries: ['/inventories/inventory/3/groups'],
|
initialEntries: ['/inventories/inventory/3/groups'],
|
||||||
});
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<Route path="/inventories/inventory/:id/groups">
|
<Route path="/inventories/inventory/:id/groups">
|
||||||
<InventoryGroupsList />
|
<InventoryGroupsList>
|
||||||
|
{({ openAdHocCommands, isDisabled }) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
variant="secondary"
|
||||||
|
className="run-command"
|
||||||
|
onClick={openAdHocCommands}
|
||||||
|
disabled={isDisabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</InventoryGroupsList>
|
||||||
</Route>,
|
</Route>,
|
||||||
{
|
{
|
||||||
context: {
|
context: {
|
||||||
@@ -147,31 +168,17 @@ describe('<InventoryGroupsList />', () => {
|
|||||||
expect(el.props().checked).toBe(false);
|
expect(el.props().checked).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
test('should render enabled ad hoc commands button', async () => {
|
||||||
|
await waitForElement(
|
||||||
|
wrapper,
|
||||||
|
'button[aria-label="Run command"]',
|
||||||
|
el => el.prop('disabled') === false
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('<InventoryGroupsList/> error handling', () => {
|
describe('<InventoryGroupsList/> error handling', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
test('should show content error when api throws error on initial render', async () => {
|
beforeEach(() => {
|
||||||
InventoriesAPI.readGroupsOptions.mockImplementationOnce(() =>
|
|
||||||
Promise.reject(new Error())
|
|
||||||
);
|
|
||||||
await act(async () => {
|
|
||||||
wrapper = mountWithContexts(<InventoryGroupsList />);
|
|
||||||
});
|
|
||||||
await waitForElement(wrapper, 'ContentError', el => el.length > 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should show content error if groups are not successfully fetched from api', async () => {
|
|
||||||
InventoriesAPI.readGroups.mockImplementation(() =>
|
|
||||||
Promise.reject(new Error())
|
|
||||||
);
|
|
||||||
await act(async () => {
|
|
||||||
wrapper = mountWithContexts(<InventoryGroupsList />);
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitForElement(wrapper, 'ContentError', el => el.length > 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should show error modal when group is not successfully deleted from api', async () => {
|
|
||||||
InventoriesAPI.readGroups.mockResolvedValue({
|
InventoriesAPI.readGroups.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
count: mockGroups.length,
|
count: mockGroups.length,
|
||||||
@@ -197,7 +204,42 @@ describe('<InventoryGroupsList/> error handling', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: { module_name: { choices: [['module']] } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
CredentialTypesAPI.read.mockResolvedValue({
|
||||||
|
data: { count: 1, results: [{ id: 1, name: 'cred' }] },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
test('should show content error when api throws error on initial render', async () => {
|
||||||
|
InventoriesAPI.readGroupsOptions.mockImplementationOnce(() =>
|
||||||
|
Promise.reject(new Error())
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<InventoryGroupsList />);
|
||||||
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentError', el => el.length > 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show content error if groups are not successfully fetched from api', async () => {
|
||||||
|
InventoriesAPI.readGroups.mockImplementation(() =>
|
||||||
|
Promise.reject(new Error())
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<InventoryGroupsList />);
|
||||||
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentError', el => el.length > 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show error modal when group is not successfully deleted from api', async () => {
|
||||||
const history = createMemoryHistory({
|
const history = createMemoryHistory({
|
||||||
initialEntries: ['/inventories/inventory/3/groups'],
|
initialEntries: ['/inventories/inventory/3/groups'],
|
||||||
});
|
});
|
||||||
@@ -249,4 +291,37 @@ describe('<InventoryGroupsList/> error handling', () => {
|
|||||||
.invoke('onClose')();
|
.invoke('onClose')();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
test('should render disabled ad hoc button', async () => {
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/inventories/inventory/3/groups'],
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<Route path="/inventories/inventory/:id/groups">
|
||||||
|
<InventoryGroupsList>
|
||||||
|
{({ openAdHocCommands, isDisabled }) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
variant="secondary"
|
||||||
|
className="run-command"
|
||||||
|
onClick={openAdHocCommands}
|
||||||
|
disabled={isDisabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</InventoryGroupsList>
|
||||||
|
</Route>,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
router: { history, route: { location: history.location } },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
|
expect(
|
||||||
|
wrapper.find('button[aria-label="Run command"]').prop('disabled')
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import { useParams, useLocation } from 'react-router-dom';
|
import { useParams, useLocation } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Tooltip,
|
||||||
|
DropdownItem,
|
||||||
|
ToolbarItem,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
import { getQSConfig, parseQueryString, mergeParams } from '../../../util/qs';
|
import { getQSConfig, parseQueryString, mergeParams } from '../../../util/qs';
|
||||||
import useRequest, {
|
import useRequest, {
|
||||||
useDismissableError,
|
useDismissableError,
|
||||||
@@ -17,6 +23,8 @@ import PaginatedDataList, {
|
|||||||
} from '../../../components/PaginatedDataList';
|
} from '../../../components/PaginatedDataList';
|
||||||
import AssociateModal from '../../../components/AssociateModal';
|
import AssociateModal from '../../../components/AssociateModal';
|
||||||
import DisassociateButton from '../../../components/DisassociateButton';
|
import DisassociateButton from '../../../components/DisassociateButton';
|
||||||
|
import { Kebabified } from '../../../contexts/Kebabified';
|
||||||
|
import AdHocCommandsButton from '../../../components/AdHocCommands/AdHocCommands';
|
||||||
import InventoryHostGroupItem from './InventoryHostGroupItem';
|
import InventoryHostGroupItem from './InventoryHostGroupItem';
|
||||||
|
|
||||||
const QS_CONFIG = getQSConfig('group', {
|
const QS_CONFIG = getQSConfig('group', {
|
||||||
@@ -201,6 +209,55 @@ function InventoryHostGroupsList({ i18n }) {
|
|||||||
/>,
|
/>,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
<Kebabified>
|
||||||
|
{({ isKebabified }) =>
|
||||||
|
isKebabified ? (
|
||||||
|
<AdHocCommandsButton
|
||||||
|
adHocItems={selected}
|
||||||
|
apiModule={InventoriesAPI}
|
||||||
|
itemId={parseInt(invId, 10)}
|
||||||
|
>
|
||||||
|
{({ openAdHocCommands, isDisabled }) => (
|
||||||
|
<DropdownItem
|
||||||
|
key="run command"
|
||||||
|
onClick={openAdHocCommands}
|
||||||
|
isDisabled={itemCount === 0 || isDisabled}
|
||||||
|
>
|
||||||
|
{i18n._(t`Run command`)}
|
||||||
|
</DropdownItem>
|
||||||
|
)}
|
||||||
|
</AdHocCommandsButton>
|
||||||
|
) : (
|
||||||
|
<ToolbarItem>
|
||||||
|
<Tooltip
|
||||||
|
content={i18n._(
|
||||||
|
t`Select an inventory source by clicking the check box beside it. The inventory source can be a single group or host, a selection of multiple hosts, or a selection of multiple groups.`
|
||||||
|
)}
|
||||||
|
position="top"
|
||||||
|
key="adhoc"
|
||||||
|
>
|
||||||
|
<AdHocCommandsButton
|
||||||
|
css="margin-right: 20px"
|
||||||
|
adHocItems={selected}
|
||||||
|
apiModule={InventoriesAPI}
|
||||||
|
itemId={parseInt(invId, 10)}
|
||||||
|
>
|
||||||
|
{({ openAdHocCommands, isDisabled }) => (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
aria-label={i18n._(t`Run command`)}
|
||||||
|
onClick={openAdHocCommands}
|
||||||
|
isDisabled={itemCount === 0 || isDisabled}
|
||||||
|
>
|
||||||
|
{i18n._(t`Run command`)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</AdHocCommandsButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ToolbarItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Kebabified>,
|
||||||
<DisassociateButton
|
<DisassociateButton
|
||||||
key="disassociate"
|
key="disassociate"
|
||||||
onDisassociate={handleDisassociate}
|
onDisassociate={handleDisassociate}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
mountWithContexts,
|
mountWithContexts,
|
||||||
waitForElement,
|
waitForElement,
|
||||||
} from '../../../../testUtils/enzymeHelpers';
|
} from '../../../../testUtils/enzymeHelpers';
|
||||||
import { HostsAPI, InventoriesAPI } from '../../../api';
|
import { HostsAPI, InventoriesAPI, CredentialTypesAPI } from '../../../api';
|
||||||
import InventoryHostGroupsList from './InventoryHostGroupsList';
|
import InventoryHostGroupsList from './InventoryHostGroupsList';
|
||||||
|
|
||||||
jest.mock('../../../api');
|
jest.mock('../../../api');
|
||||||
@@ -80,6 +80,17 @@ describe('<InventoryHostGroupsList />', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: { module_name: { choices: [['module']] } },
|
||||||
|
POST: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
CredentialTypesAPI.read.mockResolvedValue({
|
||||||
|
data: { count: 1, results: [{ id: 1, name: 'cred' }] },
|
||||||
|
});
|
||||||
const history = createMemoryHistory({
|
const history = createMemoryHistory({
|
||||||
initialEntries: ['/inventories/inventory/1/hosts/3/groups'],
|
initialEntries: ['/inventories/inventory/1/hosts/3/groups'],
|
||||||
});
|
});
|
||||||
@@ -272,4 +283,11 @@ describe('<InventoryHostGroupsList />', () => {
|
|||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(wrapper.find('AlertModal ErrorDetail').length).toBe(1);
|
expect(wrapper.find('AlertModal ErrorDetail').length).toBe(1);
|
||||||
});
|
});
|
||||||
|
test('should render enabled ad hoc commands button', async () => {
|
||||||
|
await waitForElement(
|
||||||
|
wrapper,
|
||||||
|
'button[aria-label="Run command"]',
|
||||||
|
el => el.prop('disabled') === false
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { useParams, useLocation } from 'react-router-dom';
|
import { useParams, useLocation } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Tooltip,
|
||||||
|
DropdownItem,
|
||||||
|
ToolbarItem,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
||||||
import { InventoriesAPI, HostsAPI } from '../../../api';
|
import { InventoriesAPI, HostsAPI } from '../../../api';
|
||||||
|
|
||||||
@@ -12,6 +18,8 @@ import PaginatedDataList, {
|
|||||||
ToolbarAddButton,
|
ToolbarAddButton,
|
||||||
ToolbarDeleteButton,
|
ToolbarDeleteButton,
|
||||||
} from '../../../components/PaginatedDataList';
|
} from '../../../components/PaginatedDataList';
|
||||||
|
import { Kebabified } from '../../../contexts/Kebabified';
|
||||||
|
import AdHocCommandsButton from '../../../components/AdHocCommands/AdHocCommands';
|
||||||
import InventoryHostItem from './InventoryHostItem';
|
import InventoryHostItem from './InventoryHostItem';
|
||||||
|
|
||||||
const QS_CONFIG = getQSConfig('host', {
|
const QS_CONFIG = getQSConfig('host', {
|
||||||
@@ -149,6 +157,55 @@ function InventoryHostList({ i18n }) {
|
|||||||
/>,
|
/>,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
<Kebabified>
|
||||||
|
{({ isKebabified }) =>
|
||||||
|
isKebabified ? (
|
||||||
|
<AdHocCommandsButton
|
||||||
|
adHocItems={selected}
|
||||||
|
apiModule={InventoriesAPI}
|
||||||
|
itemId={parseInt(id, 10)}
|
||||||
|
>
|
||||||
|
{({ openAdHocCommands, isDisabled }) => (
|
||||||
|
<DropdownItem
|
||||||
|
key="run command"
|
||||||
|
onClick={openAdHocCommands}
|
||||||
|
isDisabled={hostCount === 0 || isDisabled}
|
||||||
|
>
|
||||||
|
{i18n._(t`Run command`)}
|
||||||
|
</DropdownItem>
|
||||||
|
)}
|
||||||
|
</AdHocCommandsButton>
|
||||||
|
) : (
|
||||||
|
<ToolbarItem>
|
||||||
|
<Tooltip
|
||||||
|
content={i18n._(
|
||||||
|
t`Select an inventory source by clicking the check box beside it. The inventory source can be a single host or a selection of multiple hosts.`
|
||||||
|
)}
|
||||||
|
position="top"
|
||||||
|
key="adhoc"
|
||||||
|
>
|
||||||
|
<AdHocCommandsButton
|
||||||
|
css="margin-right: 20px"
|
||||||
|
adHocItems={selected}
|
||||||
|
apiModule={InventoriesAPI}
|
||||||
|
itemId={parseInt(id, 10)}
|
||||||
|
>
|
||||||
|
{({ openAdHocCommands, isDisabled }) => (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
aria-label={i18n._(t`Run command`)}
|
||||||
|
onClick={openAdHocCommands}
|
||||||
|
isDisabled={hostCount === 0 || isDisabled}
|
||||||
|
>
|
||||||
|
{i18n._(t`Run command`)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</AdHocCommandsButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ToolbarItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Kebabified>,
|
||||||
<ToolbarDeleteButton
|
<ToolbarDeleteButton
|
||||||
key="delete"
|
key="delete"
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { InventoriesAPI, HostsAPI } from '../../../api';
|
import { InventoriesAPI, HostsAPI, CredentialTypesAPI } from '../../../api';
|
||||||
import {
|
import {
|
||||||
mountWithContexts,
|
mountWithContexts,
|
||||||
waitForElement,
|
waitForElement,
|
||||||
@@ -93,6 +93,17 @@ describe('<InventoryHostList />', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: { module_name: { choices: [['module']] } },
|
||||||
|
POST: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
CredentialTypesAPI.read.mockResolvedValue({
|
||||||
|
data: { count: 1, results: [{ id: 1, name: 'cred' }] },
|
||||||
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<InventoryHostList />);
|
wrapper = mountWithContexts(<InventoryHostList />);
|
||||||
});
|
});
|
||||||
@@ -293,4 +304,11 @@ describe('<InventoryHostList />', () => {
|
|||||||
});
|
});
|
||||||
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
|
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
|
||||||
});
|
});
|
||||||
|
test('should render enabled ad hoc commands button', async () => {
|
||||||
|
await waitForElement(
|
||||||
|
wrapper,
|
||||||
|
'button[aria-label="Run command"]',
|
||||||
|
el => el.prop('disabled') === false
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ import React, { useEffect, useCallback } from 'react';
|
|||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Button } from '@patternfly/react-core';
|
import {
|
||||||
|
Button,
|
||||||
|
Tooltip,
|
||||||
|
DropdownItem,
|
||||||
|
ToolbarItem,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
import DataListToolbar from '../../../components/DataListToolbar';
|
import DataListToolbar from '../../../components/DataListToolbar';
|
||||||
import PaginatedDataList from '../../../components/PaginatedDataList';
|
import PaginatedDataList from '../../../components/PaginatedDataList';
|
||||||
import SmartInventoryHostListItem from './SmartInventoryHostListItem';
|
import SmartInventoryHostListItem from './SmartInventoryHostListItem';
|
||||||
@@ -11,6 +16,8 @@ import useSelected from '../../../util/useSelected';
|
|||||||
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
||||||
import { InventoriesAPI } from '../../../api';
|
import { InventoriesAPI } from '../../../api';
|
||||||
import { Inventory } from '../../../types';
|
import { Inventory } from '../../../types';
|
||||||
|
import { Kebabified } from '../../../contexts/Kebabified';
|
||||||
|
import AdHocCommandsButton from '../../../components/AdHocCommands/AdHocCommands';
|
||||||
|
|
||||||
const QS_CONFIG = getQSConfig('host', {
|
const QS_CONFIG = getQSConfig('host', {
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -89,12 +96,55 @@ function SmartInventoryHostList({ i18n, inventory }) {
|
|||||||
additionalControls={
|
additionalControls={
|
||||||
inventory?.summary_fields?.user_capabilities?.adhoc
|
inventory?.summary_fields?.user_capabilities?.adhoc
|
||||||
? [
|
? [
|
||||||
<Button
|
<Kebabified>
|
||||||
aria-label={i18n._(t`Run commands`)}
|
{({ isKebabified }) =>
|
||||||
isDisabled={selected.length === 0}
|
isKebabified ? (
|
||||||
>
|
<AdHocCommandsButton
|
||||||
{i18n._(t`Run commands`)}
|
adHocItems={selected}
|
||||||
</Button>,
|
apiModule={InventoriesAPI}
|
||||||
|
itemId={parseInt(inventory.id, 10)}
|
||||||
|
>
|
||||||
|
{({ openAdHocCommands, isDisabled }) => (
|
||||||
|
<DropdownItem
|
||||||
|
key="run command"
|
||||||
|
onClick={openAdHocCommands}
|
||||||
|
isDisabled={count === 0 || isDisabled}
|
||||||
|
>
|
||||||
|
{i18n._(t`Run command`)}
|
||||||
|
</DropdownItem>
|
||||||
|
)}
|
||||||
|
</AdHocCommandsButton>
|
||||||
|
) : (
|
||||||
|
<ToolbarItem>
|
||||||
|
<Tooltip
|
||||||
|
content={i18n._(
|
||||||
|
t`Select an inventory source by clicking the check box beside it. The inventory source can be a single host or a selection of multiple hosts.`
|
||||||
|
)}
|
||||||
|
position="top"
|
||||||
|
key="adhoc"
|
||||||
|
>
|
||||||
|
<AdHocCommandsButton
|
||||||
|
css="margin-right: 20px"
|
||||||
|
adHocItems={selected}
|
||||||
|
apiModule={InventoriesAPI}
|
||||||
|
itemId={parseInt(inventory.id, 10)}
|
||||||
|
>
|
||||||
|
{({ openAdHocCommands, isDisabled }) => (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
aria-label={i18n._(t`Run command`)}
|
||||||
|
onClick={openAdHocCommands}
|
||||||
|
isDisabled={count === 0 || isDisabled}
|
||||||
|
>
|
||||||
|
{i18n._(t`Run command`)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</AdHocCommandsButton>
|
||||||
|
</Tooltip>
|
||||||
|
</ToolbarItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Kebabified>,
|
||||||
]
|
]
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { InventoriesAPI } from '../../../api';
|
import { InventoriesAPI, CredentialTypesAPI } from '../../../api';
|
||||||
import {
|
import {
|
||||||
mountWithContexts,
|
mountWithContexts,
|
||||||
waitForElement,
|
waitForElement,
|
||||||
@@ -12,125 +12,107 @@ import mockHosts from '../shared/data.hosts.json';
|
|||||||
jest.mock('../../../api');
|
jest.mock('../../../api');
|
||||||
|
|
||||||
describe('<SmartInventoryHostList />', () => {
|
describe('<SmartInventoryHostList />', () => {
|
||||||
describe('User has adhoc permissions', () => {
|
// describe('User has adhoc permissions', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
const clonedInventory = {
|
const clonedInventory = {
|
||||||
...mockInventory,
|
...mockInventory,
|
||||||
summary_fields: {
|
summary_fields: {
|
||||||
...mockInventory.summary_fields,
|
...mockInventory.summary_fields,
|
||||||
user_capabilities: {
|
user_capabilities: {
|
||||||
...mockInventory.summary_fields.user_capabilities,
|
...mockInventory.summary_fields.user_capabilities,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
InventoriesAPI.readHosts.mockResolvedValue({
|
||||||
|
data: mockHosts,
|
||||||
|
});
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: { module_name: { choices: [['module']] } },
|
||||||
|
POST: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
InventoriesAPI.readHosts.mockResolvedValue({
|
|
||||||
data: mockHosts,
|
|
||||||
});
|
|
||||||
await act(async () => {
|
|
||||||
wrapper = mountWithContexts(
|
|
||||||
<SmartInventoryHostList inventory={clonedInventory} />
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
|
||||||
});
|
});
|
||||||
|
CredentialTypesAPI.read.mockResolvedValue({
|
||||||
afterAll(() => {
|
data: { count: 1, results: [{ id: 1, name: 'cred' }] },
|
||||||
jest.clearAllMocks();
|
|
||||||
wrapper.unmount();
|
|
||||||
});
|
});
|
||||||
|
await act(async () => {
|
||||||
test('initially renders successfully', () => {
|
wrapper = mountWithContexts(
|
||||||
expect(wrapper.find('SmartInventoryHostList').length).toBe(1);
|
<SmartInventoryHostList inventory={clonedInventory}>
|
||||||
});
|
{({ openAdHocCommands, isDisabled }) => (
|
||||||
|
<button
|
||||||
test('should fetch hosts from api and render them in the list', () => {
|
type="button"
|
||||||
expect(InventoriesAPI.readHosts).toHaveBeenCalled();
|
variant="secondary"
|
||||||
expect(wrapper.find('SmartInventoryHostListItem').length).toBe(3);
|
className="run-command"
|
||||||
});
|
onClick={openAdHocCommands}
|
||||||
|
disabled={isDisabled}
|
||||||
test('should disable run commands button when no hosts are selected', () => {
|
/>
|
||||||
wrapper.find('DataListCheck').forEach(el => {
|
)}
|
||||||
expect(el.props().checked).toBe(false);
|
</SmartInventoryHostList>
|
||||||
});
|
|
||||||
const runCommandsButton = wrapper.find(
|
|
||||||
'button[aria-label="Run commands"]'
|
|
||||||
);
|
);
|
||||||
expect(runCommandsButton.length).toBe(1);
|
|
||||||
expect(runCommandsButton.prop('disabled')).toEqual(true);
|
|
||||||
});
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
|
});
|
||||||
|
|
||||||
test('should enable run commands button when at least one host is selected', () => {
|
afterAll(() => {
|
||||||
act(() => {
|
jest.clearAllMocks();
|
||||||
wrapper.find('DataListCheck[id="select-host-2"]').invoke('onChange')(
|
wrapper.unmount();
|
||||||
true
|
});
|
||||||
);
|
|
||||||
});
|
test('initially renders successfully', () => {
|
||||||
wrapper.update();
|
expect(wrapper.find('SmartInventoryHostList').length).toBe(1);
|
||||||
const runCommandsButton = wrapper.find(
|
});
|
||||||
'button[aria-label="Run commands"]'
|
|
||||||
);
|
test('should fetch hosts from api and render them in the list', () => {
|
||||||
expect(runCommandsButton.prop('disabled')).toEqual(false);
|
expect(InventoriesAPI.readHosts).toHaveBeenCalled();
|
||||||
|
expect(wrapper.find('SmartInventoryHostListItem').length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should have run command button', () => {
|
||||||
|
wrapper.find('DataListCheck').forEach(el => {
|
||||||
|
expect(el.props().checked).toBe(false);
|
||||||
});
|
});
|
||||||
|
const runCommandsButton = wrapper.find('button[aria-label="Run command"]');
|
||||||
|
expect(runCommandsButton.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('should select and deselect all items', async () => {
|
test('should select and deselect all items', async () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
wrapper.find('DataListToolbar').invoke('onSelectAll')(true);
|
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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
wrapper.update();
|
||||||
test('should show content error when api throws an error', async () => {
|
wrapper.find('DataListCheck').forEach(el => {
|
||||||
InventoriesAPI.readHosts.mockImplementation(() =>
|
expect(el.props().checked).toEqual(true);
|
||||||
Promise.reject(new Error())
|
});
|
||||||
);
|
act(() => {
|
||||||
await act(async () => {
|
wrapper.find('DataListToolbar').invoke('onSelectAll')(false);
|
||||||
wrapper = mountWithContexts(
|
});
|
||||||
<SmartInventoryHostList inventory={mockInventory} />
|
wrapper.update();
|
||||||
);
|
wrapper.find('DataListCheck').forEach(el => {
|
||||||
});
|
expect(el.props().checked).toEqual(false);
|
||||||
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('User does not have adhoc permissions', () => {
|
test('should render enabled ad hoc commands button', async () => {
|
||||||
let wrapper;
|
await waitForElement(
|
||||||
const clonedInventory = {
|
wrapper,
|
||||||
...mockInventory,
|
'button[aria-label="Run command"]',
|
||||||
summary_fields: {
|
el => el.prop('disabled') === false
|
||||||
user_capabilities: {
|
);
|
||||||
adhoc: false,
|
});
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
test('should hide run commands button', async () => {
|
test('should show content error when api throws an error', async () => {
|
||||||
InventoriesAPI.readHosts.mockResolvedValue({
|
InventoriesAPI.readHosts.mockImplementation(() =>
|
||||||
data: { results: [], count: 0 },
|
Promise.reject(new Error())
|
||||||
});
|
);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<SmartInventoryHostList inventory={clonedInventory} />
|
<SmartInventoryHostList inventory={mockInventory} />
|
||||||
);
|
|
||||||
});
|
|
||||||
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();
|
|
||||||
});
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user