mirror of
https://github.com/ansible/awx.git
synced 2026-02-16 10:40:01 -03:30
Merge pull request #7396 from AlexSCorey/7235-ApplicationsAddEdit
Adds Application edit functionality Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -58,6 +58,7 @@ describe('<Application />', () => {
|
|||||||
expect(ApplicationsAPI.readDetail).toBeCalledWith(1);
|
expect(ApplicationsAPI.readDetail).toBeCalledWith(1);
|
||||||
});
|
});
|
||||||
test('should throw error', async () => {
|
test('should throw error', async () => {
|
||||||
|
ApplicationsAPI.readOptions.mockResolvedValue(options);
|
||||||
ApplicationsAPI.readDetail.mockRejectedValue(
|
ApplicationsAPI.readDetail.mockRejectedValue(
|
||||||
new Error({
|
new Error({
|
||||||
response: {
|
response: {
|
||||||
@@ -70,7 +71,6 @@ describe('<Application />', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
ApplicationsAPI.readOptions.mockResolvedValue(options);
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<Application setBreadcrumb={() => {}} />);
|
wrapper = mountWithContexts(<Application setBreadcrumb={() => {}} />);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,50 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Card, PageSection } from '@patternfly/react-core';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
function ApplicationEdit() {
|
import { Card, PageSection } from '@patternfly/react-core';
|
||||||
|
import ApplicationForm from '../shared/ApplicationForm';
|
||||||
|
import { ApplicationsAPI } from '../../../api';
|
||||||
|
import { CardBody } from '../../../components/Card';
|
||||||
|
|
||||||
|
function ApplicationEdit({
|
||||||
|
application,
|
||||||
|
authorizationOptions,
|
||||||
|
clientTypeOptions,
|
||||||
|
}) {
|
||||||
|
const history = useHistory();
|
||||||
|
const { id } = useParams();
|
||||||
|
const [submitError, setSubmitError] = useState(null);
|
||||||
|
|
||||||
|
const handleSubmit = async ({ ...values }) => {
|
||||||
|
values.organization = values.organization.id;
|
||||||
|
try {
|
||||||
|
await ApplicationsAPI.update(id, values);
|
||||||
|
history.push(`/applications/${id}/details`);
|
||||||
|
} catch (err) {
|
||||||
|
setSubmitError(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
history.push(`/applications/${id}/details`);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<>
|
||||||
<Card>Application Edit</Card>
|
<PageSection>
|
||||||
</PageSection>
|
<Card>
|
||||||
|
<CardBody>
|
||||||
|
<ApplicationForm
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
application={application}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
authorizationOptions={authorizationOptions}
|
||||||
|
clientTypeOptions={clientTypeOptions}
|
||||||
|
submitError={submitError}
|
||||||
|
/>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</PageSection>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default ApplicationEdit;
|
export default ApplicationEdit;
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
|
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||||
|
|
||||||
|
import { ApplicationsAPI } from '../../../api';
|
||||||
|
import ApplicationEdit from './ApplicationEdit';
|
||||||
|
|
||||||
|
jest.mock('../../../api/models/Applications');
|
||||||
|
jest.mock('../../../api/models/Organizations');
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
...jest.requireActual('react-router-dom'),
|
||||||
|
history: () => ({
|
||||||
|
location: '/applications/1/edit',
|
||||||
|
}),
|
||||||
|
useParams: () => ({ id: 1 }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const authorizationOptions = [
|
||||||
|
{
|
||||||
|
key: 'authorization-code',
|
||||||
|
label: 'Authorization code',
|
||||||
|
value: 'authorization-code',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'password',
|
||||||
|
label: 'Resource owner password-based',
|
||||||
|
value: 'password',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const clientTypeOptions = [
|
||||||
|
{ key: 'confidential', label: 'Confidential', value: 'confidential' },
|
||||||
|
{ key: 'public', label: 'Public', value: 'public' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const application = {
|
||||||
|
id: 1,
|
||||||
|
type: 'o_auth2_application',
|
||||||
|
url: '/api/v2/applications/10/',
|
||||||
|
related: {
|
||||||
|
named_url: '/api/v2/applications/Alex++bar/',
|
||||||
|
tokens: '/api/v2/applications/10/tokens/',
|
||||||
|
activity_stream: '/api/v2/applications/10/activity_stream/',
|
||||||
|
},
|
||||||
|
summary_fields: {
|
||||||
|
organization: {
|
||||||
|
id: 230,
|
||||||
|
name: 'bar',
|
||||||
|
description:
|
||||||
|
'SaleNameBedPersonalityManagerWhileFinanceBreakToothPerson魲',
|
||||||
|
},
|
||||||
|
user_capabilities: {
|
||||||
|
edit: true,
|
||||||
|
delete: true,
|
||||||
|
},
|
||||||
|
tokens: {
|
||||||
|
count: 0,
|
||||||
|
results: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created: '2020-06-11T17:54:33.983993Z',
|
||||||
|
modified: '2020-06-11T17:54:33.984039Z',
|
||||||
|
name: 'Alex',
|
||||||
|
description: '',
|
||||||
|
client_id: 'b1dmj8xzkbFm1ZQ27ygw2ZeE9I0AXqqeL74fiyk4',
|
||||||
|
client_secret: '************',
|
||||||
|
client_type: 'confidential',
|
||||||
|
redirect_uris: 'http://www.google.com',
|
||||||
|
authorization_grant_type: 'authorization-code',
|
||||||
|
skip_authorization: false,
|
||||||
|
organization: 230,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('<ApplicationEdit/>', () => {
|
||||||
|
let wrapper;
|
||||||
|
test('should mount properly', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ApplicationEdit
|
||||||
|
application={application}
|
||||||
|
authorizationOptions={authorizationOptions}
|
||||||
|
clientTypeOptions={clientTypeOptions}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
expect(wrapper.find('ApplicationEdit').length).toBe(1);
|
||||||
|
});
|
||||||
|
test('should cancel form properly', async () => {
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/applications/1/edit'],
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ApplicationEdit
|
||||||
|
application={application}
|
||||||
|
authorizationOptions={authorizationOptions}
|
||||||
|
clientTypeOptions={clientTypeOptions}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
context: { router: { history } },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('Button[aria-label="Cancel"]').prop('onClick')();
|
||||||
|
});
|
||||||
|
expect(history.location.pathname).toBe('/applications/1/details');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('should throw error on submit', async () => {
|
||||||
|
const error = {
|
||||||
|
response: {
|
||||||
|
config: {
|
||||||
|
method: 'patch',
|
||||||
|
url: '/api/v2/applications/',
|
||||||
|
},
|
||||||
|
data: { detail: 'An error occurred' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ApplicationsAPI.update.mockRejectedValue(error);
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/applications/1/edit'],
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ApplicationEdit
|
||||||
|
application={application}
|
||||||
|
authorizationOptions={authorizationOptions}
|
||||||
|
clientTypeOptions={clientTypeOptions}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
context: { router: { history } },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('Formik').prop('onSubmit')({
|
||||||
|
id: 1,
|
||||||
|
organization: { id: 4 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find('FormSubmitError').length).toBe(1);
|
||||||
|
});
|
||||||
|
test('expect values to be updated and submitted properly', async () => {
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/applications/1/edit'],
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ApplicationEdit
|
||||||
|
application={application}
|
||||||
|
authorizationOptions={authorizationOptions}
|
||||||
|
clientTypeOptions={clientTypeOptions}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
context: { router: { history } },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('input#name').simulate('change', {
|
||||||
|
target: { value: 'new foo', name: 'name' },
|
||||||
|
});
|
||||||
|
wrapper.find('input#description').simulate('change', {
|
||||||
|
target: { value: 'new bar', name: 'description' },
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.find('input#redirect_uris').simulate('change', {
|
||||||
|
target: { value: 'https://www.google.com', name: 'redirect_uris' },
|
||||||
|
});
|
||||||
|
wrapper.find('AnsibleSelect[name="client_type"]').prop('onChange')(
|
||||||
|
{},
|
||||||
|
'confidential'
|
||||||
|
);
|
||||||
|
wrapper.find('OrganizationLookup').invoke('onChange')({
|
||||||
|
id: 1,
|
||||||
|
name: 'organization',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find('input#name').prop('value')).toBe('new foo');
|
||||||
|
expect(wrapper.find('input#description').prop('value')).toBe('new bar');
|
||||||
|
expect(wrapper.find('Chip').length).toBe(1);
|
||||||
|
expect(wrapper.find('Chip').text()).toBe('organization');
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find('AnsibleSelect[name="authorization_grant_type"]')
|
||||||
|
.prop('value')
|
||||||
|
).toBe('authorization-code');
|
||||||
|
expect(
|
||||||
|
wrapper.find('AnsibleSelect[name="client_type"]').prop('value')
|
||||||
|
).toBe('confidential');
|
||||||
|
expect(wrapper.find('input#redirect_uris').prop('value')).toBe(
|
||||||
|
'https://www.google.com'
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('Formik').prop('onSubmit')({
|
||||||
|
authorization_grant_type: 'authorization-code',
|
||||||
|
client_type: 'confidential',
|
||||||
|
description: 'bar',
|
||||||
|
name: 'foo',
|
||||||
|
organization: { id: 1 },
|
||||||
|
redirect_uris: 'http://www.google.com',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ApplicationsAPI.update).toBeCalledWith(1, {
|
||||||
|
authorization_grant_type: 'authorization-code',
|
||||||
|
client_type: 'confidential',
|
||||||
|
description: 'bar',
|
||||||
|
name: 'foo',
|
||||||
|
organization: 1,
|
||||||
|
redirect_uris: 'http://www.google.com',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -19,7 +19,6 @@ function Applications({ i18n }) {
|
|||||||
if (!application) {
|
if (!application) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setBreadcrumbConfig({
|
setBreadcrumbConfig({
|
||||||
'/applications': i18n._(t`Applications`),
|
'/applications': i18n._(t`Applications`),
|
||||||
'/applications/add': i18n._(t`Create New Application`),
|
'/applications/add': i18n._(t`Create New Application`),
|
||||||
|
|||||||
@@ -178,4 +178,62 @@ describe('<ApplicationForm', () => {
|
|||||||
redirect_uris: 'http://www.google.com',
|
redirect_uris: 'http://www.google.com',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
test('should render required on Redirect URIs', async () => {
|
||||||
|
OrganizationsAPI.read.mockResolvedValue({
|
||||||
|
results: [{ id: 1 }, { id: 2 }],
|
||||||
|
});
|
||||||
|
const onSubmit = jest.fn();
|
||||||
|
const onCancel = jest.fn();
|
||||||
|
const application = {
|
||||||
|
id: 1,
|
||||||
|
type: 'o_auth2_application',
|
||||||
|
url: '/api/v2/applications/10/',
|
||||||
|
related: {
|
||||||
|
named_url: '/api/v2/applications/Alex++bar/',
|
||||||
|
tokens: '/api/v2/applications/10/tokens/',
|
||||||
|
activity_stream: '/api/v2/applications/10/activity_stream/',
|
||||||
|
},
|
||||||
|
summary_fields: {
|
||||||
|
organization: {
|
||||||
|
id: 230,
|
||||||
|
name: 'bar',
|
||||||
|
description:
|
||||||
|
'SaleNameBedPersonalityManagerWhileFinanceBreakToothPerson魲',
|
||||||
|
},
|
||||||
|
user_capabilities: {
|
||||||
|
edit: true,
|
||||||
|
delete: true,
|
||||||
|
},
|
||||||
|
tokens: {
|
||||||
|
count: 0,
|
||||||
|
results: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created: '2020-06-11T17:54:33.983993Z',
|
||||||
|
modified: '2020-06-11T17:54:33.984039Z',
|
||||||
|
name: 'Alex',
|
||||||
|
description: '',
|
||||||
|
client_id: 'b1dmj8xzkbFm1ZQ27ygw2ZeE9I0AXqqeL74fiyk4',
|
||||||
|
client_secret: '************',
|
||||||
|
client_type: 'confidential',
|
||||||
|
redirect_uris: 'http://www.google.com',
|
||||||
|
authorization_grant_type: 'authorization-code',
|
||||||
|
skip_authorization: false,
|
||||||
|
organization: 230,
|
||||||
|
};
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ApplicationForm
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
application={application}
|
||||||
|
onCancel={onCancel}
|
||||||
|
authorizationOptions={authorizationOptions}
|
||||||
|
clientTypeOptions={clientTypeOptions}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
wrapper.find('FormField[name="redirect_uris"]').prop('isRequired')
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user