mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 10:00:01 -03:30
Add Content Signature Validation Credential field to Projects Form page and Projects Detail page
This commit is contained in:
parent
f5a2246817
commit
e896dc1aa7
@ -7,7 +7,15 @@ class CredentialTypes extends Base {
|
||||
}
|
||||
|
||||
async loadAllTypes(
|
||||
acceptableKinds = ['machine', 'cloud', 'net', 'ssh', 'vault', 'kubernetes']
|
||||
acceptableKinds = [
|
||||
'machine',
|
||||
'cloud',
|
||||
'net',
|
||||
'ssh',
|
||||
'vault',
|
||||
'kubernetes',
|
||||
'cryptography',
|
||||
]
|
||||
) {
|
||||
const pageSize = 200;
|
||||
// The number of credential types a user can have is unlimited. In practice, it is unlikely for
|
||||
|
||||
@ -125,6 +125,21 @@ function PromptProjectDetail({ resource }) {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{summary_fields?.signature_validation_credential?.id && (
|
||||
<Detail
|
||||
label={t`Content Signature Validation Credential`}
|
||||
dataCy={`${prefixCy}-content-signature-validation-credential`}
|
||||
value={
|
||||
<CredentialChip
|
||||
key={resource.summary_fields.signature_validation_credential.id}
|
||||
credential={
|
||||
resource.summary_fields.signature_validation_credential
|
||||
}
|
||||
isReadOnly
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{optionsList && (
|
||||
<Detail
|
||||
label={t`Enabled Options`}
|
||||
|
||||
@ -18,10 +18,20 @@ function ProjectAdd() {
|
||||
// the API might throw an unexpected error if our creation request
|
||||
// has a zero-length string as its credential field. As a work-around,
|
||||
// normalize falsey credential fields by deleting them.
|
||||
delete values.credential;
|
||||
} else {
|
||||
values.credential = null;
|
||||
} else if (typeof values.credential.id === 'number') {
|
||||
values.credential = values.credential.id;
|
||||
}
|
||||
if (values.scm_type === 'git') {
|
||||
if (!values.signature_validation_credential) {
|
||||
values.signature_validation_credential = null;
|
||||
} else if (
|
||||
typeof values.signature_validation_credential.id === 'number'
|
||||
) {
|
||||
values.signature_validation_credential =
|
||||
values.signature_validation_credential.id;
|
||||
}
|
||||
}
|
||||
setFormSubmitError(null);
|
||||
try {
|
||||
const {
|
||||
|
||||
@ -20,6 +20,7 @@ describe('<ProjectAdd />', () => {
|
||||
scm_clean: true,
|
||||
scm_track_submodules: false,
|
||||
credential: 100,
|
||||
signature_validation_credential: 200,
|
||||
local_path: '',
|
||||
organization: { id: 2, name: 'Bar' },
|
||||
scm_update_on_launch: true,
|
||||
@ -73,16 +74,32 @@ describe('<ProjectAdd />', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const cryptographyCredentialResolve = {
|
||||
data: {
|
||||
results: [
|
||||
{
|
||||
id: 6,
|
||||
name: 'GPG Public Key',
|
||||
kind: 'cryptography',
|
||||
},
|
||||
],
|
||||
count: 1,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
await ProjectsAPI.readOptions.mockImplementation(
|
||||
() => projectOptionsResolve
|
||||
);
|
||||
await CredentialTypesAPI.read.mockImplementationOnce(
|
||||
await CredentialTypesAPI.read.mockImplementation(
|
||||
() => scmCredentialResolve
|
||||
);
|
||||
await CredentialTypesAPI.read.mockImplementationOnce(
|
||||
await CredentialTypesAPI.read.mockImplementation(
|
||||
() => insightsCredentialResolve
|
||||
);
|
||||
await CredentialTypesAPI.read.mockImplementation(
|
||||
() => cryptographyCredentialResolve
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -110,6 +127,7 @@ describe('<ProjectAdd />', () => {
|
||||
...projectData,
|
||||
organization: 2,
|
||||
default_environment: 1,
|
||||
signature_validation_credential: 200,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -124,7 +124,6 @@ function ProjectDetail({ project }) {
|
||||
</TextList>
|
||||
);
|
||||
}
|
||||
|
||||
const generateLastJobTooltip = (job) => (
|
||||
<>
|
||||
<div>{t`MOST RECENT SYNC`}</div>
|
||||
@ -149,6 +148,7 @@ function ProjectDetail({ project }) {
|
||||
} else if (summary_fields?.last_job) {
|
||||
job = summary_fields.last_job;
|
||||
}
|
||||
|
||||
const getSourceControlUrlHelpText = () =>
|
||||
scm_type === 'git'
|
||||
? projectHelpText.githubSourceControlUrl
|
||||
@ -234,6 +234,22 @@ function ProjectDetail({ project }) {
|
||||
label={t`Source Control Refspec`}
|
||||
value={scm_refspec}
|
||||
/>
|
||||
{summary_fields.signature_validation_credential && (
|
||||
<Detail
|
||||
label={t`Content Signature Validation Credential`}
|
||||
helpText={projectHelpText.signatureValidation}
|
||||
value={
|
||||
<CredentialChip
|
||||
key={summary_fields.signature_validation_credential.id}
|
||||
credential={summary_fields.signature_validation_credential}
|
||||
isReadOnly
|
||||
/>
|
||||
}
|
||||
isEmpty={
|
||||
summary_fields.signature_validation_credential.length === 0
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{summary_fields.credential && (
|
||||
<Detail
|
||||
label={t`Source Control Credential`}
|
||||
@ -244,6 +260,7 @@ function ProjectDetail({ project }) {
|
||||
isReadOnly
|
||||
/>
|
||||
}
|
||||
isEmpty={summary_fields.credential.length === 0}
|
||||
/>
|
||||
)}
|
||||
<Detail
|
||||
|
||||
@ -46,6 +46,11 @@ describe('<ProjectDetail />', () => {
|
||||
name: 'qux',
|
||||
kind: 'scm',
|
||||
},
|
||||
signature_validation_credential: {
|
||||
id: 2000,
|
||||
name: 'svc',
|
||||
kind: 'cryptography',
|
||||
},
|
||||
last_job: {
|
||||
id: 9000,
|
||||
status: 'successful',
|
||||
@ -78,6 +83,7 @@ describe('<ProjectDetail />', () => {
|
||||
scm_delete_on_update: true,
|
||||
scm_track_submodules: true,
|
||||
credential: 100,
|
||||
signature_validation_credential: 200,
|
||||
status: 'successful',
|
||||
organization: 10,
|
||||
scm_update_on_launch: true,
|
||||
@ -108,6 +114,10 @@ describe('<ProjectDetail />', () => {
|
||||
'Source Control Credential',
|
||||
`Scm: ${mockProject.summary_fields.credential.name}`
|
||||
);
|
||||
assertDetail(
|
||||
'Content Signature Validation Credential',
|
||||
`Cryptography: ${mockProject.summary_fields.signature_validation_credential.name}`
|
||||
);
|
||||
assertDetail(
|
||||
'Cache Timeout',
|
||||
`${mockProject.scm_update_cache_timeout} Seconds`
|
||||
|
||||
@ -18,10 +18,21 @@ function ProjectEdit({ project }) {
|
||||
// the API might throw an unexpected error if our creation request
|
||||
// has a zero-length string as its credential field. As a work-around,
|
||||
// normalize falsey credential fields by deleting them.
|
||||
delete values.credential;
|
||||
} else {
|
||||
values.credential = null;
|
||||
} else if (typeof values.credential.id === 'number') {
|
||||
values.credential = values.credential.id;
|
||||
}
|
||||
if (values.scm_type === 'git') {
|
||||
if (!values.signature_validation_credential) {
|
||||
values.signature_validation_credential = null;
|
||||
} else if (
|
||||
typeof values.signature_validation_credential.id === 'number'
|
||||
) {
|
||||
values.signature_validation_credential =
|
||||
values.signature_validation_credential.id;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
data: { id },
|
||||
|
||||
@ -21,6 +21,7 @@ describe('<ProjectEdit />', () => {
|
||||
scm_clean: true,
|
||||
scm_track_submodules: false,
|
||||
credential: 100,
|
||||
signature_validation_credential: 200,
|
||||
local_path: 'bar',
|
||||
organization: 2,
|
||||
scm_update_on_launch: true,
|
||||
@ -33,6 +34,12 @@ describe('<ProjectEdit />', () => {
|
||||
credential_type_id: 5,
|
||||
kind: 'insights',
|
||||
},
|
||||
signature_validation_credential: {
|
||||
id: 200,
|
||||
credential_type_id: 6,
|
||||
kind: 'cryptography',
|
||||
name: 'foo',
|
||||
},
|
||||
organization: {
|
||||
id: 2,
|
||||
name: 'Default',
|
||||
@ -60,6 +67,7 @@ describe('<ProjectEdit />', () => {
|
||||
|
||||
const scmCredentialResolve = {
|
||||
data: {
|
||||
count: 1,
|
||||
results: [
|
||||
{
|
||||
id: 4,
|
||||
@ -72,6 +80,7 @@ describe('<ProjectEdit />', () => {
|
||||
|
||||
const insightsCredentialResolve = {
|
||||
data: {
|
||||
count: 1,
|
||||
results: [
|
||||
{
|
||||
id: 5,
|
||||
@ -82,6 +91,19 @@ describe('<ProjectEdit />', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const cryptographyCredentialResolve = {
|
||||
data: {
|
||||
count: 1,
|
||||
results: [
|
||||
{
|
||||
id: 6,
|
||||
name: 'GPG Public Key',
|
||||
kind: 'cryptography',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
RootAPI.readAssetVariables.mockResolvedValue({
|
||||
data: {
|
||||
@ -91,12 +113,15 @@ describe('<ProjectEdit />', () => {
|
||||
await ProjectsAPI.readOptions.mockImplementation(
|
||||
() => projectOptionsResolve
|
||||
);
|
||||
await CredentialTypesAPI.read.mockImplementationOnce(
|
||||
await CredentialTypesAPI.read.mockImplementation(
|
||||
() => scmCredentialResolve
|
||||
);
|
||||
await CredentialTypesAPI.read.mockImplementationOnce(
|
||||
await CredentialTypesAPI.read.mockImplementation(
|
||||
() => insightsCredentialResolve
|
||||
);
|
||||
await CredentialTypesAPI.read.mockImplementation(
|
||||
() => cryptographyCredentialResolve
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@ -105,6 +105,10 @@ const projectHelpTextStrings = {
|
||||
you can input tags, commit hashes, and arbitrary refs. Some
|
||||
commit hashes and refs may not be available unless you also
|
||||
provide a custom refspec.`,
|
||||
signatureValidation: t`Enable content signing to verify that the content
|
||||
has remained secure when a project is synced.
|
||||
If the content has been tampered with, the
|
||||
job will not run.`,
|
||||
options: {
|
||||
clean: t`Remove any local modifications prior to performing an update.`,
|
||||
delete: t`Delete the local repository in its entirety prior to
|
||||
|
||||
@ -37,15 +37,22 @@ const fetchCredentials = async (credential) => {
|
||||
results: [insightsCredentialType],
|
||||
},
|
||||
},
|
||||
{
|
||||
data: {
|
||||
results: [cryptographyCredentialType],
|
||||
},
|
||||
},
|
||||
] = await Promise.all([
|
||||
CredentialTypesAPI.read({ kind: 'scm' }),
|
||||
CredentialTypesAPI.read({ name: 'Insights' }),
|
||||
CredentialTypesAPI.read({ kind: 'cryptography' }),
|
||||
]);
|
||||
|
||||
if (!credential) {
|
||||
return {
|
||||
scm: { typeId: scmCredentialType.id },
|
||||
insights: { typeId: insightsCredentialType.id },
|
||||
cryptography: { typeId: cryptographyCredentialType.id },
|
||||
};
|
||||
}
|
||||
|
||||
@ -60,6 +67,13 @@ const fetchCredentials = async (credential) => {
|
||||
value:
|
||||
credential_type_id === insightsCredentialType.id ? credential : null,
|
||||
},
|
||||
cryptography: {
|
||||
typeId: cryptographyCredentialType.id,
|
||||
value:
|
||||
credential_type_id === cryptographyCredentialType.id
|
||||
? credential
|
||||
: null,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -69,7 +83,9 @@ function ProjectFormFields({
|
||||
project_local_paths,
|
||||
formik,
|
||||
setCredentials,
|
||||
setSignatureValidationCredentials,
|
||||
credentials,
|
||||
signatureValidationCredentials,
|
||||
scmTypeOptions,
|
||||
setScmSubFormState,
|
||||
scmSubFormState,
|
||||
@ -79,6 +95,7 @@ function ProjectFormFields({
|
||||
scm_branch: '',
|
||||
scm_refspec: '',
|
||||
credential: '',
|
||||
signature_validation_credential: '',
|
||||
scm_clean: false,
|
||||
scm_delete_on_update: false,
|
||||
scm_track_submodules: false,
|
||||
@ -86,7 +103,6 @@ function ProjectFormFields({
|
||||
allow_override: false,
|
||||
scm_update_cache_timeout: 0,
|
||||
};
|
||||
|
||||
const { setFieldValue, setFieldTouched } = useFormikContext();
|
||||
|
||||
const [scmTypeField, scmTypeMeta, scmTypeHelpers] = useField({
|
||||
@ -147,6 +163,19 @@ function ProjectFormFields({
|
||||
[credentials, setCredentials]
|
||||
);
|
||||
|
||||
const handleSignatureValidationCredentialSelection = useCallback(
|
||||
(type, value) => {
|
||||
setSignatureValidationCredentials({
|
||||
...signatureValidationCredentials,
|
||||
[type]: {
|
||||
...signatureValidationCredentials[type],
|
||||
value,
|
||||
},
|
||||
});
|
||||
},
|
||||
[signatureValidationCredentials, setSignatureValidationCredentials]
|
||||
);
|
||||
|
||||
const handleOrganizationUpdate = useCallback(
|
||||
(value) => {
|
||||
setFieldValue('organization', value);
|
||||
@ -259,7 +288,13 @@ function ProjectFormFields({
|
||||
git: (
|
||||
<GitSubForm
|
||||
credential={credentials.scm}
|
||||
signature_validation_credential={
|
||||
signatureValidationCredentials.cryptography
|
||||
}
|
||||
onCredentialSelection={handleCredentialSelection}
|
||||
onSignatureValidationCredentialSelection={
|
||||
handleSignatureValidationCredentialSelection
|
||||
}
|
||||
scmUpdateOnLaunch={formik.values.scm_update_on_launch}
|
||||
/>
|
||||
),
|
||||
@ -295,7 +330,6 @@ function ProjectFormFields({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ProjectForm({ project, submitError, ...props }) {
|
||||
const { handleCancel, handleSubmit } = props;
|
||||
const { summary_fields = {} } = project;
|
||||
@ -307,6 +341,7 @@ function ProjectForm({ project, submitError, ...props }) {
|
||||
scm_branch: '',
|
||||
scm_refspec: '',
|
||||
credential: '',
|
||||
signature_validation_credential: '',
|
||||
scm_clean: false,
|
||||
scm_delete_on_update: false,
|
||||
scm_track_submodules: false,
|
||||
@ -318,12 +353,22 @@ function ProjectForm({ project, submitError, ...props }) {
|
||||
const [credentials, setCredentials] = useState({
|
||||
scm: { typeId: null, value: null },
|
||||
insights: { typeId: null, value: null },
|
||||
cryptography: { typeId: null, value: null },
|
||||
});
|
||||
const [signatureValidationCredentials, setSignatureValidationCredentials] =
|
||||
useState({
|
||||
scm: { typeId: null, value: null },
|
||||
insights: { typeId: null, value: null },
|
||||
cryptography: { typeId: null, value: null },
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
try {
|
||||
const credentialResponse = fetchCredentials(summary_fields.credential);
|
||||
const signatureValidationCredentialResponse = fetchCredentials(
|
||||
summary_fields.signature_validation_credential
|
||||
);
|
||||
const {
|
||||
data: {
|
||||
actions: {
|
||||
@ -335,6 +380,9 @@ function ProjectForm({ project, submitError, ...props }) {
|
||||
} = await ProjectsAPI.readOptions();
|
||||
|
||||
setCredentials(await credentialResponse);
|
||||
setSignatureValidationCredentials(
|
||||
await signatureValidationCredentialResponse
|
||||
);
|
||||
setScmTypeOptions(choices);
|
||||
} catch (error) {
|
||||
setContentError(error);
|
||||
@ -344,7 +392,10 @@ function ProjectForm({ project, submitError, ...props }) {
|
||||
}
|
||||
|
||||
fetchData();
|
||||
}, [summary_fields.credential]);
|
||||
}, [
|
||||
summary_fields.credential,
|
||||
summary_fields.signature_validation_credential,
|
||||
]);
|
||||
|
||||
if (isLoading) {
|
||||
return <ContentLoading />;
|
||||
@ -378,6 +429,8 @@ function ProjectForm({ project, submitError, ...props }) {
|
||||
scm_update_cache_timeout: project.scm_update_cache_timeout || 0,
|
||||
scm_update_on_launch: project.scm_update_on_launch || false,
|
||||
scm_url: project.scm_url || '',
|
||||
signature_validation_credential:
|
||||
project.signature_validation_credential || '',
|
||||
default_environment:
|
||||
project.summary_fields?.default_environment || null,
|
||||
}}
|
||||
@ -392,7 +445,11 @@ function ProjectForm({ project, submitError, ...props }) {
|
||||
project_local_paths={project_local_paths}
|
||||
formik={formik}
|
||||
setCredentials={setCredentials}
|
||||
setSignatureValidationCredentials={
|
||||
setSignatureValidationCredentials
|
||||
}
|
||||
credentials={credentials}
|
||||
signatureValidationCredentials={signatureValidationCredentials}
|
||||
scmTypeOptions={scmTypeOptions}
|
||||
setScmSubFormState={setScmSubFormState}
|
||||
scmSubFormState={scmSubFormState}
|
||||
|
||||
@ -19,6 +19,7 @@ describe('<ProjectForm />', () => {
|
||||
scm_clean: true,
|
||||
scm_track_submodules: false,
|
||||
credential: 100,
|
||||
signature_validation_credential: 200,
|
||||
organization: 2,
|
||||
scm_update_on_launch: true,
|
||||
scm_update_cache_timeout: 3,
|
||||
@ -35,6 +36,12 @@ describe('<ProjectForm />', () => {
|
||||
id: 2,
|
||||
name: 'Default',
|
||||
},
|
||||
signature_validation_credential: {
|
||||
id: 200,
|
||||
credential_type_id: 6,
|
||||
kind: 'cryptography',
|
||||
name: 'Svc',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -58,6 +65,7 @@ describe('<ProjectForm />', () => {
|
||||
|
||||
const scmCredentialResolve = {
|
||||
data: {
|
||||
count: 1,
|
||||
results: [
|
||||
{
|
||||
id: 4,
|
||||
@ -70,6 +78,7 @@ describe('<ProjectForm />', () => {
|
||||
|
||||
const insightsCredentialResolve = {
|
||||
data: {
|
||||
count: 1,
|
||||
results: [
|
||||
{
|
||||
id: 5,
|
||||
@ -80,6 +89,19 @@ describe('<ProjectForm />', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const cryptographyCredentialResolve = {
|
||||
data: {
|
||||
count: 1,
|
||||
results: [
|
||||
{
|
||||
id: 6,
|
||||
name: 'GPG Public Key',
|
||||
kind: 'cryptography',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
RootAPI.readAssetVariables.mockResolvedValue({
|
||||
data: {
|
||||
@ -89,12 +111,15 @@ describe('<ProjectForm />', () => {
|
||||
await ProjectsAPI.readOptions.mockImplementation(
|
||||
() => projectOptionsResolve
|
||||
);
|
||||
await CredentialTypesAPI.read.mockImplementationOnce(
|
||||
await CredentialTypesAPI.read.mockImplementation(
|
||||
() => scmCredentialResolve
|
||||
);
|
||||
await CredentialTypesAPI.read.mockImplementationOnce(
|
||||
await CredentialTypesAPI.read.mockImplementation(
|
||||
() => insightsCredentialResolve
|
||||
);
|
||||
await CredentialTypesAPI.read.mockImplementation(
|
||||
() => cryptographyCredentialResolve
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -153,9 +178,17 @@ describe('<ProjectForm />', () => {
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Source Control Refspec"]').length
|
||||
).toBe(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Content Signature Validation Credential"]')
|
||||
.length
|
||||
).toBe(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Source Control Credential"]').length
|
||||
).toBe(1);
|
||||
expect(
|
||||
wrapper.find('FormGroup[label="Content Signature Validation Credential"]')
|
||||
.length
|
||||
).toBe(1);
|
||||
expect(wrapper.find('FormGroup[label="Options"]').length).toBe(1);
|
||||
});
|
||||
|
||||
@ -177,21 +210,52 @@ describe('<ProjectForm />', () => {
|
||||
id: 1,
|
||||
name: 'organization',
|
||||
});
|
||||
wrapper.find('CredentialLookup').invoke('onBlur')();
|
||||
wrapper.find('CredentialLookup').invoke('onChange')({
|
||||
wrapper
|
||||
.find('CredentialLookup[label="Source Control Credential"]')
|
||||
.invoke('onBlur')();
|
||||
wrapper
|
||||
.find('CredentialLookup[label="Source Control Credential"]')
|
||||
.invoke('onChange')({
|
||||
id: 10,
|
||||
name: 'credential',
|
||||
});
|
||||
wrapper
|
||||
.find(
|
||||
'CredentialLookup[label="Content Signature Validation Credential"]'
|
||||
)
|
||||
.invoke('onBlur')();
|
||||
wrapper
|
||||
.find(
|
||||
'CredentialLookup[label="Content Signature Validation Credential"]'
|
||||
)
|
||||
.invoke('onChange')({
|
||||
id: 20,
|
||||
name: 'signature_validation_credential',
|
||||
});
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('OrganizationLookup').prop('value')).toEqual({
|
||||
id: 1,
|
||||
name: 'organization',
|
||||
});
|
||||
expect(wrapper.find('CredentialLookup').prop('value')).toEqual({
|
||||
expect(
|
||||
wrapper
|
||||
.find('CredentialLookup[label="Source Control Credential"]')
|
||||
.prop('value')
|
||||
).toEqual({
|
||||
id: 10,
|
||||
name: 'credential',
|
||||
});
|
||||
expect(
|
||||
wrapper
|
||||
.find(
|
||||
'CredentialLookup[label="Content Signature Validation Credential"]'
|
||||
)
|
||||
.prop('value')
|
||||
).toEqual({
|
||||
id: 20,
|
||||
name: 'signature_validation_credential',
|
||||
});
|
||||
});
|
||||
|
||||
test('should display insights credential lookup when source control type is "insights"', async () => {
|
||||
@ -358,7 +422,9 @@ describe('<ProjectForm />', () => {
|
||||
});
|
||||
|
||||
test('should display ContentError on throw', async () => {
|
||||
CredentialTypesAPI.read = () => Promise.reject(new Error());
|
||||
CredentialTypesAPI.read.mockImplementationOnce(() =>
|
||||
Promise.reject(new Error())
|
||||
);
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<ProjectForm handleSubmit={jest.fn()} handleCancel={jest.fn()} />
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import 'styled-components/macro';
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { useFormikContext } from 'formik';
|
||||
import CredentialLookup from 'components/Lookup/CredentialLookup';
|
||||
import FormField from 'components/FormField';
|
||||
import getDocsBaseUrl from 'util/getDocsBaseUrl';
|
||||
import { useConfig } from 'contexts/Config';
|
||||
@ -16,9 +18,22 @@ import projectHelpStrings from '../Project.helptext';
|
||||
|
||||
const GitSubForm = ({
|
||||
credential,
|
||||
signature_validation_credential,
|
||||
onCredentialSelection,
|
||||
onSignatureValidationCredentialSelection,
|
||||
scmUpdateOnLaunch,
|
||||
}) => {
|
||||
const { setFieldValue, setFieldTouched } = useFormikContext();
|
||||
|
||||
const onCredentialChange = useCallback(
|
||||
(value) => {
|
||||
onSignatureValidationCredentialSelection('cryptography', value);
|
||||
setFieldValue('signature_validation_credential', value);
|
||||
setFieldTouched('signature_validation_credential', true, false);
|
||||
},
|
||||
[onSignatureValidationCredentialSelection, setFieldValue, setFieldTouched]
|
||||
);
|
||||
|
||||
const docsURL = `${getDocsBaseUrl(
|
||||
useConfig()
|
||||
)}/html/userguide/projects.html#manage-playbooks-using-source-control`;
|
||||
@ -35,6 +50,13 @@ const GitSubForm = ({
|
||||
tooltipMaxWidth="400px"
|
||||
tooltip={projectHelpStrings.sourceControlRefspec(docsURL)}
|
||||
/>
|
||||
<CredentialLookup
|
||||
credentialTypeId={signature_validation_credential.typeId}
|
||||
label={t`Content Signature Validation Credential`}
|
||||
onChange={onCredentialChange}
|
||||
value={signature_validation_credential.value}
|
||||
tooltip={projectHelpStrings.signatureValidation}
|
||||
/>
|
||||
<ScmCredentialFormField
|
||||
credential={credential}
|
||||
onCredentialSelection={onCredentialSelection}
|
||||
|
||||
@ -145,6 +145,7 @@ export const Project = shape({
|
||||
summary_fields: shape({
|
||||
organization: Organization,
|
||||
credential: Credential,
|
||||
signature_validation_credential: Credential,
|
||||
last_job: shape({}),
|
||||
last_update: shape({}),
|
||||
created_by: shape({}),
|
||||
@ -163,6 +164,7 @@ export const Project = shape({
|
||||
scm_delete_on_update: bool,
|
||||
scm_track_submodules: bool,
|
||||
credential: number,
|
||||
signature_validation_credential: number,
|
||||
status: oneOf([
|
||||
'new',
|
||||
'pending',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user