mirror of
https://github.com/ansible/awx.git
synced 2026-05-07 01:17:37 -02:30
Moves JT CredentialsList Manipulation Back to CredentialsLookup
Rename CredentialsLookup to MultiCredentialLookup Removes unnecessary functions in Lookup. Puts CredentialsList manipulation on CredsLookup and removes that work from JTForm. Upates tests for CredentialsLookup and JTForm to reflect changes above.
This commit is contained in:
@@ -68,7 +68,6 @@ class Lookup extends React.Component {
|
|||||||
results: [],
|
results: [],
|
||||||
count: 0,
|
count: 0,
|
||||||
error: null,
|
error: null,
|
||||||
isDropdownOpen: false,
|
|
||||||
};
|
};
|
||||||
this.qsConfig = getQSConfig(props.qsNamespace, {
|
this.qsConfig = getQSConfig(props.qsNamespace, {
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -80,11 +79,10 @@ class Lookup extends React.Component {
|
|||||||
this.saveModal = this.saveModal.bind(this);
|
this.saveModal = this.saveModal.bind(this);
|
||||||
this.getData = this.getData.bind(this);
|
this.getData = this.getData.bind(this);
|
||||||
this.clearQSParams = this.clearQSParams.bind(this);
|
this.clearQSParams = this.clearQSParams.bind(this);
|
||||||
this.toggleDropdown = this.toggleDropdown.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
Promise.all([this.getData()]);
|
this.getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
@@ -97,11 +95,6 @@ class Lookup extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleDropdown() {
|
|
||||||
const { isDropdownOpen } = this.state;
|
|
||||||
this.setState({ isDropdownOpen: !isDropdownOpen });
|
|
||||||
}
|
|
||||||
|
|
||||||
assertCorrectValueType() {
|
assertCorrectValueType() {
|
||||||
const { multiple, value, selectCategoryOptions } = this.props;
|
const { multiple, value, selectCategoryOptions } = this.props;
|
||||||
if (selectCategoryOptions) {
|
if (selectCategoryOptions) {
|
||||||
@@ -315,7 +308,7 @@ class Lookup extends React.Component {
|
|||||||
<VerticalSeperator />
|
<VerticalSeperator />
|
||||||
<AnsibleSelect
|
<AnsibleSelect
|
||||||
css="flex: 1 1 75%;"
|
css="flex: 1 1 75%;"
|
||||||
id="credentialsLookUp-select"
|
id="multiCredentialsLookUp-select"
|
||||||
label="Selected Category"
|
label="Selected Category"
|
||||||
data={selectCategoryOptions}
|
data={selectCategoryOptions}
|
||||||
value={selectedCategory.label}
|
value={selectedCategory.label}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class CredentialsLookup extends React.Component {
|
class MultiCredentialsLookup extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@@ -26,6 +26,7 @@ class CredentialsLookup extends React.Component {
|
|||||||
this
|
this
|
||||||
);
|
);
|
||||||
this.loadCredentials = this.loadCredentials.bind(this);
|
this.loadCredentials = this.loadCredentials.bind(this);
|
||||||
|
this.toggleCredentialSelection = this.toggleCredentialSelection.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -68,6 +69,28 @@ class CredentialsLookup extends React.Component {
|
|||||||
return CredentialsAPI.read(params);
|
return CredentialsAPI.read(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleCredentialSelection(newCredential) {
|
||||||
|
const { onChange, credentials: credentialsToUpdate } = this.props;
|
||||||
|
|
||||||
|
let newCredentialsList;
|
||||||
|
const isSelectedCredentialInState =
|
||||||
|
credentialsToUpdate.filter(cred => cred.id === newCredential.id).length >
|
||||||
|
0;
|
||||||
|
|
||||||
|
if (isSelectedCredentialInState) {
|
||||||
|
newCredentialsList = credentialsToUpdate.filter(
|
||||||
|
cred => cred.id !== newCredential.id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
newCredentialsList = credentialsToUpdate.filter(
|
||||||
|
credential =>
|
||||||
|
credential.kind === 'vault' || credential.kind !== newCredential.kind
|
||||||
|
);
|
||||||
|
newCredentialsList = [...newCredentialsList, newCredential];
|
||||||
|
}
|
||||||
|
onChange(newCredentialsList);
|
||||||
|
}
|
||||||
|
|
||||||
handleCredentialTypeSelect(value, type) {
|
handleCredentialTypeSelect(value, type) {
|
||||||
const { credentialTypes } = this.state;
|
const { credentialTypes } = this.state;
|
||||||
const selectedType = credentialTypes.filter(item => item.label === type);
|
const selectedType = credentialTypes.filter(item => item.label === type);
|
||||||
@@ -76,7 +99,7 @@ class CredentialsLookup extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { selectedCredentialType, credentialTypes } = this.state;
|
const { selectedCredentialType, credentialTypes } = this.state;
|
||||||
const { tooltip, i18n, credentials, onChange } = this.props;
|
const { tooltip, i18n, credentials } = this.props;
|
||||||
return (
|
return (
|
||||||
<FormGroup label={i18n._(t`Credentials`)} fieldId="org-credentials">
|
<FormGroup label={i18n._(t`Credentials`)} fieldId="org-credentials">
|
||||||
{tooltip && (
|
{tooltip && (
|
||||||
@@ -89,7 +112,7 @@ class CredentialsLookup extends React.Component {
|
|||||||
selectCategoryOptions={credentialTypes}
|
selectCategoryOptions={credentialTypes}
|
||||||
selectCategory={this.handleCredentialTypeSelect}
|
selectCategory={this.handleCredentialTypeSelect}
|
||||||
selectedCategory={selectedCredentialType}
|
selectedCategory={selectedCredentialType}
|
||||||
onToggleItem={onChange}
|
onToggleItem={this.toggleCredentialSelection}
|
||||||
onloadCategories={this.loadCredentialTypes}
|
onloadCategories={this.loadCredentialTypes}
|
||||||
id="org-credentials"
|
id="org-credentials"
|
||||||
lookupHeader={i18n._(t`Credentials`)}
|
lookupHeader={i18n._(t`Credentials`)}
|
||||||
@@ -115,13 +138,13 @@ class CredentialsLookup extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CredentialsLookup.propTypes = {
|
MultiCredentialsLookup.propTypes = {
|
||||||
tooltip: PropTypes.string,
|
tooltip: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
CredentialsLookup.defaultProps = {
|
MultiCredentialsLookup.defaultProps = {
|
||||||
tooltip: '',
|
tooltip: '',
|
||||||
};
|
};
|
||||||
export { CredentialsLookup as _CredentialsLookup };
|
export { MultiCredentialsLookup as _MultiCredentialsLookup };
|
||||||
|
|
||||||
export default withI18n()(CredentialsLookup);
|
export default withI18n()(MultiCredentialsLookup);
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||||
import CredentialsLookup from './CredentialsLookup';
|
import MultiCredentialsLookup from './MultiCredentialsLookup';
|
||||||
import { CredentialsAPI, CredentialTypesAPI } from '@api';
|
import { CredentialsAPI, CredentialTypesAPI } from '@api';
|
||||||
|
|
||||||
jest.mock('@api');
|
jest.mock('@api');
|
||||||
|
|
||||||
describe('<CredentialsLookup />', () => {
|
describe('<MultiCredentialsLookup />', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let lookup;
|
let lookup;
|
||||||
let credLookup;
|
let credLookup;
|
||||||
@@ -14,6 +15,8 @@ describe('<CredentialsLookup />', () => {
|
|||||||
const credentials = [
|
const credentials = [
|
||||||
{ id: 1, kind: 'cloud', name: 'Foo', url: 'www.google.com' },
|
{ id: 1, kind: 'cloud', name: 'Foo', url: 'www.google.com' },
|
||||||
{ id: 2, kind: 'ssh', name: 'Alex', url: 'www.google.com' },
|
{ id: 2, kind: 'ssh', name: 'Alex', url: 'www.google.com' },
|
||||||
|
{ name: 'Gatsby', id: 21, kind: 'vault' },
|
||||||
|
{ name: 'Gatsby', id: 8, kind: 'Machine' },
|
||||||
];
|
];
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
CredentialTypesAPI.read.mockResolvedValue({
|
CredentialTypesAPI.read.mockResolvedValue({
|
||||||
@@ -45,7 +48,7 @@ describe('<CredentialsLookup />', () => {
|
|||||||
});
|
});
|
||||||
onChange = jest.fn();
|
onChange = jest.fn();
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<CredentialsLookup
|
<MultiCredentialsLookup
|
||||||
onError={() => {}}
|
onError={() => {}}
|
||||||
credentials={credentials}
|
credentials={credentials}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
@@ -53,7 +56,7 @@ describe('<CredentialsLookup />', () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
lookup = wrapper.find('Lookup');
|
lookup = wrapper.find('Lookup');
|
||||||
credLookup = wrapper.find('CredentialsLookup');
|
credLookup = wrapper.find('MultiCredentialsLookup');
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -61,17 +64,21 @@ describe('<CredentialsLookup />', () => {
|
|||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('CredentialsLookup renders properly', () => {
|
test('MultiCredentialsLookup renders properly', () => {
|
||||||
expect(wrapper.find('CredentialsLookup')).toHaveLength(1);
|
expect(wrapper.find('MultiCredentialsLookup')).toHaveLength(1);
|
||||||
expect(CredentialTypesAPI.read).toHaveBeenCalled();
|
expect(CredentialTypesAPI.read).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('onChange is called when you click to remove a credential from input', () => {
|
test('onChange is called when you click to remove a credential from input', async () => {
|
||||||
const chip = wrapper.find('PFChip');
|
const chip = wrapper.find('PFChip');
|
||||||
const button = chip.at(1).find('Button');
|
const button = chip.at(1).find('Button');
|
||||||
expect(chip).toHaveLength(2);
|
expect(chip).toHaveLength(4);
|
||||||
button.prop('onClick')();
|
button.prop('onClick')();
|
||||||
expect(onChange).toBeCalledTimes(1);
|
expect(onChange).toBeCalledWith([
|
||||||
|
{ id: 1, kind: 'cloud', name: 'Foo', url: 'www.google.com' },
|
||||||
|
{ id: 21, kind: 'vault', name: 'Gatsby' },
|
||||||
|
{ id: 8, kind: 'Machine', name: 'Gatsby' },
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can change credential types', () => {
|
test('can change credential types', () => {
|
||||||
@@ -87,4 +94,20 @@ describe('<CredentialsLookup />', () => {
|
|||||||
});
|
});
|
||||||
expect(CredentialsAPI.read).toHaveBeenCalled();
|
expect(CredentialsAPI.read).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
test('Toggle credentials only adds 1 credential per credential type except vault(see below)', () => {
|
||||||
|
lookup.prop('onToggleItem')({ name: 'Party', id: 9, kind: 'Machine' });
|
||||||
|
expect(onChange).toBeCalledWith([
|
||||||
|
{ id: 1, kind: 'cloud', name: 'Foo', url: 'www.google.com' },
|
||||||
|
{ id: 2, kind: 'ssh', name: 'Alex', url: 'www.google.com' },
|
||||||
|
{ id: 21, kind: 'vault', name: 'Gatsby' },
|
||||||
|
{ id: 9, kind: 'Machine', name: 'Party' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
test('Toggle credentials only adds 1 credential per credential type', () => {
|
||||||
|
lookup.prop('onToggleItem')({ name: 'Party', id: 22, kind: 'vault' });
|
||||||
|
expect(onChange).toBeCalledWith([
|
||||||
|
...credentials,
|
||||||
|
{ name: 'Party', id: 22, kind: 'vault' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -2,4 +2,4 @@ export { default } from './Lookup';
|
|||||||
export { default as InstanceGroupsLookup } from './InstanceGroupsLookup';
|
export { default as InstanceGroupsLookup } from './InstanceGroupsLookup';
|
||||||
export { default as InventoryLookup } from './InventoryLookup';
|
export { default as InventoryLookup } from './InventoryLookup';
|
||||||
export { default as ProjectLookup } from './ProjectLookup';
|
export { default as ProjectLookup } from './ProjectLookup';
|
||||||
export { default as CredentialsLookup } from './CredentialsLookup';
|
export { default as MultiCredentialsLookup } from './MultiCredentialsLookup';
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
InventoryLookup,
|
InventoryLookup,
|
||||||
InstanceGroupsLookup,
|
InstanceGroupsLookup,
|
||||||
ProjectLookup,
|
ProjectLookup,
|
||||||
CredentialsLookup,
|
MultiCredentialsLookup,
|
||||||
} from '@components/Lookup';
|
} from '@components/Lookup';
|
||||||
import { JobTemplatesAPI } from '@api';
|
import { JobTemplatesAPI } from '@api';
|
||||||
import LabelSelect from './LabelSelect';
|
import LabelSelect from './LabelSelect';
|
||||||
@@ -77,11 +77,9 @@ class JobTemplateForm extends Component {
|
|||||||
project: props.template.summary_fields.project,
|
project: props.template.summary_fields.project,
|
||||||
inventory: props.template.summary_fields.inventory,
|
inventory: props.template.summary_fields.inventory,
|
||||||
allowCallbacks: !!props.template.host_config_key,
|
allowCallbacks: !!props.template.host_config_key,
|
||||||
credentials: props.template.summary_fields.credentials,
|
|
||||||
};
|
};
|
||||||
this.handleProjectValidation = this.handleProjectValidation.bind(this);
|
this.handleProjectValidation = this.handleProjectValidation.bind(this);
|
||||||
this.loadRelatedInstanceGroups = this.loadRelatedInstanceGroups.bind(this);
|
this.loadRelatedInstanceGroups = this.loadRelatedInstanceGroups.bind(this);
|
||||||
this.toggleCredentialSelection = this.toggleCredentialSelection.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -108,31 +106,6 @@ class JobTemplateForm extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCredentialSelection(newCredential) {
|
|
||||||
const { credentials: credentialsToUpdate } = this.state;
|
|
||||||
const { setFieldValue } = this.props;
|
|
||||||
|
|
||||||
let newCredentialsList;
|
|
||||||
const isSelectedCredentialInState =
|
|
||||||
credentialsToUpdate.filter(cred => cred.id === newCredential.id).length >
|
|
||||||
0;
|
|
||||||
|
|
||||||
if (isSelectedCredentialInState) {
|
|
||||||
newCredentialsList = credentialsToUpdate.filter(
|
|
||||||
cred => cred.id !== newCredential.id
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
newCredentialsList = credentialsToUpdate.filter(
|
|
||||||
credential =>
|
|
||||||
credential.kind === 'vault' || credential.kind !== newCredential.kind
|
|
||||||
);
|
|
||||||
newCredentialsList = [...newCredentialsList, newCredential];
|
|
||||||
}
|
|
||||||
|
|
||||||
setFieldValue('credentials', newCredentialsList);
|
|
||||||
this.setState({ credentials: newCredentialsList });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleProjectValidation() {
|
handleProjectValidation() {
|
||||||
const { i18n, touched } = this.props;
|
const { i18n, touched } = this.props;
|
||||||
const { project } = this.state;
|
const { project } = this.state;
|
||||||
@@ -154,7 +127,6 @@ class JobTemplateForm extends Component {
|
|||||||
inventory,
|
inventory,
|
||||||
project,
|
project,
|
||||||
allowCallbacks,
|
allowCallbacks,
|
||||||
credentials,
|
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const {
|
const {
|
||||||
handleCancel,
|
handleCancel,
|
||||||
@@ -352,10 +324,12 @@ class JobTemplateForm extends Component {
|
|||||||
<Field
|
<Field
|
||||||
name="credentials"
|
name="credentials"
|
||||||
fieldId="template-credentials"
|
fieldId="template-credentials"
|
||||||
render={() => (
|
render={({ field }) => (
|
||||||
<CredentialsLookup
|
<MultiCredentialsLookup
|
||||||
credentials={credentials}
|
credentials={field.value}
|
||||||
onChange={this.toggleCredentialSelection}
|
onChange={newCredentials =>
|
||||||
|
setFieldValue('credentials', newCredentials)
|
||||||
|
}
|
||||||
onError={err => this.setState({ contentError: err })}
|
onError={err => this.setState({ contentError: err })}
|
||||||
tooltip={i18n._(
|
tooltip={i18n._(
|
||||||
t`Select credentials that allow Tower to access the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking "Prompt on launch" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check "Prompt on launch", the selected credential(s) become the defaults that can be updated at run time.`
|
t`Select credentials that allow Tower to access the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking "Prompt on launch" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check "Prompt on launch", the selected credential(s) become the defaults that can be updated at run time.`
|
||||||
|
|||||||
@@ -3,13 +3,7 @@ import { act } from 'react-dom/test-utils';
|
|||||||
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||||
import { sleep } from '@testUtils/testUtils';
|
import { sleep } from '@testUtils/testUtils';
|
||||||
import JobTemplateForm from './JobTemplateForm';
|
import JobTemplateForm from './JobTemplateForm';
|
||||||
import {
|
import { LabelsAPI, JobTemplatesAPI, ProjectsAPI, CredentialsAPI } from '@api';
|
||||||
LabelsAPI,
|
|
||||||
JobTemplatesAPI,
|
|
||||||
ProjectsAPI,
|
|
||||||
CredentialTypesAPI,
|
|
||||||
CredentialsAPI,
|
|
||||||
} from '@api';
|
|
||||||
|
|
||||||
jest.mock('@api');
|
jest.mock('@api');
|
||||||
|
|
||||||
@@ -200,44 +194,4 @@ describe('<JobTemplateForm />', () => {
|
|||||||
wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
|
wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
|
||||||
expect(handleCancel).toBeCalled();
|
expect(handleCancel).toBeCalled();
|
||||||
});
|
});
|
||||||
test('toggleCredentialSelection should handle credential selection properly', async () => {
|
|
||||||
let wrapper;
|
|
||||||
await act(async () => {
|
|
||||||
wrapper = mountWithContexts(
|
|
||||||
<JobTemplateForm
|
|
||||||
template={mockData}
|
|
||||||
handleSubmit={jest.fn()}
|
|
||||||
handleCancel={jest.fn()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function callToggleCredSelection(credential, formState) {
|
|
||||||
JobTempForm.instance().toggleCredentialSelection(credential);
|
|
||||||
|
|
||||||
expect(form.state('values').credentials).toEqual(formState);
|
|
||||||
}
|
|
||||||
const form = wrapper.find('Formik');
|
|
||||||
const JobTempForm = wrapper.find('JobTemplateForm');
|
|
||||||
|
|
||||||
callToggleCredSelection(
|
|
||||||
{ id: 3, kind: 'vault', name: 'Vault Credential' },
|
|
||||||
[
|
|
||||||
{ id: 1, kind: 'cloud', name: 'Foo' },
|
|
||||||
{ id: 2, kind: 'ssh', name: 'Bar' },
|
|
||||||
{ id: 3, kind: 'vault', name: 'Vault Credential' },
|
|
||||||
]
|
|
||||||
);
|
|
||||||
callToggleCredSelection({ id: 4, kind: 'ssh', name: 'New Bar' }, [
|
|
||||||
{ id: 1, kind: 'cloud', name: 'Foo' },
|
|
||||||
{ id: 3, kind: 'vault', name: 'Vault Credential' },
|
|
||||||
{ id: 4, kind: 'ssh', name: 'New Bar' },
|
|
||||||
]);
|
|
||||||
callToggleCredSelection({ id: 5, kind: 'vault', name: 'New Vault' }, [
|
|
||||||
{ id: 1, kind: 'cloud', name: 'Foo' },
|
|
||||||
{ id: 3, kind: 'vault', name: 'Vault Credential' },
|
|
||||||
{ id: 4, kind: 'ssh', name: 'New Bar' },
|
|
||||||
{ id: 5, kind: 'vault', name: 'New Vault' },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user