mirror of
https://github.com/ansible/awx.git
synced 2026-01-23 07:28:02 -03:30
Merge pull request #7730 from mabashian/7339-test-button
Adds support for a Test button on the credential form when the credential type is 'external' Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
commit
6c9e417eb9
@ -27,6 +27,10 @@ class CredentialTypes extends Base {
|
||||
.concat(nextResults)
|
||||
.filter(type => acceptableKinds.includes(type.kind));
|
||||
}
|
||||
|
||||
test(id, data) {
|
||||
return this.http.post(`${this.baseUrl}${id}/test/`, data);
|
||||
}
|
||||
}
|
||||
|
||||
export default CredentialTypes;
|
||||
|
||||
@ -25,6 +25,10 @@ class Credentials extends Base {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
test(id, data) {
|
||||
return this.http.post(`${this.baseUrl}${id}/test/`, data);
|
||||
}
|
||||
}
|
||||
|
||||
export default Credentials;
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Formik, useField } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { arrayOf, func, object, shape } from 'prop-types';
|
||||
import { Form, FormGroup } from '@patternfly/react-core';
|
||||
import { ActionGroup, Button, Form, FormGroup } from '@patternfly/react-core';
|
||||
import FormField, { FormSubmitError } from '../../../components/FormField';
|
||||
import FormActionGroup from '../../../components/FormActionGroup/FormActionGroup';
|
||||
import {
|
||||
FormColumnLayout,
|
||||
FormFullWidthLayout,
|
||||
} from '../../../components/FormLayout';
|
||||
import AnsibleSelect from '../../../components/AnsibleSelect';
|
||||
import { required } from '../../../util/validators';
|
||||
import OrganizationLookup from '../../../components/Lookup/OrganizationLookup';
|
||||
import { FormColumnLayout } from '../../../components/FormLayout';
|
||||
import TypeInputsSubForm from './TypeInputsSubForm';
|
||||
import ExternalTestModal from './ExternalTestModal';
|
||||
|
||||
function CredentialFormFields({
|
||||
i18n,
|
||||
@ -139,6 +142,7 @@ function CredentialFormFields({
|
||||
}
|
||||
|
||||
function CredentialForm({
|
||||
i18n,
|
||||
credential = {},
|
||||
credentialTypes,
|
||||
inputSources,
|
||||
@ -147,6 +151,7 @@ function CredentialForm({
|
||||
submitError,
|
||||
...rest
|
||||
}) {
|
||||
const [showExternalTestModal, setShowExternalTestModal] = useState(false);
|
||||
const initialValues = {
|
||||
name: credential.name || '',
|
||||
description: credential.description || '',
|
||||
@ -205,21 +210,61 @@ function CredentialForm({
|
||||
}}
|
||||
>
|
||||
{formik => (
|
||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||
<FormColumnLayout>
|
||||
<CredentialFormFields
|
||||
formik={formik}
|
||||
initialValues={initialValues}
|
||||
credentialTypes={credentialTypes}
|
||||
{...rest}
|
||||
<>
|
||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||
<FormColumnLayout>
|
||||
<CredentialFormFields
|
||||
formik={formik}
|
||||
initialValues={initialValues}
|
||||
credentialTypes={credentialTypes}
|
||||
i18n={i18n}
|
||||
{...rest}
|
||||
/>
|
||||
<FormSubmitError error={submitError} />
|
||||
<FormFullWidthLayout>
|
||||
<ActionGroup>
|
||||
<Button
|
||||
aria-label={i18n._(t`Save`)}
|
||||
variant="primary"
|
||||
type="button"
|
||||
onClick={formik.handleSubmit}
|
||||
>
|
||||
{i18n._(t`Save`)}
|
||||
</Button>
|
||||
{formik?.values?.credential_type &&
|
||||
credentialTypes[formik.values.credential_type]?.kind ===
|
||||
'external' && (
|
||||
<Button
|
||||
aria-label={i18n._(t`Test`)}
|
||||
variant="secondary"
|
||||
type="button"
|
||||
onClick={() => setShowExternalTestModal(true)}
|
||||
isDisabled={!formik.isValid}
|
||||
>
|
||||
{i18n._(t`Test`)}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
aria-label={i18n._(t`Cancel`)}
|
||||
variant="secondary"
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
>
|
||||
{i18n._(t`Cancel`)}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</FormFullWidthLayout>
|
||||
</FormColumnLayout>
|
||||
</Form>
|
||||
{showExternalTestModal && (
|
||||
<ExternalTestModal
|
||||
credential={credential}
|
||||
credentialType={credentialTypes[formik.values.credential_type]}
|
||||
credentialFormValues={formik.values}
|
||||
onClose={() => setShowExternalTestModal(false)}
|
||||
/>
|
||||
<FormSubmitError error={submitError} />
|
||||
<FormActionGroup
|
||||
onCancel={onCancel}
|
||||
onSubmit={formik.handleSubmit}
|
||||
/>
|
||||
</FormColumnLayout>
|
||||
</Form>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
|
||||
@ -99,6 +99,9 @@ describe('<CredentialForm />', () => {
|
||||
test('should display form fields on add properly', async () => {
|
||||
addFieldExpects();
|
||||
});
|
||||
test('should hide Test button initially', () => {
|
||||
expect(wrapper.find('Button[children="Test"]').length).toBe(0);
|
||||
});
|
||||
test('should update form values', async () => {
|
||||
// name and description change
|
||||
await act(async () => {
|
||||
@ -221,6 +224,18 @@ describe('<CredentialForm />', () => {
|
||||
'There was an error parsing the file. Please check the file formatting and try again.'
|
||||
);
|
||||
});
|
||||
test('should show Test button when external credential type is selected', async () => {
|
||||
await act(async () => {
|
||||
await wrapper
|
||||
.find('AnsibleSelect[id="credential_type"]')
|
||||
.invoke('onChange')(null, 21);
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('Button[children="Test"]').length).toBe(1);
|
||||
expect(wrapper.find('Button[children="Test"]').props().isDisabled).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
test('should call handleCancel when Cancel button is clicked', async () => {
|
||||
expect(onCancel).not.toHaveBeenCalled();
|
||||
wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
|
||||
|
||||
@ -110,7 +110,7 @@ function CredentialField({ credentialType, fieldOptions, i18n }) {
|
||||
>
|
||||
<AnsibleSelect
|
||||
{...subFormField}
|
||||
id="credential_type"
|
||||
id={`credential-${fieldOptions.id}`}
|
||||
data={selectOptions}
|
||||
onChange={(event, value) => {
|
||||
helpers.setValue(value);
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
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,
|
||||
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 (
|
||||
<AlertGroup isToast>
|
||||
{testMessage && testVariant && (
|
||||
<Alert
|
||||
actionClose={
|
||||
<AlertActionCloseButton
|
||||
onClose={() => {
|
||||
setTestMessage(null);
|
||||
setTestVariant(null);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<>
|
||||
<b id="credential-plugin-test-name">{credentialName}</b>
|
||||
<p id="credential-plugin-test-message">{testMessage}</p>
|
||||
</>
|
||||
}
|
||||
variant={testVariant}
|
||||
/>
|
||||
)}
|
||||
</AlertGroup>
|
||||
);
|
||||
}
|
||||
|
||||
CredentialPluginTestAlert.propTypes = {
|
||||
credentialName: string.isRequired,
|
||||
successResponse: shape({}),
|
||||
errorResponse: shape({}),
|
||||
};
|
||||
|
||||
CredentialPluginTestAlert.defaultProps = {
|
||||
successResponse: null,
|
||||
errorResponse: null,
|
||||
};
|
||||
|
||||
export default withI18n()(CredentialPluginTestAlert);
|
||||
@ -1,2 +1,3 @@
|
||||
export { default as CredentialPluginSelected } from './CredentialPluginSelected';
|
||||
export { default as CredentialPluginField } from './CredentialPluginField';
|
||||
export { default as CredentialPluginTestAlert } from './CredentialPluginTestAlert';
|
||||
|
||||
198
awx/ui_next/src/screens/Credential/shared/ExternalTestModal.jsx
Normal file
198
awx/ui_next/src/screens/Credential/shared/ExternalTestModal.jsx
Normal file
@ -0,0 +1,198 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import styled from 'styled-components';
|
||||
import { func, shape } from 'prop-types';
|
||||
import { Formik } from 'formik';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormGroup,
|
||||
Modal,
|
||||
Tooltip,
|
||||
} from '@patternfly/react-core';
|
||||
import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
|
||||
import { CredentialsAPI, CredentialTypesAPI } from '../../../api';
|
||||
import AnsibleSelect from '../../../components/AnsibleSelect';
|
||||
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';
|
||||
|
||||
const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
|
||||
margin-left: 10px;
|
||||
`;
|
||||
|
||||
function ExternalTestModal({
|
||||
i18n,
|
||||
credential,
|
||||
credentialType,
|
||||
credentialFormValues,
|
||||
onClose,
|
||||
}) {
|
||||
const {
|
||||
result: testPluginSuccess,
|
||||
error: testPluginError,
|
||||
request: testPluginMetadata,
|
||||
} = useRequest(
|
||||
useCallback(
|
||||
async values => {
|
||||
const payload = {
|
||||
inputs: credentialType.inputs.fields.reduce(
|
||||
(filteredInputs, field) => {
|
||||
filteredInputs[field.id] = credentialFormValues.inputs[field.id];
|
||||
return filteredInputs;
|
||||
},
|
||||
{}
|
||||
),
|
||||
metadata: values,
|
||||
};
|
||||
|
||||
if (credential && credential.credential_type === credentialType.id) {
|
||||
return CredentialsAPI.test(credential.id, payload);
|
||||
}
|
||||
return CredentialTypesAPI.test(credentialType.id, payload);
|
||||
},
|
||||
[
|
||||
credential,
|
||||
credentialType.id,
|
||||
credentialType.inputs.fields,
|
||||
credentialFormValues.inputs,
|
||||
]
|
||||
),
|
||||
null
|
||||
);
|
||||
|
||||
const handleTest = async values => {
|
||||
await testPluginMetadata(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Formik
|
||||
initialValues={credentialType.inputs.metadata.reduce(
|
||||
(initialValues, field) => {
|
||||
if (field.type === 'string' && field.choices) {
|
||||
initialValues[field.id] = field.default || field.choices[0];
|
||||
} else {
|
||||
initialValues[field.id] = '';
|
||||
}
|
||||
return initialValues;
|
||||
},
|
||||
{}
|
||||
)}
|
||||
onSubmit={values => handleTest(values)}
|
||||
>
|
||||
{({ handleSubmit, setFieldValue }) => (
|
||||
<Modal
|
||||
title={i18n._(t`Test External Credential`)}
|
||||
isOpen
|
||||
onClose={() => onClose()}
|
||||
variant="small"
|
||||
actions={[
|
||||
<Button
|
||||
id="run-external-credential-test"
|
||||
key="confirm"
|
||||
variant="primary"
|
||||
onClick={() => handleSubmit()}
|
||||
>
|
||||
{i18n._(t`Run`)}
|
||||
</Button>,
|
||||
<Button
|
||||
id="cancel-external-credential-test"
|
||||
key="cancel"
|
||||
variant="link"
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
{i18n._(t`Cancel`)}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Form>
|
||||
<FormFullWidthLayout>
|
||||
{credentialType.inputs.metadata.map(field => {
|
||||
const isRequired = credentialType.inputs?.required.includes(
|
||||
field.id
|
||||
);
|
||||
if (field.type === 'string') {
|
||||
if (field.choices) {
|
||||
return (
|
||||
<FormGroup
|
||||
key={field.id}
|
||||
fieldId={`credential-${field.id}`}
|
||||
label={field.label}
|
||||
labelIcon={
|
||||
field.help_text && (
|
||||
<Tooltip
|
||||
content={field.help_text}
|
||||
position="right"
|
||||
>
|
||||
<QuestionCircleIcon />
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
isRequired={isRequired}
|
||||
>
|
||||
<AnsibleSelect
|
||||
name={field.id}
|
||||
value={field.default}
|
||||
id={`credential-${field.id}`}
|
||||
data={field.choices.map(choice => {
|
||||
return {
|
||||
value: choice,
|
||||
key: choice,
|
||||
label: choice,
|
||||
};
|
||||
})}
|
||||
onChange={(event, value) => {
|
||||
setFieldValue(field.id, value);
|
||||
}}
|
||||
validate={isRequired ? required(null, i18n) : null}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormField
|
||||
key={field.id}
|
||||
id={`credential-${field.id}`}
|
||||
label={field.label}
|
||||
tooltip={field.help_text}
|
||||
name={field.id}
|
||||
type={field.multiline ? 'textarea' : 'text'}
|
||||
isRequired={isRequired}
|
||||
validate={isRequired ? required(null, i18n) : null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})}
|
||||
</FormFullWidthLayout>
|
||||
</Form>
|
||||
</Modal>
|
||||
)}
|
||||
</Formik>
|
||||
<CredentialPluginTestAlert
|
||||
credentialName={credentialFormValues.name}
|
||||
successResponse={testPluginSuccess}
|
||||
errorResponse={testPluginError}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
ExternalTestModal.proptype = {
|
||||
credential: shape({}),
|
||||
credentialType: shape({}).isRequired,
|
||||
credentialFormValues: shape({}).isRequired,
|
||||
onClose: func.isRequired,
|
||||
};
|
||||
|
||||
ExternalTestModal.defaultProps = {
|
||||
credential: null,
|
||||
};
|
||||
|
||||
export default withI18n()(ExternalTestModal);
|
||||
@ -0,0 +1,180 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||
import { CredentialsAPI, CredentialTypesAPI } from '../../../api';
|
||||
import ExternalTestModal from './ExternalTestModal';
|
||||
import credentialTypesArr from './data.credentialTypes.json';
|
||||
|
||||
jest.mock('../../../api/models/Credentials');
|
||||
jest.mock('../../../api/models/CredentialTypes');
|
||||
|
||||
const credentialType = credentialTypesArr.find(
|
||||
credType => credType.namespace === 'hashivault_kv'
|
||||
);
|
||||
|
||||
const credentialFormValues = {
|
||||
name: 'Foobar',
|
||||
credential_type: credentialType.id,
|
||||
inputs: {
|
||||
api_version: 'v2',
|
||||
token: '$encrypted$',
|
||||
url: 'http://hashivault:8200',
|
||||
},
|
||||
};
|
||||
|
||||
const credential = {
|
||||
id: 1,
|
||||
name: 'A credential',
|
||||
credential_type: credentialType.id,
|
||||
};
|
||||
|
||||
describe('<ExternalTestModal />', () => {
|
||||
let wrapper;
|
||||
afterEach(() => wrapper.unmount());
|
||||
test('should display metadata fields correctly', async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<ExternalTestModal
|
||||
credentialType={credentialType}
|
||||
credentialFormValues={credentialFormValues}
|
||||
onClose={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('FormField').length).toBe(5);
|
||||
expect(wrapper.find('input#credential-secret_backend').length).toBe(1);
|
||||
expect(wrapper.find('input#credential-secret_path').length).toBe(1);
|
||||
expect(wrapper.find('input#credential-auth_path').length).toBe(1);
|
||||
expect(wrapper.find('input#credential-secret_key').length).toBe(1);
|
||||
expect(wrapper.find('input#credential-secret_version').length).toBe(1);
|
||||
});
|
||||
test('should make the test request correctly when testing an existing credential', async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<ExternalTestModal
|
||||
credential={credential}
|
||||
credentialType={credentialType}
|
||||
credentialFormValues={credentialFormValues}
|
||||
onClose={jest.fn()}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
wrapper.find('input#credential-secret_path').simulate('change', {
|
||||
target: { value: '/secret/foo/bar/baz', name: 'secret_path' },
|
||||
});
|
||||
wrapper.find('input#credential-secret_key').simulate('change', {
|
||||
target: { value: 'password', name: 'secret_key' },
|
||||
});
|
||||
});
|
||||
wrapper.update();
|
||||
await act(async () => {
|
||||
wrapper.find('Button[children="Run"]').simulate('click');
|
||||
});
|
||||
expect(CredentialsAPI.test).toHaveBeenCalledWith(1, {
|
||||
inputs: {
|
||||
api_version: 'v2',
|
||||
cacert: undefined,
|
||||
role_id: undefined,
|
||||
secret_id: undefined,
|
||||
token: '$encrypted$',
|
||||
url: 'http://hashivault:8200',
|
||||
},
|
||||
metadata: {
|
||||
auth_path: '',
|
||||
secret_backend: '',
|
||||
secret_key: 'password',
|
||||
secret_path: '/secret/foo/bar/baz',
|
||||
secret_version: '',
|
||||
},
|
||||
});
|
||||
});
|
||||
test('should make the test request correctly when testing a new credential', async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<ExternalTestModal
|
||||
credentialType={credentialType}
|
||||
credentialFormValues={credentialFormValues}
|
||||
onClose={jest.fn()}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
wrapper.find('input#credential-secret_path').simulate('change', {
|
||||
target: { value: '/secret/foo/bar/baz', name: 'secret_path' },
|
||||
});
|
||||
wrapper.find('input#credential-secret_key').simulate('change', {
|
||||
target: { value: 'password', name: 'secret_key' },
|
||||
});
|
||||
});
|
||||
wrapper.update();
|
||||
await act(async () => {
|
||||
wrapper.find('Button[children="Run"]').simulate('click');
|
||||
});
|
||||
expect(CredentialTypesAPI.test).toHaveBeenCalledWith(21, {
|
||||
inputs: {
|
||||
api_version: 'v2',
|
||||
cacert: undefined,
|
||||
role_id: undefined,
|
||||
secret_id: undefined,
|
||||
token: '$encrypted$',
|
||||
url: 'http://hashivault:8200',
|
||||
},
|
||||
metadata: {
|
||||
auth_path: '',
|
||||
secret_backend: '',
|
||||
secret_key: 'password',
|
||||
secret_path: '/secret/foo/bar/baz',
|
||||
secret_version: '',
|
||||
},
|
||||
});
|
||||
});
|
||||
test('should display the alert after a successful test', async () => {
|
||||
CredentialTypesAPI.test.mockResolvedValue({});
|
||||
wrapper = mountWithContexts(
|
||||
<ExternalTestModal
|
||||
credentialType={credentialType}
|
||||
credentialFormValues={credentialFormValues}
|
||||
onClose={jest.fn()}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
wrapper.find('input#credential-secret_path').simulate('change', {
|
||||
target: { value: '/secret/foo/bar/baz', name: 'secret_path' },
|
||||
});
|
||||
wrapper.find('input#credential-secret_key').simulate('change', {
|
||||
target: { value: 'password', name: 'secret_key' },
|
||||
});
|
||||
});
|
||||
wrapper.update();
|
||||
await act(async () => {
|
||||
wrapper.find('Button[children="Run"]').simulate('click');
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('Alert').length).toBe(1);
|
||||
expect(wrapper.find('Alert').props().variant).toBe('success');
|
||||
});
|
||||
test('should display the alert after a failed test', async () => {
|
||||
CredentialTypesAPI.test.mockRejectedValue({
|
||||
inputs: `HTTP 404
|
||||
{"errors":["no handler for route '/secret/foo/bar/baz'"]}
|
||||
`,
|
||||
});
|
||||
wrapper = mountWithContexts(
|
||||
<ExternalTestModal
|
||||
credentialType={credentialType}
|
||||
credentialFormValues={credentialFormValues}
|
||||
onClose={jest.fn()}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
wrapper.find('input#credential-secret_path').simulate('change', {
|
||||
target: { value: '/secret/foo/bar/baz', name: 'secret_path' },
|
||||
});
|
||||
wrapper.find('input#credential-secret_key').simulate('change', {
|
||||
target: { value: 'password', name: 'secret_key' },
|
||||
});
|
||||
});
|
||||
wrapper.update();
|
||||
await act(async () => {
|
||||
wrapper.find('Button[children="Run"]').simulate('click');
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('Alert').length).toBe(1);
|
||||
expect(wrapper.find('Alert').props().variant).toBe('danger');
|
||||
});
|
||||
});
|
||||
@ -1,2 +1,3 @@
|
||||
export { default as mockCredentials } from './data.credentials.json';
|
||||
export { default as mockCredentialType } from './data.credential_type.json';
|
||||
export { default as ExternalTestModal } from './ExternalTestModal';
|
||||
|
||||
@ -38,6 +38,9 @@ export default function useRequest(makeRequest, initialValue) {
|
||||
request: useCallback(
|
||||
async (...args) => {
|
||||
setIsLoading(true);
|
||||
if (isMounted.current) {
|
||||
setError(null);
|
||||
}
|
||||
try {
|
||||
const response = await makeRequest(...args);
|
||||
if (isMounted.current) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user