diff --git a/awx/ui_next/src/screens/Credential/CredentialAdd/CredentialAdd.jsx b/awx/ui_next/src/screens/Credential/CredentialAdd/CredentialAdd.jsx index cd45400fdf..c721b56789 100644 --- a/awx/ui_next/src/screens/Credential/CredentialAdd/CredentialAdd.jsx +++ b/awx/ui_next/src/screens/Credential/CredentialAdd/CredentialAdd.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useCallback, useState, useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { PageSection, Card } from '@patternfly/react-core'; import { CardBody } from '../../../components/Card'; @@ -11,14 +11,64 @@ import { CredentialsAPI, } from '../../../api'; import CredentialForm from '../shared/CredentialForm'; +import useRequest from '../../../util/useRequest'; function CredentialAdd({ me }) { const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(true); const [credentialTypes, setCredentialTypes] = useState(null); - const [formSubmitError, setFormSubmitError] = useState(null); const history = useHistory(); + const { + error: submitError, + request: submitRequest, + result: credentialId, + } = useRequest( + useCallback( + async values => { + const { inputs, organization, ...remainingValues } = values; + const nonPluginInputs = {}; + const pluginInputs = {}; + Object.entries(inputs).forEach(([key, value]) => { + if (value.credential && value.inputs) { + pluginInputs[key] = value; + } else { + nonPluginInputs[key] = value; + } + }); + const { + data: { id: newCredentialId }, + } = await CredentialsAPI.create({ + user: (me && me.id) || null, + organization: (organization && organization.id) || null, + inputs: nonPluginInputs, + ...remainingValues, + }); + const inputSourceRequests = []; + Object.entries(pluginInputs).forEach(([key, value]) => { + inputSourceRequests.push( + CredentialInputSourcesAPI.create({ + input_field_name: key, + metadata: value.inputs, + source_credential: value.credential.id, + target_credential: newCredentialId, + }) + ); + }); + await Promise.all(inputSourceRequests); + + return newCredentialId; + }, + [me] + ) + ); + + useEffect(() => { + if (credentialId) { + history.push(`/credentials/${credentialId}/details`); + } + }, [credentialId, history]); + useEffect(() => { const loadData = async () => { try { @@ -42,45 +92,7 @@ function CredentialAdd({ me }) { }; const handleSubmit = async values => { - const { inputs, organization, ...remainingValues } = values; - const pluginInputs = []; - Object.entries(inputs).forEach(([key, value]) => { - if (value.credential && value.inputs) { - pluginInputs.push([key, value]); - delete inputs[key]; - } - }); - - setFormSubmitError(null); - - try { - const { - data: { id: credentialId }, - } = await CredentialsAPI.create({ - user: (me && me.id) || null, - organization: (organization && organization.id) || null, - inputs: inputs || {}, - ...remainingValues, - }); - const inputSourceRequests = []; - pluginInputs.forEach(([key, value]) => { - if (value.credential && value.inputs) { - inputSourceRequests.push( - CredentialInputSourcesAPI.create({ - input_field_name: key, - metadata: value.inputs, - source_credential: value.credential.id, - target_credential: credentialId, - }) - ); - } - }); - await Promise.all(inputSourceRequests); - const url = `/credentials/${credentialId}/details`; - history.push(`${url}`); - } catch (err) { - setFormSubmitError(err); - } + await submitRequest(values); }; if (error) { @@ -113,7 +125,7 @@ function CredentialAdd({ me }) { onCancel={handleCancel} onSubmit={handleSubmit} credentialTypes={credentialTypes} - submitError={formSubmitError} + submitError={submitError} /> diff --git a/awx/ui_next/src/screens/Credential/CredentialEdit/CredentialEdit.jsx b/awx/ui_next/src/screens/Credential/CredentialEdit/CredentialEdit.jsx index 766541533c..aef18ea8e9 100644 --- a/awx/ui_next/src/screens/Credential/CredentialEdit/CredentialEdit.jsx +++ b/awx/ui_next/src/screens/Credential/CredentialEdit/CredentialEdit.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useCallback, useState, useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { object } from 'prop-types'; @@ -11,15 +11,84 @@ import { import ContentError from '../../../components/ContentError'; import ContentLoading from '../../../components/ContentLoading'; import CredentialForm from '../shared/CredentialForm'; +import useRequest from '../../../util/useRequest'; function CredentialEdit({ credential, me }) { const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(true); const [credentialTypes, setCredentialTypes] = useState(null); - const [inputSources, setInputSources] = useState(null); - const [formSubmitError, setFormSubmitError] = useState(null); + const [inputSources, setInputSources] = useState({}); const history = useHistory(); + const { error: submitError, request: submitRequest, result } = useRequest( + useCallback( + async (values, inputSourceMap) => { + const createAndUpdateInputSources = pluginInputs => + Object.entries(pluginInputs).map(([fieldName, fieldValue]) => { + if (!inputSourceMap[fieldName]) { + return CredentialInputSourcesAPI.create({ + input_field_name: fieldName, + metadata: fieldValue.inputs, + source_credential: fieldValue.credential.id, + target_credential: credential.id, + }); + } + if (fieldValue.touched) { + return CredentialInputSourcesAPI.update( + inputSourceMap[fieldName].id, + { + metadata: fieldValue.inputs, + source_credential: fieldValue.credential.id, + } + ); + } + + return null; + }); + + const destroyInputSources = inputs => { + const destroyRequests = []; + Object.values(inputSourceMap).forEach(inputSource => { + const { id, input_field_name } = inputSource; + if (!inputs[input_field_name]?.credential) { + destroyRequests.push(CredentialInputSourcesAPI.destroy(id)); + } + }); + return destroyRequests; + }; + + const { inputs, organization, ...remainingValues } = values; + const nonPluginInputs = {}; + const pluginInputs = {}; + Object.entries(inputs).forEach(([key, value]) => { + if (value.credential && value.inputs) { + pluginInputs[key] = value; + } else { + nonPluginInputs[key] = value; + } + }); + const [{ data }] = await Promise.all([ + CredentialsAPI.update(credential.id, { + user: (me && me.id) || null, + organization: (organization && organization.id) || null, + inputs: nonPluginInputs, + ...remainingValues, + }), + ...destroyInputSources(inputs), + ]); + await Promise.all(createAndUpdateInputSources(pluginInputs)); + return data; + }, + [credential.id, me] + ) + ); + + useEffect(() => { + if (result) { + history.push(`/credentials/${result.id}/details`); + } + }, [result, history]); + useEffect(() => { const loadData = async () => { try { @@ -54,67 +123,11 @@ function CredentialEdit({ credential, me }) { const handleCancel = () => { const url = `/credentials/${credential.id}/details`; - history.push(`${url}`); }; - const createAndUpdateInputSources = pluginInputs => - Object.entries(pluginInputs).map(([fieldName, fieldValue]) => { - if (!inputSources[fieldName]) { - return CredentialInputSourcesAPI.create({ - input_field_name: fieldName, - metadata: fieldValue.inputs, - source_credential: fieldValue.credential.id, - target_credential: credential.id, - }); - } - if (fieldValue.touched) { - return CredentialInputSourcesAPI.update(inputSources[fieldName].id, { - metadata: fieldValue.inputs, - source_credential: fieldValue.credential.id, - }); - } - - return null; - }); - - const destroyInputSources = inputs => { - const destroyRequests = []; - Object.values(inputSources).forEach(inputSource => { - const { id, input_field_name } = inputSource; - if (!inputs[input_field_name]?.credential) { - destroyRequests.push(CredentialInputSourcesAPI.destroy(id)); - } - }); - return destroyRequests; - }; - const handleSubmit = async values => { - const { inputs, organization, ...remainingValues } = values; - const pluginInputs = {}; - Object.entries(inputs).forEach(([key, value]) => { - if (value.credential && value.inputs) { - pluginInputs[key] = value; - delete inputs[key]; - } - }); - setFormSubmitError(null); - try { - await Promise.all([ - CredentialsAPI.update(credential.id, { - user: (me && me.id) || null, - organization: (organization && organization.id) || null, - inputs: inputs || {}, - ...remainingValues, - }), - ...destroyInputSources(pluginInputs), - ]); - await Promise.all(createAndUpdateInputSources(pluginInputs)); - const url = `/credentials/${credential.id}/details`; - history.push(`${url}`); - } catch (err) { - setFormSubmitError(err); - } + await submitRequest(values, inputSources); }; if (error) { @@ -133,7 +146,7 @@ function CredentialEdit({ credential, me }) { credential={credential} credentialTypes={credentialTypes} inputSources={inputSources} - submitError={formSubmitError} + submitError={submitError} /> ); diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.jsx index d75282fc5c..77cc11ceb9 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.jsx @@ -37,9 +37,9 @@ function CredentialPluginField(props) { isValid={isValid} label={label} > - {field.value.credential ? ( + {field?.value?.credential ? ( helpers.setValue('')} onEditPlugin={() => setShowPluginWizard(true)} />