mirror of
https://github.com/ansible/awx.git
synced 2026-05-16 13:57:39 -02: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:
@@ -22,6 +22,8 @@ function FormSubmitError({ error }) {
|
|||||||
Object.values(error.response.data).forEach(value => {
|
Object.values(error.response.data).forEach(value => {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
messages = messages.concat(value);
|
messages = messages.concat(value);
|
||||||
|
} else {
|
||||||
|
messages.push(value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setErrorMessage(messages.length > 0 ? messages : null);
|
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) {
|
if (!isLoading && contentError) {
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
@@ -89,6 +84,11 @@ function CredentialType({ i18n, setBreadcrumb }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cardHeader = <RoutedTabs tabsArray={tabsArray} />;
|
||||||
|
if (pathname.endsWith('edit')) {
|
||||||
|
cardHeader = null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<Card>
|
<Card>
|
||||||
@@ -104,7 +104,7 @@ function CredentialType({ i18n, setBreadcrumb }) {
|
|||||||
{credentialType && (
|
{credentialType && (
|
||||||
<>
|
<>
|
||||||
<Route path="/credential_types/:id/edit">
|
<Route path="/credential_types/:id/edit">
|
||||||
<CredentialTypeEdit />
|
<CredentialTypeEdit credentialType={credentialType} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/credential_types/:id/details">
|
<Route path="/credential_types/:id/details">
|
||||||
<CredentialTypeDetails credentialType={credentialType} />
|
<CredentialTypeDetails credentialType={credentialType} />
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ function CredentialTypeDetails({ credentialType, i18n }) {
|
|||||||
<Button
|
<Button
|
||||||
aria-label={i18n._(t`edit`)}
|
aria-label={i18n._(t`edit`)}
|
||||||
component={Link}
|
component={Link}
|
||||||
to={`credential_types/${id}/edit`}
|
to={`/credential_types/${id}/edit`}
|
||||||
>
|
>
|
||||||
{i18n._(t`Edit`)}
|
{i18n._(t`Edit`)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,11 +1,41 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Card, PageSection } from '@patternfly/react-core';
|
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 (
|
return (
|
||||||
<PageSection>
|
<CardBody>
|
||||||
<Card>Credential Type Edit</Card>
|
<CredentialTypeForm
|
||||||
</PageSection>
|
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 => (
|
renderItem={credentialType => (
|
||||||
<CredentialTypeListItem
|
<CredentialTypeListItem
|
||||||
key={credentialTypes.id}
|
key={credentialType.id}
|
||||||
value={credentialType.name}
|
value={credentialType.name}
|
||||||
credentialType={credentialType}
|
credentialType={credentialType}
|
||||||
detailUrl={`${match.url}/${credentialType.id}/details`}
|
detailUrl={`${match.url}/${credentialType.id}/details`}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import {
|
|||||||
FormFullWidthLayout,
|
FormFullWidthLayout,
|
||||||
} from '../../../components/FormLayout';
|
} from '../../../components/FormLayout';
|
||||||
|
|
||||||
|
import { jsonToYaml } from '../../../util/yaml';
|
||||||
|
|
||||||
function CredentialTypeFormFields({ i18n }) {
|
function CredentialTypeFormFields({ i18n }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -65,8 +67,12 @@ function CredentialTypeForm({
|
|||||||
const initialValues = {
|
const initialValues = {
|
||||||
name: credentialType.name || '',
|
name: credentialType.name || '',
|
||||||
description: credentialType.description || '',
|
description: credentialType.description || '',
|
||||||
inputs: credentialType.inputs || '---',
|
inputs: credentialType.inputs
|
||||||
injectors: credentialType.injectors || '---',
|
? jsonToYaml(JSON.stringify(credentialType.inputs))
|
||||||
|
: '---',
|
||||||
|
injectors: credentialType.injectors
|
||||||
|
? jsonToYaml(JSON.stringify(credentialType.injectors))
|
||||||
|
: '---',
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Formik initialValues={initialValues} onSubmit={values => onSubmit(values)}>
|
<Formik initialValues={initialValues} onSubmit={values => onSubmit(values)}>
|
||||||
@@ -74,7 +80,7 @@ function CredentialTypeForm({
|
|||||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||||
<FormColumnLayout>
|
<FormColumnLayout>
|
||||||
<CredentialTypeFormFields {...rest} />
|
<CredentialTypeFormFields {...rest} />
|
||||||
<FormSubmitError error={submitError} />
|
{submitError && <FormSubmitError error={submitError} />}
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
|
|||||||
@@ -38,13 +38,12 @@ export function isJson(jsonString) {
|
|||||||
|
|
||||||
export function parseVariableField(variableField) {
|
export function parseVariableField(variableField) {
|
||||||
if (variableField === '---' || variableField === '{}') {
|
if (variableField === '---' || variableField === '{}') {
|
||||||
variableField = {};
|
return {};
|
||||||
} else {
|
|
||||||
if (!isJson(variableField)) {
|
|
||||||
variableField = yamlToJson(variableField);
|
|
||||||
}
|
|
||||||
variableField = JSON.parse(variableField);
|
|
||||||
}
|
}
|
||||||
|
if (!isJson(variableField)) {
|
||||||
|
variableField = yamlToJson(variableField);
|
||||||
|
}
|
||||||
|
variableField = JSON.parse(variableField);
|
||||||
|
|
||||||
return variableField;
|
return variableField;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user