mirror of
https://github.com/ansible/awx.git
synced 2026-02-28 00:08:44 -03:30
Merge pull request #6421 from AlexSCorey/6183-SurveyPreview
Adds Survey Preview Functionality Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -14,7 +14,7 @@ import {
|
|||||||
import { EyeIcon, EyeSlashIcon } from '@patternfly/react-icons';
|
import { EyeIcon, EyeSlashIcon } from '@patternfly/react-icons';
|
||||||
|
|
||||||
function PasswordField(props) {
|
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 [inputType, setInputType] = useState('password');
|
||||||
const [field, meta] = useField({ name, validate });
|
const [field, meta] = useField({ name, validate });
|
||||||
|
|
||||||
@@ -40,6 +40,7 @@ function PasswordField(props) {
|
|||||||
variant={ButtonVariant.control}
|
variant={ButtonVariant.control}
|
||||||
aria-label={i18n._(t`Toggle Password`)}
|
aria-label={i18n._(t`Toggle Password`)}
|
||||||
onClick={handlePasswordToggle}
|
onClick={handlePasswordToggle}
|
||||||
|
isDisabled={isDisabled}
|
||||||
>
|
>
|
||||||
{inputType === 'password' && <EyeSlashIcon />}
|
{inputType === 'password' && <EyeSlashIcon />}
|
||||||
{inputType === 'text' && <EyeIcon />}
|
{inputType === 'text' && <EyeIcon />}
|
||||||
@@ -50,6 +51,7 @@ function PasswordField(props) {
|
|||||||
placeholder={field.value === '$encrypted$' ? 'ENCRYPTED' : undefined}
|
placeholder={field.value === '$encrypted$' ? 'ENCRYPTED' : undefined}
|
||||||
{...field}
|
{...field}
|
||||||
value={field.value === '$encrypted$' ? '' : field.value}
|
value={field.value === '$encrypted$' ? '' : field.value}
|
||||||
|
isDisabled={isDisabled}
|
||||||
isRequired={isRequired}
|
isRequired={isRequired}
|
||||||
isValid={isValid}
|
isValid={isValid}
|
||||||
type={inputType}
|
type={inputType}
|
||||||
@@ -68,11 +70,13 @@ PasswordField.propTypes = {
|
|||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string.isRequired,
|
||||||
validate: PropTypes.func,
|
validate: PropTypes.func,
|
||||||
isRequired: PropTypes.bool,
|
isRequired: PropTypes.bool,
|
||||||
|
isDisabled: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
PasswordField.defaultProps = {
|
PasswordField.defaultProps = {
|
||||||
validate: () => {},
|
validate: () => {},
|
||||||
isRequired: false,
|
isRequired: false,
|
||||||
|
isDisabled: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withI18n()(PasswordField);
|
export default withI18n()(PasswordField);
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
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 ContentLoading from '@components/ContentLoading';
|
||||||
import ContentEmpty from '@components/ContentEmpty';
|
import ContentEmpty from '@components/ContentEmpty';
|
||||||
import AlertModal from '@components/AlertModal';
|
import AlertModal from '@components/AlertModal';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import SurveyListItem from './SurveyListItem';
|
import SurveyListItem from './SurveyListItem';
|
||||||
import SurveyToolbar from './SurveyToolbar';
|
import SurveyToolbar from './SurveyToolbar';
|
||||||
|
import SurveyPreviewModal from './SurveyPreviewModal';
|
||||||
|
|
||||||
|
const Button = styled(_Button)`
|
||||||
|
margin: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
function SurveyList({
|
function SurveyList({
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -20,7 +27,7 @@ function SurveyList({
|
|||||||
const questions = survey?.spec || [];
|
const questions = survey?.spec || [];
|
||||||
const [selected, setSelected] = useState([]);
|
const [selected, setSelected] = useState([]);
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
|
const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false);
|
||||||
const isAllSelected =
|
const isAllSelected =
|
||||||
selected.length === questions?.length && selected.length > 0;
|
selected.length === questions?.length && selected.length > 0;
|
||||||
|
|
||||||
@@ -92,6 +99,21 @@ function SurveyList({
|
|||||||
onMoveDown={moveDown}
|
onMoveDown={moveDown}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
{isPreviewModalOpen && (
|
||||||
|
<SurveyPreviewModal
|
||||||
|
isPreviewModalOpen={isPreviewModalOpen}
|
||||||
|
onToggleModalOpen={() => setIsPreviewModalOpen(false)}
|
||||||
|
questions={questions}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => setIsPreviewModalOpen(true)}
|
||||||
|
variant="primary"
|
||||||
|
aria-label={i18n._(t`Preview`)}
|
||||||
|
>
|
||||||
|
Preview
|
||||||
|
</Button>
|
||||||
</DataList>
|
</DataList>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,42 @@ describe('<SurveyList />', () => {
|
|||||||
);
|
);
|
||||||
expect(deleteSurvey).toHaveBeenCalled();
|
expect(deleteSurvey).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
test('should render Preview button ', async () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<SurveyList survey={surveyData} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.find('Button[aria-label="Preview"]').length).toBe(1);
|
||||||
|
});
|
||||||
|
test('Preview button should render Modal', async () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<SurveyList survey={surveyData} />);
|
||||||
|
});
|
||||||
|
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(<SurveyList survey={surveyData} />);
|
||||||
|
});
|
||||||
|
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', () => {
|
describe('Survey with no questions', () => {
|
||||||
|
|||||||
115
awx/ui_next/src/screens/Template/Survey/SurveyPreviewModal.jsx
Normal file
115
awx/ui_next/src/screens/Template/Survey/SurveyPreviewModal.jsx
Normal file
@@ -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 (
|
||||||
|
<Modal
|
||||||
|
title={i18n._(t`Survey Preview`)}
|
||||||
|
isOpen={isPreviewModalOpen}
|
||||||
|
onClose={() => onToggleModalOpen(false)}
|
||||||
|
isSmall
|
||||||
|
>
|
||||||
|
<Formik initialValues={initialValues}>
|
||||||
|
{() => (
|
||||||
|
<Form>
|
||||||
|
{questions.map(q => (
|
||||||
|
<div key={q.variable}>
|
||||||
|
{['text', 'integer', 'float'].includes(q.type) && (
|
||||||
|
<FormGroup
|
||||||
|
fieldId={`survey-preview-text-${q.variable}`}
|
||||||
|
label={q.question_name}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
id={`survey-preview-text-${q.variable}`}
|
||||||
|
value={q.default}
|
||||||
|
isDisabled
|
||||||
|
aria-label={i18n._(t`Text`)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
{['textarea'].includes(q.type) && (
|
||||||
|
<FormGroup
|
||||||
|
fieldId={`survey-preview-textArea-${q.variable}`}
|
||||||
|
label={q.question_name}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
id={`survey-preview-textArea-${q.variable}`}
|
||||||
|
type={`survey-preview-textArea-${q.variable}`}
|
||||||
|
value={q.default}
|
||||||
|
aria-label={i18n._(t`Text Area`)}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
{['password'].includes(q.type) && (
|
||||||
|
<PasswordField
|
||||||
|
id={`survey-preview-password-${q.variable}`}
|
||||||
|
label={q.question_name}
|
||||||
|
name={q.variable}
|
||||||
|
isDisabled
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{['multiplechoice'].includes(q.type) && (
|
||||||
|
<FormGroup
|
||||||
|
fieldId={`survey-preview-multipleChoice-${q.variable}`}
|
||||||
|
label={q.question_name}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
id={`survey-preview-multipleChoice-${q.variable}`}
|
||||||
|
isDisabled
|
||||||
|
aria-label={i18n._(t`Multiple Choice`)}
|
||||||
|
placeholderText={q.default}
|
||||||
|
onToggle={() => {}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
{['multiselect'].includes(q.type) && (
|
||||||
|
<FormGroup
|
||||||
|
fieldId={`survey-preview-multiSelect-${q.variable}`}
|
||||||
|
label={q.question_name}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
isDisabled
|
||||||
|
isReadOnly
|
||||||
|
variant={SelectVariant.typeaheadMulti}
|
||||||
|
isExpanded={false}
|
||||||
|
selections={q.default.length > 0 && q.default.split('\n')}
|
||||||
|
onToggle={() => {}}
|
||||||
|
aria-label={i18n._(t`Multi-Select`)}
|
||||||
|
id={`survey-preview-multiSelect-${q.variable}`}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default withI18n()(SurveyPreviewModal);
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { waitForElement, mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||||
|
|
||||||
|
import SurveyPreviewModal from './SurveyPreviewModal';
|
||||||
|
|
||||||
|
const questions = [
|
||||||
|
{
|
||||||
|
question_name: 'Text Question',
|
||||||
|
question_description: '',
|
||||||
|
required: true,
|
||||||
|
type: 'text',
|
||||||
|
variable: 'dfgh',
|
||||||
|
min: 0,
|
||||||
|
max: 1024,
|
||||||
|
default: 'Text Question Value',
|
||||||
|
choices: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question_name: 'Select Question',
|
||||||
|
question_description: '',
|
||||||
|
required: true,
|
||||||
|
type: 'multiplechoice',
|
||||||
|
variable: 'sdf',
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
default: 'Select Question Value',
|
||||||
|
choices: 'a\nd\nc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question_name: 'Text Area Question',
|
||||||
|
question_description: '',
|
||||||
|
required: true,
|
||||||
|
type: 'textarea',
|
||||||
|
variable: 'b',
|
||||||
|
min: 0,
|
||||||
|
max: 4096,
|
||||||
|
default: 'Text Area Question Value',
|
||||||
|
choices: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question_name: 'Password Question',
|
||||||
|
question_description: '',
|
||||||
|
required: true,
|
||||||
|
type: 'password',
|
||||||
|
variable: 'c',
|
||||||
|
min: 0,
|
||||||
|
max: 32,
|
||||||
|
default: '$encrypted$',
|
||||||
|
choices: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question_name: 'Multiple select Question',
|
||||||
|
question_description: '',
|
||||||
|
required: true,
|
||||||
|
type: 'multiselect',
|
||||||
|
variable: 'a',
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
default: 'a\nc\nd\nb',
|
||||||
|
choices: 'a\nc\nd\nb',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('<SurveyPreviewModal />', () => {
|
||||||
|
let wrapper;
|
||||||
|
beforeEach(async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<SurveyPreviewModal questions={questions} isPreviewModalOpen />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
waitForElement(wrapper, 'Form');
|
||||||
|
});
|
||||||
|
test('renders successfully', async () => {
|
||||||
|
expect(wrapper.find('SurveyPreviewModal').length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Renders proper fields', async () => {
|
||||||
|
const question1 = wrapper.find('FormGroup[label="Text Question"]');
|
||||||
|
const question1Value = wrapper.find('TextInputBase').at(0);
|
||||||
|
|
||||||
|
const question2 = wrapper
|
||||||
|
.find('FormGroup[label="Select Question"]')
|
||||||
|
.find('label');
|
||||||
|
const question2Value = wrapper.find('Select[aria-label="Multiple Choice"]');
|
||||||
|
|
||||||
|
const question3 = wrapper
|
||||||
|
.find('FormGroup[label="Text Area Question"]')
|
||||||
|
.find('label');
|
||||||
|
const question3Value = wrapper.find('TextArea');
|
||||||
|
|
||||||
|
const question4 = wrapper.find('FormGroup[label="Password Question"]');
|
||||||
|
const question4Value = wrapper.find('TextInputBase[type="password"]');
|
||||||
|
|
||||||
|
const question5 = wrapper
|
||||||
|
.find('FormGroup[label="Multiple select Question"]')
|
||||||
|
.find('label');
|
||||||
|
const question5Value = wrapper
|
||||||
|
.find('Select[aria-label="Multi-Select"]')
|
||||||
|
.find('Chip');
|
||||||
|
|
||||||
|
expect(question1.text()).toBe('Text Question');
|
||||||
|
expect(question1Value.prop('value')).toBe('Text Question Value');
|
||||||
|
expect(question1Value.prop('isDisabled')).toBe(true);
|
||||||
|
|
||||||
|
expect(question2.text()).toBe('Select Question');
|
||||||
|
expect(question2Value.find('span').text()).toBe('Select Question Value');
|
||||||
|
expect(question2Value.prop('isDisabled')).toBe(true);
|
||||||
|
|
||||||
|
expect(question3.text()).toBe('Text Area Question');
|
||||||
|
expect(question3Value.prop('value')).toBe('Text Area Question Value');
|
||||||
|
expect(question3Value.prop('disabled')).toBe(true);
|
||||||
|
|
||||||
|
expect(question4.text()).toBe('Password Question');
|
||||||
|
expect(question4Value.prop('placeholder')).toBe('ENCRYPTED');
|
||||||
|
expect(question4Value.prop('isDisabled')).toBe(true);
|
||||||
|
|
||||||
|
expect(question5.text()).toBe('Multiple select Question');
|
||||||
|
expect(question5Value.length).toBe(4);
|
||||||
|
expect(
|
||||||
|
wrapper.find('Select[aria-label="Multi-Select"]').prop('isDisabled')
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
export { default as SurveyList } from './SurveyList';
|
export { default as SurveyList } from './SurveyList';
|
||||||
export { default as SurveyQuestionAdd } from './SurveyQuestionAdd';
|
export { default as SurveyQuestionAdd } from './SurveyQuestionAdd';
|
||||||
export { default as SurveyQuestionEdit } from './SurveyQuestionEdit';
|
export { default as SurveyQuestionEdit } from './SurveyQuestionEdit';
|
||||||
|
export { default as SurveyPreviewModal } from './SurveyPreviewModal';
|
||||||
|
|||||||
Reference in New Issue
Block a user