mirror of
https://github.com/ansible/awx.git
synced 2026-01-17 12:41:19 -03:30
Merge pull request #7489 from nixocio/ui_issue_7326
Add edit credential types Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
commit
d0ac028265
@ -22,6 +22,8 @@ function FormSubmitError({ error }) {
|
||||
Object.values(error.response.data).forEach(value => {
|
||||
if (Array.isArray(value)) {
|
||||
messages = messages.concat(value);
|
||||
} else {
|
||||
messages.push(value);
|
||||
}
|
||||
});
|
||||
setErrorMessage(messages.length > 0 ? messages : null);
|
||||
|
||||
@ -65,11 +65,6 @@ function CredentialType({ i18n, setBreadcrumb }) {
|
||||
},
|
||||
];
|
||||
|
||||
let cardHeader = <RoutedTabs tabsArray={tabsArray} />;
|
||||
if (pathname.endsWith('edit')) {
|
||||
cardHeader = null;
|
||||
}
|
||||
|
||||
if (!isLoading && contentError) {
|
||||
return (
|
||||
<PageSection>
|
||||
@ -89,6 +84,11 @@ function CredentialType({ i18n, setBreadcrumb }) {
|
||||
);
|
||||
}
|
||||
|
||||
let cardHeader = <RoutedTabs tabsArray={tabsArray} />;
|
||||
if (pathname.endsWith('edit')) {
|
||||
cardHeader = null;
|
||||
}
|
||||
|
||||
return (
|
||||
<PageSection>
|
||||
<Card>
|
||||
@ -104,7 +104,7 @@ function CredentialType({ i18n, setBreadcrumb }) {
|
||||
{credentialType && (
|
||||
<>
|
||||
<Route path="/credential_types/:id/edit">
|
||||
<CredentialTypeEdit />
|
||||
<CredentialTypeEdit credentialType={credentialType} />
|
||||
</Route>
|
||||
<Route path="/credential_types/:id/details">
|
||||
<CredentialTypeDetails credentialType={credentialType} />
|
||||
|
||||
@ -70,7 +70,7 @@ function CredentialTypeDetails({ credentialType, i18n }) {
|
||||
<Button
|
||||
aria-label={i18n._(t`edit`)}
|
||||
component={Link}
|
||||
to={`credential_types/${id}/edit`}
|
||||
to={`/credential_types/${id}/edit`}
|
||||
>
|
||||
{i18n._(t`Edit`)}
|
||||
</Button>
|
||||
|
||||
@ -1,11 +1,41 @@
|
||||
import React from 'react';
|
||||
import { Card, PageSection } from '@patternfly/react-core';
|
||||
import React, { useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
function CredentialTypeEdit() {
|
||||
import { CardBody } from '../../../components/Card';
|
||||
import { CredentialTypesAPI } from '../../../api';
|
||||
import CredentialTypeForm from '../shared/CredentialTypeForm';
|
||||
import { parseVariableField } from '../../../util/yaml';
|
||||
|
||||
function CredentialTypeEdit({ credentialType }) {
|
||||
const history = useHistory();
|
||||
const [submitError, setSubmitError] = useState(null);
|
||||
const detailsUrl = `/credential_types/${credentialType.id}/details`;
|
||||
|
||||
const handleSubmit = async values => {
|
||||
try {
|
||||
await CredentialTypesAPI.update(credentialType.id, {
|
||||
...values,
|
||||
injectors: parseVariableField(values.injectors),
|
||||
inputs: parseVariableField(values.inputs),
|
||||
});
|
||||
history.push(detailsUrl);
|
||||
} catch (error) {
|
||||
setSubmitError(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
history.push(detailsUrl);
|
||||
};
|
||||
return (
|
||||
<PageSection>
|
||||
<Card>Credential Type Edit</Card>
|
||||
</PageSection>
|
||||
<CardBody>
|
||||
<CredentialTypeForm
|
||||
credentialType={credentialType}
|
||||
onSubmit={handleSubmit}
|
||||
submitError={submitError}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,140 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { createMemoryHistory } from 'history';
|
||||
|
||||
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||
import { CredentialTypesAPI } from '../../../api';
|
||||
|
||||
import CredentialTypeEdit from './CredentialTypeEdit';
|
||||
|
||||
jest.mock('../../../api');
|
||||
|
||||
const credentialTypeData = {
|
||||
id: 42,
|
||||
name: 'Foo',
|
||||
description: 'New credential',
|
||||
kind: 'cloud',
|
||||
inputs: JSON.stringify({
|
||||
fields: [
|
||||
{
|
||||
id: 'username',
|
||||
type: 'string',
|
||||
label: 'Jenkins username',
|
||||
},
|
||||
{
|
||||
id: 'password',
|
||||
type: 'string',
|
||||
label: 'Jenkins password',
|
||||
secret: true,
|
||||
},
|
||||
],
|
||||
required: ['username', 'password'],
|
||||
}),
|
||||
injectors: JSON.stringify({
|
||||
extra_vars: {
|
||||
Jenkins_password: '{{ password }}',
|
||||
Jenkins_username: '{{ username }}',
|
||||
},
|
||||
}),
|
||||
summary_fields: {
|
||||
created_by: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
},
|
||||
modified_by: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
},
|
||||
user_capabilities: {
|
||||
edit: true,
|
||||
delete: true,
|
||||
},
|
||||
},
|
||||
created: '2020-06-25T16:52:36.127008Z',
|
||||
modified: '2020-06-25T16:52:36.127022Z',
|
||||
};
|
||||
|
||||
const updateCredentialTypeData = {
|
||||
name: 'Bar',
|
||||
description: 'Updated new Credential Type',
|
||||
injectors: credentialTypeData.injectors,
|
||||
inputs: credentialTypeData.inputs,
|
||||
};
|
||||
|
||||
describe('<CredentialTypeEdit>', () => {
|
||||
let wrapper;
|
||||
let history;
|
||||
|
||||
beforeAll(async () => {
|
||||
history = createMemoryHistory();
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialTypeEdit credentialType={credentialTypeData} />,
|
||||
{
|
||||
context: { router: { history } },
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('handleSubmit should call the api and redirect to details page', async () => {
|
||||
await act(async () => {
|
||||
wrapper.find('CredentialTypeForm').invoke('onSubmit')(
|
||||
updateCredentialTypeData
|
||||
);
|
||||
wrapper.update();
|
||||
expect(CredentialTypesAPI.update).toHaveBeenCalledWith(42, {
|
||||
...updateCredentialTypeData,
|
||||
injectors: JSON.parse(credentialTypeData.injectors),
|
||||
inputs: JSON.parse(credentialTypeData.inputs),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should navigate to credential types detail when cancel is clicked', async () => {
|
||||
await act(async () => {
|
||||
wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
|
||||
});
|
||||
expect(history.location.pathname).toEqual('/credential_types/42/details');
|
||||
});
|
||||
|
||||
test('should navigate to credential type detail after successful submission', async () => {
|
||||
await act(async () => {
|
||||
wrapper.find('CredentialTypeForm').invoke('onSubmit')({
|
||||
...updateCredentialTypeData,
|
||||
injectors: JSON.parse(credentialTypeData.injectors),
|
||||
inputs: JSON.parse(credentialTypeData.inputs),
|
||||
});
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('FormSubmitError').length).toBe(0);
|
||||
expect(history.location.pathname).toEqual('/credential_types/42/details');
|
||||
});
|
||||
|
||||
test('failed form submission should show an error message', async () => {
|
||||
const error = {
|
||||
response: {
|
||||
data: { detail: 'An error occurred' },
|
||||
},
|
||||
};
|
||||
CredentialTypesAPI.update.mockImplementationOnce(() =>
|
||||
Promise.reject(error)
|
||||
);
|
||||
await act(async () => {
|
||||
wrapper.find('CredentialTypeForm').invoke('onSubmit')(
|
||||
updateCredentialTypeData
|
||||
);
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('FormSubmitError').length).toBe(1);
|
||||
});
|
||||
});
|
||||
@ -131,7 +131,7 @@ function CredentialTypeList({ i18n }) {
|
||||
)}
|
||||
renderItem={credentialType => (
|
||||
<CredentialTypeListItem
|
||||
key={credentialTypes.id}
|
||||
key={credentialType.id}
|
||||
value={credentialType.name}
|
||||
credentialType={credentialType}
|
||||
detailUrl={`${match.url}/${credentialType.id}/details`}
|
||||
|
||||
@ -14,6 +14,8 @@ import {
|
||||
FormFullWidthLayout,
|
||||
} from '../../../components/FormLayout';
|
||||
|
||||
import { jsonToYaml } from '../../../util/yaml';
|
||||
|
||||
function CredentialTypeFormFields({ i18n }) {
|
||||
return (
|
||||
<>
|
||||
@ -65,8 +67,12 @@ function CredentialTypeForm({
|
||||
const initialValues = {
|
||||
name: credentialType.name || '',
|
||||
description: credentialType.description || '',
|
||||
inputs: credentialType.inputs || '---',
|
||||
injectors: credentialType.injectors || '---',
|
||||
inputs: credentialType.inputs
|
||||
? jsonToYaml(JSON.stringify(credentialType.inputs))
|
||||
: '---',
|
||||
injectors: credentialType.injectors
|
||||
? jsonToYaml(JSON.stringify(credentialType.injectors))
|
||||
: '---',
|
||||
};
|
||||
return (
|
||||
<Formik initialValues={initialValues} onSubmit={values => onSubmit(values)}>
|
||||
@ -74,7 +80,7 @@ function CredentialTypeForm({
|
||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||
<FormColumnLayout>
|
||||
<CredentialTypeFormFields {...rest} />
|
||||
<FormSubmitError error={submitError} />
|
||||
{submitError && <FormSubmitError error={submitError} />}
|
||||
<FormActionGroup
|
||||
onCancel={onCancel}
|
||||
onSubmit={formik.handleSubmit}
|
||||
|
||||
@ -38,13 +38,12 @@ export function isJson(jsonString) {
|
||||
|
||||
export function parseVariableField(variableField) {
|
||||
if (variableField === '---' || variableField === '{}') {
|
||||
variableField = {};
|
||||
} else {
|
||||
if (!isJson(variableField)) {
|
||||
variableField = yamlToJson(variableField);
|
||||
}
|
||||
variableField = JSON.parse(variableField);
|
||||
return {};
|
||||
}
|
||||
if (!isJson(variableField)) {
|
||||
variableField = yamlToJson(variableField);
|
||||
}
|
||||
variableField = JSON.parse(variableField);
|
||||
|
||||
return variableField;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user