mirror of
https://github.com/ansible/awx.git
synced 2026-04-11 13:09:21 -02:30
Merge pull request #9710 from nixocio/ui_issue_6663
Make Org a required field for org admins on WFJT Make Org a required field for org admins on WFJT. See: #6663 Reviewed-by: Alex Corey <Alex.swansboro@gmail.com> Reviewed-by: Tiago Góes <tiago.goes2009@gmail.com>
This commit is contained in:
@@ -1,13 +1,22 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useCallback, useEffect } from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import { Card, PageSection } from '@patternfly/react-core';
|
import { Card, PageSection } from '@patternfly/react-core';
|
||||||
import { CardBody } from '../../../components/Card';
|
import { CardBody } from '../../../components/Card';
|
||||||
|
|
||||||
import { WorkflowJobTemplatesAPI, OrganizationsAPI } from '../../../api';
|
import {
|
||||||
|
WorkflowJobTemplatesAPI,
|
||||||
|
OrganizationsAPI,
|
||||||
|
UsersAPI,
|
||||||
|
} from '../../../api';
|
||||||
import WorkflowJobTemplateForm from '../shared/WorkflowJobTemplateForm';
|
import WorkflowJobTemplateForm from '../shared/WorkflowJobTemplateForm';
|
||||||
|
import { useConfig } from '../../../contexts/Config';
|
||||||
|
import useRequest from '../../../util/useRequest';
|
||||||
|
import ContentError from '../../../components/ContentError';
|
||||||
|
import ContentLoading from '../../../components/ContentLoading';
|
||||||
|
|
||||||
function WorkflowJobTemplateAdd() {
|
function WorkflowJobTemplateAdd() {
|
||||||
|
const { me = {} } = useConfig();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [formSubmitError, setFormSubmitError] = useState(null);
|
const [formSubmitError, setFormSubmitError] = useState(null);
|
||||||
|
|
||||||
@@ -60,6 +69,33 @@ function WorkflowJobTemplateAdd() {
|
|||||||
history.push(`/templates`);
|
history.push(`/templates`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
isLoading,
|
||||||
|
request: fetchUserRole,
|
||||||
|
result: { orgAdminResults, isOrgAdmin },
|
||||||
|
error: contentError,
|
||||||
|
} = useRequest(
|
||||||
|
useCallback(async () => {
|
||||||
|
const {
|
||||||
|
data: { results, count },
|
||||||
|
} = await UsersAPI.readAdminOfOrganizations(me?.id);
|
||||||
|
return { isOrgAdmin: count > 0, orgAdminResults: results };
|
||||||
|
}, [me.id]),
|
||||||
|
{ isOrgAdmin: false, orgAdminResults: null }
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchUserRole();
|
||||||
|
}, [fetchUserRole]);
|
||||||
|
|
||||||
|
if (contentError) {
|
||||||
|
return <ContentError error={contentError} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading || !orgAdminResults) {
|
||||||
|
return <ContentLoading />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<Card>
|
<Card>
|
||||||
@@ -68,6 +104,7 @@ function WorkflowJobTemplateAdd() {
|
|||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
submitError={formSubmitError}
|
submitError={formSubmitError}
|
||||||
|
isOrgAdmin={isOrgAdmin}
|
||||||
/>
|
/>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -7,8 +7,12 @@ import {
|
|||||||
OrganizationsAPI,
|
OrganizationsAPI,
|
||||||
LabelsAPI,
|
LabelsAPI,
|
||||||
ExecutionEnvironmentsAPI,
|
ExecutionEnvironmentsAPI,
|
||||||
|
UsersAPI,
|
||||||
} from '../../../api';
|
} from '../../../api';
|
||||||
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
import {
|
||||||
|
mountWithContexts,
|
||||||
|
waitForElement,
|
||||||
|
} from '../../../../testUtils/enzymeHelpers';
|
||||||
|
|
||||||
import WorkflowJobTemplateAdd from './WorkflowJobTemplateAdd';
|
import WorkflowJobTemplateAdd from './WorkflowJobTemplateAdd';
|
||||||
|
|
||||||
@@ -17,6 +21,7 @@ jest.mock('../../../api/models/Organizations');
|
|||||||
jest.mock('../../../api/models/Labels');
|
jest.mock('../../../api/models/Labels');
|
||||||
jest.mock('../../../api/models/Inventories');
|
jest.mock('../../../api/models/Inventories');
|
||||||
jest.mock('../../../api/models/ExecutionEnvironments');
|
jest.mock('../../../api/models/ExecutionEnvironments');
|
||||||
|
jest.mock('../../../api/models/Users');
|
||||||
|
|
||||||
describe('<WorkflowJobTemplateAdd/>', () => {
|
describe('<WorkflowJobTemplateAdd/>', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
@@ -40,6 +45,10 @@ describe('<WorkflowJobTemplateAdd/>', () => {
|
|||||||
data: { results: [{ id: 1, name: 'Foo', image: 'localhost.com' }] },
|
data: { results: [{ id: 1, name: 'Foo', image: 'localhost.com' }] },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
UsersAPI.readAdminOfOrganizations.mockResolvedValue({
|
||||||
|
data: { count: 0, results: [] },
|
||||||
|
});
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
history = createMemoryHistory({
|
history = createMemoryHistory({
|
||||||
initialEntries: ['/templates/workflow_job_template/add'],
|
initialEntries: ['/templates/workflow_job_template/add'],
|
||||||
@@ -67,6 +76,7 @@ describe('<WorkflowJobTemplateAdd/>', () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import { CardBody } from '../../../components/Card';
|
import { CardBody } from '../../../components/Card';
|
||||||
import { getAddedAndRemoved } from '../../../util/lists';
|
import { getAddedAndRemoved } from '../../../util/lists';
|
||||||
import { WorkflowJobTemplatesAPI, OrganizationsAPI } from '../../../api';
|
import {
|
||||||
|
WorkflowJobTemplatesAPI,
|
||||||
|
OrganizationsAPI,
|
||||||
|
UsersAPI,
|
||||||
|
} from '../../../api';
|
||||||
import { WorkflowJobTemplateForm } from '../shared';
|
import { WorkflowJobTemplateForm } from '../shared';
|
||||||
|
import { useConfig } from '../../../contexts/Config';
|
||||||
|
import useRequest from '../../../util/useRequest';
|
||||||
|
import ContentError from '../../../components/ContentError';
|
||||||
|
import ContentLoading from '../../../components/ContentLoading';
|
||||||
|
|
||||||
function WorkflowJobTemplateEdit({ template }) {
|
function WorkflowJobTemplateEdit({ template }) {
|
||||||
|
const { me = {} } = useConfig();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [formSubmitError, setFormSubmitError] = useState(null);
|
const [formSubmitError, setFormSubmitError] = useState(null);
|
||||||
|
|
||||||
@@ -69,6 +78,33 @@ function WorkflowJobTemplateEdit({ template }) {
|
|||||||
history.push(`/templates/workflow_job_template/${template.id}/details`);
|
history.push(`/templates/workflow_job_template/${template.id}/details`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
isLoading,
|
||||||
|
request: fetchUserRole,
|
||||||
|
result: { orgAdminResults, isOrgAdmin },
|
||||||
|
error: contentError,
|
||||||
|
} = useRequest(
|
||||||
|
useCallback(async () => {
|
||||||
|
const {
|
||||||
|
data: { results, count },
|
||||||
|
} = await UsersAPI.readAdminOfOrganizations(me?.id);
|
||||||
|
return { isOrgAdmin: count > 0, orgAdminResults: results };
|
||||||
|
}, [me.id]),
|
||||||
|
{ isOrgAdmin: false, orgAdminResults: null }
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchUserRole();
|
||||||
|
}, [fetchUserRole]);
|
||||||
|
|
||||||
|
if (contentError) {
|
||||||
|
return <ContentError error={contentError} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading || !orgAdminResults) {
|
||||||
|
return <ContentLoading />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<WorkflowJobTemplateForm
|
<WorkflowJobTemplateForm
|
||||||
@@ -76,6 +112,7 @@ function WorkflowJobTemplateEdit({ template }) {
|
|||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
template={template}
|
template={template}
|
||||||
submitError={formSubmitError}
|
submitError={formSubmitError}
|
||||||
|
isOrgAdmin={isOrgAdmin}
|
||||||
/>
|
/>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,8 +7,12 @@ import {
|
|||||||
OrganizationsAPI,
|
OrganizationsAPI,
|
||||||
LabelsAPI,
|
LabelsAPI,
|
||||||
ExecutionEnvironmentsAPI,
|
ExecutionEnvironmentsAPI,
|
||||||
|
UsersAPI,
|
||||||
} from '../../../api';
|
} from '../../../api';
|
||||||
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
import {
|
||||||
|
mountWithContexts,
|
||||||
|
waitForElement,
|
||||||
|
} from '../../../../testUtils/enzymeHelpers';
|
||||||
import WorkflowJobTemplateEdit from './WorkflowJobTemplateEdit';
|
import WorkflowJobTemplateEdit from './WorkflowJobTemplateEdit';
|
||||||
|
|
||||||
jest.mock('../../../api/models/WorkflowJobTemplates');
|
jest.mock('../../../api/models/WorkflowJobTemplates');
|
||||||
@@ -16,6 +20,7 @@ jest.mock('../../../api/models/Labels');
|
|||||||
jest.mock('../../../api/models/Organizations');
|
jest.mock('../../../api/models/Organizations');
|
||||||
jest.mock('../../../api/models/Inventories');
|
jest.mock('../../../api/models/Inventories');
|
||||||
jest.mock('../../../api/models/ExecutionEnvironments');
|
jest.mock('../../../api/models/ExecutionEnvironments');
|
||||||
|
jest.mock('../../../api/models/Users');
|
||||||
|
|
||||||
const mockTemplate = {
|
const mockTemplate = {
|
||||||
id: 6,
|
id: 6,
|
||||||
@@ -74,6 +79,10 @@ describe('<WorkflowJobTemplateEdit/>', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
UsersAPI.readAdminOfOrganizations.mockResolvedValue({
|
||||||
|
data: { count: 1, results: [{ id: 1 }] },
|
||||||
|
});
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
history = createMemoryHistory({
|
history = createMemoryHistory({
|
||||||
initialEntries: ['/templates/workflow_job_template/6/edit'],
|
initialEntries: ['/templates/workflow_job_template/6/edit'],
|
||||||
@@ -95,6 +104,7 @@ describe('<WorkflowJobTemplateEdit/>', () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -241,6 +251,7 @@ describe('<WorkflowJobTemplateEdit/>', () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
await waitForElement(newWrapper, 'ContentLoading', el => el.length === 0);
|
||||||
OrganizationsAPI.read.mockRejectedValue({
|
OrganizationsAPI.read.mockRejectedValue({
|
||||||
response: {
|
response: {
|
||||||
config: {
|
config: {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ function WorkflowJobTemplateForm({
|
|||||||
handleCancel,
|
handleCancel,
|
||||||
i18n,
|
i18n,
|
||||||
submitError,
|
submitError,
|
||||||
|
isOrgAdmin,
|
||||||
}) {
|
}) {
|
||||||
const { setFieldValue } = useFormikContext();
|
const { setFieldValue } = useFormikContext();
|
||||||
const [enableWebhooks, setEnableWebhooks] = useState(
|
const [enableWebhooks, setEnableWebhooks] = useState(
|
||||||
@@ -55,7 +56,9 @@ function WorkflowJobTemplateForm({
|
|||||||
);
|
);
|
||||||
const [labelsField, , labelsHelpers] = useField('labels');
|
const [labelsField, , labelsHelpers] = useField('labels');
|
||||||
const [limitField, limitMeta, limitHelpers] = useField('limit');
|
const [limitField, limitMeta, limitHelpers] = useField('limit');
|
||||||
const [organizationField, organizationMeta] = useField('organization');
|
const [organizationField, organizationMeta, organizationHelpers] = useField(
|
||||||
|
'organization'
|
||||||
|
);
|
||||||
const [scmField, , scmHelpers] = useField('scm_branch');
|
const [scmField, , scmHelpers] = useField('scm_branch');
|
||||||
const [, webhookServiceMeta, webhookServiceHelpers] = useField(
|
const [, webhookServiceMeta, webhookServiceHelpers] = useField(
|
||||||
'webhook_service'
|
'webhook_service'
|
||||||
@@ -119,9 +122,14 @@ function WorkflowJobTemplateForm({
|
|||||||
/>
|
/>
|
||||||
<OrganizationLookup
|
<OrganizationLookup
|
||||||
helperTextInvalid={organizationMeta.error}
|
helperTextInvalid={organizationMeta.error}
|
||||||
|
isValid={!organizationMeta.touched || !organizationMeta.error}
|
||||||
|
onBlur={() => organizationHelpers.setTouched()}
|
||||||
onChange={onOrganizationChange}
|
onChange={onOrganizationChange}
|
||||||
value={organizationField.value}
|
value={organizationField.value}
|
||||||
isValid={!organizationMeta.error}
|
touched={organizationMeta.touched}
|
||||||
|
error={organizationMeta.error}
|
||||||
|
required={isOrgAdmin}
|
||||||
|
autoPopulate={isOrgAdmin}
|
||||||
/>
|
/>
|
||||||
<>
|
<>
|
||||||
<InventoryLookup
|
<InventoryLookup
|
||||||
@@ -287,6 +295,7 @@ WorkflowJobTemplateForm.propTypes = {
|
|||||||
handleSubmit: PropTypes.func.isRequired,
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
handleCancel: PropTypes.func.isRequired,
|
handleCancel: PropTypes.func.isRequired,
|
||||||
submitError: shape({}),
|
submitError: shape({}),
|
||||||
|
isOrgAdmin: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
WorkflowJobTemplateForm.defaultProps = {
|
WorkflowJobTemplateForm.defaultProps = {
|
||||||
@@ -297,6 +306,7 @@ WorkflowJobTemplateForm.defaultProps = {
|
|||||||
inventory: undefined,
|
inventory: undefined,
|
||||||
project: undefined,
|
project: undefined,
|
||||||
},
|
},
|
||||||
|
isOrgAdmin: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const FormikApp = withFormik({
|
const FormikApp = withFormik({
|
||||||
|
|||||||
@@ -124,6 +124,40 @@ describe('<WorkflowJobTemplateForm/>', () => {
|
|||||||
expect(wrapper.length).toBe(1);
|
expect(wrapper.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('organization is a required field for organization admins', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<Route
|
||||||
|
path="/templates/workflow_job_template/:id/edit"
|
||||||
|
component={() => (
|
||||||
|
<WorkflowJobTemplateForm
|
||||||
|
template={mockTemplate}
|
||||||
|
handleCancel={handleCancel}
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
isOrgAdmin
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
router: {
|
||||||
|
history,
|
||||||
|
route: {
|
||||||
|
location: history.location,
|
||||||
|
match: { params: { id: 6 } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.update();
|
||||||
|
expect(
|
||||||
|
wrapper.find('FormGroup[label="Organization"]').prop('isRequired')
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
test('all the fields render successfully', () => {
|
test('all the fields render successfully', () => {
|
||||||
const fields = [
|
const fields = [
|
||||||
'FormField[name="name"]',
|
'FormField[name="name"]',
|
||||||
@@ -140,6 +174,9 @@ describe('<WorkflowJobTemplateForm/>', () => {
|
|||||||
expect(wrapper.find(`${field}`).length).toBe(1);
|
expect(wrapper.find(`${field}`).length).toBe(1);
|
||||||
};
|
};
|
||||||
fields.map((field, index) => assertField(field, index));
|
fields.map((field, index) => assertField(field, index));
|
||||||
|
expect(
|
||||||
|
wrapper.find('FormGroup[label="Organization"]').prop('isRequired')
|
||||||
|
).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('changing inputs should update values', async () => {
|
test('changing inputs should update values', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user