diff --git a/.eslintignore b/.eslintignore
index 9a23d71e..f36e16b8 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -3,3 +3,4 @@ dist/
node_modules/
jest.config.js
.eslintrc.js
+test-utils.jsx
diff --git a/.eslintrc.js b/.eslintrc.js
index 7c44dd07..50d24fdc 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,4 +1,3 @@
-// eslint-disable-next-line import/no-extraneous-dependencies
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('eslint', {
diff --git a/package-lock.json b/package-lock.json
index 23369d92..806a55f5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,7 +18,7 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@reduxjs/toolkit": "^1.9.5",
"core-js": "3.31.0",
- "moment": "^2.29.4",
+ "date-fns": "^3.3.1",
"prop-types": "15.8.1",
"react": "16.14.0",
"react-dom": "16.14.0",
@@ -9295,6 +9295,15 @@
"node": ">=10"
}
},
+ "node_modules/date-fns": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz",
+ "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -17887,14 +17896,6 @@
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
},
- "node_modules/moment": {
- "version": "2.29.4",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
- "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
- "engines": {
- "node": "*"
- }
- },
"node_modules/mrmime": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz",
diff --git a/package.json b/package.json
index a8f06228..101b134c 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@reduxjs/toolkit": "^1.9.5",
"core-js": "3.31.0",
- "moment": "^2.29.4",
+ "date-fns": "^3.3.1",
"prop-types": "15.8.1",
"react": "16.14.0",
"react-dom": "16.14.0",
diff --git a/src/assets/colors.scss b/src/assets/colors.scss
index 758f4730..3faa622a 100644
--- a/src/assets/colors.scss
+++ b/src/assets/colors.scss
@@ -5,6 +5,7 @@ $color-black: #020917;
$black-80: #333;
$color-white: #fefefe;
$color-gray: #60646d;
+$gray-70: #666666;
$gray-60: #808080;
$gray-20: #dfe1e1;
$color-pink: #ffecf0;
diff --git a/src/features/Common/data/_test_/api.test.js b/src/features/Common/data/_test_/api.test.js
index 39baa76c..b9849b49 100644
--- a/src/features/Common/data/_test_/api.test.js
+++ b/src/features/Common/data/_test_/api.test.js
@@ -1,5 +1,9 @@
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
-import { getCoursesByInstitution, getLicensesByInstitution } from 'features/Common/data/api';
+import {
+ getCoursesByInstitution,
+ getLicensesByInstitution,
+ getClassesByInstitution,
+} from 'features/Common/data/api';
jest.mock('@edx/frontend-platform/auth', () => ({
getAuthenticatedHttpClient: jest.fn(),
@@ -13,6 +17,7 @@ jest.mock('@edx/frontend-platform', () => ({
}));
describe('Common api services', () => {
+ const COURSE_OPERATIONS_API_V2 = 'http://localhost:18000/pearson_course_operation/api/v2';
test('should call getCoursesByInstitution with the correct parameters', () => {
const httpClientMock = {
get: jest.fn(),
@@ -30,7 +35,7 @@ describe('Common api services', () => {
expect(httpClientMock.get).toHaveBeenCalledTimes(1);
expect(httpClientMock.get).toHaveBeenCalledWith(
- 'http://localhost:18000/pearson_course_operation/api/v2/courses/?limit=true&institution_id=1',
+ `${COURSE_OPERATIONS_API_V2}/courses/?limit=true&institution_id=1`,
{ params: { page } },
);
});
@@ -51,7 +56,28 @@ describe('Common api services', () => {
expect(httpClientMock.get).toHaveBeenCalledTimes(1);
expect(httpClientMock.get).toHaveBeenCalledWith(
- 'http://localhost:18000/pearson_course_operation/api/v2/license-pool/?limit=true&institution_id=1',
+ `${COURSE_OPERATIONS_API_V2}/license-pool/?limit=true&institution_id=1`,
+ );
+ });
+
+ test('should call getClassesByInstitution with the correct parameters', () => {
+ const httpClientMock = {
+ get: jest.fn(),
+ };
+
+ const institutionId = 1;
+ const courseName = 'ccx1';
+
+ getAuthenticatedHttpClient.mockReturnValue(httpClientMock);
+
+ getClassesByInstitution(institutionId, courseName);
+
+ expect(getAuthenticatedHttpClient).toHaveBeenCalledTimes(3);
+ expect(getAuthenticatedHttpClient).toHaveBeenCalledWith();
+
+ expect(httpClientMock.get).toHaveBeenCalledTimes(1);
+ expect(httpClientMock.get).toHaveBeenCalledWith(
+ `${COURSE_OPERATIONS_API_V2}/classes/?limit=false&institution_id=1&course_name=ccx1&instructors=`,
);
});
});
diff --git a/src/features/Common/data/api.js b/src/features/Common/data/api.js
index ab26bcf4..c8f5e1b5 100644
--- a/src/features/Common/data/api.js
+++ b/src/features/Common/data/api.js
@@ -18,7 +18,17 @@ function getLicensesByInstitution(institutionId, limit) {
);
}
+function getClassesByInstitution(institutionId, courseName, limit = false, instructorsList = '') {
+ const encodedCourseName = encodeURIComponent(courseName);
+
+ return getAuthenticatedHttpClient().get(
+ `${getConfig().COURSE_OPERATIONS_API_V2_BASE_URL}/classes`
+ + `/?limit=${limit}&institution_id=${institutionId}&course_name=${encodedCourseName}&instructors=${instructorsList}`,
+ );
+}
+
export {
getCoursesByInstitution,
getLicensesByInstitution,
+ getClassesByInstitution,
};
diff --git a/src/features/Dashboard/DashboardPage/_test_/index.test.jsx b/src/features/Dashboard/DashboardPage/_test_/index.test.jsx
index 225d5e2e..feac9ce9 100644
--- a/src/features/Dashboard/DashboardPage/_test_/index.test.jsx
+++ b/src/features/Dashboard/DashboardPage/_test_/index.test.jsx
@@ -1,28 +1,50 @@
import React from 'react';
-import { render } from '@testing-library/react';
import DashboardPage from 'features/Dashboard/DashboardPage';
import '@testing-library/jest-dom/extend-expect';
-import { Provider } from 'react-redux';
-import { initializeStore } from 'store';
-
-let store;
-
-jest.mock('axios');
+import { renderWithProviders } from 'test-utils';
jest.mock('@edx/frontend-platform/logging', () => ({
logError: jest.fn(),
}));
describe('DashboardPage component', () => {
- beforeEach(() => {
- store = initializeStore();
- });
+ const mockStore = {
+ dashboard: {
+ tableLicense: {
+ data: [
+ {
+ licenseName: 'License Name 1',
+ purchasedSeats: 20,
+ numberOfStudents: 6,
+ numberOfPendingStudents: 11,
+ },
+ ],
+ },
+ classes: {
+ data: [
+ {
+ classId: 'ccx-v1:demo+demo1+2020+ccx1',
+ className: 'ccx 1',
+ masterCourseName: 'Demo Course 1',
+ instructors: [],
+ numberOfStudents: 0,
+ numberOfPendingStudents: 0,
+ maxStudents: 20,
+ startDate: '2024-01-23T21:50:51Z',
+ endDate: null,
+ },
+ ],
+ },
+ },
+ };
+
+ const component = renderWithProviders(
+ ,
+ { preloadedState: mockStore },
+ );
+
test('renders components', () => {
- const { getByText } = render(
-
-
- ,
- );
+ const { getByText } = component;
expect(getByText('This week')).toBeInTheDocument();
expect(getByText('Next week')).toBeInTheDocument();
@@ -30,5 +52,9 @@ describe('DashboardPage component', () => {
expect(getByText('New students registered')).toBeInTheDocument();
expect(getByText('Classes scheduled')).toBeInTheDocument();
expect(getByText('License inventory')).toBeInTheDocument();
+ expect(getByText('Instructor assignment')).toBeInTheDocument();
+ expect(getByText('ccx 1')).toBeInTheDocument();
+ expect(getByText('Demo Course 1')).toBeInTheDocument();
+ expect(getByText('License Name 1')).toBeInTheDocument();
});
});
diff --git a/src/features/Dashboard/DashboardPage/index.jsx b/src/features/Dashboard/DashboardPage/index.jsx
index 5067089d..a7af99b4 100644
--- a/src/features/Dashboard/DashboardPage/index.jsx
+++ b/src/features/Dashboard/DashboardPage/index.jsx
@@ -2,10 +2,11 @@ import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
-import { Container } from '@edx/paragon';
+import { Container, Col, Row } from '@edx/paragon';
import StudentsMetrics from 'features/Students/StudentsMetrics';
import LicensesTable from 'features/Licenses/LicensesTable';
import { Button } from 'react-paragon-topaz';
+import InstructorAssignSection from 'features/Dashboard/InstructorAssignSection';
import { fetchLicensesData } from 'features/Dashboard/data';
import { updateActiveTab } from 'features/Main/data/slice';
@@ -50,13 +51,22 @@ const DashboardPage = () => {
{stateInstitution.length === 1 ? `Welcome to ${stateInstitution[0].name}` : 'Select an institution'}
-
-
-
License inventory
-
-
-
-
+
+
+
+
+
License inventory
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/src/features/Dashboard/DashboardPage/index.scss b/src/features/Dashboard/DashboardPage/index.scss
index 38cf436e..6f9ccdab 100644
--- a/src/features/Dashboard/DashboardPage/index.scss
+++ b/src/features/Dashboard/DashboardPage/index.scss
@@ -4,3 +4,13 @@
background-color: $color-white;
padding: 2rem 0;
}
+
+.instructor-assign-section {
+ background-color: $color-white;
+}
+
+.license-section,
+.instructor-assign-section {
+ box-shadow: 0px 3px 12px 0px rgba(0, 0, 0, 0.16);
+ border-radius: 0.375rem;
+}
diff --git a/src/features/Dashboard/InstructorAssignSection/ClassCard.jsx b/src/features/Dashboard/InstructorAssignSection/ClassCard.jsx
new file mode 100644
index 00000000..6748da8b
--- /dev/null
+++ b/src/features/Dashboard/InstructorAssignSection/ClassCard.jsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { format } from 'date-fns';
+import PropTypes from 'prop-types';
+
+import { Button } from 'react-paragon-topaz';
+
+import 'features/Dashboard/InstructorAssignSection/index.scss';
+
+const ClassCard = ({ data }) => {
+ const fullDate = format(new Date(data.startDate), 'PP');
+
+ return (
+
+
{data?.className}
+
{data?.masterCourseName}
+
{fullDate}
+
+
+ );
+};
+
+ClassCard.propTypes = {
+ data: PropTypes.arrayOf(PropTypes.shape([])),
+};
+
+ClassCard.defaultProps = {
+ data: [],
+};
+
+export default ClassCard;
diff --git a/src/features/Dashboard/InstructorAssignSection/_test_/index.test.jsx b/src/features/Dashboard/InstructorAssignSection/_test_/index.test.jsx
new file mode 100644
index 00000000..c98517b1
--- /dev/null
+++ b/src/features/Dashboard/InstructorAssignSection/_test_/index.test.jsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import InstructorAssignSection from 'features/Dashboard/InstructorAssignSection';
+import '@testing-library/jest-dom/extend-expect';
+import { renderWithProviders } from 'test-utils';
+
+jest.mock('@edx/frontend-platform/logging', () => ({
+ logError: jest.fn(),
+}));
+
+describe('Instructor Assign component', () => {
+ const mockStore = {
+ dashboard: {
+ classes: {
+ data: [
+ {
+ classId: 'ccx-v1:demo+demo1+2020+ccx1',
+ className: 'ccx 1',
+ masterCourseName: 'Demo Course 1',
+ instructors: [],
+ numberOfStudents: 0,
+ numberOfPendingStudents: 0,
+ maxStudents: 20,
+ startDate: '2024-01-23T21:50:51Z',
+ endDate: null,
+ },
+ ],
+ },
+ },
+ };
+ const component = renderWithProviders(
+ ,
+ { preloadedState: mockStore },
+ );
+
+ test('renders components', () => {
+ const { getByText } = component;
+
+ expect(getByText('Instructor assignment')).toBeInTheDocument();
+ expect(getByText('ccx 1')).toBeInTheDocument();
+ expect(getByText('Demo Course 1')).toBeInTheDocument();
+ expect(getByText('Jan 23, 2024')).toBeInTheDocument();
+ expect(getByText('Assign instructor')).toBeInTheDocument();
+ });
+});
diff --git a/src/features/Dashboard/InstructorAssignSection/index.jsx b/src/features/Dashboard/InstructorAssignSection/index.jsx
new file mode 100644
index 00000000..338072ac
--- /dev/null
+++ b/src/features/Dashboard/InstructorAssignSection/index.jsx
@@ -0,0 +1,50 @@
+import React, { useEffect, useState } from 'react';
+import { useSelector, useDispatch } from 'react-redux';
+
+import { Row, Col } from '@edx/paragon';
+import ClassCard from 'features/Dashboard/InstructorAssignSection/ClassCard';
+import { Button } from 'react-paragon-topaz';
+
+import { fetchClassesData } from 'features/Dashboard/data';
+
+import 'features/Dashboard/InstructorAssignSection/index.scss';
+
+const InstructorAssignSection = () => {
+ const dispatch = useDispatch();
+ const stateInstitution = useSelector((state) => state.main.institution.data);
+ const classesData = useSelector((state) => state.dashboard.classes.data);
+ const [classCards, setClassCards] = useState([]);
+ let idInstitution = '';
+ const numberOfClasses = 2;
+ // eslint-disable-next-line no-unused-expressions
+ stateInstitution.length > 0 ? idInstitution = stateInstitution[0].id : idInstitution = '';
+
+ useEffect(() => {
+ dispatch(fetchClassesData(idInstitution));
+ }, [idInstitution]); // eslint-disable-line react-hooks/exhaustive-deps
+
+ useEffect(() => {
+ // Display only the first 'NumberOfClasses' on the homepage.
+ if (classesData.length > numberOfClasses) {
+ setClassCards(classesData.slice(0, numberOfClasses));
+ } else {
+ setClassCards(classesData);
+ }
+ }, [classesData]);
+
+ return (
+
+
+ Instructor assignment
+ {classCards.map(classInfo => )}
+ {classesData.length > numberOfClasses && (
+
+
+
+ )}
+
+
+ );
+};
+
+export default InstructorAssignSection;
diff --git a/src/features/Dashboard/InstructorAssignSection/index.scss b/src/features/Dashboard/InstructorAssignSection/index.scss
new file mode 100644
index 00000000..776320a2
--- /dev/null
+++ b/src/features/Dashboard/InstructorAssignSection/index.scss
@@ -0,0 +1,49 @@
+@import "assets/colors.scss";
+
+.title-instr-assign {
+ padding: 1rem;
+ border-bottom: 1px solid $gray-20;
+ margin: 0;
+}
+
+.instructor-assign-section .view-all-btn.btn-primary:not(:disabled):not(.disabled) {
+ text-decoration: underline;
+
+ &:hover,
+ &:active,
+ &:focus,
+ &:focus-visible {
+ color: $primary;
+ background: transparent;
+ border: none;
+ outline: none;
+ text-decoration: underline;
+ }
+}
+
+.class-card-container {
+ padding: 1rem;
+ border-bottom: 1px solid $gray-20;
+
+ .course-name {
+ margin-bottom: 0.3rem;
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ font-weight: 700;
+ color: $gray-70;
+ }
+
+ .date {
+ font-size: 1rem;
+ line-height: 21px;
+ color: $gray-70;
+ }
+
+ .fa-calendar-day {
+ margin-right: 8px;
+ }
+
+ .fa-chalkboard-user {
+ margin-right: 5px;
+ }
+}
diff --git a/src/features/Dashboard/data/_test_/redux.test.jsx b/src/features/Dashboard/data/_test_/redux.test.jsx
index 615d5894..ef6cd563 100644
--- a/src/features/Dashboard/data/_test_/redux.test.jsx
+++ b/src/features/Dashboard/data/_test_/redux.test.jsx
@@ -1,7 +1,7 @@
import MockAdapter from 'axios-mock-adapter';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { initializeMockApp } from '@edx/frontend-platform/testing';
-import { fetchLicensesData } from 'features/Dashboard/data';
+import { fetchLicensesData, fetchClassesData } from 'features/Dashboard/data';
import { executeThunk } from 'test-utils';
import { initializeStore } from 'store';
@@ -28,7 +28,8 @@ describe('Dashboard redux tests', () => {
});
test('successful fetch licenses data', async () => {
- const licensesApiUrl = `${process.env.COURSE_OPERATIONS_API_V2_BASE_URL}/license-pool/?limit=false&institution_id=1`;
+ const licensesApiUrl = `${process.env.COURSE_OPERATIONS_API_V2_BASE_URL}/license-pool/`
+ + '?limit=false&institution_id=1';
const mockResponse = [
{
licenseName: 'License Name 1',
@@ -59,7 +60,8 @@ describe('Dashboard redux tests', () => {
});
test('failed fetch licenses data', async () => {
- const licensesApiUrl = `${process.env.COURSE_OPERATIONS_API_V2_BASE_URL}/license-pool/?limit=false&institution_id=1`;
+ const licensesApiUrl = `${process.env.COURSE_OPERATIONS_API_V2_BASE_URL}/license-pool/`
+ + '?limit=false&institution_id=1';
axiosMock.onGet(licensesApiUrl)
.reply(500);
@@ -74,4 +76,53 @@ describe('Dashboard redux tests', () => {
expect(store.getState().dashboard.tableLicense.status)
.toEqual('error');
});
+
+ test('successful fetch classes data', async () => {
+ const classesApiUrl = `${process.env.COURSE_OPERATIONS_API_V2_BASE_URL}/classes/`
+ + '?limit=false&institution_id=1&course_name=&instructors=null';
+ const mockResponse = [
+ {
+ classId: 'ccx-v1:demo+demo1+2020+ccx1',
+ className: 'ccx 1',
+ masterCourseName: 'Demo Course 1',
+ instructors: [],
+ numberOfStudents: 0,
+ numberOfPendingStudents: 0,
+ maxStudents: 20,
+ startDate: '2024-01-23T21:50:51Z',
+ endDate: null,
+ },
+ ];
+ axiosMock.onGet(classesApiUrl)
+ .reply(200, mockResponse);
+
+ expect(store.getState().dashboard.classes.status)
+ .toEqual('loading');
+
+ await executeThunk(fetchClassesData(1), store.dispatch, store.getState);
+
+ expect(store.getState().dashboard.classes.data)
+ .toEqual(mockResponse);
+
+ expect(store.getState().dashboard.classes.status)
+ .toEqual('success');
+ });
+
+ test('failed fetch licenses data', async () => {
+ const classesApiUrl = `${process.env.COURSE_OPERATIONS_API_V2_BASE_URL}/classes/`
+ + '?limit=false&institution_id=1&course_name=&instructors=null';
+ axiosMock.onGet(classesApiUrl)
+ .reply(500);
+
+ expect(store.getState().dashboard.classes.status)
+ .toEqual('loading');
+
+ await executeThunk(fetchClassesData(1), store.dispatch, store.getState);
+
+ expect(store.getState().dashboard.classes.data)
+ .toEqual([]);
+
+ expect(store.getState().dashboard.classes.status)
+ .toEqual('error');
+ });
});
diff --git a/src/features/Dashboard/data/index.js b/src/features/Dashboard/data/index.js
index 96f810e7..910c1431 100644
--- a/src/features/Dashboard/data/index.js
+++ b/src/features/Dashboard/data/index.js
@@ -1,2 +1,2 @@
export { reducer } from 'features/Dashboard/data/slice';
-export { fetchLicensesData } from 'features/Dashboard/data/thunks';
+export { fetchLicensesData, fetchClassesData } from 'features/Dashboard/data/thunks';
diff --git a/src/features/Dashboard/data/slice.js b/src/features/Dashboard/data/slice.js
index 165c8e4e..31adc748 100644
--- a/src/features/Dashboard/data/slice.js
+++ b/src/features/Dashboard/data/slice.js
@@ -6,6 +6,9 @@ const initialState = {
tableLicense: {
...initialStateService,
},
+ classes: {
+ ...initialStateService,
+ },
};
export const dashboardSlice = createSlice({
@@ -22,6 +25,16 @@ export const dashboardSlice = createSlice({
fetchLicensesDataFailed: (state) => {
state.tableLicense.status = RequestStatus.ERROR;
},
+ fetchClassesDataRequest: (state) => {
+ state.classes.status = RequestStatus.LOADING;
+ },
+ fetchClassesDataSuccess: (state, { payload }) => {
+ state.classes.status = RequestStatus.SUCCESS;
+ state.classes.data = payload;
+ },
+ fetchClassesDataFailed: (state) => {
+ state.classes.status = RequestStatus.ERROR;
+ },
},
});
@@ -29,6 +42,9 @@ export const {
fetchLicensesDataRequest,
fetchLicensesDataSuccess,
fetchLicensesDataFailed,
+ fetchClassesDataRequest,
+ fetchClassesDataSuccess,
+ fetchClassesDataFailed,
} = dashboardSlice.actions;
export const { reducer } = dashboardSlice;
diff --git a/src/features/Dashboard/data/thunks.js b/src/features/Dashboard/data/thunks.js
index 910479a8..9988ee98 100644
--- a/src/features/Dashboard/data/thunks.js
+++ b/src/features/Dashboard/data/thunks.js
@@ -1,11 +1,14 @@
import { logError } from '@edx/frontend-platform/logging';
import { camelCaseObject } from '@edx/frontend-platform';
-import { getLicensesByInstitution } from 'features/Common/data/api';
+import { getLicensesByInstitution, getClassesByInstitution } from 'features/Common/data/api';
import {
fetchLicensesDataRequest,
fetchLicensesDataSuccess,
fetchLicensesDataFailed,
+ fetchClassesDataRequest,
+ fetchClassesDataSuccess,
+ fetchClassesDataFailed,
} from 'features/Dashboard/data/slice';
function fetchLicensesData(id) {
@@ -21,6 +24,20 @@ function fetchLicensesData(id) {
};
}
+function fetchClassesData(id) {
+ return async (dispatch) => {
+ dispatch(fetchClassesDataRequest());
+ try {
+ const response = camelCaseObject(await getClassesByInstitution(id, '', false, 'null'));
+ dispatch(fetchClassesDataSuccess(response.data));
+ } catch (error) {
+ dispatch(fetchClassesDataFailed());
+ logError(error);
+ }
+ };
+}
+
export {
fetchLicensesData,
+ fetchClassesData,
};
diff --git a/src/features/Instructors/InstructorsTable/columns.jsx b/src/features/Instructors/InstructorsTable/columns.jsx
index 669ddc1a..1e7592e2 100644
--- a/src/features/Instructors/InstructorsTable/columns.jsx
+++ b/src/features/Instructors/InstructorsTable/columns.jsx
@@ -1,5 +1,5 @@
/* eslint-disable react/prop-types, no-nested-ternary */
-import moment from 'moment';
+import { differenceInHours, differenceInDays, differenceInWeeks } from 'date-fns';
const columns = [
{
@@ -10,11 +10,11 @@ const columns = [
Header: 'Last seen',
accessor: 'lastAccess',
Cell: ({ row }) => {
- const currentDate = moment(Date.now());
- const lastDate = moment(new Date(row.values.lastAccess));
- const diffHours = currentDate.diff(lastDate, 'hours');
- const diffDays = currentDate.diff(lastDate, 'days');
- const diffWeeks = currentDate.diff(lastDate, 'weeks');
+ const currentDate = Date.now();
+ const lastDate = new Date(row.values.lastAccess);
+ const diffHours = differenceInHours(currentDate, lastDate);
+ const diffDays = differenceInDays(currentDate, lastDate);
+ const diffWeeks = differenceInWeeks(currentDate, lastDate);
return (
{diffHours < 24
? 'Today'
diff --git a/src/features/Students/data/_test_/api.test.js b/src/features/Students/data/_test_/api.test.js
index 4d600b26..3e09f249 100644
--- a/src/features/Students/data/_test_/api.test.js
+++ b/src/features/Students/data/_test_/api.test.js
@@ -1,5 +1,5 @@
import {
- getStudentbyInstitutionAdmin, handleEnrollments, getClassesByInstitution,
+ getStudentbyInstitutionAdmin, handleEnrollments,
} from 'features/Students/data/api';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
@@ -59,26 +59,3 @@ describe('handleEnrollments', () => {
);
});
});
-
-describe('getClassesByInstitution', () => {
- test('should call getClassesByInstitution with the correct parameters', () => {
- const httpClientMock = {
- get: jest.fn(),
- };
-
- const institutionId = 1;
- const courseName = 'ccx1';
-
- getAuthenticatedHttpClient.mockReturnValue(httpClientMock);
-
- getClassesByInstitution(institutionId, courseName);
-
- expect(getAuthenticatedHttpClient).toHaveBeenCalledTimes(3);
- expect(getAuthenticatedHttpClient).toHaveBeenCalledWith();
-
- expect(httpClientMock.get).toHaveBeenCalledTimes(1);
- expect(httpClientMock.get).toHaveBeenCalledWith(
- 'http://localhost:18000/pearson_course_operation/api/v2/classes/?limit=false&institution_id=1&course_name=ccx1',
- );
- });
-});
diff --git a/src/features/Students/data/_test_/redux.test.js b/src/features/Students/data/_test_/redux.test.js
index 37a7db6f..ceb71659 100644
--- a/src/features/Students/data/_test_/redux.test.js
+++ b/src/features/Students/data/_test_/redux.test.js
@@ -130,7 +130,7 @@ describe('Students redux tests', () => {
});
test('successful fetch classes data', async () => {
- const studentsApiUrl = `${process.env.COURSE_OPERATIONS_API_V2_BASE_URL}/classes/?limit=false&institution_id=1&course_name=Demo`;
+ const studentsApiUrl = `${process.env.COURSE_OPERATIONS_API_V2_BASE_URL}/classes/?limit=false&institution_id=1&course_name=Demo&instructors=`;
const mockResponse = [
{
classId: 'ccx-v1:demo+demo1+2020+ccx@2',
@@ -154,14 +154,14 @@ describe('Students redux tests', () => {
});
test('failed fetch classes data', async () => {
- const studentsApiUrl = `${process.env.COURSE_OPERATIONS_API_V2_BASE_URL}/classes/?limit=false&institution_id=1`;
+ const studentsApiUrl = `${process.env.COURSE_OPERATIONS_API_V2_BASE_URL}/classes/?limit=false&institution_id=1&course_name=Demo&instructors=`;
axiosMock.onGet(studentsApiUrl)
.reply(500);
expect(store.getState().students.classes.status)
.toEqual('loading');
- await executeThunk(fetchClassesData(1), store.dispatch, store.getState);
+ await executeThunk(fetchClassesData(1, 'Demo'), store.dispatch, store.getState);
expect(store.getState().students.classes.data)
.toEqual([]);
diff --git a/src/features/Students/data/api.js b/src/features/Students/data/api.js
index 2985b06c..c8543e9e 100644
--- a/src/features/Students/data/api.js
+++ b/src/features/Students/data/api.js
@@ -23,15 +23,6 @@ function handleEnrollments(data, courseId) {
);
}
-function getClassesByInstitution(institutionId, courseName) {
- const encodedCourseName = encodeURIComponent(courseName);
-
- return getAuthenticatedHttpClient().get(
- `${getConfig().COURSE_OPERATIONS_API_V2_BASE_URL}/classes`
- + `/?limit=false&institution_id=${institutionId}&course_name=${encodedCourseName}`,
- );
-}
-
function getMetricsStudents() {
const metricsData = {
data: {
@@ -45,6 +36,5 @@ function getMetricsStudents() {
export {
getStudentbyInstitutionAdmin,
handleEnrollments,
- getClassesByInstitution,
getMetricsStudents,
};
diff --git a/src/features/Students/data/thunks.js b/src/features/Students/data/thunks.js
index 160579c5..5613bde3 100644
--- a/src/features/Students/data/thunks.js
+++ b/src/features/Students/data/thunks.js
@@ -14,8 +14,8 @@ import {
fetchMetricsDataSuccess,
fetchMetricsDataFailed,
} from 'features/Students/data/slice';
-import { getStudentbyInstitutionAdmin, getClassesByInstitution, getMetricsStudents } from 'features/Students/data/api';
-import { getCoursesByInstitution } from 'features/Common/data/api';
+import { getStudentbyInstitutionAdmin, getMetricsStudents } from 'features/Students/data/api';
+import { getCoursesByInstitution, getClassesByInstitution } from 'features/Common/data/api';
function fetchStudentsData(currentPage, filtersData) {
return async (dispatch) => {
@@ -50,7 +50,7 @@ function fetchClassesData(id, courseName) {
dispatch(fetchClassesDataRequest());
try {
- const response = camelCaseObject(await getClassesByInstitution(id, courseName));
+ const response = camelCaseObject(await getClassesByInstitution(id, courseName, false));
dispatch(fetchClassesDataSuccess(response.data));
} catch (error) {
dispatch(fetchClassesDataFailed());
diff --git a/src/test-utils.js b/src/test-utils.js
deleted file mode 100644
index 705b41ec..00000000
--- a/src/test-utils.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export const executeThunk = async (thunk, dispatch, getState) => {
- await thunk(dispatch, getState);
-};
diff --git a/src/test-utils.jsx b/src/test-utils.jsx
new file mode 100644
index 00000000..79082c13
--- /dev/null
+++ b/src/test-utils.jsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import { Provider } from 'react-redux';
+
+import { initializeStore } from 'store';
+
+export const executeThunk = async (thunk, dispatch, getState) => {
+ await thunk(dispatch, getState);
+};
+
+export function renderWithProviders(
+ ui,
+ {
+ preloadedState = {},
+ // Automatically create a store instance if no store was passed in
+ store = initializeStore(preloadedState),
+ ...renderOptions
+ } = {},
+) {
+ const Wrapper = ({ children }) => {children};
+ return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
+}