Skip to content

Commit

Permalink
Add organization as part of creating/editing an execution environments
Browse files Browse the repository at this point in the history
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: #7887
  • Loading branch information
nixocio committed Oct 6, 2020
1 parent 3b407ef commit 39f878d
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 28 deletions.
2 changes: 2 additions & 0 deletions awx/ui_next/src/components/Lookup/OrganizationLookup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function OrganizationLookup({
value,
history,
autoPopulate,
helperText,
}) {
const autoPopulateLookup = useAutoPopulateLookup(onChange);

Expand Down Expand Up @@ -78,6 +79,7 @@ function OrganizationLookup({
isRequired={required}
validated={isValid ? 'default' : 'error'}
label={i18n._(t`Organization`)}
helperText={helperText}
>
<Lookup
id="organization"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import React, { useState } from 'react';
import { Card, PageSection } from '@patternfly/react-core';
import { useHistory } from 'react-router-dom';

import ExecutionEnvironmentForm from '../shared/ExecutionEnvironmentForm';
import { CardBody } from '../../../components/Card';
import { ExecutionEnvironmentsAPI } from '../../../api';
import { Config } from '../../../contexts/Config';
import { CardBody } from '../../../components/Card';
import ExecutionEnvironmentForm from '../shared/ExecutionEnvironmentForm';

function ExecutionEnvironmentAdd() {
const history = useHistory();
Expand All @@ -14,7 +15,8 @@ function ExecutionEnvironmentAdd() {
try {
const { data: response } = await ExecutionEnvironmentsAPI.create({
...values,
credential: values?.credential?.id,
credential: values.credential?.id,
organization: values.organization?.id,
});
history.push(`/execution_environments/${response.id}/details`);
} catch (error) {
Expand All @@ -29,11 +31,16 @@ function ExecutionEnvironmentAdd() {
<PageSection>
<Card>
<CardBody>
<ExecutionEnvironmentForm
onSubmit={handleSubmit}
submitError={submitError}
onCancel={handleCancel}
/>
<Config>
{({ me }) => (
<ExecutionEnvironmentForm
onSubmit={handleSubmit}
submitError={submitError}
onCancel={handleCancel}
me={me || {}}
/>
)}
</Config>
</CardBody>
</Card>
</PageSection>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import ExecutionEnvironmentAdd from './ExecutionEnvironmentAdd';

jest.mock('../../../api');

const mockMe = {
is_superuser: true,
is_system_auditor: false,
};

const executionEnvironmentData = {
credential: 4,
description: 'A simple EE',
Expand All @@ -29,7 +34,7 @@ describe('<ExecutionEnvironmentAdd/>', () => {
initialEntries: ['/execution_environments'],
});
await act(async () => {
wrapper = mountWithContexts(<ExecutionEnvironmentAdd />, {
wrapper = mountWithContexts(<ExecutionEnvironmentAdd me={mockMe} />, {
context: { router: { history } },
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useHistory } from 'react-router-dom';
import { CardBody } from '../../../components/Card';
import { ExecutionEnvironmentsAPI } from '../../../api';
import ExecutionEnvironmentForm from '../shared/ExecutionEnvironmentForm';
import { Config } from '../../../contexts/Config';

function ExecutionEnvironmentEdit({ executionEnvironment }) {
const history = useHistory();
Expand All @@ -15,6 +16,7 @@ function ExecutionEnvironmentEdit({ executionEnvironment }) {
await ExecutionEnvironmentsAPI.update(executionEnvironment.id, {
...values,
credential: values.credential ? values.credential.id : null,
organization: values.organization ? values.organization.id : null,
});
history.push(detailsUrl);
} catch (error) {
Expand All @@ -27,12 +29,17 @@ function ExecutionEnvironmentEdit({ executionEnvironment }) {
};
return (
<CardBody>
<ExecutionEnvironmentForm
executionEnvironment={executionEnvironment}
onSubmit={handleSubmit}
submitError={submitError}
onCancel={handleCancel}
/>
<Config>
{({ me }) => (
<ExecutionEnvironmentForm
executionEnvironment={executionEnvironment}
onSubmit={handleSubmit}
submitError={submitError}
onCancel={handleCancel}
me={me || {}}
/>
)}
</Config>
</CardBody>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import ExecutionEnvironmentEdit from './ExecutionEnvironmentEdit';

jest.mock('../../../api');

const mockMe = {
is_superuser: true,
is_system_auditor: false,
};

const executionEnvironmentData = {
id: 42,
credential: { id: 4 },
Expand All @@ -31,6 +36,7 @@ describe('<ExecutionEnvironmentEdit/>', () => {
wrapper = mountWithContexts(
<ExecutionEnvironmentEdit
executionEnvironment={executionEnvironmentData}
me={mockMe}
/>,
{
context: { router: { history } },
Expand All @@ -53,6 +59,7 @@ describe('<ExecutionEnvironmentEdit/>', () => {
expect(ExecutionEnvironmentsAPI.update).toHaveBeenCalledWith(42, {
...updateExecutionEnvironmentData,
credential: null,
organization: null,
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,42 @@
import React from 'react';
import React, { useCallback } from 'react';
import { func, shape } from 'prop-types';
import { Formik, useField } from 'formik';
import { Formik, useField, useFormikContext } from 'formik';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';

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 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]
);

function ExecutionEnvironmentFormFields({ i18n }) {
const [credentialField, , credentialHelpers] = useField('credential');
return (
<>
<FormField
Expand All @@ -32,9 +56,26 @@ function ExecutionEnvironmentFormFields({ i18n }) {
name="description"
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
label={i18n._(t`Registry credential`)}
onChange={value => credentialHelpers.setValue(value)}
onChange={onCredentialChange}
value={credentialField.value}
/>
</>
Expand All @@ -46,19 +87,21 @@ function ExecutionEnvironmentForm({
onSubmit,
onCancel,
submitError,
me,
...rest
}) {
const initialValues = {
image: executionEnvironment.image || '',
description: executionEnvironment.description || '',
credential: executionEnvironment?.summary_fields?.credential || null,
credential: executionEnvironment.summary_fields?.credential || null,
organization: executionEnvironment.summary_fields?.organization || null,
};
return (
<Formik initialValues={initialValues} onSubmit={values => onSubmit(values)}>
{formik => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormColumnLayout>
<ExecutionEnvironmentFormFields {...rest} />
<ExecutionEnvironmentFormFields me={me} {...rest} />
{submitError && <FormSubmitError error={submitError} />}
<FormActionGroup
onCancel={onCancel}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import ExecutionEnvironmentForm from './ExecutionEnvironmentForm';

jest.mock('../../../api');

const mockMe = {
is_superuser: true,
is_super_auditor: false,
};

const executionEnvironment = {
id: 16,
type: 'execution_environment',
Expand Down Expand Up @@ -47,6 +52,7 @@ describe('<ExecutionEnvironmentForm/>', () => {
onCancel={onCancel}
onSubmit={onSubmit}
executionEnvironment={executionEnvironment}
me={mockMe}
/>
);
});
Expand Down Expand Up @@ -75,8 +81,8 @@ describe('<ExecutionEnvironmentForm/>', () => {
expect(onSubmit).toHaveBeenCalledTimes(1);
});

test('should update form values', () => {
act(() => {
test('should update form values', async () => {
await act(async () => {
wrapper.find('input#execution-environment-image').simulate('change', {
target: {
value: 'https://registry.com/image/container2',
Expand All @@ -93,8 +99,19 @@ describe('<ExecutionEnvironmentForm/>', () => {
id: 99,
name: 'credential',
});

wrapper.find('OrganizationLookup').invoke('onBlur')();
wrapper.find('OrganizationLookup').invoke('onChange')({
id: 3,
name: 'organization',
});
});

wrapper.update();
expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
id: 3,
name: 'organization',
});
expect(
wrapper.find('input#execution-environment-image').prop('value')
).toEqual('https://registry.com/image/container2');
Expand Down

0 comments on commit 39f878d

Please sign in to comment.