mirror of
https://github.com/ansible/awx.git
synced 2026-03-20 18:37:39 -02:30
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:
@@ -36,6 +36,10 @@ class Jobs extends RelaunchMixin(Base) {
|
|||||||
return this.http.post(`/api/v2${getBaseURL(type)}${id}/cancel/`);
|
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) {
|
readDetail(id, type) {
|
||||||
return this.http.get(`/api/v2${getBaseURL(type)}${id}/`);
|
return this.http.get(`/api/v2${getBaseURL(type)}${id}/`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,17 @@ function CredentialChip({ credential, i18n, i18nHash, ...props }) {
|
|||||||
type = toTitleCase(credential.kind);
|
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 (
|
return (
|
||||||
<Chip {...props}>
|
<Chip {...props}>
|
||||||
<strong>{type}: </strong>
|
<strong>{type}: </strong>
|
||||||
{credential.name}
|
{buildCredentialName()}
|
||||||
</Chip>
|
</Chip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,16 @@ function MultiCredentialsLookup(props) {
|
|||||||
loadCredentials(params, selectedType.id),
|
loadCredentials(params, selectedType.id),
|
||||||
CredentialsAPI.readOptions(),
|
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 {
|
return {
|
||||||
credentials: results,
|
credentials: results,
|
||||||
credentialsCount: count,
|
credentialsCount: count,
|
||||||
@@ -108,7 +118,6 @@ function MultiCredentialsLookup(props) {
|
|||||||
credential={item}
|
credential={item}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const isVault = selectedType?.kind === 'vault';
|
const isVault = selectedType?.kind === 'vault';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -187,6 +196,7 @@ function MultiCredentialsLookup(props) {
|
|||||||
relatedSearchableKeys={relatedSearchableKeys}
|
relatedSearchableKeys={relatedSearchableKeys}
|
||||||
multiple={isVault}
|
multiple={isVault}
|
||||||
header={i18n._(t`Credentials`)}
|
header={i18n._(t`Credentials`)}
|
||||||
|
displayKey={isVault ? 'label' : 'name'}
|
||||||
name="credentials"
|
name="credentials"
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
readOnly={!canDelete}
|
readOnly={!canDelete}
|
||||||
|
|||||||
@@ -87,6 +87,23 @@ describe('<MultiCredentialsLookup />', () => {
|
|||||||
name: 'Cred 5',
|
name: 'Cred 5',
|
||||||
url: 'www.google.com',
|
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,
|
count: 3,
|
||||||
},
|
},
|
||||||
@@ -196,7 +213,13 @@ describe('<MultiCredentialsLookup />', () => {
|
|||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(CredentialsAPI.read).toHaveBeenCalledTimes(2);
|
expect(CredentialsAPI.read).toHaveBeenCalledTimes(2);
|
||||||
expect(wrapper.find('OptionsList').prop('options')).toEqual([
|
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 () => {
|
test('should allow multiple vault credentials with no vault id', async () => {
|
||||||
const onChange = jest.fn();
|
const onChange = jest.fn();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
|||||||
@@ -29,10 +29,18 @@ function Job({ i18n, setBreadcrumb }) {
|
|||||||
const { isLoading, error, request: fetchJob, result } = useRequest(
|
const { isLoading, error, request: fetchJob, result } = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const { data } = await JobsAPI.readDetail(id, type);
|
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);
|
setBreadcrumb(data);
|
||||||
return data;
|
return data;
|
||||||
}, [id, type, setBreadcrumb]),
|
}, [id, type, setBreadcrumb])
|
||||||
null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -44,6 +44,19 @@ function Project({ i18n, setBreadcrumb }) {
|
|||||||
role_level: 'notification_admin_role',
|
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 {
|
return {
|
||||||
project: data,
|
project: data,
|
||||||
isNotifAdmin: notifAdminRes.data.results.length > 0,
|
isNotifAdmin: notifAdminRes.data.results.length > 0,
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
/* eslint react/no-unused-state: 0 */
|
/* eslint react/no-unused-state: 0 */
|
||||||
import React, { useState } from 'react';
|
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 { CardBody } from '../../../components/Card';
|
||||||
|
|
||||||
import { JobTemplatesAPI } from '../../../api';
|
import { JobTemplatesAPI } from '../../../api';
|
||||||
import { JobTemplate } from '../../../types';
|
import { JobTemplate } from '../../../types';
|
||||||
import { getAddedAndRemoved } from '../../../util/lists';
|
import { getAddedAndRemoved } from '../../../util/lists';
|
||||||
import JobTemplateForm from '../shared/JobTemplateForm';
|
import JobTemplateForm from '../shared/JobTemplateForm';
|
||||||
|
|
||||||
import ContentLoading from '../../../components/ContentLoading';
|
import ContentLoading from '../../../components/ContentLoading';
|
||||||
|
|
||||||
function JobTemplateEdit({ template }) {
|
function JobTemplateEdit({ template }) {
|
||||||
@@ -95,9 +93,7 @@ function JobTemplateEdit({ template }) {
|
|||||||
return Promise.all([disassociatePromise, associatePromise]);
|
return Promise.all([disassociatePromise, associatePromise]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => history.push(detailsUrl);
|
||||||
history.push(detailsUrl);
|
|
||||||
};
|
|
||||||
|
|
||||||
const canEdit = template?.summary_fields?.user_capabilities?.edit;
|
const canEdit = template?.summary_fields?.user_capabilities?.edit;
|
||||||
|
|
||||||
@@ -122,5 +118,4 @@ function JobTemplateEdit({ template }) {
|
|||||||
JobTemplateEdit.propTypes = {
|
JobTemplateEdit.propTypes = {
|
||||||
template: JobTemplate.isRequired,
|
template: JobTemplate.isRequired,
|
||||||
};
|
};
|
||||||
|
export default JobTemplateEdit;
|
||||||
export default withRouter(JobTemplateEdit);
|
|
||||||
|
|||||||
@@ -46,8 +46,21 @@ function Template({ i18n, setBreadcrumb }) {
|
|||||||
role_level: 'notification_admin_role',
|
role_level: 'notification_admin_role',
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
if (actions?.data?.actions?.PUT) {
|
if (data.summary_fields.credentials) {
|
||||||
if (data?.webhook_service && data?.related?.webhook_key) {
|
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 {
|
const {
|
||||||
data: { webhook_key },
|
data: { webhook_key },
|
||||||
} = await JobTemplatesAPI.readWebhookKey(templateId);
|
} = await JobTemplatesAPI.readWebhookKey(templateId);
|
||||||
@@ -142,7 +155,7 @@ function Template({ i18n, setBreadcrumb }) {
|
|||||||
<PageSection>
|
<PageSection>
|
||||||
<Card>
|
<Card>
|
||||||
<ContentError error={contentError}>
|
<ContentError error={contentError}>
|
||||||
{contentError.response.status === 404 && (
|
{contentError.response?.status === 404 && (
|
||||||
<span>
|
<span>
|
||||||
{i18n._(t`Template not found.`)}{' '}
|
{i18n._(t`Template not found.`)}{' '}
|
||||||
<Link to="/templates">{i18n._(t`View all Templates.`)}</Link>
|
<Link to="/templates">{i18n._(t`View all Templates.`)}</Link>
|
||||||
|
|||||||
@@ -28,6 +28,22 @@ describe('<Template />', () => {
|
|||||||
actions: { PUT: true },
|
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({
|
OrganizationsAPI.read.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
count: 1,
|
count: 1,
|
||||||
|
|||||||
Reference in New Issue
Block a user