Merge pull request #143 from openedx/aansari/INF-156
fix: add validation for edit reason code dropdown
This commit is contained in:
@@ -10,6 +10,7 @@ import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { Button, Form, StatefulButton } from '@edx/paragon';
|
||||
|
||||
import { TinyMCEEditor } from '../../../components';
|
||||
import FormikErrorFeedback from '../../../components/FormikErrorFeedback';
|
||||
import { useDispatchWithState } from '../../../data/hooks';
|
||||
import { selectModerationSettings, selectUserIsPrivileged } from '../../data/selectors';
|
||||
import { formikCompatibleHandler, isFormikFieldInvalid } from '../../utils';
|
||||
@@ -22,14 +23,38 @@ function CommentEditor({
|
||||
onCloseEditor,
|
||||
edit,
|
||||
}) {
|
||||
const editorRef = useRef(null);
|
||||
const { authenticatedUser } = useContext(AppContext);
|
||||
const userIsPrivileged = useSelector(selectUserIsPrivileged);
|
||||
const { reasonCodesEnabled, editReasons } = useSelector(selectModerationSettings);
|
||||
const [submitting, dispatch] = useDispatchWithState();
|
||||
const editorRef = useRef(null);
|
||||
|
||||
const canDisplayEditReason = (reasonCodesEnabled && userIsPrivileged
|
||||
&& edit && comment.author !== authenticatedUser.username
|
||||
);
|
||||
|
||||
const editReasonCodeValidation = canDisplayEditReason && {
|
||||
editReasonCode: Yup.string().required(intl.formatMessage(messages.editReasonCodeError)),
|
||||
};
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
comment: Yup.string()
|
||||
.required(),
|
||||
...editReasonCodeValidation,
|
||||
});
|
||||
|
||||
const initialValues = {
|
||||
comment: comment.rawBody,
|
||||
editReasonCode: comment?.lastEdit?.reasonCode || '',
|
||||
};
|
||||
|
||||
const saveUpdatedComment = async (values) => {
|
||||
if (comment.id) {
|
||||
await dispatch(editComment(comment.id, values));
|
||||
const payload = {
|
||||
...values,
|
||||
editReasonCode: values.editReasonCode || undefined,
|
||||
};
|
||||
await dispatch(editComment(comment.id, payload));
|
||||
} else {
|
||||
await dispatch(addComment(values.comment, comment.threadId, comment.parentId));
|
||||
}
|
||||
@@ -42,17 +67,11 @@ function CommentEditor({
|
||||
// The editorId is used to autosave contents to localstorage. This format means that the autosave is scoped to
|
||||
// the current comment id, or the current comment parent or the curren thread.
|
||||
const editorId = `comment-editor-${comment.id || comment.parentId || comment.threadId}`;
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{ comment: comment.rawBody }}
|
||||
validationSchema={Yup.object()
|
||||
.shape({
|
||||
comment: Yup.string()
|
||||
.required(),
|
||||
editReasonCode: Yup.string()
|
||||
.nullable()
|
||||
.default(undefined),
|
||||
})}
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={saveUpdatedComment}
|
||||
>
|
||||
{({
|
||||
@@ -64,12 +83,13 @@ function CommentEditor({
|
||||
handleChange,
|
||||
}) => (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
{(reasonCodesEnabled
|
||||
&& userIsPrivileged
|
||||
&& comment.author !== authenticatedUser.username
|
||||
&& edit
|
||||
) && (
|
||||
<Form.Group>
|
||||
{canDisplayEditReason && (
|
||||
<Form.Group
|
||||
isInvalid={isFormikFieldInvalid('editReasonCode', {
|
||||
errors,
|
||||
touched,
|
||||
})}
|
||||
>
|
||||
<Form.Control
|
||||
name="editReasonCode"
|
||||
className="mt-2"
|
||||
@@ -88,6 +108,7 @@ function CommentEditor({
|
||||
<option key={code} value={code}>{label}</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
<FormikErrorFeedback name="editReasonCode" />
|
||||
</Form.Group>
|
||||
)}
|
||||
<TinyMCEEditor
|
||||
@@ -142,6 +163,7 @@ CommentEditor.propTypes = {
|
||||
parentId: PropTypes.string,
|
||||
rawBody: PropTypes.string,
|
||||
author: PropTypes.string,
|
||||
lastEdit: PropTypes.object,
|
||||
}).isRequired,
|
||||
onCloseEditor: PropTypes.func.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
|
||||
@@ -26,7 +26,7 @@ const messages = defineMessages({
|
||||
},
|
||||
endorsedResponseCount: {
|
||||
id: 'discussions.comments.comment.endorsedResponseCount',
|
||||
defaultMessage: `{num, plural,
|
||||
defaultMessage: `{num, plural,
|
||||
=0 {No endorsed responses}
|
||||
one {Showing # endorsed response}
|
||||
other {Showing # endorsed responses}
|
||||
@@ -147,6 +147,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Reason for editing',
|
||||
description: 'Label for field visible to moderators that allows them to select a reason for editing another user\'s response',
|
||||
},
|
||||
editReasonCodeError: {
|
||||
id: 'discussions.editor.posts.editReasonCode.error',
|
||||
defaultMessage: 'Select reason for editing',
|
||||
description: 'Error message visible to moderators when they submit the post/response/comment without select reason for editing',
|
||||
},
|
||||
editedBy: {
|
||||
id: 'discussions.comment.comments.editedBy',
|
||||
defaultMessage: 'Edited by',
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useContext, useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Formik } from 'formik';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
||||
import * as Yup from 'yup';
|
||||
@@ -83,14 +84,21 @@ function PostEditor({
|
||||
const nonCoursewareTopics = useSelector(selectNonCoursewareTopics);
|
||||
const nonCoursewareIds = useSelector(selectNonCoursewareIds);
|
||||
const coursewareTopics = useSelector(selectCoursewareTopics);
|
||||
const {
|
||||
allowAnonymous,
|
||||
allowAnonymousToPeers,
|
||||
} = useSelector(selectAnonymousPostingConfig);
|
||||
const cohorts = useSelector(selectCourseCohorts);
|
||||
const post = useSelector(selectThread(postId));
|
||||
const userIsPrivileged = useSelector(selectUserIsPrivileged);
|
||||
const settings = useSelector(selectDivisionSettings);
|
||||
const { allowAnonymous, allowAnonymousToPeers } = useSelector(selectAnonymousPostingConfig);
|
||||
const { reasonCodesEnabled, editReasons } = useSelector(selectModerationSettings);
|
||||
|
||||
const canDisplayEditReason = (reasonCodesEnabled && editExisting
|
||||
&& userIsPrivileged && post.author !== authenticatedUser.username
|
||||
);
|
||||
|
||||
const editReasonCodeValidation = canDisplayEditReason && {
|
||||
editReasonCode: Yup.string().required(intl.formatMessage(messages.editReasonCodeError)),
|
||||
};
|
||||
|
||||
const canSelectCohort = (tId) => {
|
||||
// If the user isn't privileged, they can't edit the cohort.
|
||||
// If the topic is being edited the cohort can't be changed.
|
||||
@@ -164,60 +172,48 @@ function PostEditor({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let initialValues = {
|
||||
postType: 'discussion',
|
||||
topic: topicId || nonCoursewareTopics?.[0]?.id,
|
||||
title: '',
|
||||
comment: '',
|
||||
follow: true,
|
||||
anonymous: false,
|
||||
anonymousToPeers: false,
|
||||
|
||||
const initialValues = {
|
||||
postType: post?.type || 'discussion',
|
||||
topic: post?.topicId || topicId || nonCoursewareTopics?.[0]?.id,
|
||||
title: post?.title || '',
|
||||
comment: post?.rawBody || '',
|
||||
follow: isEmpty(post?.following) ? true : post?.following,
|
||||
anonymous: allowAnonymous ? false : undefined,
|
||||
anonymousToPeers: allowAnonymousToPeers ? false : undefined,
|
||||
editReasonCode: post?.lastEdit?.reasonCode || '',
|
||||
};
|
||||
if (editExisting) {
|
||||
initialValues = {
|
||||
postType: post.type,
|
||||
topic: post.topicId,
|
||||
title: post.title,
|
||||
comment: post.rawBody,
|
||||
follow: (post.following === null || post.following === undefined) ? true : post.following,
|
||||
anonymous: allowAnonymous ? false : undefined,
|
||||
anonymousToPeers: allowAnonymousToPeers ? false : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
postType: Yup.mixed()
|
||||
.oneOf(['discussion', 'question']),
|
||||
topic: Yup.string()
|
||||
.required(),
|
||||
title: Yup.string()
|
||||
.required(intl.formatMessage(messages.titleError)),
|
||||
comment: Yup.string()
|
||||
.required(intl.formatMessage(messages.commentError)),
|
||||
follow: Yup.bool()
|
||||
.default(true),
|
||||
anonymous: Yup.bool()
|
||||
.default(false)
|
||||
.nullable(),
|
||||
anonymousToPeers: Yup.bool()
|
||||
.default(false)
|
||||
.nullable(),
|
||||
cohort: Yup.string()
|
||||
.nullable()
|
||||
.default(null),
|
||||
...editReasonCodeValidation,
|
||||
});
|
||||
|
||||
const postEditorId = `post-editor-${editExisting ? postId : 'new'}`;
|
||||
|
||||
const { reasonCodesEnabled, editReasons } = useSelector(selectModerationSettings);
|
||||
|
||||
return (
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
validationSchema={Yup.object()
|
||||
.shape({
|
||||
postType: Yup.mixed()
|
||||
.oneOf(['discussion', 'question']),
|
||||
topic: Yup.string()
|
||||
.required(),
|
||||
title: Yup.string()
|
||||
.required(intl.formatMessage(messages.titleError)),
|
||||
comment: Yup.string()
|
||||
.required(intl.formatMessage(messages.commentError)),
|
||||
follow: Yup.bool()
|
||||
.default(true),
|
||||
anonymous: Yup.bool()
|
||||
.default(false)
|
||||
.nullable(),
|
||||
anonymousToPeers: Yup.bool()
|
||||
.default(false)
|
||||
.nullable(),
|
||||
cohort: Yup.string()
|
||||
.nullable()
|
||||
.default(null),
|
||||
editReasonCode: Yup.string()
|
||||
.nullable()
|
||||
.default(null),
|
||||
})}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={submitForm}
|
||||
>{
|
||||
({
|
||||
@@ -304,7 +300,7 @@ function PostEditor({
|
||||
<div className="border-bottom my-1" />
|
||||
<div className="d-flex flex-row py-2 mt-4 justify-content-between">
|
||||
<Form.Group
|
||||
className="d-flex flex-fill"
|
||||
className="w-100"
|
||||
isInvalid={isFormikFieldInvalid('title', {
|
||||
errors,
|
||||
touched,
|
||||
@@ -321,27 +317,31 @@ function PostEditor({
|
||||
/>
|
||||
<FormikErrorFeedback name="title" />
|
||||
</Form.Group>
|
||||
{(reasonCodesEnabled
|
||||
&& editExisting
|
||||
&& userIsPrivileged
|
||||
&& post.author !== authenticatedUser.username) && (
|
||||
<Form.Group className="d-flex flex-fill">
|
||||
<Form.Control
|
||||
name="editReasonCode"
|
||||
className="ml-4"
|
||||
as="select"
|
||||
value={values.editReasonCode}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
aria-describedby="editReasonCodeInput"
|
||||
floatingLabel={intl.formatMessage(messages.editReasonCode)}
|
||||
>
|
||||
<option key="empty" value="">---</option>
|
||||
{editReasons.map(({ code, label }) => (
|
||||
<option key={code} value={code}>{label}</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
{canDisplayEditReason && (
|
||||
<Form.Group
|
||||
className="w-100"
|
||||
isInvalid={isFormikFieldInvalid('editReasonCode', {
|
||||
errors,
|
||||
touched,
|
||||
})}
|
||||
>
|
||||
<Form.Control
|
||||
name="editReasonCode"
|
||||
className="m-0"
|
||||
as="select"
|
||||
value={values.editReasonCode}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
aria-describedby="editReasonCodeInput"
|
||||
floatingLabel={intl.formatMessage(messages.editReasonCode)}
|
||||
>
|
||||
<option key="empty" value="">---</option>
|
||||
{editReasons.map(({ code, label }) => (
|
||||
<option key={code} value={code}>{label}</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
<FormikErrorFeedback name="editReasonCode" />
|
||||
</Form.Group>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-2">
|
||||
|
||||
@@ -106,6 +106,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Reason for editing',
|
||||
description: 'Label for field visible to moderators that allows them to select a reason for editing another user\'s post',
|
||||
},
|
||||
editReasonCodeError: {
|
||||
id: 'discussions.editor.posts.editReasonCode.error',
|
||||
defaultMessage: 'Select reason for editing',
|
||||
description: 'Error message visible to moderators when they submit the post/response/comment without select reason for editing',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
Reference in New Issue
Block a user