mirror of
https://github.com/ansible/awx.git
synced 2026-05-07 17:37:37 -02:30
adds advanced search functionality and lists correct EEs
This commit is contained in:
@@ -35,22 +35,29 @@ function AdHocCommands({ adHocItems, i18n, hasListItems, onLaunchLoading }) {
|
|||||||
}, [isKebabified, isWizardOpen, onKebabModalChange]);
|
}, [isKebabified, isWizardOpen, onKebabModalChange]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
result: { moduleOptions, credentialTypeId, isAdHocDisabled },
|
result: {
|
||||||
|
moduleOptions,
|
||||||
|
credentialTypeId,
|
||||||
|
isAdHocDisabled,
|
||||||
|
organizationId,
|
||||||
|
},
|
||||||
request: fetchData,
|
request: fetchData,
|
||||||
error: fetchError,
|
error: fetchError,
|
||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const [options, cred] = await Promise.all([
|
const [options, { data }, cred] = await Promise.all([
|
||||||
InventoriesAPI.readAdHocOptions(id),
|
InventoriesAPI.readAdHocOptions(id),
|
||||||
|
InventoriesAPI.readDetail(id),
|
||||||
CredentialTypesAPI.read({ namespace: 'ssh' }),
|
CredentialTypesAPI.read({ namespace: 'ssh' }),
|
||||||
]);
|
]);
|
||||||
return {
|
return {
|
||||||
moduleOptions: options.data.actions.GET.module_name.choices,
|
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,
|
isAdHocDisabled: !options.data.actions.POST,
|
||||||
|
organizationId: data.organization,
|
||||||
};
|
};
|
||||||
}, [id]),
|
}, [id]),
|
||||||
{ moduleOptions: [], isAdHocDisabled: true }
|
{ moduleOptions: [], isAdHocDisabled: true, organizationId: null }
|
||||||
);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
@@ -141,6 +148,7 @@ function AdHocCommands({ adHocItems, i18n, hasListItems, onLaunchLoading }) {
|
|||||||
{isWizardOpen && (
|
{isWizardOpen && (
|
||||||
<AdHocCommandsWizard
|
<AdHocCommandsWizard
|
||||||
adHocItems={adHocItems}
|
adHocItems={adHocItems}
|
||||||
|
organizationId={organizationId}
|
||||||
moduleOptions={moduleOptions}
|
moduleOptions={moduleOptions}
|
||||||
verbosityOptions={verbosityOptions}
|
verbosityOptions={verbosityOptions}
|
||||||
credentialTypeId={credentialTypeId}
|
credentialTypeId={credentialTypeId}
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ describe('<AdHocCommands />', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
InventoriesAPI.readDetail.mockResolvedValue({ data: { organization: 1 } });
|
||||||
CredentialTypesAPI.read.mockResolvedValue({
|
CredentialTypesAPI.read.mockResolvedValue({
|
||||||
data: { results: [{ id: 1 }] },
|
data: { results: [{ id: 1 }] },
|
||||||
});
|
});
|
||||||
@@ -135,6 +136,10 @@ describe('<AdHocCommands />', () => {
|
|||||||
|
|
||||||
test('should submit properly', async () => {
|
test('should submit properly', async () => {
|
||||||
InventoriesAPI.launchAdHocCommands.mockResolvedValue({ data: { id: 1 } });
|
InventoriesAPI.launchAdHocCommands.mockResolvedValue({ data: { id: 1 } });
|
||||||
|
InventoriesAPI.readDetail.mockResolvedValue({
|
||||||
|
data: { organization: 1 },
|
||||||
|
});
|
||||||
|
|
||||||
CredentialsAPI.read.mockResolvedValue({
|
CredentialsAPI.read.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
results: credentials,
|
results: credentials,
|
||||||
@@ -150,6 +155,9 @@ describe('<AdHocCommands />', () => {
|
|||||||
count: 2,
|
count: 2,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue({
|
||||||
|
data: { actions: { GET: {} } },
|
||||||
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<AdHocCommands
|
<AdHocCommands
|
||||||
@@ -275,6 +283,9 @@ describe('<AdHocCommands />', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
InventoriesAPI.readDetail.mockResolvedValue({
|
||||||
|
data: { organization: 1 },
|
||||||
|
});
|
||||||
CredentialTypesAPI.read.mockResolvedValue({
|
CredentialTypesAPI.read.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
results: [
|
results: [
|
||||||
@@ -307,6 +318,9 @@ describe('<AdHocCommands />', () => {
|
|||||||
count: 2,
|
count: 2,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue({
|
||||||
|
data: { actions: { GET: {} } },
|
||||||
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<AdHocCommands
|
<AdHocCommands
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { withI18n } from '@lingui/react';
|
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ExclamationCircleIcon as PFExclamationCircleIcon } from '@patternfly/react-icons';
|
import { ExclamationCircleIcon as PFExclamationCircleIcon } from '@patternfly/react-icons';
|
||||||
import { Tooltip } from '@patternfly/react-core';
|
import { Tooltip } from '@patternfly/react-core';
|
||||||
@@ -24,11 +24,11 @@ const ExclamationCircleIcon = styled(PFExclamationCircleIcon)`
|
|||||||
|
|
||||||
function AdHocCommandsWizard({
|
function AdHocCommandsWizard({
|
||||||
onLaunch,
|
onLaunch,
|
||||||
i18n,
|
|
||||||
moduleOptions,
|
moduleOptions,
|
||||||
verbosityOptions,
|
verbosityOptions,
|
||||||
onCloseWizard,
|
onCloseWizard,
|
||||||
credentialTypeId,
|
credentialTypeId,
|
||||||
|
organizationId,
|
||||||
}) {
|
}) {
|
||||||
const [currentStepId, setCurrentStepId] = useState(1);
|
const [currentStepId, setCurrentStepId] = useState(1);
|
||||||
const [enableLaunch, setEnableLaunch] = useState(false);
|
const [enableLaunch, setEnableLaunch] = useState(false);
|
||||||
@@ -58,17 +58,17 @@ function AdHocCommandsWizard({
|
|||||||
key: 1,
|
key: 1,
|
||||||
name: hasDetailsStepError ? (
|
name: hasDetailsStepError ? (
|
||||||
<AlertText>
|
<AlertText>
|
||||||
{i18n._(t`Details`)}
|
{t`Details`}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
position="right"
|
position="right"
|
||||||
content={i18n._(t`This step contains errors`)}
|
content={t`This step contains errors`}
|
||||||
trigger="click mouseenter focus"
|
trigger="click mouseenter focus"
|
||||||
>
|
>
|
||||||
<ExclamationCircleIcon />
|
<ExclamationCircleIcon />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</AlertText>
|
</AlertText>
|
||||||
) : (
|
) : (
|
||||||
i18n._(t`Details`)
|
t`Details`
|
||||||
),
|
),
|
||||||
component: (
|
component: (
|
||||||
<AdHocDetailsStep
|
<AdHocDetailsStep
|
||||||
@@ -77,20 +77,25 @@ function AdHocCommandsWizard({
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableNext: enabledNextOnDetailsStep(),
|
enableNext: enabledNextOnDetailsStep(),
|
||||||
nextButtonText: i18n._(t`Next`),
|
nextButtonText: t`Next`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
key: 2,
|
key: 2,
|
||||||
name: t`Execution Environment`,
|
name: t`Execution Environment`,
|
||||||
component: <AdHocExecutionEnvironmentStep />,
|
component: (
|
||||||
|
<AdHocExecutionEnvironmentStep organizationId={organizationId} />
|
||||||
|
),
|
||||||
|
// Removed this line when https://github.com/patternfly/patternfly-react/issues/5729 is fixed
|
||||||
|
stepNavItemProps: { style: { 'white-space': 'nowrap' } },
|
||||||
enableNext: true,
|
enableNext: true,
|
||||||
|
nextButtonText: t`Next`,
|
||||||
canJumpTo: currentStepId >= 2,
|
canJumpTo: currentStepId >= 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
key: 3,
|
key: 3,
|
||||||
name: i18n._(t`Machine credential`),
|
name: t`Machine credential`,
|
||||||
component: (
|
component: (
|
||||||
<AdHocCredentialStep
|
<AdHocCredentialStep
|
||||||
credentialTypeId={credentialTypeId}
|
credentialTypeId={credentialTypeId}
|
||||||
@@ -98,7 +103,7 @@ function AdHocCommandsWizard({
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableNext: enableLaunch && Object.values(errors).length === 0,
|
enableNext: enableLaunch && Object.values(errors).length === 0,
|
||||||
nextButtonText: i18n._(t`Launch`),
|
nextButtonText: t`Launch`,
|
||||||
canJumpTo: currentStepId >= 2,
|
canJumpTo: currentStepId >= 2,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -115,10 +120,10 @@ function AdHocCommandsWizard({
|
|||||||
onLaunch(values);
|
onLaunch(values);
|
||||||
}}
|
}}
|
||||||
steps={steps}
|
steps={steps}
|
||||||
title={i18n._(t`Run command`)}
|
title={t`Run command`}
|
||||||
nextButtonText={currentStep.nextButtonText || undefined}
|
nextButtonText={currentStep.nextButtonText || undefined}
|
||||||
backButtonText={i18n._(t`Back`)}
|
backButtonText={t`Back`}
|
||||||
cancelButtonText={i18n._(t`Cancel`)}
|
cancelButtonText={t`Cancel`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -149,4 +154,4 @@ FormikApp.propTypes = {
|
|||||||
onCloseWizard: PropTypes.func.isRequired,
|
onCloseWizard: PropTypes.func.isRequired,
|
||||||
credentialTypeId: PropTypes.number.isRequired,
|
credentialTypeId: PropTypes.number.isRequired,
|
||||||
};
|
};
|
||||||
export default withI18n()(FormikApp);
|
export default FormikApp;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ describe('<AdHocCommandsWizard/>', () => {
|
|||||||
verbosityOptions={verbosityOptions}
|
verbosityOptions={verbosityOptions}
|
||||||
onCloseWizard={() => {}}
|
onCloseWizard={() => {}}
|
||||||
credentialTypeId={1}
|
credentialTypeId={1}
|
||||||
|
organizationId={1}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -108,6 +109,9 @@ describe('<AdHocCommandsWizard/>', () => {
|
|||||||
count: 2,
|
count: 2,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue({
|
||||||
|
data: { actions: { GET: {} } },
|
||||||
|
});
|
||||||
CredentialsAPI.read.mockResolvedValue({
|
CredentialsAPI.read.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
results: [
|
results: [
|
||||||
|
|||||||
@@ -22,10 +22,13 @@ describe('<AdHocExecutionEnvironmentStep />', () => {
|
|||||||
count: 2,
|
count: 2,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue({
|
||||||
|
data: { actions: { GET: {} } },
|
||||||
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<Formik>
|
<Formik>
|
||||||
<AdHocExecutionEnvironmentStep />
|
<AdHocExecutionEnvironmentStep organizationId={1} />
|
||||||
</Formik>
|
</Formik>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -6,18 +6,18 @@ import { Form, FormGroup } from '@patternfly/react-core';
|
|||||||
import { ExecutionEnvironmentsAPI } from '../../api';
|
import { ExecutionEnvironmentsAPI } from '../../api';
|
||||||
import Popover from '../Popover';
|
import Popover from '../Popover';
|
||||||
|
|
||||||
import { parseQueryString, getQSConfig } from '../../util/qs';
|
import { parseQueryString, getQSConfig, mergeParams } from '../../util/qs';
|
||||||
import useRequest from '../../util/useRequest';
|
import useRequest from '../../util/useRequest';
|
||||||
import ContentError from '../ContentError';
|
import ContentError from '../ContentError';
|
||||||
import ContentLoading from '../ContentLoading';
|
import ContentLoading from '../ContentLoading';
|
||||||
import OptionsList from '../OptionsList';
|
import OptionsList from '../OptionsList';
|
||||||
|
|
||||||
const QS_CONFIG = getQSConfig('execution_environemts', {
|
const QS_CONFIG = getQSConfig('execution_environments', {
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 5,
|
page_size: 5,
|
||||||
order_by: 'name',
|
order_by: 'name',
|
||||||
});
|
});
|
||||||
function AdHocExecutionEnvironmentStep() {
|
function AdHocExecutionEnvironmentStep({ organizationId }) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [executionEnvironmentField, , executionEnvironmentHelpers] = useField(
|
const [executionEnvironmentField, , executionEnvironmentHelpers] = useField(
|
||||||
'execution_environment'
|
'execution_environment'
|
||||||
@@ -26,21 +26,51 @@ function AdHocExecutionEnvironmentStep() {
|
|||||||
error,
|
error,
|
||||||
isLoading,
|
isLoading,
|
||||||
request: fetchExecutionEnvironments,
|
request: fetchExecutionEnvironments,
|
||||||
result: { executionEnvironments, executionEnvironmentsCount },
|
result: {
|
||||||
|
executionEnvironments,
|
||||||
|
executionEnvironmentsCount,
|
||||||
|
relatedSearchableKeys,
|
||||||
|
searchableKeys,
|
||||||
|
},
|
||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const params = parseQueryString(QS_CONFIG, history.location.search);
|
const params = parseQueryString(QS_CONFIG, history.location.search);
|
||||||
|
const globallyAvailableParams = { or__organization__isnull: 'True' };
|
||||||
|
const organizationIdParams = organizationId
|
||||||
|
? { or__organization__id: organizationId }
|
||||||
|
: {};
|
||||||
|
|
||||||
const {
|
const [
|
||||||
data: { results, count },
|
{
|
||||||
} = await ExecutionEnvironmentsAPI.read(params);
|
data: { results, count },
|
||||||
|
},
|
||||||
|
actionsResponse,
|
||||||
|
] = await Promise.all([
|
||||||
|
ExecutionEnvironmentsAPI.read(
|
||||||
|
mergeParams(params, {
|
||||||
|
...globallyAvailableParams,
|
||||||
|
...organizationIdParams,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
ExecutionEnvironmentsAPI.readOptions(),
|
||||||
|
]);
|
||||||
return {
|
return {
|
||||||
executionEnvironments: results,
|
executionEnvironments: results,
|
||||||
executionEnvironmentsCount: count,
|
executionEnvironmentsCount: count,
|
||||||
|
relatedSearchableKeys: (
|
||||||
|
actionsResponse?.data?.related_search_fields || []
|
||||||
|
).map(val => val.slice(0, -8)),
|
||||||
|
searchableKeys: Object.keys(
|
||||||
|
actionsResponse.data.actions?.GET || {}
|
||||||
|
).filter(key => actionsResponse.data.actions?.GET[key].filterable),
|
||||||
};
|
};
|
||||||
}, [history.location.search]),
|
}, [history.location.search, organizationId]),
|
||||||
{ executionEnvironments: [], executionEnvironmentsCount: 0 }
|
{
|
||||||
|
executionEnvironments: [],
|
||||||
|
executionEnvironmentsCount: 0,
|
||||||
|
relatedSearchableKeys: [],
|
||||||
|
searchableKeys: [],
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -62,11 +92,12 @@ function AdHocExecutionEnvironmentStep() {
|
|||||||
aria-label={t`Execution Environments`}
|
aria-label={t`Execution Environments`}
|
||||||
labelIcon={
|
labelIcon={
|
||||||
<Popover
|
<Popover
|
||||||
content={t`Select the Execution Environment you want this command to run inside`}
|
content={t`Select the Execution Environment you want this command to run inside.`}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<OptionsList
|
<OptionsList
|
||||||
|
isLoading={isLoading}
|
||||||
value={executionEnvironmentField.value || []}
|
value={executionEnvironmentField.value || []}
|
||||||
options={executionEnvironments}
|
options={executionEnvironments}
|
||||||
optionCount={executionEnvironmentsCount}
|
optionCount={executionEnvironmentsCount}
|
||||||
@@ -75,7 +106,7 @@ function AdHocExecutionEnvironmentStep() {
|
|||||||
searchColumns={[
|
searchColumns={[
|
||||||
{
|
{
|
||||||
name: t`Name`,
|
name: t`Name`,
|
||||||
key: 'name',
|
key: 'name__icontains',
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -94,6 +125,8 @@ function AdHocExecutionEnvironmentStep() {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
name="execution_environment"
|
name="execution_environment"
|
||||||
|
searchableKeys={searchableKeys}
|
||||||
|
relatedSearchableKeys={relatedSearchableKeys}
|
||||||
selectItem={value => {
|
selectItem={value => {
|
||||||
executionEnvironmentHelpers.setValue([value]);
|
executionEnvironmentHelpers.setValue([value]);
|
||||||
}}
|
}}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -92,11 +92,11 @@ describe('<InventoryDetail />', () => {
|
|||||||
expectDetailToMatch(wrapper, 'Type', 'Inventory');
|
expectDetailToMatch(wrapper, 'Type', 'Inventory');
|
||||||
const org = wrapper.find('Detail[label="Organization"]');
|
const org = wrapper.find('Detail[label="Organization"]');
|
||||||
expect(org.prop('value')).toMatchInlineSnapshot(`
|
expect(org.prop('value')).toMatchInlineSnapshot(`
|
||||||
<ForwardRef
|
<Link
|
||||||
to="/organizations/1/details"
|
to="/organizations/1/details"
|
||||||
>
|
>
|
||||||
The Organization
|
The Organization
|
||||||
</ForwardRef>
|
</Link>
|
||||||
`);
|
`);
|
||||||
const vars = wrapper.find('VariablesDetail');
|
const vars = wrapper.find('VariablesDetail');
|
||||||
expect(vars).toHaveLength(1);
|
expect(vars).toHaveLength(1);
|
||||||
|
|||||||
Reference in New Issue
Block a user