diff --git a/awx/ui_next/src/components/AdHocCommands/AdHocCommandsWizard.jsx b/awx/ui_next/src/components/AdHocCommands/AdHocCommandsWizard.jsx
index 4a0716f320..83eee59d88 100644
--- a/awx/ui_next/src/components/AdHocCommands/AdHocCommandsWizard.jsx
+++ b/awx/ui_next/src/components/AdHocCommands/AdHocCommandsWizard.jsx
@@ -1,13 +1,26 @@
import React, { useState } from 'react';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
+import { ExclamationCircleIcon as PFExclamationCircleIcon } from '@patternfly/react-icons';
+import { Tooltip } from '@patternfly/react-core';
import { withFormik, useFormikContext } from 'formik';
import PropTypes from 'prop-types';
+import styled from 'styled-components';
import Wizard from '../Wizard';
import AdHocCredentialStep from './AdHocCredentialStep';
import AdHocDetailsStep from './AdHocDetailsStep';
+const AlertText = styled.div`
+ color: var(--pf-global--danger-color--200);
+ font-weight: var(--pf-global--FontWeight--bold);
+`;
+
+const ExclamationCircleIcon = styled(PFExclamationCircleIcon)`
+ margin-left: 10px;
+ color: var(--pf-global--danger-color--100);
+`;
+
function AdHocCommandsWizard({
onLaunch,
i18n,
@@ -19,7 +32,7 @@ function AdHocCommandsWizard({
const [currentStepId, setCurrentStepId] = useState(1);
const [enableLaunch, setEnableLaunch] = useState(false);
- const { values } = useFormikContext();
+ const { values, errors, touched } = useFormikContext();
const enabledNextOnDetailsStep = () => {
if (!values.module_name) {
@@ -36,11 +49,26 @@ function AdHocCommandsWizard({
}
return undefined; // makes the linter happy;
};
+ const hasDetailsStepError = errors.module_args && touched.module_args;
+
const steps = [
{
id: 1,
key: 1,
- name: i18n._(t`Details`),
+ name: hasDetailsStepError ? (
+
+ {i18n._(t`Details`)}
+
+
+
+
+ ) : (
+ i18n._(t`Details`)
+ ),
component: (
setEnableLaunch(true)}
/>
),
- enableNext: enableLaunch,
+ enableNext: enableLaunch && Object.values(errors).length === 0,
nextButtonText: i18n._(t`Launch`),
canJumpTo: currentStepId >= 2,
},
diff --git a/awx/ui_next/src/components/AdHocCommands/AdHocCommandsWizard.test.jsx b/awx/ui_next/src/components/AdHocCommands/AdHocCommandsWizard.test.jsx
index ed3ab4ed99..d38aa28bbc 100644
--- a/awx/ui_next/src/components/AdHocCommands/AdHocCommandsWizard.test.jsx
+++ b/awx/ui_next/src/components/AdHocCommands/AdHocCommandsWizard.test.jsx
@@ -148,6 +148,20 @@ describe('', () => {
expect(onLaunch).toHaveBeenCalled();
});
+ test('should show error in navigation bar', async () => {
+ await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0);
+
+ await act(async () => {
+ wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')(
+ {},
+ 'command'
+ );
+ wrapper.find('input#module_args').simulate('change', {
+ target: { value: '', name: 'module_args' },
+ });
+ });
+ waitForElement(wrapper, 'ExclamationCircleIcon', el => el.length > 0);
+ });
test('expect credential step to throw error', async () => {
CredentialsAPI.read.mockRejectedValue(
diff --git a/awx/ui_next/src/components/AdHocCommands/AdHocCredentialStep.jsx b/awx/ui_next/src/components/AdHocCommands/AdHocCredentialStep.jsx
index ade3528ea1..9a1f4fb094 100644
--- a/awx/ui_next/src/components/AdHocCommands/AdHocCredentialStep.jsx
+++ b/awx/ui_next/src/components/AdHocCommands/AdHocCredentialStep.jsx
@@ -65,6 +65,7 @@ function AdHocCredentialStep({ i18n, credentialTypeId, onEnableLaunch }) {
argumentsHelpers.setTouched(true)}
placeholder={i18n._(t`Enter arguments`)}
isRequired={
moduleNameField.value === 'command' ||
@@ -133,6 +136,7 @@ function AdHocDetailsStep({ i18n, verbosityOptions, moduleOptions }) {
/>
{i18n._(
@@ -185,6 +190,7 @@ function AdHocDetailsStep({ i18n, verbosityOptions, moduleOptions }) {
type="number"
min="0"
label={i18n._(t`Forks`)}
+ aria-label={i18n._(t`Forks`)}
tooltip={
{i18n._(
@@ -203,6 +209,7 @@ function AdHocDetailsStep({ i18n, verbosityOptions, moduleOptions }) {
}
label={i18n._(t`Extra variables`)}
+ aria-label={i18n._(t`Extra variables`)}
/>