Merge pull request #8738 from AlexSCorey/8532-VaultIdsOnCredChips

Adds vault IDs to Vault credential chips

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot] 2021-01-07 19:49:13 +00:00 committed by GitHub
commit 764511f33f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 135 additions and 16 deletions

View File

@ -36,6 +36,10 @@ class Jobs extends RelaunchMixin(Base) {
return this.http.post(`/api/v2${getBaseURL(type)}${id}/cancel/`);
}
readCredentials(id, type) {
return this.http.get(`/api/v2${getBaseURL(type)}${id}/credentials/`);
}
readDetail(id, type) {
return this.http.get(`/api/v2${getBaseURL(type)}${id}/`);
}

View File

@ -16,10 +16,17 @@ function CredentialChip({ credential, i18n, i18nHash, ...props }) {
type = toTitleCase(credential.kind);
}
const buildCredentialName = () => {
if (credential.kind === 'vault' && credential.inputs?.vault_id) {
return `${credential.name} | ${credential.inputs.vault_id}`;
}
return `${credential.name}`;
};
return (
<Chip {...props}>
<strong>{type}: </strong>
{credential.name}
{buildCredentialName()}
</Chip>
);
}

View File

@ -71,6 +71,16 @@ function MultiCredentialsLookup(props) {
loadCredentials(params, selectedType.id),
CredentialsAPI.readOptions(),
]);
results.map(result => {
if (result.kind === 'vault' && result.inputs?.vault_id) {
result.label = `${result.name} | ${result.inputs.vault_id}`;
return result;
}
result.label = `${result.name}`;
return result;
});
return {
credentials: results,
credentialsCount: count,
@ -108,7 +118,6 @@ function MultiCredentialsLookup(props) {
credential={item}
/>
);
const isVault = selectedType?.kind === 'vault';
return (
@ -187,6 +196,7 @@ function MultiCredentialsLookup(props) {
relatedSearchableKeys={relatedSearchableKeys}
multiple={isVault}
header={i18n._(t`Credentials`)}
displayKey={isVault ? 'label' : 'name'}
name="credentials"
qsConfig={QS_CONFIG}
readOnly={!canDelete}

View File

@ -87,6 +87,23 @@ describe('<MultiCredentialsLookup />', () => {
name: 'Cred 5',
url: 'www.google.com',
},
{
id: 6,
credential_type: 5,
kind: 'vault',
name: 'Cred 6',
url: 'www.google.com',
inputs: { vault_id: 'vault ID' },
},
{
id: 7,
credential_type: 5,
kind: 'vault',
name: 'Cred 7',
url: 'www.google.com',
inputs: {},
},
],
count: 3,
},
@ -196,7 +213,13 @@ describe('<MultiCredentialsLookup />', () => {
wrapper.update();
expect(CredentialsAPI.read).toHaveBeenCalledTimes(2);
expect(wrapper.find('OptionsList').prop('options')).toEqual([
{ id: 1, kind: 'cloud', name: 'New Cred', url: 'www.google.com' },
{
id: 1,
kind: 'cloud',
name: 'New Cred',
url: 'www.google.com',
label: 'New Cred',
},
]);
});
@ -268,6 +291,36 @@ describe('<MultiCredentialsLookup />', () => {
]);
});
test('should properly render vault credential labels', async () => {
await act(async () => {
wrapper = mountWithContexts(
<MultiCredentialsLookup
value={credentials}
tooltip="This is credentials look up"
onChange={() => {}}
onError={() => {}}
/>
);
});
const searchButton = await waitForElement(
wrapper,
'Button[aria-label="Search"]'
);
await act(async () => {
searchButton.invoke('onClick')();
});
wrapper.update();
const typeSelect = wrapper.find('AnsibleSelect');
act(() => {
typeSelect.invoke('onChange')({}, 500);
});
wrapper.update();
const optionsList = wrapper.find('OptionsList');
expect(optionsList.prop('multiple')).toEqual(true);
expect(wrapper.find('CheckboxListItem[label="Cred 6 | vault ID"]'));
expect(wrapper.find('CheckboxListItem[label="Cred 7"]'));
});
test('should allow multiple vault credentials with no vault id', async () => {
const onChange = jest.fn();
await act(async () => {

View File

@ -29,10 +29,18 @@ function Job({ i18n, setBreadcrumb }) {
const { isLoading, error, request: fetchJob, result } = useRequest(
useCallback(async () => {
const { data } = await JobsAPI.readDetail(id, type);
if (
data?.summary_fields?.credentials?.find(cred => cred.kind === 'vault')
) {
const {
data: { results },
} = await JobsAPI.readCredentials(data.id, type);
data.summary_fields.credentials = results;
}
setBreadcrumb(data);
return data;
}, [id, type, setBreadcrumb]),
null
}, [id, type, setBreadcrumb])
);
useEffect(() => {

View File

@ -44,6 +44,19 @@ function Project({ i18n, setBreadcrumb }) {
role_level: 'notification_admin_role',
}),
]);
if (data.summary_fields.credentials) {
const params = {
page: 1,
page_size: 200,
order_by: 'name',
};
const {
data: { results },
} = await ProjectsAPI.readCredentials(data.id, params);
data.summary_fields.credentials = results;
}
return {
project: data,
isNotifAdmin: notifAdminRes.data.results.length > 0,

View File

@ -1,13 +1,11 @@
/* eslint react/no-unused-state: 0 */
import React, { useState } from 'react';
import { withRouter, Redirect, useHistory } from 'react-router-dom';
import { Redirect, useHistory } from 'react-router-dom';
import { CardBody } from '../../../components/Card';
import { JobTemplatesAPI } from '../../../api';
import { JobTemplate } from '../../../types';
import { getAddedAndRemoved } from '../../../util/lists';
import JobTemplateForm from '../shared/JobTemplateForm';
import ContentLoading from '../../../components/ContentLoading';
function JobTemplateEdit({ template }) {
@ -95,9 +93,7 @@ function JobTemplateEdit({ template }) {
return Promise.all([disassociatePromise, associatePromise]);
};
const handleCancel = () => {
history.push(detailsUrl);
};
const handleCancel = () => history.push(detailsUrl);
const canEdit = template?.summary_fields?.user_capabilities?.edit;
@ -122,5 +118,4 @@ function JobTemplateEdit({ template }) {
JobTemplateEdit.propTypes = {
template: JobTemplate.isRequired,
};
export default withRouter(JobTemplateEdit);
export default JobTemplateEdit;

View File

@ -46,8 +46,21 @@ function Template({ i18n, setBreadcrumb }) {
role_level: 'notification_admin_role',
}),
]);
if (actions?.data?.actions?.PUT) {
if (data?.webhook_service && data?.related?.webhook_key) {
if (data.summary_fields.credentials) {
const params = {
page: 1,
page_size: 200,
order_by: 'name',
};
const {
data: { results },
} = await JobTemplatesAPI.readCredentials(data.id, params);
data.summary_fields.credentials = results;
}
if (actions.data.actions.PUT) {
if (data.webhook_service && data?.related?.webhook_key) {
const {
data: { webhook_key },
} = await JobTemplatesAPI.readWebhookKey(templateId);
@ -142,7 +155,7 @@ function Template({ i18n, setBreadcrumb }) {
<PageSection>
<Card>
<ContentError error={contentError}>
{contentError.response.status === 404 && (
{contentError.response?.status === 404 && (
<span>
{i18n._(t`Template not found.`)}{' '}
<Link to="/templates">{i18n._(t`View all Templates.`)}</Link>

View File

@ -28,6 +28,22 @@ describe('<Template />', () => {
actions: { PUT: true },
},
});
JobTemplatesAPI.readCredentials.mockResolvedValue({
data: {
results: [
{
id: 3,
type: 'credential',
url: '/api/v2/credentials/3/',
name: 'Vault1Id1',
inputs: {
vault_id: '1',
},
kind: 'vault',
},
],
},
});
OrganizationsAPI.read.mockResolvedValue({
data: {
count: 1,