diff --git a/awx/ui_next/src/components/FormField/PasswordField.jsx b/awx/ui_next/src/components/FormField/PasswordField.jsx index 34823cf8a0..d865a8b70c 100644 --- a/awx/ui_next/src/components/FormField/PasswordField.jsx +++ b/awx/ui_next/src/components/FormField/PasswordField.jsx @@ -14,7 +14,7 @@ import { import { EyeIcon, EyeSlashIcon } from '@patternfly/react-icons'; function PasswordField(props) { - const { id, name, label, validate, isRequired, i18n } = props; + const { id, name, label, validate, isRequired, isDisabled, i18n } = props; const [inputType, setInputType] = useState('password'); const [field, meta] = useField({ name, validate }); @@ -40,6 +40,7 @@ function PasswordField(props) { variant={ButtonVariant.control} aria-label={i18n._(t`Toggle Password`)} onClick={handlePasswordToggle} + isDisabled={isDisabled} > {inputType === 'password' && } {inputType === 'text' && } @@ -50,6 +51,7 @@ function PasswordField(props) { placeholder={field.value === '$encrypted$' ? 'ENCRYPTED' : undefined} {...field} value={field.value === '$encrypted$' ? '' : field.value} + isDisabled={isDisabled} isRequired={isRequired} isValid={isValid} type={inputType} @@ -68,11 +70,13 @@ PasswordField.propTypes = { label: PropTypes.string.isRequired, validate: PropTypes.func, isRequired: PropTypes.bool, + isDisabled: PropTypes.bool, }; PasswordField.defaultProps = { validate: () => {}, isRequired: false, + isDisabled: false, }; export default withI18n()(PasswordField); diff --git a/awx/ui_next/src/screens/Template/Survey/SurveyList.jsx b/awx/ui_next/src/screens/Template/Survey/SurveyList.jsx index 5c8a53f0bd..8f859af943 100644 --- a/awx/ui_next/src/screens/Template/Survey/SurveyList.jsx +++ b/awx/ui_next/src/screens/Template/Survey/SurveyList.jsx @@ -1,12 +1,19 @@ import React, { useState } from 'react'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { DataList, Button } from '@patternfly/react-core'; +import { DataList, Button as _Button } from '@patternfly/react-core'; import ContentLoading from '@components/ContentLoading'; import ContentEmpty from '@components/ContentEmpty'; import AlertModal from '@components/AlertModal'; +import styled from 'styled-components'; + import SurveyListItem from './SurveyListItem'; import SurveyToolbar from './SurveyToolbar'; +import SurveyPreviewModal from './SurveyPreviewModal'; + +const Button = styled(_Button)` + margin: 20px; +`; function SurveyList({ isLoading, @@ -20,7 +27,7 @@ function SurveyList({ const questions = survey?.spec || []; const [selected, setSelected] = useState([]); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - + const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false); const isAllSelected = selected.length === questions?.length && selected.length > 0; @@ -92,6 +99,21 @@ function SurveyList({ onMoveDown={moveDown} /> ))} + {isPreviewModalOpen && ( + setIsPreviewModalOpen(false)} + questions={questions} + /> + )} + + ); } diff --git a/awx/ui_next/src/screens/Template/Survey/SurveyList.test.jsx b/awx/ui_next/src/screens/Template/Survey/SurveyList.test.jsx index acce909fd7..9621886314 100644 --- a/awx/ui_next/src/screens/Template/Survey/SurveyList.test.jsx +++ b/awx/ui_next/src/screens/Template/Survey/SurveyList.test.jsx @@ -88,6 +88,42 @@ describe('', () => { ); expect(deleteSurvey).toHaveBeenCalled(); }); + test('should render Preview button ', async () => { + let wrapper; + + await act(async () => { + wrapper = mountWithContexts(); + }); + + expect(wrapper.find('Button[aria-label="Preview"]').length).toBe(1); + }); + test('Preview button should render Modal', async () => { + let wrapper; + + await act(async () => { + wrapper = mountWithContexts(); + }); + act(() => wrapper.find('Button[aria-label="Preview"]').prop('onClick')()); + wrapper.update(); + + expect(wrapper.find('SurveyPreviewModal').length).toBe(1); + }); + test('Modal close button should close modal', async () => { + let wrapper; + + await act(async () => { + wrapper = mountWithContexts(); + }); + act(() => wrapper.find('Button[aria-label="Preview"]').prop('onClick')()); + wrapper.update(); + + expect(wrapper.find('SurveyPreviewModal').length).toBe(1); + + wrapper.find('Modal').prop('onClose')(); + wrapper.update(); + + expect(wrapper.find('SurveyPreviewModal').length).toBe(0); + }); }); describe('Survey with no questions', () => { diff --git a/awx/ui_next/src/screens/Template/Survey/SurveyPreviewModal.jsx b/awx/ui_next/src/screens/Template/Survey/SurveyPreviewModal.jsx new file mode 100644 index 0000000000..3d9919dca9 --- /dev/null +++ b/awx/ui_next/src/screens/Template/Survey/SurveyPreviewModal.jsx @@ -0,0 +1,115 @@ +import React from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { PasswordField } from '@components/FormField'; +import { Formik } from 'formik'; + +import { + Form, + FormGroup, + Modal, + TextInput, + TextArea, + Select, + SelectVariant, +} from '@patternfly/react-core'; + +function SurveyPreviewModal({ + questions, + isPreviewModalOpen, + onToggleModalOpen, + i18n, +}) { + const initialValues = {}; + questions.forEach(q => { + initialValues[q.variable] = q.default; + return initialValues; + }); + + return ( + onToggleModalOpen(false)} + isSmall + > + + {() => ( +
+ {questions.map(q => ( +
+ {['text', 'integer', 'float'].includes(q.type) && ( + + + + )} + {['textarea'].includes(q.type) && ( + +