From 67f46b4d7eb724c860f342beb214aad3e0b48ac4 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 11 Jun 2020 13:23:36 -0400 Subject: [PATCH 1/8] Hook up Test button on Metadata step in credential plugin wizard --- .../CredentialPluginPrompt.test.jsx | 10 +++ .../CredentialPluginPrompt/MetadataStep.jsx | 30 +++++-- .../CredentialPluginTestAlert.jsx | 82 +++++++++++++++++++ .../CredentialPluginTestAlert.test.jsx | 63 ++++++++++++++ awx/ui_next/src/util/useRequest.js | 2 + awx/ui_next/src/util/useRequest.test.jsx | 31 +++++++ 6 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx create mode 100644 awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.test.jsx diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.jsx index bdcd89398a..550c5caf6c 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.jsx @@ -13,6 +13,8 @@ import CredentialPluginPrompt from './CredentialPluginPrompt'; jest.mock('../../../../../../api/models/Credentials'); jest.mock('../../../../../../api/models/CredentialTypes'); +CredentialsAPI.test.mockResolvedValue({}); + CredentialsAPI.read.mockResolvedValue({ data: { count: 3, @@ -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#credential-plugin-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/MetadataStep.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx index 69dd105a5c..86e8f148c6 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx @@ -13,6 +13,7 @@ import FormField from '../../../../../../components/FormField'; import { FormFullWidthLayout } from '../../../../../../components/FormLayout'; import useRequest from '../../../../../../util/useRequest'; import { required } from '../../../../../../util/validators'; +import { CredentialPluginTestAlert } from '..'; const QuestionCircleIcon = styled(PFQuestionCircleIcon)` margin-left: 10px; @@ -27,6 +28,21 @@ function MetadataStep({ i18n }) { const [selectedCredential] = useField('credential'); const [inputValues] = useField('inputs'); + const { + result: testPluginSuccess, + error: testPluginError, + request: testPluginMetadata, + } = useRequest( + useCallback( + async (credential, metadata) => + CredentialsAPI.test(credential.id, { + metadata, + }), + [] + ), + null + ); + const { result: fields, error, @@ -65,10 +81,6 @@ function MetadataStep({ i18n }) { fetchMetadataOptions(); }, [fetchMetadataOptions]); - const testMetadata = () => { - // https://github.com/ansible/awx/issues/7126 - }; - if (isLoading) { return ; } @@ -143,13 +155,21 @@ function MetadataStep({ i18n }) { position="right" > testMetadata()} + onClick={() => + testPluginMetadata(selectedCredential.value, inputValues.value) + } > {i18n._(t`Test`)} + ); } diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx new file mode 100644 index 0000000000..866ca1dae3 --- /dev/null +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx @@ -0,0 +1,82 @@ +import React, { useEffect, useState } from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { + Alert, + AlertActionCloseButton, + AlertGroup, +} from '@patternfly/react-core'; + +function CredentialPluginTestAlert({ + i18n, + credentialName, + successResponse, + errorResponse, +}) { + const [testMessage, setTestMessage] = useState(''); + const [testVariant, setTestVariant] = useState(false); + useEffect(() => { + if (errorResponse) { + if (errorResponse?.response?.data?.inputs) { + if (errorResponse.response.data.inputs.startsWith('HTTP')) { + const [ + errorCode, + errorStr, + ] = errorResponse.response.data.inputs.split('\n'); + try { + const errorJSON = JSON.parse(errorStr); + setTestMessage( + `${errorCode}${ + errorJSON?.errors[0] ? `: ${errorJSON.errors[0]}` : '' + }` + ); + } catch { + setTestMessage(errorResponse.response.data.inputs); + } + } else { + setTestMessage(errorResponse.response.data.inputs); + } + } else { + setTestMessage( + i18n._( + t`Something went wrong with the request to test this credential and metadata.` + ) + ); + } + setTestVariant('danger'); + } else if (successResponse) { + setTestMessage(i18n._(t`Test passed`)); + setTestVariant('success'); + } + }, [i18n, successResponse, errorResponse]); + + return ( + + {testMessage && testVariant && ( + + {credentialName} +

{testMessage}

+ + } + variant={testVariant} + action={ + { + setTestMessage(null); + setTestVariant(null); + }} + /> + } + /> + )} +
+ ); +} + +CredentialPluginTestAlert.propTypes = {}; + +CredentialPluginTestAlert.defaultProps = {}; + +export default withI18n()(CredentialPluginTestAlert); 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/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; From aed96de1954b11675be9513f02e68efa75d23e31 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 18 Jun 2020 14:36:59 -0400 Subject: [PATCH 2/8] Fix merge conflict --- .../CredentialPluginPrompt/MetadataStep.jsx | 2 +- .../CredentialPluginTestAlert.jsx | 29 +++---- .../CredentialPluginTestAlert.test.jsx | 0 .../CredentialPluginTestAlert.jsx | 82 ------------------- 4 files changed, 11 insertions(+), 102 deletions(-) rename awx/ui_next/src/screens/Credential/shared/{ => CredentialFormFields}/CredentialPlugins/CredentialPluginTestAlert.test.jsx (100%) delete mode 100644 awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx index 86e8f148c6..b9537fe456 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx @@ -5,7 +5,7 @@ import { useField, useFormikContext } from 'formik'; import styled from 'styled-components'; import { Button, Form, FormGroup, Tooltip } from '@patternfly/react-core'; import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons'; -import { CredentialTypesAPI } from '../../../../../../api'; +import { CredentialsAPI, CredentialTypesAPI } from '../../../../../../api'; import AnsibleSelect from '../../../../../../components/AnsibleSelect'; import ContentError from '../../../../../../components/ContentError'; import ContentLoading from '../../../../../../components/ContentLoading'; diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginTestAlert.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginTestAlert.jsx index f1c4a4ae97..866ca1dae3 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginTestAlert.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginTestAlert.jsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { string, shape } from 'prop-types'; import { Alert, AlertActionCloseButton, @@ -16,7 +15,6 @@ function CredentialPluginTestAlert({ }) { const [testMessage, setTestMessage] = useState(''); const [testVariant, setTestVariant] = useState(false); - useEffect(() => { if (errorResponse) { if (errorResponse?.response?.data?.inputs) { @@ -56,14 +54,6 @@ function CredentialPluginTestAlert({ {testMessage && testVariant && ( { - setTestMessage(null); - setTestVariant(null); - }} - /> - } title={ <> {credentialName} @@ -71,21 +61,22 @@ function CredentialPluginTestAlert({ } variant={testVariant} + action={ + { + setTestMessage(null); + setTestVariant(null); + }} + /> + } /> )} ); } -CredentialPluginTestAlert.propTypes = { - credentialName: string.isRequired, - successResponse: shape({}), - errorResponse: shape({}), -}; +CredentialPluginTestAlert.propTypes = {}; -CredentialPluginTestAlert.defaultProps = { - successResponse: null, - errorResponse: null, -}; +CredentialPluginTestAlert.defaultProps = {}; export default withI18n()(CredentialPluginTestAlert); diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.test.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginTestAlert.test.jsx similarity index 100% rename from awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.test.jsx rename to awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginTestAlert.test.jsx diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx deleted file mode 100644 index 866ca1dae3..0000000000 --- a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { withI18n } from '@lingui/react'; -import { t } from '@lingui/macro'; -import { - Alert, - AlertActionCloseButton, - AlertGroup, -} from '@patternfly/react-core'; - -function CredentialPluginTestAlert({ - i18n, - credentialName, - successResponse, - errorResponse, -}) { - const [testMessage, setTestMessage] = useState(''); - const [testVariant, setTestVariant] = useState(false); - useEffect(() => { - if (errorResponse) { - if (errorResponse?.response?.data?.inputs) { - if (errorResponse.response.data.inputs.startsWith('HTTP')) { - const [ - errorCode, - errorStr, - ] = errorResponse.response.data.inputs.split('\n'); - try { - const errorJSON = JSON.parse(errorStr); - setTestMessage( - `${errorCode}${ - errorJSON?.errors[0] ? `: ${errorJSON.errors[0]}` : '' - }` - ); - } catch { - setTestMessage(errorResponse.response.data.inputs); - } - } else { - setTestMessage(errorResponse.response.data.inputs); - } - } else { - setTestMessage( - i18n._( - t`Something went wrong with the request to test this credential and metadata.` - ) - ); - } - setTestVariant('danger'); - } else if (successResponse) { - setTestMessage(i18n._(t`Test passed`)); - setTestVariant('success'); - } - }, [i18n, successResponse, errorResponse]); - - return ( - - {testMessage && testVariant && ( - - {credentialName} -

{testMessage}

- - } - variant={testVariant} - action={ - { - setTestMessage(null); - setTestVariant(null); - }} - /> - } - /> - )} -
- ); -} - -CredentialPluginTestAlert.propTypes = {}; - -CredentialPluginTestAlert.defaultProps = {}; - -export default withI18n()(CredentialPluginTestAlert); From 62054bbfc8fef0323659bf7f3ad60448eb710b77 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 18 Jun 2020 14:43:07 -0400 Subject: [PATCH 3/8] Pulls CredentialPlugins out of CredentialFormFields and into the root of the shared dir --- .../CredentialFormFields/CredentialField.jsx | 2 +- .../CredentialPlugins/CredentialPluginField.jsx | 4 ++-- .../CredentialPluginField.test.jsx | 2 +- .../CredentialPluginPrompt.jsx | 0 .../CredentialPluginPrompt.test.jsx | 14 +++++++------- .../CredentialPluginPrompt/CredentialsStep.jsx | 14 +++++++------- .../CredentialPluginPrompt/MetadataStep.jsx | 16 ++++++++-------- .../CredentialPluginPrompt/index.js | 0 .../CredentialPluginSelected.jsx | 4 ++-- .../CredentialPluginSelected.test.jsx | 4 ++-- .../CredentialPluginTestAlert.jsx | 0 .../CredentialPluginTestAlert.test.jsx | 0 .../CredentialPlugins/index.js | 0 13 files changed, 30 insertions(+), 30 deletions(-) rename awx/ui_next/src/screens/Credential/shared/{CredentialFormFields => }/CredentialPlugins/CredentialPluginField.jsx (96%) rename awx/ui_next/src/screens/Credential/shared/{CredentialFormFields => }/CredentialPlugins/CredentialPluginField.test.jsx (97%) rename awx/ui_next/src/screens/Credential/shared/{CredentialFormFields => }/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.jsx (100%) rename awx/ui_next/src/screens/Credential/shared/{CredentialFormFields => }/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.jsx (94%) rename awx/ui_next/src/screens/Credential/shared/{CredentialFormFields => }/CredentialPlugins/CredentialPluginPrompt/CredentialsStep.jsx (86%) rename awx/ui_next/src/screens/Credential/shared/{CredentialFormFields => }/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx (91%) rename awx/ui_next/src/screens/Credential/shared/{CredentialFormFields => }/CredentialPlugins/CredentialPluginPrompt/index.js (100%) rename awx/ui_next/src/screens/Credential/shared/{CredentialFormFields => }/CredentialPlugins/CredentialPluginSelected.jsx (93%) rename awx/ui_next/src/screens/Credential/shared/{CredentialFormFields => }/CredentialPlugins/CredentialPluginSelected.test.jsx (87%) rename awx/ui_next/src/screens/Credential/shared/{CredentialFormFields => }/CredentialPlugins/CredentialPluginTestAlert.jsx (100%) rename awx/ui_next/src/screens/Credential/shared/{CredentialFormFields => }/CredentialPlugins/CredentialPluginTestAlert.test.jsx (100%) rename awx/ui_next/src/screens/Credential/shared/{CredentialFormFields => }/CredentialPlugins/index.js (100%) 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/CredentialPluginField.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.jsx similarity index 96% 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..b3c541ed7f 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'; diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginField.test.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.test.jsx similarity index 97% rename from awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginField.test.jsx rename to awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.test.jsx index a4bbc4fb74..88e2bf52d2 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginField.test.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.test.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { Formik } from 'formik'; import { TextInput } from '@patternfly/react-core'; -import { mountWithContexts } from '../../../../../../testUtils/enzymeHelpers'; +import { mountWithContexts } from '../../../../../testUtils/enzymeHelpers'; import CredentialPluginField from './CredentialPluginField'; const fieldOptions = { diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.jsx similarity index 100% rename from awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.jsx rename to awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.jsx 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 94% 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 550c5caf6c..df33091452 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,15 @@ 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({}); 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 91% 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 b9537fe456..29dac08080 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 @@ -5,14 +5,14 @@ import { useField, useFormikContext } from 'formik'; import styled from 'styled-components'; import { Button, Form, FormGroup, Tooltip } from '@patternfly/react-core'; import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons'; -import { CredentialsAPI, 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 { CredentialsAPI, 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 { CredentialPluginTestAlert } from '..'; const QuestionCircleIcon = styled(PFQuestionCircleIcon)` 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 100% 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 diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginTestAlert.test.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.test.jsx similarity index 100% rename from awx/ui_next/src/screens/Credential/shared/CredentialFormFields/CredentialPlugins/CredentialPluginTestAlert.test.jsx rename to awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.test.jsx 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 From 9f8691dbeac4967053501432e0230aa352589335 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 18 Jun 2020 16:16:35 -0400 Subject: [PATCH 4/8] Add the credential id and metadata from the form to the dependency array of useCallback when testing cred plugin configuration. This allows us to remove the variables passed in to the request. --- .../CredentialPluginPrompt/MetadataStep.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx index 29dac08080..acbabd551b 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx @@ -34,11 +34,11 @@ function MetadataStep({ i18n }) { request: testPluginMetadata, } = useRequest( useCallback( - async (credential, metadata) => - CredentialsAPI.test(credential.id, { - metadata, + async () => + CredentialsAPI.test(selectedCredential.value.id, { + metadata: inputValues.value, }), - [] + [selectedCredential.value.id, inputValues.value] ), null ); @@ -159,7 +159,7 @@ function MetadataStep({ i18n }) { variant="primary" type="submit" onClick={() => - testPluginMetadata(selectedCredential.value, inputValues.value) + testPluginMetadata() } > {i18n._(t`Test`)} From 5cfca7896fcbb802b700fb669be85979a15ded37 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 18 Jun 2020 16:22:53 -0400 Subject: [PATCH 5/8] Adds proptypes to CredentialPluginTestAlert component --- .../CredentialPluginPrompt/MetadataStep.jsx | 4 +--- .../CredentialPlugins/CredentialPluginTestAlert.jsx | 12 ++++++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx index acbabd551b..0597bfb253 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx @@ -158,9 +158,7 @@ function MetadataStep({ i18n }) { id="credential-plugin-test" variant="primary" type="submit" - onClick={() => - testPluginMetadata() - } + onClick={() => testPluginMetadata()} > {i18n._(t`Test`)} diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx index 866ca1dae3..741893eb16 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; +import { string, shape } from 'prop-types'; import { Alert, AlertActionCloseButton, @@ -75,8 +76,15 @@ function CredentialPluginTestAlert({ ); } -CredentialPluginTestAlert.propTypes = {}; +CredentialPluginTestAlert.propTypes = { + credentialName: string.isRequired, + successResponse: shape({}), + errorResponse: shape({}), +}; -CredentialPluginTestAlert.defaultProps = {}; +CredentialPluginTestAlert.defaultProps = { + successResponse: null, + errorResponse: null, +}; export default withI18n()(CredentialPluginTestAlert); From d654af77cfcaf6b429634b80815cd9fdc04b75fe Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 22 Jul 2020 12:08:25 -0400 Subject: [PATCH 6/8] Moves cred plugin test button down into wizard footer --- .../CredentialPluginPrompt.jsx | 109 ++++++++++++++++-- .../CredentialPluginPrompt.test.jsx | 2 +- .../CredentialPluginPrompt/MetadataStep.jsx | 45 +------- .../CredentialPluginTestAlert.jsx | 16 +-- 4 files changed, 110 insertions(+), 62 deletions(-) diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.jsx index 831291871e..51d832c8c4 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.jsx @@ -1,38 +1,127 @@ -import React from 'react'; +import React, { useCallback } 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 { + Button, + Tooltip, + Wizard, + WizardContextConsumer, + WizardFooter, +} from '@patternfly/react-core'; import CredentialsStep from './CredentialsStep'; import MetadataStep from './MetadataStep'; +import { CredentialsAPI } from '../../../../../api'; +import useRequest from '../../../../../util/useRequest'; +import { CredentialPluginTestAlert } from '..'; function CredentialPluginWizard({ i18n, handleSubmit, onClose }) { const [selectedCredential] = useField('credential'); + const [inputValues] = useField('inputs'); + + const { + result: testPluginSuccess, + error: testPluginError, + request: testPluginMetadata, + } = useRequest( + useCallback( + async () => + CredentialsAPI.test(selectedCredential.value.id, { + metadata: inputValues.value, + }), + [selectedCredential, inputValues] + ), + null + ); + const steps = [ { id: 1, name: i18n._(t`Credential`), + key: 'credential', component: , enableNext: !!selectedCredential.value, }, { id: 2, name: i18n._(t`Metadata`), + key: 'metadata', component: , canJumpTo: !!selectedCredential.value, - nextButtonText: i18n._(t`OK`), }, ]; + const CustomFooter = ( + + + {({ activeStep, onNext, onBack }) => ( + <> + + {activeStep && activeStep.key === 'metadata' && ( + <> + + + + + + + )} + + + )} + + + ); + return ( - + <> + + {selectedCredential.value && ( + + )} + ); } diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.jsx index df33091452..bff46c7cd6 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/CredentialPluginPrompt.test.jsx @@ -238,7 +238,7 @@ describe('', () => { }); test('clicking Test button makes correct call', async () => { await act(async () => { - wrapper.find('Button#credential-plugin-test').simulate('click'); + 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/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx index 0597bfb253..8f4c0b2e5c 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx @@ -1,11 +1,10 @@ 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 { CredentialsAPI, CredentialTypesAPI } from '../../../../../api'; +import { CredentialTypesAPI } from '../../../../../api'; import AnsibleSelect from '../../../../../components/AnsibleSelect'; import ContentError from '../../../../../components/ContentError'; import ContentLoading from '../../../../../components/ContentLoading'; @@ -13,36 +12,16 @@ import FormField from '../../../../../components/FormField'; import { FormFullWidthLayout } from '../../../../../components/FormLayout'; import useRequest from '../../../../../util/useRequest'; import { required } from '../../../../../util/validators'; -import { CredentialPluginTestAlert } from '..'; const QuestionCircleIcon = styled(PFQuestionCircleIcon)` margin-left: 10px; `; -const TestButton = styled(Button)` - margin-top: 20px; -`; - function MetadataStep({ i18n }) { const form = useFormikContext(); const [selectedCredential] = useField('credential'); const [inputValues] = useField('inputs'); - const { - result: testPluginSuccess, - error: testPluginError, - request: testPluginMetadata, - } = useRequest( - useCallback( - async () => - CredentialsAPI.test(selectedCredential.value.id, { - metadata: inputValues.value, - }), - [selectedCredential.value.id, inputValues.value] - ), - null - ); - const { result: fields, error, @@ -148,26 +127,6 @@ function MetadataStep({ i18n }) { )} - - testPluginMetadata()} - > - {i18n._(t`Test`)} - - - ); } diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx index 741893eb16..01b2c56598 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginTestAlert.jsx @@ -55,14 +55,7 @@ function CredentialPluginTestAlert({ {testMessage && testVariant && ( - {credentialName} -

{testMessage}

- - } - variant={testVariant} - action={ + actionClose={ { setTestMessage(null); @@ -70,6 +63,13 @@ function CredentialPluginTestAlert({ }} /> } + title={ + <> + {credentialName} +

{testMessage}

+ + } + variant={testVariant} /> )}
From 68426daaff35e491fdea31c518e1a87320a7a15f Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 31 Aug 2020 15:53:21 -0400 Subject: [PATCH 7/8] Fix residual error from merge conflict --- awx/ui_next/src/screens/Credential/shared/ExternalTestModal.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From ad1c4b1586d41750814ab39053a3a89c99be551d Mon Sep 17 00:00:00 2001 From: mabashian Date: Fri, 11 Sep 2020 14:17:35 -0400 Subject: [PATCH 8/8] Add unique ID to cred field external plugin button(s) --- .../shared/CredentialPlugins/CredentialPluginField.jsx | 1 + 1 file changed, 1 insertion(+) 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 b3c541ed7f..b37040fc31 100644 --- a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.jsx +++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginField.jsx @@ -55,6 +55,7 @@ function CredentialPluginInput(props) { )} >