updates tooltip component, fixes formik configuration on ad hoc qizard

This commit is contained in:
Alex Corey
2020-09-02 11:09:25 -04:00
parent 0e3fbb74d4
commit 678dcad437
9 changed files with 98 additions and 75 deletions

View File

@@ -65,7 +65,7 @@ function AdHocCommands({ children, apiModule, adHocItems, itemId, i18n }) {
useCallback( useCallback(
async values => { async values => {
const { data } = await apiModule.launchAdHocCommands(itemId, values); const { data } = await apiModule.launchAdHocCommands(itemId, values);
history.push(`/jobs/${data.module_name}/${data.id}/output`); history.push(`/jobs/command/${data.id}/output`);
}, },
[apiModule, itemId, history] [apiModule, itemId, history]

View File

@@ -157,12 +157,12 @@ describe('<AdHocCommands />', () => {
).toBe(true); ).toBe(true);
await act(async () => { await act(async () => {
wrapper.find('AnsibleSelect[name="module_args"]').prop('onChange')( wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')(
{}, {},
'command' 'command'
); );
wrapper.find('input#arguments').simulate('change', { wrapper.find('input#module_args').simulate('change', {
target: { value: 'foo', name: 'arguments' }, target: { value: 'foo', name: 'module_args' },
}); });
wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1); wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1);
}); });
@@ -194,14 +194,15 @@ describe('<AdHocCommands />', () => {
); );
expect(InventoriesAPI.launchAdHocCommands).toBeCalledWith(1, { expect(InventoriesAPI.launchAdHocCommands).toBeCalledWith(1, {
arguments: 'foo', module_args: 'foo',
changes: false, diff_mode: false,
credential: 4, credential: 4,
escalation: false, job_type: 'run',
become_enabled: '',
extra_vars: '---', extra_vars: '---',
forks: 0, forks: 0,
limit: 'Inventory 1 Org 0, Inventory 2 Org 0', limit: 'Inventory 1 Org 0, Inventory 2 Org 0',
module_args: 'command', module_name: 'command',
verbosity: 1, verbosity: 1,
}); });
@@ -271,12 +272,12 @@ describe('<AdHocCommands />', () => {
).toBe(true); ).toBe(true);
await act(async () => { await act(async () => {
wrapper.find('AnsibleSelect[name="module_args"]').prop('onChange')( wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')(
{}, {},
'command' 'command'
); );
wrapper.find('input#arguments').simulate('change', { wrapper.find('input#module_args').simulate('change', {
target: { value: 'foo', name: 'arguments' }, target: { value: 'foo', name: 'module_args' },
}); });
wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1); wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1);
}); });

View File

@@ -22,12 +22,12 @@ function AdHocCommandsWizard({
const { values } = useFormikContext(); const { values } = useFormikContext();
const enabledNextOnDetailsStep = () => { const enabledNextOnDetailsStep = () => {
if (!values.module_args) { if (!values.module_name) {
return false; return false;
} }
if (values.module_args === 'shell' || values.module_args === 'command') { if (values.module_name === 'shell' || values.module_name === 'command') {
if (values.arguments) { if (values.module_args) {
return true; return true;
// eslint-disable-next-line no-else-return // eslint-disable-next-line no-else-return
} else { } else {
@@ -90,15 +90,16 @@ const FormikApp = withFormik({
mapPropsToValues({ adHocItems, verbosityOptions }) { mapPropsToValues({ adHocItems, verbosityOptions }) {
const adHocItemStrings = adHocItems.map(item => item.name).join(', '); const adHocItemStrings = adHocItems.map(item => item.name).join(', ');
return { return {
limit: adHocItemStrings || [], limit: adHocItemStrings || 'all',
credential: [], credential: [],
module_args: '', module_args: '',
arguments: '',
verbosity: verbosityOptions[0].value, verbosity: verbosityOptions[0].value,
forks: 0, forks: 0,
changes: false, diff_mode: false,
escalation: false, become_enabled: '',
module_name: '',
extra_vars: '---', extra_vars: '---',
job_type: 'run',
}; };
}, },
})(AdHocCommandsWizard); })(AdHocCommandsWizard);

View File

@@ -73,12 +73,12 @@ describe('<AdHocCommandsWizard/>', () => {
await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0); await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0);
await act(async () => { await act(async () => {
wrapper.find('AnsibleSelect[name="module_args"]').prop('onChange')( wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')(
{}, {},
'command' 'command'
); );
wrapper.find('input#arguments').simulate('change', { wrapper.find('input#module_args').simulate('change', {
target: { value: 'foo', name: 'arguments' }, target: { value: 'foo', name: 'module_args' },
}); });
wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1); wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1);
}); });
@@ -105,12 +105,12 @@ describe('<AdHocCommandsWizard/>', () => {
await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0); await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0);
await act(async () => { await act(async () => {
wrapper.find('AnsibleSelect[name="module_args"]').prop('onChange')( wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')(
{}, {},
'command' 'command'
); );
wrapper.find('input#arguments').simulate('change', { wrapper.find('input#module_args').simulate('change', {
target: { value: 'foo', name: 'arguments' }, target: { value: 'foo', name: 'module_args' },
}); });
wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1); wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1);
}); });
@@ -165,12 +165,12 @@ describe('<AdHocCommandsWizard/>', () => {
await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0); await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0);
await act(async () => { await act(async () => {
wrapper.find('AnsibleSelect[name="module_args"]').prop('onChange')( wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')(
{}, {},
'command' 'command'
); );
wrapper.find('input#arguments').simulate('change', { wrapper.find('input#module_args').simulate('change', {
target: { value: 'foo', name: 'arguments' }, target: { value: 'foo', name: 'module_args' },
}); });
wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1); wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1);
}); });

View File

@@ -28,29 +28,36 @@ const TooltipWrapper = styled.div`
const brandName = BrandName; const brandName = BrandName;
function CredentialStep({ i18n, verbosityOptions, moduleOptions }) { function CredentialStep({ i18n, verbosityOptions, moduleOptions }) {
const [moduleField, moduleMeta, moduleHelpers] = useField({ const [module_nameField, module_nameMeta, module_nameHelpers] = useField({
name: 'module_name',
validate: required(null, i18n),
});
const [module_argsField] = useField({
name: 'module_args', name: 'module_args',
validate: required(null, i18n), validate: required(null, i18n),
}); });
const [variablesField] = useField('extra_vars'); const [variablesField] = useField('extra_vars');
const [changesField, , changesHelpers] = useField('changes'); const [diff_modeField, , diff_modeHelpers] = useField('diff_mode');
const [escalationField, , escalationHelpers] = useField('escalation'); const [become_enabledField, , become_enabledHelpers] = useField(
'become_enabled'
);
const [verbosityField, verbosityMeta, verbosityHelpers] = useField({ const [verbosityField, verbosityMeta, verbosityHelpers] = useField({
name: 'verbosity', name: 'verbosity',
validate: required(null, i18n), validate: required(null, i18n),
}); });
return ( return (
<Form> <Form>
<FormColumnLayout> <FormColumnLayout>
<FormFullWidthLayout> <FormFullWidthLayout>
<FormGroup <FormGroup
fieldId="module" fieldId="module_name"
label={i18n._(t`Module`)} label={i18n._(t`Module`)}
isRequired isRequired
helperTextInvalid={moduleMeta.error} helperTextInvalid={module_nameMeta.error}
validated={ validated={
!moduleMeta.touched || !moduleMeta.error ? 'default' : 'error' !module_nameMeta.touched || !module_nameMeta.error
? 'default'
: 'error'
} }
labelIcon={ labelIcon={
<FieldTooltip <FieldTooltip
@@ -61,26 +68,43 @@ function CredentialStep({ i18n, verbosityOptions, moduleOptions }) {
} }
> >
<AnsibleSelect <AnsibleSelect
{...moduleField} {...module_nameField}
isValid={!moduleMeta.touched || !moduleMeta.error} isValid={!module_nameMeta.touched || !module_nameMeta.error}
id="module" id="module_name"
data={moduleOptions || []} data={moduleOptions || []}
onChange={(event, value) => { onChange={(event, value) => {
moduleHelpers.setValue(value); module_nameHelpers.setValue(value);
}} }}
/> />
</FormGroup> </FormGroup>
<FormField <FormField
id="arguments" id="module_args"
name="arguments" name="module_args"
type="text" type="text"
label={i18n._(t`Arguments`)} label={i18n._(t`Arguments`)}
isRequired={ isRequired={
moduleField.value === 'command' || moduleField.value === 'shell' module_nameField.value === 'command' ||
module_nameField.value === 'shell'
}
tooltip={
module_nameField.value ? (
<>
{i18n._(
t`These arguments are used with the specified module. You can find information about the ${module_argsField.value} by clicking `
)}
<a
href={`https://docs.ansible.com/ansible/latest/modules/${module_argsField.value}_module.html`}
target="_blank"
rel="noopener noreferrer"
>
{' '}
{i18n._(t`here.`)}
</a>
</>
) : (
i18n._(t`These arguments are used with the specified module.`)
)
} }
tooltip={i18n._(
t`These arguments are used with the specified module.`
)}
/> />
<FormGroup <FormGroup
fieldId="verbosity" fieldId="verbosity"
@@ -106,7 +130,7 @@ function CredentialStep({ i18n, verbosityOptions, moduleOptions }) {
id="verbosity" id="verbosity"
data={verbosityOptions || []} data={verbosityOptions || []}
onChange={(event, value) => { onChange={(event, value) => {
verbosityHelpers.setValue(value); verbosityHelpers.setValue(parseInt(value, 10));
}} }}
/> />
</FormGroup> </FormGroup>
@@ -164,20 +188,17 @@ function CredentialStep({ i18n, verbosityOptions, moduleOptions }) {
> >
<Switch <Switch
css="display: inline-flex;" css="display: inline-flex;"
id="changes" id="diff_mode"
label={i18n._(t`On`)} label={i18n._(t`On`)}
labelOff={i18n._(t`Off`)} labelOff={i18n._(t`Off`)}
isChecked={changesField.value} isChecked={diff_modeField.value}
onChange={() => { onChange={() => {
changesHelpers.setValue(!changesField.value); diff_modeHelpers.setValue(!diff_modeField.value);
}} }}
aria-label={i18n._(t`toggle changes`)} aria-label={i18n._(t`toggle changes`)}
/> />
</FormGroup> </FormGroup>
<FormGroup <FormGroup name="become_enabled" fieldId="become_enabled">
name={i18n._(t`enable privilege escalation`)}
fieldId="escalation"
>
<FormCheckboxLayout> <FormCheckboxLayout>
<Checkbox <Checkbox
aria-label={i18n._(t`Enable privilege escalation`)} aria-label={i18n._(t`Enable privilege escalation`)}
@@ -202,10 +223,10 @@ function CredentialStep({ i18n, verbosityOptions, moduleOptions }) {
/> />
</span> </span>
} }
id="escalation" id="become_enabled"
isChecked={escalationField.value} isChecked={become_enabledField.value}
onChange={checked => { onChange={checked => {
escalationHelpers.setValue(checked); become_enabledHelpers.setValue(checked);
}} }}
/> />
</FormCheckboxLayout> </FormCheckboxLayout>

View File

@@ -29,7 +29,7 @@ const initialValues = {
extra_vars: '---', extra_vars: '---',
}; };
describe('<DetailsStep />', () => { describe('<AdHocDetailsStep />', () => {
let wrapper; let wrapper;
afterEach(() => { afterEach(() => {
@@ -64,14 +64,12 @@ describe('<DetailsStep />', () => {
); );
}); });
expect(wrapper.find('FormGroup[label="Module"]').length).toBe(1); expect(wrapper.find('FormGroup[label="Module"]').length).toBe(1);
expect(wrapper.find('FormField[name="arguments"]').length).toBe(1); expect(wrapper.find('FormField[label="Arguments"]').length).toBe(1);
expect(wrapper.find('FormGroup[label="Verbosity"]').length).toBe(1); expect(wrapper.find('FormGroup[label="Verbosity"]').length).toBe(1);
expect(wrapper.find('FormField[label="Limit"]').length).toBe(1); expect(wrapper.find('FormField[label="Limit"]').length).toBe(1);
expect(wrapper.find('FormField[name="forks"]').length).toBe(1); expect(wrapper.find('FormField[name="forks"]').length).toBe(1);
expect(wrapper.find('FormGroup[label="Show changes"]').length).toBe(1); expect(wrapper.find('FormGroup[label="Show changes"]').length).toBe(1);
expect( expect(wrapper.find('FormGroup[name="become_enabled"]').length).toBe(1);
wrapper.find('FormGroup[name="enable privilege escalation"]').length
).toBe(1);
expect(wrapper.find('VariablesField').length).toBe(1); expect(wrapper.find('VariablesField').length).toBe(1);
}); });
@@ -89,12 +87,12 @@ describe('<DetailsStep />', () => {
}); });
await act(async () => { await act(async () => {
wrapper.find('AnsibleSelect[name="module_args"]').prop('onChange')( wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')(
{}, {},
'command' 'command'
); );
wrapper.find('input#arguments').simulate('change', { wrapper.find('input#module_args').simulate('change', {
target: { value: 'foo', name: 'arguments' }, target: { value: 'foo', name: 'module_args' },
}); });
wrapper.find('input#limit').simulate('change', { wrapper.find('input#limit').simulate('change', {
target: { target: {
@@ -116,9 +114,9 @@ describe('<DetailsStep />', () => {
}); });
wrapper.update(); wrapper.update();
expect( expect(
wrapper.find('AnsibleSelect[name="module_args"]').prop('value') wrapper.find('AnsibleSelect[name="module_name"]').prop('value')
).toBe('command'); ).toBe('command');
expect(wrapper.find('input#arguments').prop('value')).toBe('foo'); expect(wrapper.find('input#module_args').prop('value')).toBe('foo');
expect(wrapper.find('AnsibleSelect[name="verbosity"]').prop('value')).toBe( expect(wrapper.find('AnsibleSelect[name="verbosity"]').prop('value')).toBe(
1 1
); );

View File

@@ -83,7 +83,7 @@ describe('VariablesField', () => {
)} )}
</Formik> </Formik>
); );
expect(wrapper.find('Tooltip').length).toBe(1); expect(wrapper.find('Popover').length).toBe(1);
}); });
it('should submit value through Formik', async () => { it('should submit value through Formik', async () => {

View File

@@ -61,6 +61,6 @@ describe('FieldWithPrompt', () => {
</Formik> </Formik>
); );
expect(wrapper.find('.pf-c-form__label-required')).toHaveLength(1); expect(wrapper.find('.pf-c-form__label-required')).toHaveLength(1);
expect(wrapper.find('Tooltip')).toHaveLength(1); expect(wrapper.find('Popover')).toHaveLength(1);
}); });
}); });

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React, { useState } from 'react';
import { node } from 'prop-types'; import { node } from 'prop-types';
import { Tooltip } from '@patternfly/react-core'; import { Popover } from '@patternfly/react-core';
import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons'; import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -9,18 +9,20 @@ const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
`; `;
function FieldTooltip({ content, ...rest }) { function FieldTooltip({ content, ...rest }) {
const [showTooltip, setShowTooltip] = useState(false);
if (!content) { if (!content) {
return null; return null;
} }
return ( return (
<Tooltip <Popover
position="right" bodyContent={content}
content={content} isVisible={showTooltip}
trigger="click mouseenter focus" hideOnOutsideClick
shouldClose={() => setShowTooltip(false)}
{...rest} {...rest}
> >
<QuestionCircleIcon /> <QuestionCircleIcon onClick={() => setShowTooltip(!showTooltip)} />
</Tooltip> </Popover>
); );
} }
FieldTooltip.propTypes = { FieldTooltip.propTypes = {