diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialField.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialField.jsx index b5203bc72a..000aebe2f0 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialField.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialField.jsx @@ -14,7 +14,7 @@ import { FieldTooltip, PasswordInput } from '../../../../components/FormField'; import AnsibleSelect from '../../../../components/AnsibleSelect'; import { CredentialType } from '../../../../types'; import { required } from '../../../../util/validators'; -import { CredentialPluginField } from './CredentialPlugins'; +import { CredentialPluginField } from '../CredentialPlugins'; import BecomeMethodField from './BecomeMethodField'; const FileUpload = styled(PFFileUpload)` diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.jsx deleted file mode 100644 index 831291871e..0000000000 --- a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.jsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import { func, shape } from 'prop-types'; -import { Formik, useField } from 'formik'; -import { withI18n } from '@lingui/react'; -import { t } from '@lingui/macro'; -import { Wizard } from '@patternfly/react-core'; -import CredentialsStep from './CredentialsStep'; -import MetadataStep from './MetadataStep'; - -function CredentialPluginWizard({ i18n, handleSubmit, onClose }) { - const [selectedCredential] = useField('credential'); - const steps = [ - { - id: 1, - name: i18n._(t`Credential`), - component: , - enableNext: !!selectedCredential.value, - }, - { - id: 2, - name: i18n._(t`Metadata`), - component: , - canJumpTo: !!selectedCredential.value, - nextButtonText: i18n._(t`OK`), - }, - ]; - - return ( - - ); -} - -function CredentialPluginPrompt({ i18n, onClose, onSubmit, initialValues }) { - return ( - - {({ handleSubmit }) => ( - - )} - - ); -} - -CredentialPluginPrompt.propTypes = { - onClose: func.isRequired, - onSubmit: func.isRequired, - initialValues: shape({}), -}; - -CredentialPluginPrompt.defaultProps = { - initialValues: {}, -}; - -export default withI18n()(CredentialPluginPrompt); diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginField.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.jsx similarity index 95% rename from awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginField.jsx rename to awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.jsx index 3a90676982..b37040fc31 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginField.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.jsx @@ -11,8 +11,8 @@ import { Tooltip, } from '@patternfly/react-core'; import { KeyIcon } from '@patternfly/react-icons'; -import { FieldTooltip } from '../../../../../components/FormField'; -import FieldWithPrompt from '../../../../../components/FieldWithPrompt'; +import { FieldTooltip } from '../../../../components/FormField'; +import FieldWithPrompt from '../../../../components/FieldWithPrompt'; import { CredentialPluginPrompt } from './CredentialPluginPrompt'; import CredentialPluginSelected from './CredentialPluginSelected'; @@ -55,6 +55,7 @@ function CredentialPluginInput(props) { )} > + {activeStep && activeStep.key === 'metadata' && ( + <> + + + + + + + )} + + + )} + + + ); + + return ( + <> + + {selectedCredential.value && ( + + )} + + ); +} + +function CredentialPluginPrompt({ i18n, onClose, onSubmit, initialValues }) { + return ( + + {({ handleSubmit }) => ( + + )} + + ); +} + +CredentialPluginPrompt.propTypes = { + onClose: func.isRequired, + onSubmit: func.isRequired, + initialValues: shape({}), +}; + +CredentialPluginPrompt.defaultProps = { + initialValues: {}, +}; + +export default withI18n()(CredentialPluginPrompt); diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.jsx similarity index 90% rename from awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.jsx rename to awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.jsx index bdcd89398a..bff46c7cd6 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.jsx @@ -3,15 +3,17 @@ import { act } from 'react-dom/test-utils'; import { mountWithContexts, waitForElement, -} from '../../../../../../../testUtils/enzymeHelpers'; -import { CredentialsAPI, CredentialTypesAPI } from '../../../../../../api'; -import selectedCredential from '../../../data.cyberArkCredential.json'; -import azureVaultCredential from '../../../data.azureVaultCredential.json'; -import hashiCorpCredential from '../../../data.hashiCorpCredential.json'; +} from '../../../../../../testUtils/enzymeHelpers'; +import { CredentialsAPI, CredentialTypesAPI } from '../../../../../api'; +import selectedCredential from '../../data.cyberArkCredential.json'; +import azureVaultCredential from '../../data.azureVaultCredential.json'; +import hashiCorpCredential from '../../data.hashiCorpCredential.json'; import CredentialPluginPrompt from './CredentialPluginPrompt'; -jest.mock('../../../../../../api/models/Credentials'); -jest.mock('../../../../../../api/models/CredentialTypes'); +jest.mock('../../../../../api/models/Credentials'); +jest.mock('../../../../../api/models/CredentialTypes'); + +CredentialsAPI.test.mockResolvedValue({}); CredentialsAPI.read.mockResolvedValue({ data: { @@ -234,5 +236,13 @@ describe('', () => { wrapper.find('input#credential-secret_version').prop('value') ).toBe('9000'); }); + test('clicking Test button makes correct call', async () => { + await act(async () => { + wrapper.find('Button[children="Test"]').simulate('click'); + }); + expect(CredentialsAPI.test).toHaveBeenCalledWith(1, { + metadata: { secret_path: '/foo/bar', secret_version: '9000' }, + }); + }); }); }); diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.jsx similarity index 86% rename from awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.jsx rename to awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.jsx index bc91a98894..328d17c8a9 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.jsx @@ -3,13 +3,13 @@ import { useHistory } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; import { useField } from 'formik'; -import { CredentialsAPI } from '../../../../../../api'; -import CheckboxListItem from '../../../../../../components/CheckboxListItem'; -import ContentError from '../../../../../../components/ContentError'; -import DataListToolbar from '../../../../../../components/DataListToolbar'; -import PaginatedDataList from '../../../../../../components/PaginatedDataList'; -import { getQSConfig, parseQueryString } from '../../../../../../util/qs'; -import useRequest from '../../../../../../util/useRequest'; +import { CredentialsAPI } from '../../../../../api'; +import CheckboxListItem from '../../../../../components/CheckboxListItem'; +import ContentError from '../../../../../components/ContentError'; +import DataListToolbar from '../../../../../components/DataListToolbar'; +import PaginatedDataList from '../../../../../components/PaginatedDataList'; +import { getQSConfig, parseQueryString } from '../../../../../util/qs'; +import useRequest from '../../../../../util/useRequest'; const QS_CONFIG = getQSConfig('credential', { page: 1, diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx similarity index 76% rename from awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx rename to awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx index 69dd105a5c..8f4c0b2e5c 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx @@ -1,27 +1,22 @@ import React, { useCallback, useEffect } from 'react'; import { withI18n } from '@lingui/react'; -import { t } from '@lingui/macro'; import { useField, useFormikContext } from 'formik'; import styled from 'styled-components'; -import { Button, Form, FormGroup, Tooltip } from '@patternfly/react-core'; +import { Form, FormGroup, Tooltip } from '@patternfly/react-core'; import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons'; -import { CredentialTypesAPI } from '../../../../../../api'; -import AnsibleSelect from '../../../../../../components/AnsibleSelect'; -import ContentError from '../../../../../../components/ContentError'; -import ContentLoading from '../../../../../../components/ContentLoading'; -import FormField from '../../../../../../components/FormField'; -import { FormFullWidthLayout } from '../../../../../../components/FormLayout'; -import useRequest from '../../../../../../util/useRequest'; -import { required } from '../../../../../../util/validators'; +import { CredentialTypesAPI } from '../../../../../api'; +import AnsibleSelect from '../../../../../components/AnsibleSelect'; +import ContentError from '../../../../../components/ContentError'; +import ContentLoading from '../../../../../components/ContentLoading'; +import FormField from '../../../../../components/FormField'; +import { FormFullWidthLayout } from '../../../../../components/FormLayout'; +import useRequest from '../../../../../util/useRequest'; +import { required } from '../../../../../util/validators'; const QuestionCircleIcon = styled(PFQuestionCircleIcon)` margin-left: 10px; `; -const TestButton = styled(Button)` - margin-top: 20px; -`; - function MetadataStep({ i18n }) { const form = useFormikContext(); const [selectedCredential] = useField('credential'); @@ -65,10 +60,6 @@ function MetadataStep({ i18n }) { fetchMetadataOptions(); }, [fetchMetadataOptions]); - const testMetadata = () => { - // https://github.com/ansible/awx/issues/7126 - }; - if (isLoading) { return ; } @@ -136,20 +127,6 @@ function MetadataStep({ i18n }) { )} - - testMetadata()} - > - {i18n._(t`Test`)} - - ); } diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/index.js b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/index.js similarity index 100% rename from awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/index.js rename to awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/index.js diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginSelected.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.jsx similarity index 93% rename from awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginSelected.jsx rename to awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.jsx index af5b228cf2..d97a7a39df 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginSelected.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.jsx @@ -5,8 +5,8 @@ import { t, Trans } from '@lingui/macro'; import styled from 'styled-components'; import { Button, ButtonVariant, Tooltip } from '@patternfly/react-core'; import { KeyIcon } from '@patternfly/react-icons'; -import CredentialChip from '../../../../../components/CredentialChip'; -import { Credential } from '../../../../../types'; +import CredentialChip from '../../../../components/CredentialChip'; +import { Credential } from '../../../../types'; const SelectedCredential = styled.div` display: flex; diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginSelected.test.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.test.jsx similarity index 87% rename from awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginSelected.test.jsx rename to awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.test.jsx index de17450c6d..ce69724904 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginSelected.test.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.test.jsx @@ -1,6 +1,6 @@ import React from 'react'; -import { mountWithContexts } from '../../../../../../testUtils/enzymeHelpers'; -import selectedCredential from '../../data.cyberArkCredential.json'; +import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers'; +import selectedCredential from '../data.cyberArkCredential.json'; import CredentialPluginSelected from './CredentialPluginSelected'; describe('', () => { diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginTestAlert.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx similarity index 99% rename from awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginTestAlert.jsx rename to awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx index f1c4a4ae97..01b2c56598 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginTestAlert.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx @@ -16,7 +16,6 @@ function CredentialPluginTestAlert({ }) { const [testMessage, setTestMessage] = useState(''); const [testVariant, setTestVariant] = useState(false); - useEffect(() => { if (errorResponse) { if (errorResponse?.response?.data?.inputs) { diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.test.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.test.jsx new file mode 100644 index 0000000000..3934c866e6 --- /dev/null +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.test.jsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers'; +import CredentialPluginTestAlert from './CredentialPluginTestAlert'; + +describe('', () => { + let wrapper; + afterEach(() => { + wrapper.unmount(); + }); + test('renders expected content when test is successful', () => { + wrapper = mountWithContexts( + + ); + expect(wrapper.find('b#credential-plugin-test-name').text()).toBe('Foobar'); + expect(wrapper.find('p#credential-plugin-test-message').text()).toBe( + 'Test passed' + ); + }); + test('renders expected content when test fails with the expected return string formatting', () => { + wrapper = mountWithContexts( + + ); + expect(wrapper.find('b#credential-plugin-test-name').text()).toBe('Foobar'); + expect(wrapper.find('p#credential-plugin-test-message').text()).toBe( + 'HTTP 404: not found' + ); + }); + test('renders expected content when test fails without the expected return string formatting', () => { + wrapper = mountWithContexts( + + ); + expect(wrapper.find('b#credential-plugin-test-name').text()).toBe('Foobar'); + expect(wrapper.find('p#credential-plugin-test-message').text()).toBe( + 'usernamee is not present at /secret/foo/bar/baz' + ); + }); +}); diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/index.js b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/index.js similarity index 100% rename from awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/index.js rename to awx/ui_next/src/screens/Credential/shared/CredentialPlugins/index.js diff --git a/awx/ui_next/src/screens/Credential/shared/ExternalTestModal.jsx b/awx/ui_next/src/screens/Credential/shared/ExternalTestModal.jsx index fda8bc4492..76380e070c 100644 --- a/awx/ui_next/src/screens/Credential/shared/ExternalTestModal.jsx +++ b/awx/ui_next/src/screens/Credential/shared/ExternalTestModal.jsx @@ -18,7 +18,7 @@ import FormField from '../../../components/FormField'; import { FormFullWidthLayout } from '../../../components/FormLayout'; import { required } from '../../../util/validators'; import useRequest from '../../../util/useRequest'; -import { CredentialPluginTestAlert } from './CredentialFormFields/CredentialPlugins'; +import { CredentialPluginTestAlert } from './CredentialPlugins'; const QuestionCircleIcon = styled(PFQuestionCircleIcon)` margin-left: 10px; diff --git a/awx/ui_next/src/util/useRequest.js b/awx/ui_next/src/util/useRequest.js index 027e82f86f..764297941a 100644 --- a/awx/ui_next/src/util/useRequest.js +++ b/awx/ui_next/src/util/useRequest.js @@ -39,6 +39,7 @@ export default function useRequest(makeRequest, initialValue) { async (...args) => { setIsLoading(true); if (isMounted.current) { + setResult(initialValue); setError(null); } try { @@ -56,6 +57,7 @@ export default function useRequest(makeRequest, initialValue) { } } }, + /* eslint-disable-next-line react-hooks/exhaustive-deps */ [makeRequest] ), setValue: setResult, diff --git a/awx/ui_next/src/util/useRequest.test.jsx b/awx/ui_next/src/util/useRequest.test.jsx index d0b1072d26..369a7ee07a 100644 --- a/awx/ui_next/src/util/useRequest.test.jsx +++ b/awx/ui_next/src/util/useRequest.test.jsx @@ -96,6 +96,37 @@ describe('useRequest hooks', () => { expect(wrapper.find('TestInner').prop('error')).toEqual(error); }); + test('should reset error/result on each request', async () => { + const error = new Error('error'); + const makeRequest = throwError => { + if (throwError) { + throw error; + } + + return { data: 'foo' }; + }; + const wrapper = mount(); + + await act(async () => { + wrapper.find('TestInner').invoke('request')(true); + }); + wrapper.update(); + expect(wrapper.find('TestInner').prop('result')).toEqual({}); + expect(wrapper.find('TestInner').prop('error')).toEqual(error); + await act(async () => { + wrapper.find('TestInner').invoke('request')(); + }); + wrapper.update(); + expect(wrapper.find('TestInner').prop('result')).toEqual({ data: 'foo' }); + expect(wrapper.find('TestInner').prop('error')).toEqual(null); + await act(async () => { + wrapper.find('TestInner').invoke('request')(true); + }); + wrapper.update(); + expect(wrapper.find('TestInner').prop('result')).toEqual({}); + expect(wrapper.find('TestInner').prop('error')).toEqual(error); + }); + test('should not update state after unmount', async () => { const makeRequest = jest.fn(); let resolve;