Skip to content

Commit

Permalink
fix: solve issues found when testing use cases (#17)
Browse files Browse the repository at this point in the history
* refactor: add course id to the courses list
* refactor: sort the courses list alphabetically
* refactor: show user in each validation process event
* refactor: show reason field only when the is a reason
* refactor: improve some labels, messages & remove properties unused
* refactor: sort past events according to the date of creation
* refactor: remove reason column from main table
* fix: receive only the courses current user is staff
* fix: show course author properly in the validation process
* refactor: change category field value to number
* fix: add custom option to allow using form fields
* refactor: add subtitle to clarify the table source
* refactor: clarify some messages
* feat: update validation bodies according with course id
* fix: styles in course selector and fix a typo
* refactor: show username if user have no name
* feat: update validation bodies according with course id

---------
Co-authored-by: Diana Catalina Olarte <diana.olarte@edunext.co>
  • Loading branch information
bra-i-am authored Oct 6, 2023
1 parent c46cb59 commit b0f6f3f
Show file tree
Hide file tree
Showing 16 changed files with 185 additions and 64 deletions.
5 changes: 4 additions & 1 deletion src/ValidationPanelPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ const ValidationPanelPage = () => {
: (
<div className="container-lg">
<Stack direction="horizontal" className="my-5 justify-content-between">
<h1>{intl.formatMessage(messages.heading)}</h1>
<div>
<h1>{intl.formatMessage(messages.heading)}</h1>
<h3>{isValidator ? 'Course validation processes' : 'My course validation processes' }</h3>
</div>
{!isValidator && <Button onClick={open} variant="brand">{intl.formatMessage(messages.newRecordCreatorButton)}</Button>}
</Stack>
<ValidationTableLayout
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import * as Yup from 'yup';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { FormikProvider, useFormik } from 'formik';
import { Button, Stack } from '@edx/paragon';
import { useDispatch, useSelector } from 'react-redux';
Expand All @@ -13,13 +13,14 @@ import {

import { SelectField } from '../SelectField';
import { ModalLayout } from '../ModalLayout';
import { getAdaptedData, getCourseValidationRequestForm } from './helpers';
import { getCourseValidationRequestForm } from './helpers';
import { getValidationBodies } from '../../data/api';

const CourseValidationRequestForm = ({ isOpen, close }) => {
const dispatch = useDispatch();

const availableUserCourses = useSelector((state) => state.courses.availableUserCourses.data.results);
const availableValidationBodies = useSelector((state) => state.validationBody.availableValidationBodies.data);
const [availableValidationBodies, setAvailableValidationBodies] = useState([]);
const availableCourseCategories = useSelector((state) => (
state.courseCategories.availableCourseCategories.data));

Expand All @@ -38,13 +39,17 @@ const CourseValidationRequestForm = ({ isOpen, close }) => {
courseId: null,
comment: '',
validationBodyId: null,
categoryIds: [],
// When is needed category as array
// categoryIds: [],
categoryId: null,
};

const FormSchema = Yup.object().shape({
courseId: Yup.string().required('Please select a course!'),
validationBodyId: Yup.number().required('Please select a validation body!'),
categoryIds: Yup.array().of(Yup.string()).min(1, 'Please select at least one category!'),
// When is needed category as array
// categoryIds: Yup.array().of(Yup.string()).min(1, 'Please select at least one category!'),
categoryId: Yup.number().required('Please select at least one category!'),
comment: Yup.string().required('Please insert at least a short description about your submission'),
});

Expand All @@ -53,7 +58,7 @@ const CourseValidationRequestForm = ({ isOpen, close }) => {
validationSchema: FormSchema,
onSubmit: async (formData) => {
const { error } = await dispatch(
createValidationProcess(getAdaptedData(formData, availableCourseCategories)),
createValidationProcess(formData),
);

// eslint-disable-next-line no-use-before-define
Expand All @@ -65,6 +70,20 @@ const CourseValidationRequestForm = ({ isOpen, close }) => {
},
});

useEffect(() => {
let isCurrent = true;
if (formik.values.courseId) {
getValidationBodies(formik.values.courseId).then((data) => {
if (isCurrent) {
setAvailableValidationBodies(data);
}
});
}
return () => {
isCurrent = false;
};
}, [formik.values.courseId, setAvailableValidationBodies, dispatch]);

const handleClose = () => {
close();
formik.resetForm();
Expand Down
20 changes: 10 additions & 10 deletions src/components/CourseValidationRequestForm/helpers.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
/* eslint-disable import/prefer-default-export */
import { adaptOptions } from '../../utils/helpers';

export const getAdaptedData = (formData, availableCourseCategories) => ({
...formData,
categoryIds: formData.categoryIds.map((categoryName) => (
availableCourseCategories.find(category => category.name === categoryName).id)),
});

export const getCourseValidationRequestForm = (
availableUserCourses,
availableCourseCategories,
Expand All @@ -19,7 +14,11 @@ export const getCourseValidationRequestForm = (
description: 'Please select one of your courses from the list below',
// prop when 'as' property is 'select'
// This properties come from API or Constants
options: adaptOptions(availableUserCourses),
options: adaptOptions(availableUserCourses)?.sort((a, b) => {
if (a.label.toLowerCase() > b.label.toLowerCase()) { return 1; }
if (a.label.toLowerCase() < b.label.toLowerCase()) { return -1; }
return 0;
}),
},
{
name: 'validationBodyId',
Expand All @@ -29,9 +28,10 @@ export const getCourseValidationRequestForm = (
options: adaptOptions(availableValidationBodies),
},
{
name: 'categoryIds',
isArray: true,
as: 'select',
name: 'categoryId',
// When is needed category as array
// isArray: true,
as: 'autosuggest',
label: 'Category',
description: 'Please select the appropriate category for your course',
options: adaptOptions(availableCourseCategories),
Expand Down
43 changes: 41 additions & 2 deletions src/components/SelectField/SelectField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ const SelectField = ({
{options?.map((optionInfo) => (
<Form.AutosuggestOption
key={optionInfo.key}
value={optionInfo.id}
// eslint-disable-next-line no-use-before-define
as={CustomOption}
optionId={optionInfo.id}
label={label}
>
{optionInfo.label}
</Form.AutosuggestOption>
Expand Down Expand Up @@ -131,7 +134,43 @@ SelectField.defaultProps = {
errorMessage: null,
isArray: false,
disabled: false,
setFieldValue: () => {},
setFieldValue: () => { },
};

const CustomOption = ({
className, onClick, value, optionId, label, children,
}) => (
<Stack
className={className}
onClick={(e) => {
e.currentTarget = { value };
onClick(e);
}}
>
<span>
{children}
</span>
{label.toLowerCase().includes('course') && (
<p className="text-gray x-small m-0">
{optionId}
</p>
)}
</Stack>
);

CustomOption.propTypes = {
className: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
value: PropTypes.string,
optionId: PropTypes.oneOfType(PropTypes.string, PropTypes.number),
label: PropTypes.string,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]).isRequired,
};

CustomOption.defaultProps = {
value: '',
optionId: '',
label: '',
};

export default SelectField;
35 changes: 26 additions & 9 deletions src/components/Timeline/RecordItem.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import { FormattedDate, FormattedMessage } from '@edx/frontend-platform/i18n';
import { VALIDATION_STATUS_LABEL } from '../../data/constants';
import { VALIDATION_STATUS, VALIDATION_STATUS_LABEL } from '../../data/constants';

const bold = (str) => <b>{str}</b>;

Expand All @@ -12,6 +12,7 @@ const RecordItem = ({
comment,
first,
last,
user,
}) => (
<li className="record-item pb-4 px-5">
{/* Top Line */}
Expand Down Expand Up @@ -56,6 +57,16 @@ const RecordItem = ({
status: VALIDATION_STATUS_LABEL[status],
}}
/>
<FormattedMessage
id="record.item.validation.reportedBy"
defaultMessage="<b>Reported by</b>: {user}"
description="reportedBy of the record"
tagName="div"
values={{
b: bold,
user,
}}
/>
<FormattedMessage
id="record.item.validation.body"
defaultMessage="<b>Validator body</b>: {validationBody}"
Expand All @@ -66,18 +77,22 @@ const RecordItem = ({
validationBody,
}}
/>
<FormattedMessage
id="record.item.validation.reason"
defaultMessage="Reason: "
description="identify who was in charge to validate the course"
tagName="b"
/>
{reason}
{status === VALIDATION_STATUS.DISAPPROVED && (
<div>
<FormattedMessage
id="record.item.validation.reason"
defaultMessage="Reason: "
description="identify who was in charge to validate the course"
tagName="b"
/>
{reason}
</div>
)}
</div>
<div className="mt-2">
<FormattedMessage
id="record.item.validation.comment"
defaultMessage="Comments: "
defaultMessage="Info: "
description="Shown the label for additional information left for the validator"
tagName="b"
/>
Expand All @@ -96,13 +111,15 @@ RecordItem.propTypes = {
comment: PropTypes.string,
first: PropTypes.bool,
last: PropTypes.bool,
user: PropTypes.string,
};

RecordItem.defaultProps = {
comment: null,
reason: null,
first: false,
last: false,
user: '',
};

export default RecordItem;
35 changes: 25 additions & 10 deletions src/components/Timeline/Timeline.jsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
import PropTypes from 'prop-types';
import RecordItem from './RecordItem';

const Timeline = ({ pastProcessEvents, validationBody }) => (
const Timeline = ({ pastProcessEvents, validationBody, availableReasons }) => (
<ul className="px-2">
{pastProcessEvents.map((processEvent, index) => (
<RecordItem
key={`validationEvent-${processEvent.createdAt}-${processEvent.user}`}
{...processEvent}
validationBody={validationBody}
first={index === 0}
last={index === pastProcessEvents.length - 1}
/>
))}
{pastProcessEvents.map((processEvent, index) => {
const processEventWithReasonName = {
...processEvent,
reason: availableReasons?.find((reason) => reason.id === processEvent?.reason)?.name,
};
return (
<RecordItem
key={`validationEvent-${processEvent.createdAt}-${processEvent.user}`}
{...processEventWithReasonName}
validationBody={validationBody}
first={index === 0}
last={index === pastProcessEvents.length - 1}
/>
);
})}
</ul>
);

Timeline.propTypes = {
pastProcessEvents: PropTypes.arrayOf(Object),
validationBody: PropTypes.string.isRequired,
availableReasons: PropTypes.arrayOf(
PropTypes.shape(
{
name: PropTypes.string,
id: PropTypes.number,
},
),
),
};

Timeline.defaultProps = {
pastProcessEvents: [],
availableReasons: [],
};

export default Timeline;
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const submissionFieldUtilProps = [
name: 'courseAuthor', label: 'Course Author', type: 'col', pos: 4,
},
{
name: 'categories', label: 'Categories', type: 'col', pos: 5,
name: 'categories', label: 'Category', type: 'col', pos: 5,
},
{
name: 'submissionComments', label: 'Submission Comments', type: 'row', pos: 6,
Expand Down
4 changes: 4 additions & 0 deletions src/components/ValidationProcess/FormLayout/FormField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const FormField = ({
const isDisabled = !isValidator || field.disabled || (field?.name === 'reason' && disableReason);
const isColumn = field.type === 'col';

if (!isValidator && field.name === 'reason' && !values[field.name]) {
return null;
}

return (
<div style={{ width: isColumn ? COL_WIDTH : FULL_WIDTH }} key={field.name}>
{field.isSelect ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ const ValidatorReview = ({
<div>
{(!isValidator || isReviewConfirmed) && (
<FormLayout
isValidator={isValidator}
data={lastValidationReviewInfoWithUtilsProps}
onSubmit={handleSubmit}
onCancel={onClose}
Expand Down
13 changes: 11 additions & 2 deletions src/components/ValidationTable/ValidationTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,13 @@ const ValidationTable = ({ data, isLoading }) => {
}, [data?.length, courseIdsCurrentUserIsReviewing.length]);

const currentValidationRecord = useSelector((state) => state.currentValidationRecord);
const sortedCurrentValidationRecordEvents = [...currentValidationRecord?.validationProcessEvents || []].sort(
(a, b) => {
if (a.id > b.id) { return -1; }
if (a.id < b.id) { return 1; }
return 0;
},
);

return (
<>
Expand All @@ -171,10 +178,11 @@ const ValidationTable = ({ data, isLoading }) => {
},
{
name: 'past_processes',
label: 'Past process(es)',
label: 'Past events',
component: <PastProcesses
pastProcessEvents={currentValidationRecord.validationProcessEvents}
pastProcessEvents={sortedCurrentValidationRecordEvents}
validationBody={currentValidationRecord?.validationBody}
availableReasons={availableReasons}
/>,
},
]}
Expand All @@ -189,6 +197,7 @@ const ValidationTable = ({ data, isLoading }) => {
columns={columnsWithClickableNames}
additionalColumns={data.length ? [
{
Header: 'Action',
Cell: ({ row }) => {
const userPermission = isValidator ? VALIDATION_ACCESS_ROLE.VALIDATOR : VALIDATION_ACCESS_ROLE.AUTHOR;
const isInReview = row.values.status === VALIDATION_STATUS_LABEL.revi;
Expand Down
Loading

0 comments on commit b0f6f3f

Please sign in to comment.