mirror of
https://github.com/ansible/awx.git
synced 2026-03-05 10:41:05 -03:30
Add organization as part of creating/editing an execution environments
Add organization as part of creating/editing an execution environments If one is a `system admin` the Organization is an optional field. Not providing an Organization makes the execution environment globally available. If one is a `org admin` the Organization is a required field. See: https://github.com/ansible/awx/issues/7887
This commit is contained in:
@@ -30,6 +30,7 @@ function OrganizationLookup({
|
|||||||
history,
|
history,
|
||||||
autoPopulate,
|
autoPopulate,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
|
helperText,
|
||||||
}) {
|
}) {
|
||||||
const autoPopulateLookup = useAutoPopulateLookup(onChange);
|
const autoPopulateLookup = useAutoPopulateLookup(onChange);
|
||||||
|
|
||||||
@@ -79,6 +80,7 @@ function OrganizationLookup({
|
|||||||
isRequired={required}
|
isRequired={required}
|
||||||
validated={isValid ? 'default' : 'error'}
|
validated={isValid ? 'default' : 'error'}
|
||||||
label={i18n._(t`Organization`)}
|
label={i18n._(t`Organization`)}
|
||||||
|
helperText={helperText}
|
||||||
>
|
>
|
||||||
<Lookup
|
<Lookup
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import React, { useState } from 'react';
|
|||||||
import { Card, PageSection } from '@patternfly/react-core';
|
import { Card, PageSection } from '@patternfly/react-core';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import ExecutionEnvironmentForm from '../shared/ExecutionEnvironmentForm';
|
|
||||||
import { CardBody } from '../../../components/Card';
|
|
||||||
import { ExecutionEnvironmentsAPI } from '../../../api';
|
import { ExecutionEnvironmentsAPI } from '../../../api';
|
||||||
|
import { Config } from '../../../contexts/Config';
|
||||||
|
import { CardBody } from '../../../components/Card';
|
||||||
|
import ExecutionEnvironmentForm from '../shared/ExecutionEnvironmentForm';
|
||||||
|
|
||||||
function ExecutionEnvironmentAdd() {
|
function ExecutionEnvironmentAdd() {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -14,7 +15,8 @@ function ExecutionEnvironmentAdd() {
|
|||||||
try {
|
try {
|
||||||
const { data: response } = await ExecutionEnvironmentsAPI.create({
|
const { data: response } = await ExecutionEnvironmentsAPI.create({
|
||||||
...values,
|
...values,
|
||||||
credential: values?.credential?.id,
|
credential: values.credential?.id,
|
||||||
|
organization: values.organization?.id,
|
||||||
});
|
});
|
||||||
history.push(`/execution_environments/${response.id}/details`);
|
history.push(`/execution_environments/${response.id}/details`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -29,11 +31,16 @@ function ExecutionEnvironmentAdd() {
|
|||||||
<PageSection>
|
<PageSection>
|
||||||
<Card>
|
<Card>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<ExecutionEnvironmentForm
|
<Config>
|
||||||
onSubmit={handleSubmit}
|
{({ me }) => (
|
||||||
submitError={submitError}
|
<ExecutionEnvironmentForm
|
||||||
onCancel={handleCancel}
|
onSubmit={handleSubmit}
|
||||||
/>
|
submitError={submitError}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
me={me || {}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Config>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ import ExecutionEnvironmentAdd from './ExecutionEnvironmentAdd';
|
|||||||
|
|
||||||
jest.mock('../../../api');
|
jest.mock('../../../api');
|
||||||
|
|
||||||
|
const mockMe = {
|
||||||
|
is_superuser: true,
|
||||||
|
is_system_auditor: false,
|
||||||
|
};
|
||||||
|
|
||||||
const executionEnvironmentData = {
|
const executionEnvironmentData = {
|
||||||
credential: 4,
|
credential: 4,
|
||||||
description: 'A simple EE',
|
description: 'A simple EE',
|
||||||
@@ -29,7 +34,7 @@ describe('<ExecutionEnvironmentAdd/>', () => {
|
|||||||
initialEntries: ['/execution_environments'],
|
initialEntries: ['/execution_environments'],
|
||||||
});
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<ExecutionEnvironmentAdd />, {
|
wrapper = mountWithContexts(<ExecutionEnvironmentAdd me={mockMe} />, {
|
||||||
context: { router: { history } },
|
context: { router: { history } },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useHistory } from 'react-router-dom';
|
|||||||
import { CardBody } from '../../../components/Card';
|
import { CardBody } from '../../../components/Card';
|
||||||
import { ExecutionEnvironmentsAPI } from '../../../api';
|
import { ExecutionEnvironmentsAPI } from '../../../api';
|
||||||
import ExecutionEnvironmentForm from '../shared/ExecutionEnvironmentForm';
|
import ExecutionEnvironmentForm from '../shared/ExecutionEnvironmentForm';
|
||||||
|
import { Config } from '../../../contexts/Config';
|
||||||
|
|
||||||
function ExecutionEnvironmentEdit({ executionEnvironment }) {
|
function ExecutionEnvironmentEdit({ executionEnvironment }) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -15,6 +16,7 @@ function ExecutionEnvironmentEdit({ executionEnvironment }) {
|
|||||||
await ExecutionEnvironmentsAPI.update(executionEnvironment.id, {
|
await ExecutionEnvironmentsAPI.update(executionEnvironment.id, {
|
||||||
...values,
|
...values,
|
||||||
credential: values.credential ? values.credential.id : null,
|
credential: values.credential ? values.credential.id : null,
|
||||||
|
organization: values.organization ? values.organization.id : null,
|
||||||
});
|
});
|
||||||
history.push(detailsUrl);
|
history.push(detailsUrl);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -27,12 +29,17 @@ function ExecutionEnvironmentEdit({ executionEnvironment }) {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<ExecutionEnvironmentForm
|
<Config>
|
||||||
executionEnvironment={executionEnvironment}
|
{({ me }) => (
|
||||||
onSubmit={handleSubmit}
|
<ExecutionEnvironmentForm
|
||||||
submitError={submitError}
|
executionEnvironment={executionEnvironment}
|
||||||
onCancel={handleCancel}
|
onSubmit={handleSubmit}
|
||||||
/>
|
submitError={submitError}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
me={me || {}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Config>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ import ExecutionEnvironmentEdit from './ExecutionEnvironmentEdit';
|
|||||||
|
|
||||||
jest.mock('../../../api');
|
jest.mock('../../../api');
|
||||||
|
|
||||||
|
const mockMe = {
|
||||||
|
is_superuser: true,
|
||||||
|
is_system_auditor: false,
|
||||||
|
};
|
||||||
|
|
||||||
const executionEnvironmentData = {
|
const executionEnvironmentData = {
|
||||||
id: 42,
|
id: 42,
|
||||||
credential: { id: 4 },
|
credential: { id: 4 },
|
||||||
@@ -31,6 +36,7 @@ describe('<ExecutionEnvironmentEdit/>', () => {
|
|||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<ExecutionEnvironmentEdit
|
<ExecutionEnvironmentEdit
|
||||||
executionEnvironment={executionEnvironmentData}
|
executionEnvironment={executionEnvironmentData}
|
||||||
|
me={mockMe}
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
context: { router: { history } },
|
context: { router: { history } },
|
||||||
@@ -53,6 +59,7 @@ describe('<ExecutionEnvironmentEdit/>', () => {
|
|||||||
expect(ExecutionEnvironmentsAPI.update).toHaveBeenCalledWith(42, {
|
expect(ExecutionEnvironmentsAPI.update).toHaveBeenCalledWith(42, {
|
||||||
...updateExecutionEnvironmentData,
|
...updateExecutionEnvironmentData,
|
||||||
credential: null,
|
credential: null,
|
||||||
|
organization: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,42 @@
|
|||||||
import React from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { func, shape } from 'prop-types';
|
import { func, shape } from 'prop-types';
|
||||||
import { Formik, useField } from 'formik';
|
import { Formik, useField, useFormikContext } from 'formik';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
import { Form } from '@patternfly/react-core';
|
import { Form } from '@patternfly/react-core';
|
||||||
import FormField, { FormSubmitError } from '../../../components/FormField';
|
|
||||||
import FormActionGroup from '../../../components/FormActionGroup';
|
|
||||||
import CredentialLookup from '../../../components/Lookup/CredentialLookup';
|
|
||||||
import { url } from '../../../util/validators';
|
|
||||||
import { FormColumnLayout } from '../../../components/FormLayout';
|
|
||||||
|
|
||||||
function ExecutionEnvironmentFormFields({ i18n }) {
|
import CredentialLookup from '../../../components/Lookup/CredentialLookup';
|
||||||
const [credentialField, , credentialHelpers] = useField('credential');
|
import FormActionGroup from '../../../components/FormActionGroup';
|
||||||
|
import FormField, { FormSubmitError } from '../../../components/FormField';
|
||||||
|
import { FormColumnLayout } from '../../../components/FormLayout';
|
||||||
|
import { OrganizationLookup } from '../../../components/Lookup';
|
||||||
|
import { required, url } from '../../../util/validators';
|
||||||
|
|
||||||
|
function ExecutionEnvironmentFormFields({ i18n, me, executionEnvironment }) {
|
||||||
|
const [credentialField] = useField('credential');
|
||||||
|
const [organizationField, organizationMeta, organizationHelpers] = useField({
|
||||||
|
name: 'organization',
|
||||||
|
validate:
|
||||||
|
!me?.is_superuser &&
|
||||||
|
required(i18n._(t`Select a value for this field`), i18n),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { setFieldValue } = useFormikContext();
|
||||||
|
|
||||||
|
const onCredentialChange = useCallback(
|
||||||
|
value => {
|
||||||
|
setFieldValue('credential', value);
|
||||||
|
},
|
||||||
|
[setFieldValue]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onOrganizationChange = useCallback(
|
||||||
|
value => {
|
||||||
|
setFieldValue('organization', value);
|
||||||
|
},
|
||||||
|
[setFieldValue]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
@@ -32,9 +56,26 @@ function ExecutionEnvironmentFormFields({ i18n }) {
|
|||||||
name="description"
|
name="description"
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
|
<OrganizationLookup
|
||||||
|
helperTextInvalid={organizationMeta.error}
|
||||||
|
isValid={!organizationMeta.touched || !organizationMeta.error}
|
||||||
|
onBlur={() => organizationHelpers.setTouched()}
|
||||||
|
onChange={onOrganizationChange}
|
||||||
|
value={organizationField.value}
|
||||||
|
required={!me.is_superuser}
|
||||||
|
helperText={
|
||||||
|
me?.is_superuser
|
||||||
|
? i18n._(
|
||||||
|
t`Leave this field blank to make the execution environment globally available.`
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
autoPopulate={!me?.is_superuser ? !executionEnvironment?.id : null}
|
||||||
|
/>
|
||||||
|
|
||||||
<CredentialLookup
|
<CredentialLookup
|
||||||
label={i18n._(t`Registry credential`)}
|
label={i18n._(t`Registry credential`)}
|
||||||
onChange={value => credentialHelpers.setValue(value)}
|
onChange={onCredentialChange}
|
||||||
value={credentialField.value}
|
value={credentialField.value}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@@ -46,19 +87,21 @@ function ExecutionEnvironmentForm({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
submitError,
|
submitError,
|
||||||
|
me,
|
||||||
...rest
|
...rest
|
||||||
}) {
|
}) {
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
image: executionEnvironment.image || '',
|
image: executionEnvironment.image || '',
|
||||||
description: executionEnvironment.description || '',
|
description: executionEnvironment.description || '',
|
||||||
credential: executionEnvironment?.summary_fields?.credential || null,
|
credential: executionEnvironment.summary_fields?.credential || null,
|
||||||
|
organization: executionEnvironment.summary_fields?.organization || null,
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Formik initialValues={initialValues} onSubmit={values => onSubmit(values)}>
|
<Formik initialValues={initialValues} onSubmit={values => onSubmit(values)}>
|
||||||
{formik => (
|
{formik => (
|
||||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||||
<FormColumnLayout>
|
<FormColumnLayout>
|
||||||
<ExecutionEnvironmentFormFields {...rest} />
|
<ExecutionEnvironmentFormFields me={me} {...rest} />
|
||||||
{submitError && <FormSubmitError error={submitError} />}
|
{submitError && <FormSubmitError error={submitError} />}
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ import ExecutionEnvironmentForm from './ExecutionEnvironmentForm';
|
|||||||
|
|
||||||
jest.mock('../../../api');
|
jest.mock('../../../api');
|
||||||
|
|
||||||
|
const mockMe = {
|
||||||
|
is_superuser: true,
|
||||||
|
is_super_auditor: false,
|
||||||
|
};
|
||||||
|
|
||||||
const executionEnvironment = {
|
const executionEnvironment = {
|
||||||
id: 16,
|
id: 16,
|
||||||
type: 'execution_environment',
|
type: 'execution_environment',
|
||||||
@@ -47,6 +52,7 @@ describe('<ExecutionEnvironmentForm/>', () => {
|
|||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
executionEnvironment={executionEnvironment}
|
executionEnvironment={executionEnvironment}
|
||||||
|
me={mockMe}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -75,8 +81,8 @@ describe('<ExecutionEnvironmentForm/>', () => {
|
|||||||
expect(onSubmit).toHaveBeenCalledTimes(1);
|
expect(onSubmit).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should update form values', () => {
|
test('should update form values', async () => {
|
||||||
act(() => {
|
await act(async () => {
|
||||||
wrapper.find('input#execution-environment-image').simulate('change', {
|
wrapper.find('input#execution-environment-image').simulate('change', {
|
||||||
target: {
|
target: {
|
||||||
value: 'https://registry.com/image/container2',
|
value: 'https://registry.com/image/container2',
|
||||||
@@ -93,8 +99,19 @@ describe('<ExecutionEnvironmentForm/>', () => {
|
|||||||
id: 99,
|
id: 99,
|
||||||
name: 'credential',
|
name: 'credential',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
wrapper.find('OrganizationLookup').invoke('onBlur')();
|
||||||
|
wrapper.find('OrganizationLookup').invoke('onChange')({
|
||||||
|
id: 3,
|
||||||
|
name: 'organization',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
|
||||||
|
id: 3,
|
||||||
|
name: 'organization',
|
||||||
|
});
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('input#execution-environment-image').prop('value')
|
wrapper.find('input#execution-environment-image').prop('value')
|
||||||
).toEqual('https://registry.com/image/container2');
|
).toEqual('https://registry.com/image/container2');
|
||||||
|
|||||||
Reference in New Issue
Block a user