mirror of
https://github.com/ansible/awx.git
synced 2026-05-09 02:17:37 -02:30
Merge pull request #10510 from AlexSCorey/10440-8660-fix
Renders Command Module in job detail view and improves Ad Hoc Commands execution permissions SUMMARY This closes #10440 and #8660. On the job details view we now render the command module name, and the arguments. We also remove the run command button for user that do not have permission to launch a ad hoc command. ISSUE TYPE -enhancement COMPONENT NAME UI AWX VERSION ADDITIONAL INFORMATION Reviewed-by: Kersom <None> Reviewed-by: Sarah Akus <sakus@redhat.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import { useHistory, useParams } from 'react-router-dom';
|
|||||||
|
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, DropdownItem } from '@patternfly/react-core';
|
import { Button, DropdownItem, Tooltip } from '@patternfly/react-core';
|
||||||
|
|
||||||
import useRequest, { useDismissableError } from 'hooks/useRequest';
|
import useRequest, { useDismissableError } from 'hooks/useRequest';
|
||||||
import { InventoriesAPI, CredentialTypesAPI } from 'api';
|
import { InventoriesAPI, CredentialTypesAPI } from 'api';
|
||||||
@@ -14,7 +14,12 @@ import ErrorDetail from '../ErrorDetail';
|
|||||||
import AdHocCommandsWizard from './AdHocCommandsWizard';
|
import AdHocCommandsWizard from './AdHocCommandsWizard';
|
||||||
import ContentError from '../ContentError';
|
import ContentError from '../ContentError';
|
||||||
|
|
||||||
function AdHocCommands({ adHocItems, hasListItems, onLaunchLoading }) {
|
function AdHocCommands({
|
||||||
|
adHocItems,
|
||||||
|
hasListItems,
|
||||||
|
onLaunchLoading,
|
||||||
|
moduleOptions,
|
||||||
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
@@ -35,29 +40,21 @@ function AdHocCommands({ adHocItems, hasListItems, onLaunchLoading }) {
|
|||||||
}, [isKebabified, isWizardOpen, onKebabModalChange]);
|
}, [isKebabified, isWizardOpen, onKebabModalChange]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
result: {
|
result: { credentialTypeId, organizationId },
|
||||||
moduleOptions,
|
|
||||||
credentialTypeId,
|
|
||||||
isAdHocDisabled,
|
|
||||||
organizationId,
|
|
||||||
},
|
|
||||||
request: fetchData,
|
request: fetchData,
|
||||||
error: fetchError,
|
error: fetchError,
|
||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const [options, { data }, cred] = await Promise.all([
|
const [{ data }, cred] = await Promise.all([
|
||||||
InventoriesAPI.readAdHocOptions(id),
|
|
||||||
InventoriesAPI.readDetail(id),
|
InventoriesAPI.readDetail(id),
|
||||||
CredentialTypesAPI.read({ namespace: 'ssh' }),
|
CredentialTypesAPI.read({ namespace: 'ssh' }),
|
||||||
]);
|
]);
|
||||||
return {
|
return {
|
||||||
moduleOptions: options.data.actions.GET.module_name.choices,
|
|
||||||
credentialTypeId: cred.data.results[0].id,
|
credentialTypeId: cred.data.results[0].id,
|
||||||
isAdHocDisabled: !options.data.actions.POST,
|
|
||||||
organizationId: data.organization,
|
organizationId: data.organization,
|
||||||
};
|
};
|
||||||
}, [id]),
|
}, [id]),
|
||||||
{ moduleOptions: [], isAdHocDisabled: true, organizationId: null }
|
{ organizationId: null }
|
||||||
);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
@@ -77,10 +74,6 @@ function AdHocCommands({ adHocItems, hasListItems, onLaunchLoading }) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const { error, dismissError } = useDismissableError(
|
|
||||||
launchError || fetchError
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSubmit = async values => {
|
const handleSubmit = async values => {
|
||||||
const { credential, execution_environment, ...remainingValues } = values;
|
const { credential, execution_environment, ...remainingValues } = values;
|
||||||
const newCredential = credential[0].id;
|
const newCredential = credential[0].id;
|
||||||
@@ -97,6 +90,10 @@ function AdHocCommands({ adHocItems, hasListItems, onLaunchLoading }) {
|
|||||||
onLaunchLoading,
|
onLaunchLoading,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const { error, dismissError } = useDismissableError(
|
||||||
|
launchError || fetchError
|
||||||
|
);
|
||||||
|
|
||||||
if (error && isWizardOpen) {
|
if (error && isWizardOpen) {
|
||||||
return (
|
return (
|
||||||
<AlertModal
|
<AlertModal
|
||||||
@@ -123,27 +120,29 @@ function AdHocCommands({ adHocItems, hasListItems, onLaunchLoading }) {
|
|||||||
// render buttons for drop down and for toolbar
|
// render buttons for drop down and for toolbar
|
||||||
// if modal is open render the modal
|
// if modal is open render the modal
|
||||||
<>
|
<>
|
||||||
{isKebabified ? (
|
<Tooltip content={t`Run ad hoc command`}>
|
||||||
<DropdownItem
|
{isKebabified ? (
|
||||||
key="cancel-job"
|
<DropdownItem
|
||||||
isDisabled={isAdHocDisabled || !hasListItems}
|
key="cancel-job"
|
||||||
component="button"
|
isDisabled={!hasListItems}
|
||||||
aria-label={t`Run Command`}
|
component="button"
|
||||||
onClick={() => setIsWizardOpen(true)}
|
aria-label={t`Run Command`}
|
||||||
>
|
onClick={() => setIsWizardOpen(true)}
|
||||||
{t`Run Command`}
|
>
|
||||||
</DropdownItem>
|
{t`Run Command`}
|
||||||
) : (
|
</DropdownItem>
|
||||||
<Button
|
) : (
|
||||||
ouiaId="run-command-button"
|
<Button
|
||||||
variant="secondary"
|
ouiaId="run-command-button"
|
||||||
aria-label={t`Run Command`}
|
variant="secondary"
|
||||||
onClick={() => setIsWizardOpen(true)}
|
aria-label={t`Run Command`}
|
||||||
isDisabled={isAdHocDisabled || !hasListItems}
|
onClick={() => setIsWizardOpen(true)}
|
||||||
>
|
isDisabled={!hasListItems}
|
||||||
{t`Run Command`}
|
>
|
||||||
</Button>
|
{t`Run Command`}
|
||||||
)}
|
</Button>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
{isWizardOpen && (
|
{isWizardOpen && (
|
||||||
<AdHocCommandsWizard
|
<AdHocCommandsWizard
|
||||||
|
|||||||
@@ -47,21 +47,6 @@ describe('<AdHocCommands />', () => {
|
|||||||
BRAND_NAME: 'AWX',
|
BRAND_NAME: 'AWX',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
actions: {
|
|
||||||
GET: {
|
|
||||||
module_name: {
|
|
||||||
choices: [
|
|
||||||
['command', 'command'],
|
|
||||||
['shell', 'shell'],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
POST: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
CredentialTypesAPI.read.mockResolvedValue({
|
CredentialTypesAPI.read.mockResolvedValue({
|
||||||
data: { count: 1, results: [{ id: 1, name: 'cred' }] },
|
data: { count: 1, results: [{ id: 1, name: 'cred' }] },
|
||||||
});
|
});
|
||||||
@@ -95,21 +80,6 @@ describe('<AdHocCommands />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should open the wizard', async () => {
|
test('should open the wizard', async () => {
|
||||||
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
actions: {
|
|
||||||
GET: {
|
|
||||||
module_name: {
|
|
||||||
choices: [
|
|
||||||
['command', 'command'],
|
|
||||||
['foo', 'foo'],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
verbosity: { choices: [[1], [2]] },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
InventoriesAPI.readDetail.mockResolvedValue({ data: { organization: 1 } });
|
InventoriesAPI.readDetail.mockResolvedValue({ data: { organization: 1 } });
|
||||||
CredentialTypesAPI.read.mockResolvedValue({
|
CredentialTypesAPI.read.mockResolvedValue({
|
||||||
data: { results: [{ id: 1 }] },
|
data: { results: [{ id: 1 }] },
|
||||||
@@ -126,12 +96,21 @@ describe('<AdHocCommands />', () => {
|
|||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<AdHocCommands
|
<AdHocCommands
|
||||||
|
moduleOptions={[
|
||||||
|
['command', 'command'],
|
||||||
|
['foo', 'foo'],
|
||||||
|
]}
|
||||||
adHocItems={adHocItems}
|
adHocItems={adHocItems}
|
||||||
hasListItems
|
hasListItems
|
||||||
onLaunchLoading={() => jest.fn()}
|
onLaunchLoading={() => jest.fn()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
await waitForElement(
|
||||||
|
wrapper,
|
||||||
|
'button[aria-label="Run Command"]',
|
||||||
|
el => el.length === 1
|
||||||
|
);
|
||||||
await act(async () =>
|
await act(async () =>
|
||||||
wrapper.find('button[aria-label="Run Command"]').prop('onClick')()
|
wrapper.find('button[aria-label="Run Command"]').prop('onClick')()
|
||||||
);
|
);
|
||||||
@@ -167,18 +146,26 @@ describe('<AdHocCommands />', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue({
|
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue({
|
||||||
data: { actions: { GET: {} } },
|
data: { actions: { GET: {}, POST: {} } },
|
||||||
});
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<AdHocCommands
|
<AdHocCommands
|
||||||
adHocItems={adHocItems}
|
adHocItems={adHocItems}
|
||||||
hasListItems
|
hasListItems
|
||||||
|
moduleOptions={[
|
||||||
|
['command', 'command'],
|
||||||
|
['foo', 'foo'],
|
||||||
|
]}
|
||||||
onLaunchLoading={() => jest.fn()}
|
onLaunchLoading={() => jest.fn()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
await waitForElement(
|
||||||
|
wrapper,
|
||||||
|
'button[aria-label="Run Command"]',
|
||||||
|
el => el.length === 1
|
||||||
|
);
|
||||||
await act(async () =>
|
await act(async () =>
|
||||||
wrapper.find('button[aria-label="Run Command"]').prop('onClick')()
|
wrapper.find('button[aria-label="Run Command"]').prop('onClick')()
|
||||||
);
|
);
|
||||||
@@ -277,23 +264,6 @@ describe('<AdHocCommands />', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
actions: {
|
|
||||||
GET: {
|
|
||||||
module_name: {
|
|
||||||
choices: [
|
|
||||||
['command', 'command'],
|
|
||||||
['foo', 'foo'],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
verbosity: {
|
|
||||||
choices: [[1], [2]],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
InventoriesAPI.readDetail.mockResolvedValue({
|
InventoriesAPI.readDetail.mockResolvedValue({
|
||||||
data: { organization: 1 },
|
data: { organization: 1 },
|
||||||
});
|
});
|
||||||
@@ -334,18 +304,26 @@ describe('<AdHocCommands />', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue({
|
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue({
|
||||||
data: { actions: { GET: {} } },
|
data: { actions: { GET: {}, POST: {} } },
|
||||||
});
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<AdHocCommands
|
<AdHocCommands
|
||||||
adHocItems={adHocItems}
|
adHocItems={adHocItems}
|
||||||
|
moduleOptions={[
|
||||||
|
['command', 'command'],
|
||||||
|
['foo', 'foo'],
|
||||||
|
]}
|
||||||
hasListItems
|
hasListItems
|
||||||
onLaunchLoading={() => jest.fn()}
|
onLaunchLoading={() => jest.fn()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
await waitForElement(
|
||||||
|
wrapper,
|
||||||
|
'button[aria-label="Run Command"]',
|
||||||
|
el => el.length === 1
|
||||||
|
);
|
||||||
await act(async () =>
|
await act(async () =>
|
||||||
wrapper.find('button[aria-label="Run Command"]').prop('onClick')()
|
wrapper.find('button[aria-label="Run Command"]').prop('onClick')()
|
||||||
);
|
);
|
||||||
@@ -424,45 +402,18 @@ describe('<AdHocCommands />', () => {
|
|||||||
await waitForElement(wrapper, 'ErrorDetail', el => el.length > 0);
|
await waitForElement(wrapper, 'ErrorDetail', el => el.length > 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should disable run command button due to permissions', async () => {
|
|
||||||
InventoriesAPI.readHosts.mockResolvedValue({
|
|
||||||
data: { results: [], count: 0 },
|
|
||||||
});
|
|
||||||
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
actions: {
|
|
||||||
GET: { module_name: { choices: [['module']] } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await act(async () => {
|
|
||||||
wrapper = mountWithContexts(
|
|
||||||
<AdHocCommands
|
|
||||||
adHocItems={adHocItems}
|
|
||||||
hasListItems
|
|
||||||
onLaunchLoading={() => jest.fn()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
|
||||||
const runCommandsButton = wrapper.find('button[aria-label="Run Command"]');
|
|
||||||
expect(runCommandsButton.prop('disabled')).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should disable run command button due to lack of list items', async () => {
|
test('should disable run command button due to lack of list items', async () => {
|
||||||
InventoriesAPI.readHosts.mockResolvedValue({
|
InventoriesAPI.readHosts.mockResolvedValue({
|
||||||
data: { results: [], count: 0 },
|
data: { results: [], count: 0 },
|
||||||
});
|
});
|
||||||
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
actions: {
|
|
||||||
GET: { module_name: { choices: [['module']] } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<AdHocCommands
|
<AdHocCommands
|
||||||
|
moduleOptions={[
|
||||||
|
['command', 'command'],
|
||||||
|
['foo', 'foo'],
|
||||||
|
]}
|
||||||
adHocItems={adHocItems}
|
adHocItems={adHocItems}
|
||||||
hasListItems={false}
|
hasListItems={false}
|
||||||
onLaunchLoading={() => jest.fn()}
|
onLaunchLoading={() => jest.fn()}
|
||||||
@@ -475,7 +426,7 @@ describe('<AdHocCommands />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should open alert modal when error on fetching data', async () => {
|
test('should open alert modal when error on fetching data', async () => {
|
||||||
InventoriesAPI.readAdHocOptions.mockRejectedValue(
|
InventoriesAPI.readDetail.mockRejectedValue(
|
||||||
new Error({
|
new Error({
|
||||||
response: {
|
response: {
|
||||||
config: {
|
config: {
|
||||||
@@ -490,6 +441,10 @@ describe('<AdHocCommands />', () => {
|
|||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<AdHocCommands
|
<AdHocCommands
|
||||||
|
moduleOptions={[
|
||||||
|
['command', 'command'],
|
||||||
|
['foo', 'foo'],
|
||||||
|
]}
|
||||||
adHocItems={adHocItems}
|
adHocItems={adHocItems}
|
||||||
hasListItems
|
hasListItems
|
||||||
onLaunchLoading={() => jest.fn()}
|
onLaunchLoading={() => jest.fn()}
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ function InventoryGroupHostList() {
|
|||||||
actions,
|
actions,
|
||||||
relatedSearchableKeys,
|
relatedSearchableKeys,
|
||||||
searchableKeys,
|
searchableKeys,
|
||||||
|
moduleOptions,
|
||||||
|
isAdHocDisabled,
|
||||||
},
|
},
|
||||||
error: contentError,
|
error: contentError,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -50,12 +52,15 @@ function InventoryGroupHostList() {
|
|||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const params = parseQueryString(QS_CONFIG, location.search);
|
const params = parseQueryString(QS_CONFIG, location.search);
|
||||||
const [response, actionsResponse] = await Promise.all([
|
const [response, actionsResponse, options] = await Promise.all([
|
||||||
GroupsAPI.readAllHosts(groupId, params),
|
GroupsAPI.readAllHosts(groupId, params),
|
||||||
InventoriesAPI.readHostsOptions(inventoryId),
|
InventoriesAPI.readHostsOptions(inventoryId),
|
||||||
|
InventoriesAPI.readAdHocOptions(inventoryId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
moduleOptions: options.data.actions.GET.module_name.choices,
|
||||||
|
isAdHocDisabled: !options.data.actions.POST,
|
||||||
hosts: response.data.results,
|
hosts: response.data.results,
|
||||||
hostCount: response.data.count,
|
hostCount: response.data.count,
|
||||||
actions: actionsResponse.data.actions,
|
actions: actionsResponse.data.actions,
|
||||||
@@ -68,6 +73,8 @@ function InventoryGroupHostList() {
|
|||||||
};
|
};
|
||||||
}, [groupId, inventoryId, location.search]),
|
}, [groupId, inventoryId, location.search]),
|
||||||
{
|
{
|
||||||
|
moduleOptions: [],
|
||||||
|
isAdHocDisabled: true,
|
||||||
hosts: [],
|
hosts: [],
|
||||||
hostCount: 0,
|
hostCount: 0,
|
||||||
actions: {},
|
actions: {},
|
||||||
@@ -225,11 +232,16 @@ function InventoryGroupHostList() {
|
|||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
additionalControls={[
|
additionalControls={[
|
||||||
...(canAdd ? [addButton] : []),
|
...(canAdd ? [addButton] : []),
|
||||||
<AdHocCommands
|
...(!isAdHocDisabled
|
||||||
adHocItems={selected}
|
? [
|
||||||
hasListItems={hostCount > 0}
|
<AdHocCommands
|
||||||
onLaunchLoading={setIsAdHocLaunchLoading}
|
adHocItems={selected}
|
||||||
/>,
|
hasListItems={hostCount > 0}
|
||||||
|
moduleOptions={moduleOptions}
|
||||||
|
onLaunchLoading={setIsAdHocLaunchLoading}
|
||||||
|
/>,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
<DisassociateButton
|
<DisassociateButton
|
||||||
key="disassociate"
|
key="disassociate"
|
||||||
onDisassociate={handleDisassociate}
|
onDisassociate={handleDisassociate}
|
||||||
|
|||||||
@@ -35,6 +35,21 @@ describe('<InventoryGroupHostList />', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: {
|
||||||
|
module_name: {
|
||||||
|
choices: [
|
||||||
|
['command', 'command'],
|
||||||
|
['shell', 'shell'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
POST: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<InventoryGroupHostList />);
|
wrapper = mountWithContexts(<InventoryGroupHostList />);
|
||||||
});
|
});
|
||||||
@@ -52,6 +67,7 @@ describe('<InventoryGroupHostList />', () => {
|
|||||||
test('should fetch inventory group hosts from api and render them in the list', () => {
|
test('should fetch inventory group hosts from api and render them in the list', () => {
|
||||||
expect(GroupsAPI.readAllHosts).toHaveBeenCalled();
|
expect(GroupsAPI.readAllHosts).toHaveBeenCalled();
|
||||||
expect(InventoriesAPI.readHostsOptions).toHaveBeenCalled();
|
expect(InventoriesAPI.readHostsOptions).toHaveBeenCalled();
|
||||||
|
expect(InventoriesAPI.readAdHocOptions).toHaveBeenCalled();
|
||||||
expect(wrapper.find('InventoryGroupHostListItem').length).toBe(3);
|
expect(wrapper.find('InventoryGroupHostListItem').length).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -95,7 +111,7 @@ describe('<InventoryGroupHostList />', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show add dropdown button according to permissions', async () => {
|
test('should show add dropdown button and Run Commands according to permissions', async () => {
|
||||||
expect(wrapper.find('AddDropDownButton').length).toBe(1);
|
expect(wrapper.find('AddDropDownButton').length).toBe(1);
|
||||||
InventoriesAPI.readHostsOptions.mockResolvedValueOnce({
|
InventoriesAPI.readHostsOptions.mockResolvedValueOnce({
|
||||||
data: {
|
data: {
|
||||||
@@ -109,6 +125,7 @@ describe('<InventoryGroupHostList />', () => {
|
|||||||
});
|
});
|
||||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
expect(wrapper.find('AddDropDownButton').length).toBe(0);
|
expect(wrapper.find('AddDropDownButton').length).toBe(0);
|
||||||
|
expect(wrapper.find('AdHocCommands').length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('expected api calls are made for multi-delete', async () => {
|
test('expected api calls are made for multi-delete', async () => {
|
||||||
@@ -271,4 +288,24 @@ describe('<InventoryGroupHostList />', () => {
|
|||||||
});
|
});
|
||||||
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
|
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
|
||||||
});
|
});
|
||||||
|
test('should not render ad hoc commands button', async () => {
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: {
|
||||||
|
module_name: {
|
||||||
|
choices: [
|
||||||
|
['command', 'command'],
|
||||||
|
['shell', 'shell'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<InventoryGroupHostList />);
|
||||||
|
});
|
||||||
|
expect(wrapper.find('AdHocCommands')).toHaveLength(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ function InventoryGroupsList() {
|
|||||||
actions,
|
actions,
|
||||||
relatedSearchableKeys,
|
relatedSearchableKeys,
|
||||||
searchableKeys,
|
searchableKeys,
|
||||||
|
moduleOptions,
|
||||||
|
isAdHocDisabled,
|
||||||
},
|
},
|
||||||
error: contentError,
|
error: contentError,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -45,12 +47,15 @@ function InventoryGroupsList() {
|
|||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const params = parseQueryString(QS_CONFIG, location.search);
|
const params = parseQueryString(QS_CONFIG, location.search);
|
||||||
const [response, groupOptions] = await Promise.all([
|
const [response, groupOptions, options] = await Promise.all([
|
||||||
InventoriesAPI.readGroups(inventoryId, params),
|
InventoriesAPI.readGroups(inventoryId, params),
|
||||||
InventoriesAPI.readGroupsOptions(inventoryId),
|
InventoriesAPI.readGroupsOptions(inventoryId),
|
||||||
|
InventoriesAPI.readAdHocOptions(inventoryId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
moduleOptions: options.data.actions.GET.module_name.choices,
|
||||||
|
isAdHocDisabled: !options.data.actions.POST,
|
||||||
groups: response.data.results,
|
groups: response.data.results,
|
||||||
groupCount: response.data.count,
|
groupCount: response.data.count,
|
||||||
actions: groupOptions.data.actions,
|
actions: groupOptions.data.actions,
|
||||||
@@ -68,6 +73,8 @@ function InventoryGroupsList() {
|
|||||||
actions: {},
|
actions: {},
|
||||||
relatedSearchableKeys: [],
|
relatedSearchableKeys: [],
|
||||||
searchableKeys: [],
|
searchableKeys: [],
|
||||||
|
moduleOptions: [],
|
||||||
|
isAdHocDisabled: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -171,22 +178,29 @@ function InventoryGroupsList() {
|
|||||||
/>,
|
/>,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
<AdHocCommands
|
...(!isAdHocDisabled
|
||||||
adHocItems={selected}
|
? [
|
||||||
hasListItems={groupCount > 0}
|
<AdHocCommands
|
||||||
onLaunchLoading={setIsAdHocLaunchLoading}
|
adHocItems={selected}
|
||||||
/>,
|
hasListItems={groupCount > 0}
|
||||||
|
onLaunchLoading={setIsAdHocLaunchLoading}
|
||||||
|
moduleOptions={moduleOptions}
|
||||||
|
/>,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
<Tooltip content={renderTooltip()} position="top" key="delete">
|
<Tooltip content={renderTooltip()} position="top" key="delete">
|
||||||
<InventoryGroupsDeleteModal
|
<div>
|
||||||
groups={selected}
|
<InventoryGroupsDeleteModal
|
||||||
isDisabled={
|
groups={selected}
|
||||||
selected.length === 0 || selected.some(cannotDelete)
|
isDisabled={
|
||||||
}
|
selected.length === 0 || selected.some(cannotDelete)
|
||||||
onAfterDelete={() => {
|
}
|
||||||
fetchData();
|
onAfterDelete={() => {
|
||||||
clearSelected();
|
fetchData();
|
||||||
}}
|
clearSelected();
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Tooltip>,
|
</Tooltip>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -76,6 +76,21 @@ describe('<InventoryGroupsList />', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: {
|
||||||
|
module_name: {
|
||||||
|
choices: [
|
||||||
|
['command', 'command'],
|
||||||
|
['shell', 'shell'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
POST: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
const history = createMemoryHistory({
|
const history = createMemoryHistory({
|
||||||
initialEntries: ['/inventories/inventory/3/groups'],
|
initialEntries: ['/inventories/inventory/3/groups'],
|
||||||
});
|
});
|
||||||
@@ -86,7 +101,12 @@ describe('<InventoryGroupsList />', () => {
|
|||||||
</Route>,
|
</Route>,
|
||||||
{
|
{
|
||||||
context: {
|
context: {
|
||||||
router: { history, route: { location: history.location } },
|
router: {
|
||||||
|
history,
|
||||||
|
route: {
|
||||||
|
location: history.location,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -103,6 +123,10 @@ describe('<InventoryGroupsList />', () => {
|
|||||||
expect(wrapper.find('InventoryGroupItem').length).toBe(3);
|
expect(wrapper.find('InventoryGroupItem').length).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should render Run Commands button', async () => {
|
||||||
|
expect(wrapper.find('AdHocCommands')).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('should check and uncheck the row item', async () => {
|
test('should check and uncheck the row item', async () => {
|
||||||
expect(
|
expect(
|
||||||
wrapper
|
wrapper
|
||||||
@@ -165,6 +189,27 @@ describe('<InventoryGroupsList />', () => {
|
|||||||
expect(el.find('input').props().checked).toBe(false);
|
expect(el.find('input').props().checked).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not render ad hoc commands button', async () => {
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: {
|
||||||
|
module_name: {
|
||||||
|
choices: [
|
||||||
|
['command', 'command'],
|
||||||
|
['shell', 'shell'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<InventoryGroupsList />);
|
||||||
|
});
|
||||||
|
expect(wrapper.find('AdHocCommands')).toHaveLength(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('<InventoryGroupsList/> error handling', () => {
|
describe('<InventoryGroupsList/> error handling', () => {
|
||||||
@@ -185,6 +230,21 @@ describe('<InventoryGroupsList/> error handling', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: {
|
||||||
|
module_name: {
|
||||||
|
choices: [
|
||||||
|
['command', 'command'],
|
||||||
|
['shell', 'shell'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
POST: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
GroupsAPI.destroy.mockRejectedValue(
|
GroupsAPI.destroy.mockRejectedValue(
|
||||||
new Error({
|
new Error({
|
||||||
response: {
|
response: {
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ function InventoryHostGroupsList() {
|
|||||||
actions,
|
actions,
|
||||||
relatedSearchableKeys,
|
relatedSearchableKeys,
|
||||||
searchableKeys,
|
searchableKeys,
|
||||||
|
moduleOptions,
|
||||||
|
isAdHocDisabled,
|
||||||
},
|
},
|
||||||
error: contentError,
|
error: contentError,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -54,12 +56,16 @@ function InventoryHostGroupsList() {
|
|||||||
data: { count, results },
|
data: { count, results },
|
||||||
},
|
},
|
||||||
hostGroupOptions,
|
hostGroupOptions,
|
||||||
|
adHocOptions,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
HostsAPI.readAllGroups(hostId, params),
|
HostsAPI.readAllGroups(hostId, params),
|
||||||
HostsAPI.readGroupsOptions(hostId),
|
HostsAPI.readGroupsOptions(hostId),
|
||||||
|
InventoriesAPI.readAdHocOptions(invId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
moduleOptions: adHocOptions.data.actions.GET.module_name.choices,
|
||||||
|
isAdHocDisabled: !adHocOptions.data.actions.POST,
|
||||||
groups: results,
|
groups: results,
|
||||||
itemCount: count,
|
itemCount: count,
|
||||||
actions: hostGroupOptions.data.actions,
|
actions: hostGroupOptions.data.actions,
|
||||||
@@ -77,6 +83,8 @@ function InventoryHostGroupsList() {
|
|||||||
actions: {},
|
actions: {},
|
||||||
relatedSearchableKeys: [],
|
relatedSearchableKeys: [],
|
||||||
searchableKeys: [],
|
searchableKeys: [],
|
||||||
|
moduleOptions: [],
|
||||||
|
isAdHocDisabled: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -209,11 +217,16 @@ function InventoryHostGroupsList() {
|
|||||||
/>,
|
/>,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
<AdHocCommands
|
...(!isAdHocDisabled
|
||||||
adHocItems={selected}
|
? [
|
||||||
hasListItems={itemCount > 0}
|
<AdHocCommands
|
||||||
onLaunchLoading={setIsAdHocLaunchLoading}
|
adHocItems={selected}
|
||||||
/>,
|
hasListItems={itemCount > 0}
|
||||||
|
moduleOptions={moduleOptions}
|
||||||
|
onLaunchLoading={setIsAdHocLaunchLoading}
|
||||||
|
/>,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
<DisassociateButton
|
<DisassociateButton
|
||||||
key="disassociate"
|
key="disassociate"
|
||||||
onDisassociate={handleDisassociate}
|
onDisassociate={handleDisassociate}
|
||||||
|
|||||||
@@ -80,6 +80,21 @@ describe('<InventoryHostGroupsList />', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: {
|
||||||
|
module_name: {
|
||||||
|
choices: [
|
||||||
|
['command', 'command'],
|
||||||
|
['shell', 'shell'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
POST: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
const history = createMemoryHistory({
|
const history = createMemoryHistory({
|
||||||
initialEntries: ['/inventories/inventory/1/hosts/3/groups'],
|
initialEntries: ['/inventories/inventory/1/hosts/3/groups'],
|
||||||
});
|
});
|
||||||
@@ -111,6 +126,10 @@ describe('<InventoryHostGroupsList />', () => {
|
|||||||
expect(wrapper.find('InventoryHostGroupItem').length).toBe(3);
|
expect(wrapper.find('InventoryHostGroupItem').length).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should render Run Commands button', async () => {
|
||||||
|
expect(wrapper.find('AdHocCommands')).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('should check and uncheck the row item', async () => {
|
test('should check and uncheck the row item', async () => {
|
||||||
expect(
|
expect(
|
||||||
wrapper
|
wrapper
|
||||||
@@ -174,6 +193,27 @@ describe('<InventoryHostGroupsList />', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not render Run Commands button', async () => {
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: {
|
||||||
|
module_name: {
|
||||||
|
choices: [
|
||||||
|
['command', 'command'],
|
||||||
|
['shell', 'shell'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<InventoryHostGroupsList />);
|
||||||
|
});
|
||||||
|
expect(wrapper.find('AdHocCommands')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
test('should show content error when api throws error on initial render', async () => {
|
test('should show content error when api throws error on initial render', async () => {
|
||||||
HostsAPI.readAllGroups.mockImplementation(() =>
|
HostsAPI.readAllGroups.mockImplementation(() =>
|
||||||
Promise.reject(new Error())
|
Promise.reject(new Error())
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ function InventoryHostList() {
|
|||||||
actions,
|
actions,
|
||||||
relatedSearchableKeys,
|
relatedSearchableKeys,
|
||||||
searchableKeys,
|
searchableKeys,
|
||||||
|
moduleOptions,
|
||||||
|
isAdHocDisabled,
|
||||||
},
|
},
|
||||||
error: contentError,
|
error: contentError,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -42,12 +44,15 @@ function InventoryHostList() {
|
|||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const params = parseQueryString(QS_CONFIG, search);
|
const params = parseQueryString(QS_CONFIG, search);
|
||||||
const [response, hostOptions] = await Promise.all([
|
const [response, hostOptions, adHocOptions] = await Promise.all([
|
||||||
InventoriesAPI.readHosts(id, params),
|
InventoriesAPI.readHosts(id, params),
|
||||||
InventoriesAPI.readHostsOptions(id),
|
InventoriesAPI.readHostsOptions(id),
|
||||||
|
InventoriesAPI.readAdHocOptions(id),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
moduleOptions: adHocOptions.data.actions.GET.module_name.choices,
|
||||||
|
isAdHocDisabled: !adHocOptions.data.actions.POST,
|
||||||
hosts: response.data.results,
|
hosts: response.data.results,
|
||||||
hostCount: response.data.count,
|
hostCount: response.data.count,
|
||||||
actions: hostOptions.data.actions,
|
actions: hostOptions.data.actions,
|
||||||
@@ -65,6 +70,8 @@ function InventoryHostList() {
|
|||||||
actions: {},
|
actions: {},
|
||||||
relatedSearchableKeys: [],
|
relatedSearchableKeys: [],
|
||||||
searchableKeys: [],
|
searchableKeys: [],
|
||||||
|
moduleOptions: [],
|
||||||
|
isAdHocDisabled: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -152,11 +159,16 @@ function InventoryHostList() {
|
|||||||
/>,
|
/>,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
<AdHocCommands
|
...(!isAdHocDisabled
|
||||||
adHocItems={selected}
|
? [
|
||||||
hasListItems={hostCount > 0}
|
<AdHocCommands
|
||||||
onLaunchLoading={setIsAdHocLaunchLoading}
|
moduleOptions={moduleOptions}
|
||||||
/>,
|
adHocItems={selected}
|
||||||
|
hasListItems={hostCount > 0}
|
||||||
|
onLaunchLoading={setIsAdHocLaunchLoading}
|
||||||
|
/>,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
<ToolbarDeleteButton
|
<ToolbarDeleteButton
|
||||||
key="delete"
|
key="delete"
|
||||||
onDelete={handleDeleteHosts}
|
onDelete={handleDeleteHosts}
|
||||||
|
|||||||
@@ -93,6 +93,23 @@ describe('<InventoryHostList />', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: {
|
||||||
|
module_name: {
|
||||||
|
choices: [
|
||||||
|
['command', 'command'],
|
||||||
|
['shell', 'shell'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
POST: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<InventoryHostList />);
|
wrapper = mountWithContexts(<InventoryHostList />);
|
||||||
});
|
});
|
||||||
@@ -108,6 +125,10 @@ describe('<InventoryHostList />', () => {
|
|||||||
expect(wrapper.find('InventoryHostItem').length).toBe(3);
|
expect(wrapper.find('InventoryHostItem').length).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should render Run Commands button', async () => {
|
||||||
|
expect(wrapper.find('AdHocCommands')).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('should check and uncheck the row item', async () => {
|
test('should check and uncheck the row item', async () => {
|
||||||
expect(
|
expect(
|
||||||
wrapper
|
wrapper
|
||||||
@@ -322,4 +343,27 @@ describe('<InventoryHostList />', () => {
|
|||||||
});
|
});
|
||||||
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
|
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not render Run Commands button', async () => {
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: {
|
||||||
|
module_name: {
|
||||||
|
choices: [
|
||||||
|
['command', 'command'],
|
||||||
|
['shell', 'shell'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<InventoryHostList inventory={mockInventory} />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
expect(wrapper.find('AdHocCommands')).toHaveLength(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,18 +43,23 @@ function InventoryRelatedGroupList() {
|
|||||||
relatedSearchableKeys,
|
relatedSearchableKeys,
|
||||||
searchableKeys,
|
searchableKeys,
|
||||||
canAdd,
|
canAdd,
|
||||||
|
moduleOptions,
|
||||||
|
isAdHocDisabled,
|
||||||
},
|
},
|
||||||
isLoading,
|
isLoading,
|
||||||
error: contentError,
|
error: contentError,
|
||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const params = parseQueryString(QS_CONFIG, location.search);
|
const params = parseQueryString(QS_CONFIG, location.search);
|
||||||
const [response, actions] = await Promise.all([
|
const [response, actions, adHocOptions] = await Promise.all([
|
||||||
GroupsAPI.readChildren(groupId, params),
|
GroupsAPI.readChildren(groupId, params),
|
||||||
InventoriesAPI.readGroupsOptions(inventoryId),
|
InventoriesAPI.readGroupsOptions(inventoryId),
|
||||||
|
InventoriesAPI.readAdHocOptions(inventoryId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
moduleOptions: adHocOptions.data.actions.GET.module_name.choices,
|
||||||
|
isAdHocDisabled: !adHocOptions.data.actions.POST,
|
||||||
groups: response.data.results,
|
groups: response.data.results,
|
||||||
itemCount: response.data.count,
|
itemCount: response.data.count,
|
||||||
relatedSearchableKeys: (
|
relatedSearchableKeys: (
|
||||||
@@ -68,7 +73,13 @@ function InventoryRelatedGroupList() {
|
|||||||
Object.prototype.hasOwnProperty.call(actions.data.actions, 'POST'),
|
Object.prototype.hasOwnProperty.call(actions.data.actions, 'POST'),
|
||||||
};
|
};
|
||||||
}, [groupId, location.search, inventoryId]),
|
}, [groupId, location.search, inventoryId]),
|
||||||
{ groups: [], itemCount: 0, canAdd: false }
|
{
|
||||||
|
groups: [],
|
||||||
|
itemCount: 0,
|
||||||
|
canAdd: false,
|
||||||
|
moduleOptions: [],
|
||||||
|
isAdHocDisabled: true,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchRelated();
|
fetchRelated();
|
||||||
@@ -199,11 +210,16 @@ function InventoryRelatedGroupList() {
|
|||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
additionalControls={[
|
additionalControls={[
|
||||||
...(canAdd ? [addButton] : []),
|
...(canAdd ? [addButton] : []),
|
||||||
<AdHocCommands
|
...(!isAdHocDisabled
|
||||||
adHocItems={selected}
|
? [
|
||||||
hasListItems={itemCount > 0}
|
<AdHocCommands
|
||||||
onLaunchLoading={setIsAdHocLaunchLoading}
|
adHocItems={selected}
|
||||||
/>,
|
hasListItems={itemCount > 0}
|
||||||
|
onLaunchLoading={setIsAdHocLaunchLoading}
|
||||||
|
moduleOptions={moduleOptions}
|
||||||
|
/>,
|
||||||
|
]
|
||||||
|
: []),
|
||||||
<DisassociateButton
|
<DisassociateButton
|
||||||
key="disassociate"
|
key="disassociate"
|
||||||
onDisassociate={disassociateGroups}
|
onDisassociate={disassociateGroups}
|
||||||
|
|||||||
@@ -87,6 +87,21 @@ describe('<InventoryRelatedGroupList />', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: {
|
||||||
|
module_name: {
|
||||||
|
choices: [
|
||||||
|
['command', 'command'],
|
||||||
|
['shell', 'shell'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
POST: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<InventoryRelatedGroupList />);
|
wrapper = mountWithContexts(<InventoryRelatedGroupList />);
|
||||||
});
|
});
|
||||||
@@ -107,6 +122,10 @@ describe('<InventoryRelatedGroupList />', () => {
|
|||||||
expect(wrapper.find('InventoryRelatedGroupListItem').length).toBe(3);
|
expect(wrapper.find('InventoryRelatedGroupListItem').length).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should render Run Commands Button', async () => {
|
||||||
|
expect(wrapper.find('AdHocCommands')).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('should check and uncheck the row item', async () => {
|
test('should check and uncheck the row item', async () => {
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('input[aria-label="Select row 0"]').props().checked
|
wrapper.find('input[aria-label="Select row 0"]').props().checked
|
||||||
@@ -220,4 +239,25 @@ describe('<InventoryRelatedGroupList />', () => {
|
|||||||
);
|
);
|
||||||
expect(GroupsAPI.associateChildGroup).toBeCalledTimes(1);
|
expect(GroupsAPI.associateChildGroup).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not render Run Commands button', async () => {
|
||||||
|
InventoriesAPI.readAdHocOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: {
|
||||||
|
module_name: {
|
||||||
|
choices: [
|
||||||
|
['command', 'command'],
|
||||||
|
['shell', 'shell'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<InventoryRelatedGroupList />);
|
||||||
|
});
|
||||||
|
expect(wrapper.find('AdHocCommands')).toHaveLength(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ function JobDetail({ job }) {
|
|||||||
project_update: t`Source Control Update`,
|
project_update: t`Source Control Update`,
|
||||||
inventory_update: t`Inventory Sync`,
|
inventory_update: t`Inventory Sync`,
|
||||||
job: job.job_type === 'check' ? t`Playbook Check` : t`Playbook Run`,
|
job: job.job_type === 'check' ? t`Playbook Check` : t`Playbook Run`,
|
||||||
ad_hoc_command: t`Command`,
|
ad_hoc_command: t`Run Command`,
|
||||||
system_job: t`Management Job`,
|
system_job: t`Management Job`,
|
||||||
workflow_job: t`Workflow Job`,
|
workflow_job: t`Workflow Job`,
|
||||||
};
|
};
|
||||||
@@ -337,6 +337,8 @@ function JobDetail({ job }) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<Detail label={t`Module Name`} value={job.module_name} />
|
||||||
|
<Detail label={t`Module Arguments`} value={job.module_args} />
|
||||||
<UserDateDetail
|
<UserDateDetail
|
||||||
label={t`Created`}
|
label={t`Created`}
|
||||||
date={job.created}
|
date={job.created}
|
||||||
|
|||||||
@@ -110,6 +110,38 @@ describe('<JobDetail />', () => {
|
|||||||
).toHaveLength(1);
|
).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should display module name and module arguments', () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<JobDetail
|
||||||
|
job={{
|
||||||
|
...mockJobData,
|
||||||
|
type: 'ad_hoc_command',
|
||||||
|
module_name: 'command',
|
||||||
|
module_args: 'echo hello_world',
|
||||||
|
summary_fields: {
|
||||||
|
...mockJobData.summary_fields,
|
||||||
|
credential: {
|
||||||
|
id: 2,
|
||||||
|
name: 'Machine cred',
|
||||||
|
description: '',
|
||||||
|
kind: 'ssh',
|
||||||
|
cloud: false,
|
||||||
|
kubernetes: false,
|
||||||
|
credential_type_id: 1,
|
||||||
|
},
|
||||||
|
source_workflow_job: {
|
||||||
|
id: 1234,
|
||||||
|
name: 'Test Source Workflow',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
assertDetail('Module Name', 'command');
|
||||||
|
assertDetail('Module Arguments', 'echo hello_world');
|
||||||
|
assertDetail('Job Type', 'Run Command');
|
||||||
|
});
|
||||||
|
|
||||||
test('should show schedule that launched workflow job', async () => {
|
test('should show schedule that launched workflow job', async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<JobDetail
|
<JobDetail
|
||||||
|
|||||||
Reference in New Issue
Block a user