mirror of
https://github.com/ansible/awx.git
synced 2026-01-16 20:30:46 -03:30
adds dragging functionality to modal
This commit is contained in:
parent
0aa82c2784
commit
b4243c6f03
@ -3,7 +3,7 @@ import React, { useState } from 'react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { useRouteMatch } from 'react-router-dom';
|
||||
import {
|
||||
Button as _Button,
|
||||
Button,
|
||||
Title,
|
||||
EmptyState,
|
||||
EmptyStateIcon,
|
||||
@ -11,7 +11,6 @@ import {
|
||||
} from '@patternfly/react-core';
|
||||
import { TableComposable, Thead, Tr, Th, Tbody } from '@patternfly/react-table';
|
||||
import { CubesIcon } from '@patternfly/react-icons';
|
||||
import styled from 'styled-components';
|
||||
import ContentLoading from 'components/ContentLoading';
|
||||
import AlertModal from 'components/AlertModal';
|
||||
import { ToolbarAddButton } from 'components/PaginatedTable';
|
||||
@ -19,12 +18,7 @@ import { ToolbarAddButton } from 'components/PaginatedTable';
|
||||
import useSelected from 'hooks/useSelected';
|
||||
import SurveyListItem from './SurveyListItem';
|
||||
import SurveyToolbar from './SurveyToolbar';
|
||||
import SurveyPreviewModal from './SurveyPreviewModal';
|
||||
|
||||
const Button = styled(_Button)`
|
||||
margin: 20px;
|
||||
max-width: 85px;
|
||||
`;
|
||||
import SurveyReorderModal from './SurveyReorderModal';
|
||||
|
||||
function SurveyList({
|
||||
isLoading,
|
||||
@ -39,7 +33,7 @@ function SurveyList({
|
||||
|
||||
const questions = survey?.spec || [];
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false);
|
||||
const [isOrderModalOpen, setIsOrderModalOpen] = useState(false);
|
||||
|
||||
const { selected, isAllSelected, setSelected, selectAll, clearSelected } =
|
||||
useSelected(questions);
|
||||
@ -62,26 +56,6 @@ function SurveyList({
|
||||
clearSelected();
|
||||
};
|
||||
|
||||
const moveUp = (question) => {
|
||||
const index = questions.indexOf(question);
|
||||
if (index < 1) {
|
||||
return;
|
||||
}
|
||||
const beginning = questions.slice(0, index - 1);
|
||||
const swapWith = questions[index - 1];
|
||||
const end = questions.slice(index + 1);
|
||||
updateSurvey([...beginning, question, swapWith, ...end]);
|
||||
};
|
||||
const moveDown = (question) => {
|
||||
const index = questions.indexOf(question);
|
||||
if (index === -1 || index > questions.length - 1) {
|
||||
return;
|
||||
}
|
||||
const beginning = questions.slice(0, index);
|
||||
const swapWith = questions[index + 1];
|
||||
const end = questions.slice(index + 2);
|
||||
updateSurvey([...beginning, swapWith, question, ...end]);
|
||||
};
|
||||
const deleteModal = (
|
||||
<AlertModal
|
||||
variant="danger"
|
||||
@ -131,14 +105,14 @@ function SurveyList({
|
||||
} else {
|
||||
content = (
|
||||
<>
|
||||
<TableComposable>
|
||||
<TableComposable ouiaId="survey-list">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th />
|
||||
<Th>{t`Name`}</Th>
|
||||
<Th>{t`Type`}</Th>
|
||||
<Th>{t`Default`}</Th>
|
||||
<Th>{t`Actions`}</Th>
|
||||
<Th datalabel={t`Name`}>{t`Name`}</Th>
|
||||
<Th datalabel={t`Type`}>{t`Type`}</Th>
|
||||
<Th datalabel={t`Default`}>{t`Default`}</Th>
|
||||
<Th datalabel={t`Actions`}>{t`Actions`}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
@ -152,27 +126,22 @@ function SurveyList({
|
||||
(q) => q.variable === question.variable
|
||||
)}
|
||||
onSelect={() => handleSelect(question)}
|
||||
onMoveUp={moveUp}
|
||||
onMoveDown={moveDown}
|
||||
canEdit={canEdit}
|
||||
rowIndex={index}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</TableComposable>
|
||||
<Button
|
||||
onClick={() => setIsPreviewModalOpen(true)}
|
||||
variant="primary"
|
||||
aria-label={t`Preview`}
|
||||
>
|
||||
{t`Preview`}
|
||||
</Button>
|
||||
{isDeleteModalOpen && deleteModal}
|
||||
{isPreviewModalOpen && (
|
||||
<SurveyPreviewModal
|
||||
isPreviewModalOpen={isPreviewModalOpen}
|
||||
onToggleModalOpen={() => setIsPreviewModalOpen(false)}
|
||||
{isOrderModalOpen && (
|
||||
<SurveyReorderModal
|
||||
isOrderModalOpen={isOrderModalOpen}
|
||||
onCloseOrderModal={() => setIsOrderModalOpen(false)}
|
||||
questions={questions}
|
||||
onSave={(newOrder) => {
|
||||
updateSurvey(newOrder);
|
||||
setIsOrderModalOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@ -194,6 +163,12 @@ function SurveyList({
|
||||
return (
|
||||
<>
|
||||
<SurveyToolbar
|
||||
onOpenOrderModal={
|
||||
questions.length > 1 &&
|
||||
(() => {
|
||||
setIsOrderModalOpen(true);
|
||||
})
|
||||
}
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={selectAll}
|
||||
surveyEnabled={surveyEnabled}
|
||||
|
||||
@ -59,9 +59,10 @@ describe('<SurveyList />', () => {
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('Button[variant="secondary"]').prop('isDisabled')).toBe(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
wrapper.find('Button[ouiaId="survey-delete-button"]').prop('isDisabled')
|
||||
).toBe(true);
|
||||
expect(wrapper.find('Button[ouiaId="edit-order"]')).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('Checkbox[aria-label="Select all"]').prop('isChecked')
|
||||
).toBe(false);
|
||||
@ -76,11 +77,11 @@ describe('<SurveyList />', () => {
|
||||
expect(
|
||||
wrapper.find('Checkbox[aria-label="Select all"]').prop('isChecked')
|
||||
).toBe(true);
|
||||
expect(wrapper.find('Button[variant="secondary"]').prop('isDisabled')).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
wrapper.find('Button[ouiaId="survey-delete-button"]').prop('isDisabled')
|
||||
).toBe(false);
|
||||
act(() => {
|
||||
wrapper.find('Button[variant="secondary"]').invoke('onClick')();
|
||||
wrapper.find('Button[ouiaId="survey-delete-button"]').invoke('onClick')();
|
||||
});
|
||||
|
||||
wrapper.update();
|
||||
@ -91,38 +92,38 @@ describe('<SurveyList />', () => {
|
||||
expect(deleteSurvey).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should render Preview button ', async () => {
|
||||
test('should render Edit Order button ', async () => {
|
||||
let wrapper;
|
||||
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<SurveyList survey={surveyData} />);
|
||||
wrapper = mountWithContexts(<SurveyList survey={surveyData} canEdit />);
|
||||
});
|
||||
|
||||
expect(wrapper.find('Button[aria-label="Preview"]').length).toBe(1);
|
||||
expect(wrapper.find('Button[ouiaId="edit-order"]').length).toBe(1);
|
||||
});
|
||||
|
||||
test('Preview button should render Modal', async () => {
|
||||
test('Edit Order button should render Modal', async () => {
|
||||
let wrapper;
|
||||
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<SurveyList survey={surveyData} />);
|
||||
wrapper = mountWithContexts(<SurveyList survey={surveyData} canEdit />);
|
||||
});
|
||||
act(() => wrapper.find('Button[aria-label="Preview"]').prop('onClick')());
|
||||
act(() => wrapper.find('Button[ouiaId="edit-order"]').prop('onClick')());
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('SurveyPreviewModal').length).toBe(1);
|
||||
expect(wrapper.find('SurveyReorderModal').length).toBe(1);
|
||||
});
|
||||
|
||||
test('Modal close button should close modal', async () => {
|
||||
let wrapper;
|
||||
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<SurveyList survey={surveyData} />);
|
||||
wrapper = mountWithContexts(<SurveyList survey={surveyData} canEdit />);
|
||||
});
|
||||
act(() => wrapper.find('Button[aria-label="Preview"]').prop('onClick')());
|
||||
act(() => wrapper.find('Button[ouiaId="edit-order"]').prop('onClick')());
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('SurveyPreviewModal').length).toBe(1);
|
||||
expect(wrapper.find('SurveyReorderModal').length).toBe(1);
|
||||
|
||||
act(() => wrapper.find('Modal').prop('onClose')());
|
||||
|
||||
|
||||
@ -2,47 +2,26 @@ import 'styled-components/macro';
|
||||
import React from 'react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
Button as _Button,
|
||||
Chip,
|
||||
Stack,
|
||||
StackItem,
|
||||
Tooltip,
|
||||
} from '@patternfly/react-core';
|
||||
import { Chip, Tooltip, Button } from '@patternfly/react-core';
|
||||
|
||||
import { Tr, Td } from '@patternfly/react-table';
|
||||
import {
|
||||
CaretDownIcon,
|
||||
CaretUpIcon,
|
||||
PencilAltIcon,
|
||||
} from '@patternfly/react-icons';
|
||||
import { PencilAltIcon } from '@patternfly/react-icons';
|
||||
import styled from 'styled-components';
|
||||
import ChipGroup from 'components/ChipGroup';
|
||||
import { ActionItem, ActionsTd } from 'components/PaginatedTable';
|
||||
|
||||
const StackButton = styled(_Button)`
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
padding-left: 20px;
|
||||
`;
|
||||
|
||||
const Required = styled.span`
|
||||
color: var(--pf-global--danger-color--100);
|
||||
margin-left: var(--pf-global--spacer--xs);
|
||||
`;
|
||||
|
||||
const EditButton = styled(_Button)``;
|
||||
const SurveyActionsTd = styled(ActionsTd)`
|
||||
&& {
|
||||
padding-right: 35px;
|
||||
}
|
||||
`;
|
||||
|
||||
function SurveyListItem({
|
||||
canEdit,
|
||||
question,
|
||||
isLast,
|
||||
isFirst,
|
||||
isChecked,
|
||||
onSelect,
|
||||
onMoveUp,
|
||||
onMoveDown,
|
||||
rowIndex,
|
||||
}) {
|
||||
function SurveyListItem({ canEdit, question, isChecked, onSelect, rowIndex }) {
|
||||
return (
|
||||
<Tr>
|
||||
<Td
|
||||
@ -96,53 +75,22 @@ function SurveyListItem({
|
||||
<span>{question.default}</span>
|
||||
)}
|
||||
</Td>
|
||||
<ActionsTd dataLabel={t`Actions`}>
|
||||
<SurveyActionsTd dataLabel={t`Actions`}>
|
||||
<ActionItem visible={canEdit}>
|
||||
<EditButton variant="plain">
|
||||
<Tooltip content={t`Edit Survey`} position="top">
|
||||
<EditButton
|
||||
ouiaId={`edit-survey-${question.variable}`}
|
||||
aria-label={t`edit survey`}
|
||||
variant="plain"
|
||||
component={Link}
|
||||
to={`survey/edit?question_variable=${encodeURIComponent(
|
||||
question.variable
|
||||
)}`}
|
||||
>
|
||||
<PencilAltIcon />
|
||||
</EditButton>
|
||||
</Tooltip>
|
||||
</EditButton>
|
||||
<Tooltip content={t`Edit Survey`} position="top">
|
||||
<Button
|
||||
ouiaId={`edit-survey-${question.variable}`}
|
||||
variant="plain"
|
||||
component={Link}
|
||||
to={`survey/edit?question_variable=${encodeURIComponent(
|
||||
question.variable
|
||||
)}`}
|
||||
>
|
||||
<PencilAltIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</ActionItem>
|
||||
<ActionItem visible={canEdit}>
|
||||
<>
|
||||
<Stack>
|
||||
<StackItem>
|
||||
<StackButton
|
||||
ouiaId={`${question.variable}-move-up-button`}
|
||||
variant="plain"
|
||||
aria-label={t`move up`}
|
||||
isDisabled={isFirst || !canEdit}
|
||||
onClick={() => onMoveUp(question)}
|
||||
>
|
||||
<CaretUpIcon />
|
||||
</StackButton>
|
||||
</StackItem>
|
||||
<StackItem>
|
||||
<StackButton
|
||||
ouiaId={`${question.variable}-move-down-button`}
|
||||
variant="plain"
|
||||
aria-label={t`move down`}
|
||||
isDisabled={isLast || !canEdit}
|
||||
onClick={() => onMoveDown(question)}
|
||||
>
|
||||
<CaretDownIcon />
|
||||
</StackButton>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
</>
|
||||
</ActionItem>
|
||||
</ActionsTd>
|
||||
</SurveyActionsTd>
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
|
||||
@ -41,43 +41,10 @@ describe('<SurveyListItem />', () => {
|
||||
</table>
|
||||
);
|
||||
});
|
||||
const moveUp = wrapper.find('Button[aria-label="move up"]');
|
||||
const moveDown = wrapper.find('Button[aria-label="move down"]');
|
||||
expect(moveUp.length).toBe(1);
|
||||
expect(moveDown.length).toBe(1);
|
||||
expect(wrapper.find('SelectColumn').length).toBe(1);
|
||||
expect(wrapper.find('Td').length).toBe(5);
|
||||
});
|
||||
|
||||
test('move up and move down buttons are disabled', () => {
|
||||
let wrapper;
|
||||
act(() => {
|
||||
wrapper = mountWithContexts(
|
||||
<table>
|
||||
<tbody>
|
||||
<SurveyListItem
|
||||
rowIndex={1}
|
||||
question={item}
|
||||
isChecked={false}
|
||||
isFirst
|
||||
isLast
|
||||
canEdit
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
});
|
||||
const moveUp = wrapper
|
||||
.find('Button[aria-label="move up"]')
|
||||
.prop('isDisabled');
|
||||
const moveDown = wrapper
|
||||
.find('Button[aria-label="move down"]')
|
||||
.prop('isDisabled');
|
||||
|
||||
expect(moveUp).toBe(true);
|
||||
expect(moveDown).toBe(true);
|
||||
});
|
||||
|
||||
test('required item has required asterisk', () => {
|
||||
const newItem = {
|
||||
question_name: 'Foo',
|
||||
|
||||
@ -1,132 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { t } from '@lingui/macro';
|
||||
import { Formik } from 'formik';
|
||||
|
||||
import {
|
||||
Form,
|
||||
FormGroup,
|
||||
Modal,
|
||||
TextInput,
|
||||
TextArea,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
} from '@patternfly/react-core';
|
||||
import { PasswordField } from 'components/FormField';
|
||||
|
||||
function SurveyPreviewModal({
|
||||
questions,
|
||||
isPreviewModalOpen,
|
||||
onToggleModalOpen,
|
||||
}) {
|
||||
const initialValues = {};
|
||||
questions.forEach((q) => {
|
||||
initialValues[q.variable] = q.default;
|
||||
return initialValues;
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t`Survey Preview`}
|
||||
aria-label={t`Survey preview modal`}
|
||||
isOpen={isPreviewModalOpen}
|
||||
onClose={() => onToggleModalOpen(false)}
|
||||
variant="small"
|
||||
>
|
||||
<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}
|
||||
isRequired={q.required}
|
||||
>
|
||||
<TextInput
|
||||
id={`survey-preview-text-${q.variable}`}
|
||||
value={q.default}
|
||||
isDisabled
|
||||
aria-label={t`Text`}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
{['textarea'].includes(q.type) && (
|
||||
<FormGroup
|
||||
fieldId={`survey-preview-textArea-${q.variable}`}
|
||||
label={q.question_name}
|
||||
isRequired={q.required}
|
||||
>
|
||||
<TextArea
|
||||
id={`survey-preview-textArea-${q.variable}`}
|
||||
type={`survey-preview-textArea-${q.variable}`}
|
||||
value={q.default}
|
||||
aria-label={t`Text Area`}
|
||||
isDisabled
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
{['password'].includes(q.type) && (
|
||||
<PasswordField
|
||||
id={`survey-preview-password-${q.variable}`}
|
||||
label={q.question_name}
|
||||
name={q.variable}
|
||||
isDisabled
|
||||
isRequired={q.required}
|
||||
/>
|
||||
)}
|
||||
{['multiplechoice'].includes(q.type) && (
|
||||
<FormGroup
|
||||
fieldId={`survey-preview-multipleChoice-${q.variable}`}
|
||||
label={q.question_name}
|
||||
isRequired={q.required}
|
||||
>
|
||||
<Select
|
||||
id={`survey-preview-multipleChoice-${q.variable}`}
|
||||
isDisabled
|
||||
aria-label={t`Multiple Choice`}
|
||||
typeAheadAriaLabel={t`Multiple Choice`}
|
||||
placeholderText={q.default}
|
||||
onToggle={() => {}}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
{['multiselect'].includes(q.type) && (
|
||||
<FormGroup
|
||||
fieldId={`survey-preview-multiSelect-${q.variable}`}
|
||||
label={q.question_name}
|
||||
isRequired={q.required}
|
||||
>
|
||||
<Select
|
||||
isDisabled
|
||||
isReadOnly
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
isOpen={false}
|
||||
selections={
|
||||
q.default.length > 0 ? q.default.split('\n') : []
|
||||
}
|
||||
onToggle={() => {}}
|
||||
aria-label={t`Multi-Select`}
|
||||
typeAheadAriaLabel={t`Multi-Select`}
|
||||
id={`survey-preview-multiSelect-${q.variable}`}
|
||||
>
|
||||
{q.choices.length > 0 &&
|
||||
q.choices
|
||||
.split('\n')
|
||||
.map((option) => (
|
||||
<SelectOption key={option} value={option} />
|
||||
))}
|
||||
</Select>
|
||||
</FormGroup>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
export default SurveyPreviewModal;
|
||||
235
awx/ui/src/screens/Template/Survey/SurveyReorderModal.js
Normal file
235
awx/ui/src/screens/Template/Survey/SurveyReorderModal.js
Normal file
@ -0,0 +1,235 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { GripVerticalIcon } from '@patternfly/react-icons';
|
||||
import {
|
||||
Modal,
|
||||
TextInput,
|
||||
TextArea,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
Button,
|
||||
} from '@patternfly/react-core';
|
||||
import {
|
||||
TableComposable,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
} from '@patternfly/react-table';
|
||||
|
||||
function SurveyReorderModal({
|
||||
questions,
|
||||
isOrderModalOpen,
|
||||
onCloseOrderModal,
|
||||
onSave,
|
||||
}) {
|
||||
const [surveyQuestions, setSurveyQuestions] = useState([...questions]);
|
||||
const [itemStartIndex, setStartItemIndex] = useState(null);
|
||||
const [draggedItemId, setDraggedItemId] = useState(null);
|
||||
const ref = useRef(null);
|
||||
|
||||
const isValidDrop = (evt) => {
|
||||
const ulRect = ref.current.getBoundingClientRect();
|
||||
return (
|
||||
evt.clientX > ulRect.x &&
|
||||
evt.clientX < ulRect.x + ulRect.width &&
|
||||
evt.clientY > ulRect.y &&
|
||||
evt.clientY < ulRect.y + ulRect.height
|
||||
);
|
||||
};
|
||||
const onDrop = (evt) => {
|
||||
if (!isValidDrop(evt)) {
|
||||
onDragCancel();
|
||||
}
|
||||
};
|
||||
|
||||
const onDragCancel = () => {
|
||||
Array.from(ref.current.children).forEach((el) => {
|
||||
el.setAttribute('aria-pressed', 'false');
|
||||
});
|
||||
setDraggedItemId(null);
|
||||
setStartItemIndex(null);
|
||||
};
|
||||
|
||||
const onDragOver = (evt) => {
|
||||
evt.preventDefault();
|
||||
|
||||
const curListItem = evt.target.closest('tr');
|
||||
|
||||
const dragId = curListItem.id;
|
||||
const newDraggedItemIndex = Array.from(ref.current.children).findIndex(
|
||||
(item) => item.id === dragId
|
||||
);
|
||||
|
||||
if (newDraggedItemIndex !== itemStartIndex) {
|
||||
const temporaryOrder = moveItem(
|
||||
[...surveyQuestions],
|
||||
draggedItemId,
|
||||
newDraggedItemIndex
|
||||
);
|
||||
|
||||
setSurveyQuestions(temporaryOrder);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const moveItem = (arr, itemId, toIndex) => {
|
||||
const fromIndex = arr.findIndex((item) => item.variable === itemId);
|
||||
|
||||
if (fromIndex === toIndex) {
|
||||
return arr;
|
||||
}
|
||||
const temp = arr.splice(fromIndex, 1);
|
||||
arr.splice(toIndex, 0, temp[0]);
|
||||
|
||||
return arr;
|
||||
};
|
||||
|
||||
const onDragLeave = (evt) => {
|
||||
if (!isValidDrop(evt)) {
|
||||
setStartItemIndex(null);
|
||||
}
|
||||
};
|
||||
|
||||
const onDragEnd = (evt) => {
|
||||
evt.target.setAttribute('aria-pressed', 'false');
|
||||
|
||||
setDraggedItemId(null);
|
||||
setStartItemIndex(null);
|
||||
};
|
||||
|
||||
const onDragStart = (evt) => {
|
||||
evt.dataTransfer.effectAllowed = 'move';
|
||||
const newDraggedItemId = evt.currentTarget.id;
|
||||
|
||||
const originalStartIndex = Array.from(ref.current.children).findIndex(
|
||||
(item) => item.id === evt.currentTarget.id
|
||||
);
|
||||
|
||||
evt.currentTarget.setAttribute('aria-pressed', 'true');
|
||||
setDraggedItemId(newDraggedItemId);
|
||||
setStartItemIndex(originalStartIndex);
|
||||
};
|
||||
|
||||
const defaultAnswer = (q) => {
|
||||
let component = null;
|
||||
switch (q.type) {
|
||||
case 'textarea':
|
||||
component = (
|
||||
<TextArea
|
||||
id={`survey-preview-textArea-${q.variable}`}
|
||||
type={`survey-preview-textArea-${q.variable}`}
|
||||
value={q.default}
|
||||
aria-label={t`Text Area`}
|
||||
isDisabled
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'multiplechoice':
|
||||
component = (
|
||||
<Select
|
||||
id={`survey-preview-multipleChoice-${q.variable}`}
|
||||
isDisabled
|
||||
aria-label={t`Multiple Choice`}
|
||||
typeAheadAriaLabel={t`Multiple Choice`}
|
||||
placeholderText={q.default}
|
||||
onToggle={() => {}}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'multiselect':
|
||||
component = (
|
||||
<Select
|
||||
isDisabled
|
||||
isReadOnly
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
isOpen={false}
|
||||
selections={q.default.length > 0 ? q.default.split('\n') : []}
|
||||
onToggle={() => {}}
|
||||
aria-label={t`Multi-Select`}
|
||||
typeAheadAriaLabel={t`Multi-Select`}
|
||||
id={`survey-preview-multiSelect-${q.variable}`}
|
||||
>
|
||||
{q.choices.length > 0 &&
|
||||
q.choices
|
||||
.split('\n')
|
||||
.map((option) => <SelectOption key={option} value={option} />)}
|
||||
</Select>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
component = (
|
||||
<TextInput
|
||||
id={`survey-preview-text-${q.variable}`}
|
||||
value={q.default}
|
||||
isDisabled
|
||||
aria-label={t`Text`}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return component;
|
||||
};
|
||||
return (
|
||||
<Modal
|
||||
title={t`Survey Question Order`}
|
||||
aria-label={t`Survey preview modal`}
|
||||
isOpen={isOrderModalOpen}
|
||||
description={t`To reoder the survey questions drag and drop them in the desired location.`}
|
||||
onClose={() => onCloseOrderModal()}
|
||||
variant="medium"
|
||||
actions={[
|
||||
<Button
|
||||
variant="primary"
|
||||
ouiaId="survey-order-save"
|
||||
key="save"
|
||||
onClick={() => {
|
||||
onSave(surveyQuestions);
|
||||
}}
|
||||
>{t`Save`}</Button>,
|
||||
<Button
|
||||
ouiaId="survey-order-cancel"
|
||||
key="cancel"
|
||||
variant="link"
|
||||
onClick={() => onCloseOrderModal()}
|
||||
>{t`Cancel`}</Button>,
|
||||
]}
|
||||
>
|
||||
<TableComposable>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th dataLabel={t`Order`}>{t`Order`}</Th>
|
||||
<Th dataLabel={t`Name`}>{t`Name`}</Th>
|
||||
<Th dataLabel={t`Default Answer(s)`}>{t`Default Answer(s)`}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody onDragOver={onDragOver} onDragLeave={onDragLeave} ref={ref}>
|
||||
{surveyQuestions.map((q) => (
|
||||
<Tr
|
||||
key={q.variable}
|
||||
id={q.variable}
|
||||
draggable
|
||||
onDrop={onDrop}
|
||||
onDragEnd={onDragEnd}
|
||||
onDragStart={onDragStart}
|
||||
>
|
||||
<Td dataLabel={t`Order`}>
|
||||
<Button variant="plain">
|
||||
<GripVerticalIcon />
|
||||
</Button>
|
||||
</Td>
|
||||
<Td dataLabel={t`Name`} aria-label={q.question_name}>
|
||||
{q.question_name}
|
||||
</Td>
|
||||
<Td dataLabel={t`Default Answer(s)`}>{defaultAnswer(q)}</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</TableComposable>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
export default SurveyReorderModal;
|
||||
@ -5,7 +5,7 @@ import {
|
||||
mountWithContexts,
|
||||
} from '../../../../testUtils/enzymeHelpers';
|
||||
|
||||
import SurveyPreviewModal from './SurveyPreviewModal';
|
||||
import SurveyReorderModal from './SurveyReorderModal';
|
||||
|
||||
const questions = [
|
||||
{
|
||||
@ -65,59 +65,55 @@ const questions = [
|
||||
},
|
||||
];
|
||||
|
||||
describe('<SurveyPreviewModal />', () => {
|
||||
describe('<SurveyReorderModal />', () => {
|
||||
let wrapper;
|
||||
beforeAll(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<SurveyPreviewModal questions={questions} isPreviewModalOpen />
|
||||
<SurveyReorderModal questions={questions} isOrderModalOpen />
|
||||
);
|
||||
});
|
||||
waitForElement(wrapper, 'Form');
|
||||
});
|
||||
|
||||
test('Renders proper fields', async () => {
|
||||
const question1 = wrapper.find('FormGroup[label="Text Question"]');
|
||||
const question1Value = wrapper.find('TextInputBase').at(0);
|
||||
const question1 = wrapper.find('td[aria-label="Text Question"]');
|
||||
const question1Value = wrapper
|
||||
.find('TextInput#survey-preview-text-dfgh')
|
||||
.find('input');
|
||||
|
||||
const question2 = wrapper
|
||||
.find('FormGroup[label="Select Question"]')
|
||||
.find('label');
|
||||
const question2 = wrapper.find('td[aria-label="Select Question"]');
|
||||
const question2Value = wrapper.find('Select[aria-label="Multiple Choice"]');
|
||||
|
||||
const question3 = wrapper
|
||||
.find('FormGroup[label="Text Area Question"]')
|
||||
.find('label');
|
||||
const question3 = wrapper;
|
||||
wrapper.find('td[aria-label="Text Area Question"]');
|
||||
const question3Value = wrapper.find('textarea');
|
||||
|
||||
const question4 = wrapper.find('FormGroup[label="Password Question"]');
|
||||
const question4Value = wrapper.find('TextInputBase[type="password"]');
|
||||
const question4 = wrapper.find('td[aria-label="Password Question"]');
|
||||
const question4Value = wrapper.find('TextInputBase#survey-preview-text-c');
|
||||
|
||||
const question5 = wrapper
|
||||
.find('FormGroup[label="Multiple select Question"]')
|
||||
.find('label');
|
||||
const question5 = wrapper.find('td[aria-label="Multiple select Question"]');
|
||||
const question5Value = wrapper
|
||||
.find('Select[aria-label="Multi-Select"]')
|
||||
.find('Chip');
|
||||
|
||||
expect(question1.text()).toBe('Text Question * ');
|
||||
expect(question1).toHaveLength(1);
|
||||
expect(question1Value.prop('value')).toBe('Text Question Value');
|
||||
expect(question1Value.prop('isDisabled')).toBe(true);
|
||||
expect(question1Value.prop('disabled')).toBe(true);
|
||||
|
||||
expect(question2.text()).toBe('Select Question *');
|
||||
expect(question2Value.find('.pf-c-select__toggle-text').text()).toBe(
|
||||
expect(question2).toHaveLength(1);
|
||||
expect(question2Value.prop('placeholderText')).toBe(
|
||||
'Select Question Value'
|
||||
);
|
||||
expect(question2Value.prop('isDisabled')).toBe(true);
|
||||
|
||||
expect(question3.text()).toBe('Text Area Question *');
|
||||
expect(question3).toHaveLength(1);
|
||||
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(question4).toHaveLength(1);
|
||||
expect(question4Value.prop('value')).toBe('$encrypted$');
|
||||
expect(question4Value.prop('isDisabled')).toBe(true);
|
||||
|
||||
expect(question5.text()).toBe('Multiple select Question *');
|
||||
expect(question5).toHaveLength(1);
|
||||
expect(question5Value.length).toBe(4);
|
||||
expect(
|
||||
wrapper.find('Select[aria-label="Multi-Select"]').prop('isDisabled')
|
||||
@ -12,12 +12,16 @@ import {
|
||||
ToolbarContent,
|
||||
ToolbarGroup,
|
||||
ToolbarItem,
|
||||
Tooltip,
|
||||
} from '@patternfly/react-core';
|
||||
import { ToolbarAddButton } from 'components/PaginatedTable';
|
||||
|
||||
const Toolbar = styled(_Toolbar)`
|
||||
margin-left: 10px;
|
||||
`;
|
||||
const SwitchWrapper = styled(ToolbarItem)`
|
||||
padding-left: 4px;
|
||||
`;
|
||||
|
||||
function SurveyToolbar({
|
||||
canEdit,
|
||||
@ -27,6 +31,7 @@ function SurveyToolbar({
|
||||
onToggleSurvey,
|
||||
isDeleteDisabled,
|
||||
onToggleDeleteModal,
|
||||
onOpenOrderModal,
|
||||
}) {
|
||||
isDeleteDisabled = !canEdit || isDeleteDisabled;
|
||||
const match = useRouteMatch();
|
||||
@ -44,17 +49,6 @@ function SurveyToolbar({
|
||||
id="select-all"
|
||||
/>
|
||||
</ToolbarItem>
|
||||
<ToolbarItem>
|
||||
<Switch
|
||||
aria-label={t`Survey Toggle`}
|
||||
id="survey-toggle"
|
||||
label={t`On`}
|
||||
labelOff={t`Off`}
|
||||
isChecked={surveyEnabled}
|
||||
isDisabled={!canEdit}
|
||||
onChange={() => onToggleSurvey(!surveyEnabled)}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
<ToolbarGroup>
|
||||
<ToolbarItem>
|
||||
<ToolbarAddButton
|
||||
@ -62,17 +56,55 @@ function SurveyToolbar({
|
||||
linkTo={`${match.url}/add`}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
{canEdit && onOpenOrderModal && (
|
||||
<ToolbarItem>
|
||||
<Tooltip
|
||||
content={t`Click to rearrange the order of the survey questions`}
|
||||
>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onOpenOrderModal();
|
||||
}}
|
||||
variant="secondary"
|
||||
ouiaId="edit-order"
|
||||
>
|
||||
{t`Edit Order`}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</ToolbarItem>
|
||||
)}
|
||||
<ToolbarItem>
|
||||
<Button
|
||||
ouiaId="survey-delete-button"
|
||||
variant="secondary"
|
||||
isDisabled={isDeleteDisabled}
|
||||
onClick={() => onToggleDeleteModal(true)}
|
||||
<Tooltip
|
||||
content={
|
||||
isDeleteDisabled
|
||||
? t`Select a question to delete`
|
||||
: t`Delete survey question`
|
||||
}
|
||||
>
|
||||
{t`Delete`}
|
||||
</Button>
|
||||
<div>
|
||||
<Button
|
||||
ouiaId="survey-delete-button"
|
||||
variant="secondary"
|
||||
isDisabled={isDeleteDisabled}
|
||||
onClick={() => onToggleDeleteModal(true)}
|
||||
>
|
||||
{t`Delete`}
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</ToolbarItem>
|
||||
</ToolbarGroup>
|
||||
<SwitchWrapper>
|
||||
<Switch
|
||||
aria-label={t`Survey Toggle`}
|
||||
id="survey-toggle"
|
||||
label={t`Survey Enabled`}
|
||||
labelOff={t`Survey Disabled`}
|
||||
isChecked={surveyEnabled}
|
||||
isDisabled={!canEdit}
|
||||
onChange={() => onToggleSurvey(!surveyEnabled)}
|
||||
/>
|
||||
</SwitchWrapper>
|
||||
</ToolbarContent>
|
||||
</Toolbar>
|
||||
);
|
||||
|
||||
@ -17,16 +17,18 @@ describe('<SurveyToolbar />', () => {
|
||||
isAllSelected
|
||||
onToggleDeleteModal={jest.fn()}
|
||||
onToggleSurvey={jest.fn()}
|
||||
canEdit={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
expect(wrapper.find('Button[variant="secondary"]').prop('isDisabled')).toBe(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
wrapper.find('Button[ouiaId="survey-delete-button"]').prop('isDisabled')
|
||||
).toBe(true);
|
||||
expect(wrapper.find('Button[ouiaId="edit-order"]')).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('delete Button is enabled', () => {
|
||||
test('delete Button is enabled and Edit order button is rendered', () => {
|
||||
let wrapper;
|
||||
act(() => {
|
||||
wrapper = mountWithContexts(
|
||||
@ -36,6 +38,7 @@ describe('<SurveyToolbar />', () => {
|
||||
isAllSelected
|
||||
onToggleDeleteModal={jest.fn()}
|
||||
onToggleSurvey={jest.fn()}
|
||||
onOpenOrderModal={jest.fn()}
|
||||
canEdit
|
||||
/>
|
||||
);
|
||||
@ -43,9 +46,10 @@ describe('<SurveyToolbar />', () => {
|
||||
expect(
|
||||
wrapper.find('Checkbox[aria-label="Select all"]').prop('isChecked')
|
||||
).toBe(true);
|
||||
expect(wrapper.find('Button[variant="secondary"]').prop('isDisabled')).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
wrapper.find('Button[ouiaId="survey-delete-button"]').prop('isDisabled')
|
||||
).toBe(false);
|
||||
expect(wrapper.find('Button[ouiaId="edit-order"]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('switch is off', () => {
|
||||
@ -105,8 +109,9 @@ describe('<SurveyToolbar />', () => {
|
||||
);
|
||||
expect(wrapper.find('Switch').prop('isDisabled')).toBe(true);
|
||||
expect(wrapper.find('ToolbarAddButton').prop('isDisabled')).toBe(true);
|
||||
expect(wrapper.find('Button[variant="secondary"]').prop('isDisabled')).toBe(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
wrapper.find('Button[ouiaId="survey-delete-button"]').prop('isDisabled')
|
||||
).toBe(true);
|
||||
expect(wrapper.find('Button[ouiaId="edit-order"]')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export { default as SurveyList } from './SurveyList';
|
||||
export { default as SurveyQuestionAdd } from './SurveyQuestionAdd';
|
||||
export { default as SurveyQuestionEdit } from './SurveyQuestionEdit';
|
||||
export { default as SurveyPreviewModal } from './SurveyPreviewModal';
|
||||
export { default as SurveyReorderModal } from './SurveyReorderModal';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user