Skip to content

Commit

Permalink
Merge pull request #112 from Pearson-Advance/vue/PADV-1554
Browse files Browse the repository at this point in the history
PADV-1554 - Change course/class title by a unique identifier (course_id/class_id)
  • Loading branch information
sergivalero20 authored Aug 16, 2024
2 parents 03ec792 + 5b61d4f commit 80ad2cb
Show file tree
Hide file tree
Showing 31 changed files with 209 additions and 151 deletions.
18 changes: 8 additions & 10 deletions src/features/Classes/Class/ClassPage/Actions.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { useParams, useLocation, useHistory } from 'react-router-dom';
import { useParams, useHistory } from 'react-router-dom';
import { getConfig } from '@edx/frontend-platform';

import { Button } from 'react-paragon-topaz';
Expand All @@ -17,17 +17,15 @@ import AddClass from 'features/Courses/AddClass';
import EnrollStudent from 'features/Classes/EnrollStudent';

const Actions = ({ previousPage }) => {
const location = useLocation();
const history = useHistory();
const { courseName, className } = useParams();
const { courseId, classId } = useParams();
const classIdDecoded = decodeURIComponent(classId);
const classes = useSelector((state) => state.classes.allClasses.data);

const queryParams = new URLSearchParams(location.search);
const queryClassId = queryParams.get('classId')?.replaceAll(' ', '+');
const classLink = `${getConfig().LEARNING_MICROFRONTEND_URL}/course/${queryClassId}/home`;
const classLink = `${getConfig().LEARNING_MICROFRONTEND_URL}/course/${classIdDecoded}/home`;

const [classInfo] = classes.filter(
(classElement) => classElement.classId === queryClassId,
(classElement) => classElement.classId === classIdDecoded,
);

const [isOpenEditModal, openEditModal, closeEditModal] = useToggle(false);
Expand All @@ -37,7 +35,7 @@ const Actions = ({ previousPage }) => {
const addQueryParam = useInstitutionIdQueryParam();

const handleManageButton = () => {
history.push(addQueryParam(`/manage-instructors/${courseName}/${className}?classId=${queryClassId}&previous=${previousPage}`));
history.push(addQueryParam(`/manage-instructors/${courseId}/${classId}?previous=${previousPage}`));
};

return (
Expand All @@ -51,7 +49,7 @@ const Actions = ({ previousPage }) => {
</Button>
<Button
as="a"
onClick={() => setAssignStaffRole(classLink, queryClassId)}
onClick={() => setAssignStaffRole(classLink, classIdDecoded)}
className="text-decoration-none text-white button-view-class mr-3"
>
<i className="fa-solid fa-arrow-up-right-from-square mr-2 mb-1" />
Expand Down Expand Up @@ -93,7 +91,7 @@ const Actions = ({ previousPage }) => {
isEditing
isDetailClassPage
/>
<EnrollStudent isOpen={isEnrollModalOpen} onClose={handleEnrollStudentModal} queryClassId={queryClassId} />
<EnrollStudent isOpen={isEnrollModalOpen} onClose={handleEnrollStudentModal} />
</>
);
};
Expand Down
12 changes: 6 additions & 6 deletions src/features/Classes/Class/ClassPage/__test__/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ const mockStore = {
{
learnerName: 'Test User',
learnerEmail: '[email protected]',
courseId: 'course-v1:demo+demo1+2020',
courseId: 'course-v1:XXX+YYY+2023',
courseName: 'Demo Course 1',
classId: 'ccx-v1:demo+demo1+2020+ccx@3',
classId: 'ccx-v1:XXX+YYY+2023+ccx@111',
className: 'test ccx1',
created: '2024-02-13T18:31:27.399407Z',
status: 'Active',
Expand All @@ -48,8 +48,8 @@ const mockStore = {
describe('ClassesPage', () => {
test('renders classes data and pagination', async () => {
const component = renderWithProviders(
<MemoryRouter initialEntries={['/courses/Demo%20Course%201/test%20ccx1?classId=ccx-v1:demo+demo1+2020+ccx@3']}>
<Route path="/courses/:courseName/:className">
<MemoryRouter initialEntries={[`/courses/${encodeURIComponent('course-v1:XXX+YYY+2023')}/${encodeURIComponent('ccx-v1:XXX+YYY+2023+ccx@111')}`]}>
<Route path="/courses/:courseId/:classId">
<ClassPage />
</Route>
</MemoryRouter>,
Expand All @@ -71,8 +71,8 @@ describe('ClassesPage', () => {

test('renders actions', async () => {
const { getByText, getByTestId } = renderWithProviders(
<MemoryRouter initialEntries={['/courses/Demo%20Course%201/test%20ccx1?classId=ccx-v1:demo+demo1+2020+ccx@3']}>
<Route path="/courses/:courseName/:className">
<MemoryRouter initialEntries={[`/courses/${encodeURIComponent('course-v1:XXX+YYY+2023')}/${encodeURIComponent('ccx-v1:XXX+YYY+2023+ccx@111')}`]}>
<Route path="/courses/:courseId/:classId">
<ClassPage />
</Route>
</MemoryRouter>,
Expand Down
36 changes: 24 additions & 12 deletions src/features/Classes/Class/ClassPage/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import React, { useState, useEffect, useRef } from 'react';
import React, {
useState,
useEffect,
useRef,
useMemo,
} from 'react';
import { useParams, useHistory, useLocation } from 'react-router-dom';
import { Container, Pagination } from '@edx/paragon';
import { useDispatch, useSelector } from 'react-redux';
Expand All @@ -25,11 +30,11 @@ const ClassPage = () => {
const location = useLocation();
const history = useHistory();
const dispatch = useDispatch();
const { courseName, className } = useParams();
const { courseId, classId } = useParams();
const queryParams = new URLSearchParams(location.search);
const previousPage = queryParams.get('previous') || 'classes';
const courseNameDecoded = decodeURIComponent(courseName);
const classNameDecoded = decodeURIComponent(className);
const courseIdDecoded = decodeURIComponent(courseId);
const classIdDecoded = decodeURIComponent(classId);

const institutionRef = useRef(undefined);
const [currentPage, setCurrentPage] = useState(initialPage);
Expand All @@ -44,23 +49,30 @@ const ClassPage = () => {
dispatch(updateCurrentPage(targetPage));
};

const defaultClassInfo = useMemo(() => ({
className: '',
}), []);

const classInfo = useSelector((state) => state.classes.allClasses.data)
.find((classElement) => classElement?.classId === classIdDecoded) || defaultClassInfo;

useEffect(() => {
const initialTitle = document.title;

document.title = classNameDecoded;
document.title = classIdDecoded;
// Leaves a gap time space to prevent being override by ActiveTabUpdater component
setTimeout(() => dispatch(updateActiveTab(previousPage)), 100);

return () => {
document.title = initialTitle;
};
}, [dispatch, classNameDecoded, previousPage]);
}, [dispatch, classIdDecoded, previousPage]);

useEffect(() => {
if (institution.id) {
const params = {
course_name: courseNameDecoded,
class_name: classNameDecoded,
course_id: courseIdDecoded,
class_id: classIdDecoded,
limit: true,
};

Expand All @@ -71,18 +83,18 @@ const ClassPage = () => {
dispatch(resetStudentsTable());
dispatch(updateCurrentPage(initialPage));
};
}, [dispatch, institution.id, courseNameDecoded, classNameDecoded, currentPage]);
}, [dispatch, institution.id, courseIdDecoded, classIdDecoded, currentPage]);

useEffect(() => {
if (institution.id) {
dispatch(fetchAllClassesData(institution.id, courseNameDecoded));
dispatch(fetchAllClassesData(institution.id, courseIdDecoded));
}

return () => {
dispatch(resetClassesTable());
dispatch(resetClasses());
};
}, [dispatch, institution.id, courseNameDecoded]);
}, [dispatch, institution.id, courseIdDecoded]);

useEffect(() => {
if (institution.id !== undefined && institutionRef.current === undefined) {
Expand All @@ -102,7 +114,7 @@ const ClassPage = () => {
<Button onClick={() => history.goBack()} className="mr-3 link back-arrow" variant="tertiary">
<i className="fa-solid fa-arrow-left" />
</Button>
<h3 className="h2 mb-0 course-title">Class details: {classNameDecoded}</h3>
<h3 className="h2 mb-0 course-title">Class details: {classInfo.className}</h3>
</div>
</div>

Expand Down
4 changes: 2 additions & 2 deletions src/features/Classes/ClassesTable/columns.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const columns = [
accessor: 'className',
Cell: ({ row }) => (
<LinkWithQuery
to={`/courses/${encodeURIComponent(row.original.masterCourseName)}/${encodeURIComponent(row.values.className)}?classId=${row.original.classId}&previous=classes`}
to={`/courses/${encodeURIComponent(row.original.masterCourseId)}/${encodeURIComponent(row.original.classId)}?previous=classes`}
className="text-truncate link"
>
{row.values.className}
Expand Down Expand Up @@ -125,7 +125,7 @@ const columns = [
</Dropdown.Item>
<Dropdown.Item>
<LinkWithQuery
to={`/manage-instructors/${encodeURIComponent(masterCourseName)}/${encodeURIComponent(row.values.className)}?classId=${classId}&previous=classes`}
to={`/manage-instructors/${encodeURIComponent(masterCourseId)}/${encodeURIComponent(classId)}?previous=classes`}
className="text-truncate text-decoration-none custom-text-black"
>
<i className="fa-regular fa-chalkboard-user mr-2 mb-1" />
Expand Down
32 changes: 22 additions & 10 deletions src/features/Classes/EnrollStudent/__test__/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import EnrollStudent from 'features/Classes/EnrollStudent';
import * as api from 'features/Students/data/api';

jest.mock('react-router-dom', () => ({
useParams: jest.fn(() => ({ courseName: 'Demo course', className: 'demo class' })),
useLocation: jest.fn().mockReturnValue({ search: '?classId=demo class' }),
useParams: jest.fn(() => ({ courseId: 'Demo course', classId: 'ccx-v1' })),
}));

jest.mock('features/Students/data/api', () => ({
Expand All @@ -21,15 +20,28 @@ jest.mock('@edx/frontend-platform/logging', () => ({
logError: jest.fn(),
}));

const mockStore = {
classes: {
allClasses: {
data: [
{
classId: 'ccx-v1',
className: 'demo class',
},
],
},
},
};

describe('EnrollStudent', () => {
afterEach(() => {
jest.clearAllMocks();
});

test('Should render with correct elements', () => {
const { getByText, getByPlaceholderText } = renderWithProviders(
<EnrollStudent isOpen onClose={() => {}} queryClassId="ccx1" />,
{ preloadedState: {} },
<EnrollStudent isOpen onClose={() => {}} />,
{ preloadedState: mockStore },
);

expect(getByText('Invite student to enroll')).toBeInTheDocument();
Expand All @@ -42,8 +54,8 @@ describe('EnrollStudent', () => {
const onCloseMock = jest.fn();

const { getByPlaceholderText, getByText, getByTestId } = renderWithProviders(
<EnrollStudent isOpen onClose={onCloseMock} queryClassId="ccx1" />,
{ preloadedState: {} },
<EnrollStudent isOpen onClose={onCloseMock} />,
{ preloadedState: mockStore },
);

const handleEnrollmentsMock = jest.spyOn(api, 'handleEnrollments').mockResolvedValue({
Expand Down Expand Up @@ -78,8 +90,8 @@ describe('EnrollStudent', () => {
});

const { getByPlaceholderText, getByText } = renderWithProviders(
<EnrollStudent isOpen onClose={onCloseMock} queryClassId="ccx1" />,
{ preloadedState: {} },
<EnrollStudent isOpen onClose={onCloseMock} />,
{ preloadedState: mockStore },
);

const emailInput = getByPlaceholderText('Enter email of the student to enroll');
Expand All @@ -101,8 +113,8 @@ describe('EnrollStudent', () => {
const onCloseMock = jest.fn();

const { getByPlaceholderText, getByText, getByTestId } = renderWithProviders(
<EnrollStudent isOpen onClose={onCloseMock} queryClassId="ccx1" />,
{ preloadedState: {} },
<EnrollStudent isOpen onClose={onCloseMock} />,
{ preloadedState: mockStore },
);

const handleEnrollmentsMock = jest.spyOn(api, 'handleEnrollments').mockResolvedValue({
Expand Down
28 changes: 17 additions & 11 deletions src/features/Classes/EnrollStudent/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import PropTypes from 'prop-types';
Expand All @@ -21,16 +21,23 @@ import { initialPage } from 'features/constants';

import 'features/Classes/EnrollStudent/index.scss';

const EnrollStudent = ({ isOpen, onClose, queryClassId }) => {
const EnrollStudent = ({ isOpen, onClose }) => {
const dispatch = useDispatch();

const { courseName, className } = useParams();
const { courseId, classId } = useParams();
const [showToast, setShowToast] = useState(false);
const [isLoading, setLoading] = useState(false);
const [toastMessage, setToastMessage] = useState('');
const institution = useSelector((state) => state.main.selectedInstitution);
const courseNameDecoded = decodeURIComponent(courseName);
const classNameDecoded = decodeURIComponent(className);
const courseIdDecoded = decodeURIComponent(courseId);
const classIdDecoded = decodeURIComponent(classId);

const defaultClassInfo = useMemo(() => ({
className: '',
}), []);

const classInfo = useSelector((state) => state.classes.allClasses.data)
.find((classElement) => classElement?.classId === classIdDecoded) || defaultClassInfo;

const handleEnrollStudent = async (e) => {
e.preventDefault();
Expand All @@ -48,7 +55,7 @@ const EnrollStudent = ({ isOpen, onClose, queryClassId }) => {

try {
setLoading(true);
const response = await handleEnrollments(formData, queryClassId);
const response = await handleEnrollments(formData, classIdDecoded);
const validationEmailList = response?.data?.results;
const messages = await getMessages();
const validEmails = [];
Expand Down Expand Up @@ -85,15 +92,15 @@ const EnrollStudent = ({ isOpen, onClose, queryClassId }) => {
setToastMessage(textToast);

const params = {
course_name: courseNameDecoded,
class_name: classNameDecoded,
course_id: courseIdDecoded,
class_id: classIdDecoded,
limit: true,
};

dispatch(fetchStudentsData(institution.id, initialPage, params));

// Get the classes info updated with the new number of students enrolled.
dispatch(fetchAllClassesData(institution.id, courseNameDecoded));
dispatch(fetchAllClassesData(institution.id, courseIdDecoded));

setShowToast(true);
return onClose();
Expand Down Expand Up @@ -125,7 +132,7 @@ const EnrollStudent = ({ isOpen, onClose, queryClassId }) => {
</ModalDialog.Header>
<ModalDialog.Body className="body-container h-100">
<p className="text-uppercase font-weight-bold sub-title">
Class: {classNameDecoded}
Class: {classInfo.className}
</p>
{isLoading && (
<div className="w-100 h-100 d-flex justify-content-center align-items-center">
Expand Down Expand Up @@ -166,7 +173,6 @@ const EnrollStudent = ({ isOpen, onClose, queryClassId }) => {
EnrollStudent.propTypes = {
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
queryClassId: PropTypes.string.isRequired,
};

export default EnrollStudent;
8 changes: 4 additions & 4 deletions src/features/Classes/InstructorCard/__test__/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import InstructorCard from 'features/Classes/InstructorCard';

jest.mock('react-router-dom', () => ({
useParams: jest.fn(() => ({
courseName: 'Demo course',
className: 'demo class',
classId: 'ccx-v1',
})),
useLocation: jest.fn().mockReturnValue({ search: '?classId=demo+class' }),
}));

jest.mock('@edx/frontend-platform/logging', () => ({
Expand All @@ -32,7 +30,9 @@ const stateMock = {
allClasses: {
data: [{
startDate: '2024-02-13T17:42:22Z',
classId: 'demo+class',
classId: 'ccx-v1',
className: 'demo class',
masterCourseName: 'Demo course',
instructors: ['Sam Sepiol'],
numberOfStudents: 2,
numberOfPendingStudents: 1,
Expand Down
Loading

0 comments on commit 80ad2cb

Please sign in to comment.