Adds CredentialLookUp to JT Form

This commit is contained in:
Alex Corey
2019-10-22 16:20:59 -04:00
parent d10e727b3c
commit 2828d31141
8 changed files with 183 additions and 142 deletions

View File

@@ -1,7 +1,7 @@
import AdHocCommands from './models/AdHocCommands'; import AdHocCommands from './models/AdHocCommands';
import Config from './models/Config'; import Config from './models/Config';
import CredentialTypes from './models/CredentialTypes' import CredentialTypes from './models/CredentialTypes';
import Credentials from './models/Credentials' import Credentials from './models/Credentials';
import InstanceGroups from './models/InstanceGroups'; import InstanceGroups from './models/InstanceGroups';
import Inventories from './models/Inventories'; import Inventories from './models/Inventories';
import InventorySources from './models/InventorySources'; import InventorySources from './models/InventorySources';

View File

@@ -46,11 +46,16 @@ class JobTemplates extends InstanceGroupsMixin(NotificationsMixin(Base)) {
} }
associateCredentials(id, credential) { associateCredentials(id, credential) {
return this.http.post(`${this.baseUrl}${id}/credentials/`, { id: credential }); return this.http.post(`${this.baseUrl}${id}/credentials/`, {
id: credential,
});
} }
disassociateCredentials(id, credential) { disassociateCredentials(id, credential) {
return this.http.post(`${this.baseUrl}${id}/credentials/`, { id: credential, disassociate: true }); return this.http.post(`${this.baseUrl}${id}/credentials/`, {
id: credential,
disassociate: true,
});
} }
} }

View File

@@ -16,67 +16,73 @@ const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
class CredentialsLookup extends React.Component { class CredentialsLookup extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props);
this.state = { this.state = {
credentials: props.credentials, credentials: props.credentials,
selectedCredentialType: {label: "Machine", id: 1, kind: 'ssh'}, selectedCredentialType: { label: 'Machine', id: 1, kind: 'ssh' },
credentialTypes: [] credentialTypes: [],
} };
this.handleCredentialTypeSelect = this.handleCredentialTypeSelect.bind(this); this.handleCredentialTypeSelect = this.handleCredentialTypeSelect.bind(
this
);
this.loadCredentials = this.loadCredentials.bind(this); this.loadCredentials = this.loadCredentials.bind(this);
this.loadCredentialTypes = this.loadCredentialTypes.bind(this); this.loadCredentialTypes = this.loadCredentialTypes.bind(this);
this.toggleCredential = this.toggleCredential.bind(this); this.toggleCredential = this.toggleCredential.bind(this);
} }
componentDidMount() { componentDidMount() {
this.loadCredentials({page: 1, page_size: 5, order_by:'name'}) this.loadCredentials({ page: 1, page_size: 5, order_by: 'name' });
this.loadCredentialTypes(); this.loadCredentialTypes();
} }
componentDidUpdate(prevState) { componentDidUpdate(prevState) {
const {selectedType} = this.state const { selectedType } = this.state;
if (prevState.selectedType !== selectedType) { if (prevState.selectedType !== selectedType) {
Promise.all([this.loadCredentials()]); Promise.all([this.loadCredentials()]);
} }
} }
async loadCredentialTypes() { async loadCredentialTypes() {
const {onError} =this.props const { onError } = this.props;
try { try {
const { data } = await CredentialTypesAPI.read() const { data } = await CredentialTypesAPI.read();
const acceptableTypes = ["machine", "cloud", "net", "ssh", "vault"] const acceptableTypes = ['machine', 'cloud', 'net', 'ssh', 'vault'];
const credentialTypes =[] const credentialTypes = [];
data.results data.results.forEach(cred => {
.forEach(cred => { acceptableTypes.forEach(aT => {
acceptableTypes.forEach(aT => { if (aT === cred.kind) {
if (aT === cred.kind) { // This object has several repeated values as some of it's children
// This object has several repeated values as some of it's children // require different field values.
// require different field values. cred = {
cred = { id: cred.id,
id: cred.id, kind: cred.kind, type: cred.namespace, kind: cred.kind,
value: cred.name, label: cred.name, isDisabled: false type: cred.namespace,
} value: cred.name,
credentialTypes.push(cred) label: cred.name,
}}) isDisabled: false,
}) };
this.setState({ credentialTypes }) credentialTypes.push(cred);
} catch (err){ }
onError(err) });
} });
} this.setState({ credentialTypes });
} catch (err) {
onError(err);
}
}
async loadCredentials(params) { async loadCredentials(params) {
const { selectedCredentialType } = this.state; const { selectedCredentialType } = this.state;
params.credential_type = selectedCredentialType.id || 1 params.credential_type = selectedCredentialType.id || 1;
return CredentialsAPI.read(params) return CredentialsAPI.read(params);
} }
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);
this.setState({selectedCredentialType: selectedType[0]}) this.setState({ selectedCredentialType: selectedType[0] });
} }
toggleCredential(item) { toggleCredential(item) {
@@ -84,26 +90,31 @@ class CredentialsLookup extends React.Component {
const { onChange } = this.props; const { onChange } = this.props;
const index = stateToUpdate.findIndex( const index = stateToUpdate.findIndex(
credential => credential.id === item.id credential => credential.id === item.id
) );
if (index > -1) { if (index > -1) {
const newCredentialsList = stateToUpdate.filter(cred => cred.id !== item.id) const newCredentialsList = stateToUpdate.filter(
cred => cred.id !== item.id
);
this.setState({ credentials: newCredentialsList }); this.setState({ credentials: newCredentialsList });
onChange(newCredentialsList) onChange(newCredentialsList);
return; return;
} }
const credentialTypeOccupied = stateToUpdate.some(cred => cred.kind === item.kind); const credentialTypeOccupied = stateToUpdate.some(
if (selectedCredentialType.value === "Vault" || !credentialTypeOccupied ) { cred => cred.kind === item.kind
item.credentialType = selectedCredentialType );
this.setState({ credentials: [...stateToUpdate, item] }) if (selectedCredentialType.value === 'Vault' || !credentialTypeOccupied) {
onChange([...stateToUpdate, item]) item.credentialType = selectedCredentialType;
this.setState({ credentials: [...stateToUpdate, item] });
onChange([...stateToUpdate, item]);
} else { } else {
const credsList = [...stateToUpdate] const credsList = [...stateToUpdate];
const occupyingCredIndex = stateToUpdate.findIndex(occupyingCred => const occupyingCredIndex = stateToUpdate.findIndex(
occupyingCred.kind === item.kind) occupyingCred => occupyingCred.kind === item.kind
credsList.splice(occupyingCredIndex, 1, item) );
this.setState({ credentials: credsList }) credsList.splice(occupyingCredIndex, 1, item);
onChange(credsList) this.setState({ credentials: credsList });
onChange(credsList);
} }
} }
@@ -112,21 +123,18 @@ class CredentialsLookup extends React.Component {
const { credentials, selectedCredentialType, credentialTypes } = this.state; const { credentials, selectedCredentialType, credentialTypes } = this.state;
return ( return (
<FormGroup <FormGroup label={i18n._(t`Credentials`)} fieldId="org-credentials">
label={i18n._(t`Credentials`)} {tooltip && (
fieldId="org-credentials" <Tooltip position="right" content={tooltip}>
> <QuestionCircleIcon />
{tooltip && ( </Tooltip>
<Tooltip position="right" content={tooltip}> )}
<QuestionCircleIcon /> {credentialTypes && (
</Tooltip> <Lookup
)}
{credentialTypes && (
<Lookup
selectCategoryOptions={credentialTypes} selectCategoryOptions={credentialTypes}
selectCategory={this.handleCredentialTypeSelect} selectCategory={this.handleCredentialTypeSelect}
selectedCategory={selectedCredentialType} selectedCategory={selectedCredentialType}
onToggleItem = {this.toggleCredential} onToggleItem={this.toggleCredential}
onloadCategories={this.loadCredentialTypes} onloadCategories={this.loadCredentialTypes}
id="org-credentials" id="org-credentials"
lookupHeader={i18n._(t`Credentials`)} lookupHeader={i18n._(t`Credentials`)}
@@ -146,8 +154,8 @@ class CredentialsLookup extends React.Component {
]} ]}
sortedColumnKey="name" sortedColumnKey="name"
/> />
)} )}
</FormGroup> </FormGroup>
); );
} }
} }

View File

@@ -21,9 +21,9 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import styled from 'styled-components'; import styled from 'styled-components';
import AnsibleSelect from '../AnsibleSelect' import AnsibleSelect from '../AnsibleSelect';
import PaginatedDataList from '../PaginatedDataList'; import PaginatedDataList from '../PaginatedDataList';
import VerticalSeperator from '../VerticalSeparator' import VerticalSeperator from '../VerticalSeparator';
import DataListToolbar from '../DataListToolbar'; import DataListToolbar from '../DataListToolbar';
import CheckboxListItem from '../CheckboxListItem'; import CheckboxListItem from '../CheckboxListItem';
import SelectedList from '../SelectedList'; import SelectedList from '../SelectedList';
@@ -68,13 +68,13 @@ class Lookup extends React.Component {
results: [], results: [],
count: 0, count: 0,
error: null, error: null,
isDropdownOpen: false isDropdownOpen: false,
}; };
this.qsConfig = getQSConfig(props.qsNamespace, { this.qsConfig = getQSConfig(props.qsNamespace, {
page: 1, page: 1,
page_size: 5, page_size: 5,
order_by: props.sortedColumnKey, order_by: props.sortedColumnKey,
}); });
this.handleModalToggle = this.handleModalToggle.bind(this); this.handleModalToggle = this.handleModalToggle.bind(this);
this.toggleSelected = this.toggleSelected.bind(this); this.toggleSelected = this.toggleSelected.bind(this);
this.saveModal = this.saveModal.bind(this); this.saveModal = this.saveModal.bind(this);
@@ -84,8 +84,7 @@ class Lookup extends React.Component {
} }
componentDidMount() { componentDidMount() {
const {onLoadCredentialTypes} = this.props const { onLoadCredentialTypes } = this.props;
;
if (onLoadCredentialTypes) { if (onLoadCredentialTypes) {
Promise.all([onLoadCredentialTypes(), this.getData()]); Promise.all([onLoadCredentialTypes(), this.getData()]);
} else { } else {
@@ -95,21 +94,23 @@ class Lookup extends React.Component {
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { location, selectedCategory } = this.props; const { location, selectedCategory } = this.props;
if ((location !== prevProps.location) || if (
(prevProps.selectedCategory !== selectedCategory)) { location !== prevProps.location ||
prevProps.selectedCategory !== selectedCategory
) {
this.getData(); this.getData();
} }
} }
toggleDropdown() { toggleDropdown() {
const { isDropdownOpen } = this.state; const { isDropdownOpen } = this.state;
this.setState({isDropdownOpen: !isDropdownOpen}) this.setState({ isDropdownOpen: !isDropdownOpen });
} }
assertCorrectValueType() { assertCorrectValueType() {
const { multiple, value, selectCategoryOptions } = this.props; const { multiple, value, selectCategoryOptions } = this.props;
if (selectCategoryOptions) { if (selectCategoryOptions) {
return return;
} }
if (!multiple && Array.isArray(value)) { if (!multiple && Array.isArray(value)) {
throw new Error( throw new Error(
@@ -130,30 +131,37 @@ class Lookup extends React.Component {
this.setState({ error: false }); this.setState({ error: false });
try { try {
const { data } = await getItems(queryParams); const { data } = await getItems(queryParams);
const { results, count } = data; const { results, count } = data;
this.setState({ this.setState({
results, results,
count, count,
}); });
} catch (err) { } catch (err) {
this.setState({ error: true }); this.setState({ error: true });
} }
} }
toggleSelected(row) { toggleSelected(row) {
const { name, onLookupSave, multiple, onToggleItem, selectCategoryOptions } = this.props;
const { const {
lookupSelectedItems: updatedSelectedItems, isModalOpen name,
onLookupSave,
multiple,
onToggleItem,
selectCategoryOptions,
} = this.props;
const {
lookupSelectedItems: updatedSelectedItems,
isModalOpen,
} = this.state; } = this.state;
const selectedIndex = updatedSelectedItems.findIndex( const selectedIndex = updatedSelectedItems.findIndex(
selectedRow => selectedRow.id === row.id selectedRow => selectedRow.id === row.id
); );
if (multiple) { if (multiple) {
if (selectCategoryOptions) { if (selectCategoryOptions) {
onToggleItem(row, isModalOpen) onToggleItem(row, isModalOpen);
} }
if (selectedIndex > -1) { if (selectedIndex > -1) {
updatedSelectedItems.splice(selectedIndex, 1); updatedSelectedItems.splice(selectedIndex, 1);
@@ -171,8 +179,8 @@ class Lookup extends React.Component {
// This handles the case where the user removes chips from the lookup input // This handles the case where the user removes chips from the lookup input
// while the modal is closed // while the modal is closed
if (!isModalOpen) { if (!isModalOpen) {
onLookupSave(updatedSelectedItems, name) onLookupSave(updatedSelectedItems, name);
}; }
} }
handleModalToggle() { handleModalToggle() {
@@ -190,7 +198,7 @@ class Lookup extends React.Component {
} else { } else {
this.clearQSParams(); this.clearQSParams();
if (selectCategory) { if (selectCategory) {
selectCategory(null, "Machine"); selectCategory(null, 'Machine');
} }
} }
this.setState(prevState => ({ this.setState(prevState => ({
@@ -201,11 +209,12 @@ class Lookup extends React.Component {
saveModal() { saveModal() {
const { onLookupSave, name, multiple } = this.props; const { onLookupSave, name, multiple } = this.props;
const { lookupSelectedItems } = this.state; const { lookupSelectedItems } = this.state;
const value = multiple ? lookupSelectedItems : lookupSelectedItems[0] || null; const value = multiple
? lookupSelectedItems
this.handleModalToggle(); : lookupSelectedItems[0] || null;
onLookupSave(value, name);
this.handleModalToggle();
onLookupSave(value, name);
} }
clearQSParams() { clearQSParams() {
@@ -237,12 +246,12 @@ class Lookup extends React.Component {
required, required,
i18n, i18n,
selectCategoryOptions, selectCategoryOptions,
selectedCategory selectedCategory,
} = this.props; } = this.props;
const header = lookupHeader || i18n._(t`Items`); const header = lookupHeader || i18n._(t`Items`);
const canDelete = !required || (multiple && value.length > 1); const canDelete = !required || (multiple && value.length > 1);
const chips = () => { const chips = () => {
return (selectCategoryOptions && selectCategoryOptions.length > 0) ? ( return selectCategoryOptions && selectCategoryOptions.length > 0 ? (
<ChipGroup> <ChipGroup>
{(multiple ? value : [value]).map(chip => ( {(multiple ? value : [value]).map(chip => (
<CredentialChip <CredentialChip
@@ -265,8 +274,8 @@ class Lookup extends React.Component {
</Chip> </Chip>
))} ))}
</ChipGroup> </ChipGroup>
) );
} };
return ( return (
<Fragment> <Fragment>
<InputGroup onBlur={onBlur}> <InputGroup onBlur={onBlur}>
@@ -305,11 +314,19 @@ class Lookup extends React.Component {
</Button>, </Button>,
]} ]}
> >
{(selectCategoryOptions && selectCategoryOptions.length > 0) && ( {selectCategoryOptions && selectCategoryOptions.length > 0 && (
<ToolbarItem css=" display: flex; align-items: center;"> <ToolbarItem css=" display: flex; align-items: center;">
<span css="flex: 0 0 25%;">Selected Category</span> <span css="flex: 0 0 25%;">Selected Category</span>
<VerticalSeperator /> <VerticalSeperator />
<AnsibleSelect css="flex: 1 1 75%;" id="credentialsLookUp-select" label="Selected Category" data={selectCategoryOptions} value={selectedCategory.label} onChange={selectCategory} form={form}/> <AnsibleSelect
css="flex: 1 1 75%;"
id="credentialsLookUp-select"
label="Selected Category"
data={selectCategoryOptions}
value={selectedCategory.label}
onChange={selectCategory}
form={form}
/>
</ToolbarItem> </ToolbarItem>
)} )}
<PaginatedDataList <PaginatedDataList
@@ -324,10 +341,18 @@ class Lookup extends React.Component {
itemId={item.id} itemId={item.id}
name={multiple ? item.name : name} name={multiple ? item.name : name}
label={item.name} label={item.name}
isSelected={selectCategoryOptions ? value.some(i => i.id === item.id) isSelected={
: lookupSelectedItems.some(i => i.id === item.id)} selectCategoryOptions
? value.some(i => i.id === item.id)
: lookupSelectedItems.some(i => i.id === item.id)
}
onSelect={() => this.toggleSelected(item)} onSelect={() => this.toggleSelected(item)}
isRadio={!multiple || ((selectCategoryOptions && selectCategoryOptions.length) && selectedCategory.value !== "Vault")} isRadio={
!multiple ||
(selectCategoryOptions &&
selectCategoryOptions.length &&
selectedCategory.value !== 'Vault')
}
/> />
)} )}
renderToolbar={props => <DataListToolbar {...props} fillWidth />} renderToolbar={props => <DataListToolbar {...props} fillWidth />}
@@ -340,7 +365,9 @@ class Lookup extends React.Component {
showOverflowAfter={5} showOverflowAfter={5}
onRemove={this.toggleSelected} onRemove={this.toggleSelected}
isReadOnly={!canDelete} isReadOnly={!canDelete}
isCredentialList={selectCategoryOptions && selectCategoryOptions.length > 0} isCredentialList={
selectCategoryOptions && selectCategoryOptions.length > 0
}
/> />
)} )}
{error ? <div>error</div> : ''} {error ? <div>error</div> : ''}

View File

@@ -27,34 +27,34 @@ class SelectedList extends Component {
onRemove, onRemove,
displayKey, displayKey,
isReadOnly, isReadOnly,
isCredentialList isCredentialList,
} = this.props; } = this.props;
const chips = isCredentialList ? selected.map(item => ( const chips = isCredentialList
<CredentialChip ? selected.map(item => (
key={item.id} <CredentialChip
isReadOnly={isReadOnly} key={item.id}
onClick={() => onRemove(item)} isReadOnly={isReadOnly}
credential={item} onClick={() => onRemove(item)}
> credential={item}
{item[displayKey]} >
</CredentialChip> {item[displayKey]}
)) : selected.map(item => ( </CredentialChip>
<Chip ))
key={item.id} : selected.map(item => (
isReadOnly={isReadOnly} <Chip
onClick={() => onRemove(item)} key={item.id}
> isReadOnly={isReadOnly}
{item[displayKey]} onClick={() => onRemove(item)}
</Chip> >
)) {item[displayKey]}
</Chip>
));
return ( return (
<Split> <Split>
<SplitLabelItem>{label}</SplitLabelItem> <SplitLabelItem>{label}</SplitLabelItem>
<VerticalSeparator /> <VerticalSeparator />
<SplitItem> <SplitItem>
<ChipGroup showOverflowAfter={showOverflowAfter}> <ChipGroup showOverflowAfter={showOverflowAfter}>{chips}</ChipGroup>
{chips}
</ChipGroup>
</SplitItem> </SplitItem>
</Split> </Split>
); );

View File

@@ -34,7 +34,7 @@ function JobTemplateAdd({ history, i18n }) {
await Promise.all([ await Promise.all([
submitLabels(id, labels, organizationId), submitLabels(id, labels, organizationId),
submitInstanceGroups(id, instanceGroups), submitInstanceGroups(id, instanceGroups),
submitCredentials(id, credentials) submitCredentials(id, credentials),
]); ]);
history.push(`/templates/${type}/${id}/details`); history.push(`/templates/${type}/${id}/details`);
} catch (error) { } catch (error) {
@@ -65,8 +65,8 @@ function JobTemplateAdd({ history, i18n }) {
function submitCredentials(templateId, credentials = []) { function submitCredentials(templateId, credentials = []) {
const associateCredentials = credentials.map(cred => const associateCredentials = credentials.map(cred =>
JobTemplatesAPI.associateCredentials(templateId, cred.id) JobTemplatesAPI.associateCredentials(templateId, cred.id)
) );
return Promise.all(associateCredentials) return Promise.all(associateCredentials);
} }
function handleCancel() { function handleCancel() {

View File

@@ -120,7 +120,7 @@ class JobTemplateEdit extends Component {
await Promise.all([ await Promise.all([
this.submitLabels(labels, organizationId), this.submitLabels(labels, organizationId),
this.submitInstanceGroups(instanceGroups, initialInstanceGroups), this.submitInstanceGroups(instanceGroups, initialInstanceGroups),
this.submitCredentials(credentials) this.submitCredentials(credentials),
]); ]);
history.push(this.detailsUrl); history.push(this.detailsUrl);
} catch (formSubmitError) { } catch (formSubmitError) {
@@ -163,7 +163,7 @@ class JobTemplateEdit extends Component {
const associatePromises = await added.map(group => const associatePromises = await added.map(group =>
JobTemplatesAPI.associateInstanceGroup(template.id, group.id) JobTemplatesAPI.associateInstanceGroup(template.id, group.id)
); );
return Promise.all([...disassociatePromises, ...associatePromises, ]); return Promise.all([...disassociatePromises, ...associatePromises]);
} }
async submitCredentials(newCredentials) { async submitCredentials(newCredentials) {
@@ -178,9 +178,9 @@ class JobTemplateEdit extends Component {
const disassociatePromise = await Promise.all(disassociateCredentials); const disassociatePromise = await Promise.all(disassociateCredentials);
const associateCredentials = added.map(cred => const associateCredentials = added.map(cred =>
JobTemplatesAPI.associateCredentials(template.id, cred.id) JobTemplatesAPI.associateCredentials(template.id, cred.id)
) );
const associatePromise = Promise.all(associateCredentials) const associatePromise = Promise.all(associateCredentials);
return Promise.all([disassociatePromise, associatePromise]) return Promise.all([disassociatePromise, associatePromise]);
} }
handleCancel() { handleCancel() {

View File

@@ -27,7 +27,7 @@ import {
InventoryLookup, InventoryLookup,
InstanceGroupsLookup, InstanceGroupsLookup,
ProjectLookup, ProjectLookup,
CredentialsLookup CredentialsLookup,
} from '@components/Lookup'; } from '@components/Lookup';
import { JobTemplatesAPI } from '@api'; import { JobTemplatesAPI } from '@api';
import LabelSelect from './LabelSelect'; import LabelSelect from './LabelSelect';
@@ -86,8 +86,7 @@ class JobTemplateForm extends Component {
const { validateField } = this.props; const { validateField } = this.props;
this.setState({ contentError: null, hasContentLoading: true }); this.setState({ contentError: null, hasContentLoading: true });
// TODO: determine when LabelSelect has finished loading labels // TODO: determine when LabelSelect has finished loading labels
Promise.all([this.loadRelatedInstanceGroups(), Promise.all([this.loadRelatedInstanceGroups()]).then(() => {
]).then(() => {
this.setState({ hasContentLoading: false }); this.setState({ hasContentLoading: false });
validateField('project'); validateField('project');
}); });
@@ -329,8 +328,10 @@ class JobTemplateForm extends Component {
<CredentialsLookup <CredentialsLookup
onError={err => this.setState({ contentError: err })} onError={err => this.setState({ contentError: err })}
credentials={template.summary_fields.credentials} credentials={template.summary_fields.credentials}
onChange={value => (form.setFieldValue('credentials', value))} onChange={value => form.setFieldValue('credentials', value)}
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.`)} 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.`
)}
/> />
)} )}
/> />
@@ -604,7 +605,7 @@ const FormikApp = withFormik({
initialInstanceGroups: [], initialInstanceGroups: [],
instanceGroups: [], instanceGroups: [],
initialCredentials: summary_fields.credentials || [], initialCredentials: summary_fields.credentials || [],
credentials: [] credentials: [],
}; };
}, },
handleSubmit: (values, { props }) => props.handleSubmit(values), handleSubmit: (values, { props }) => props.handleSubmit(values),