From a2925a9407e796cdaea38be3a86d7750567dfec8 Mon Sep 17 00:00:00 2001 From: Sergio Valero Date: Sat, 2 Mar 2024 17:59:47 -0400 Subject: [PATCH] feat: Integrate Metric services to component --- .env.development | 1 + .../Students/StudentsMetrics/index.jsx | 26 +++++-- .../StudentsPage/_test_/index.test.jsx | 17 ++++- .../Students/data/_test_/redux.test.js | 75 ++++++++++++++++--- src/features/Students/data/api.js | 31 ++++++-- src/features/Students/data/slice.js | 38 +++++++--- src/features/Students/data/thunks.js | 42 ++++++++--- 7 files changed, 181 insertions(+), 49 deletions(-) diff --git a/.env.development b/.env.development index f18f7ad9..4dd7f8c2 100644 --- a/.env.development +++ b/.env.development @@ -20,3 +20,4 @@ ACCOUNT_PROFILE_URL='http://localhost:1995/u' MFE_CONFIG_API_URL='http://localhost:18000/api/mfe_config/v1' INSTITUTION_PORTAL_PATH='' COURSE_OPERATIONS_API_V2_BASE_URL='http://localhost:18000/pearson_course_operation/api/v2' +COURSE_OPERATIONS_API_METRICS_BASE_URL='http://localhost:18000/pearson_course_operation/api/metrics' diff --git a/src/features/Students/StudentsMetrics/index.jsx b/src/features/Students/StudentsMetrics/index.jsx index 7556da3c..011f9c88 100644 --- a/src/features/Students/StudentsMetrics/index.jsx +++ b/src/features/Students/StudentsMetrics/index.jsx @@ -2,17 +2,23 @@ import React, { useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { Card, CardGrid, ToggleButton } from '@edx/paragon'; import { ToggleButtonGroup } from 'react-paragon-topaz'; -import { fetchMetricsData } from 'features/Students/data/thunks'; +import { fetchClassesMetricsData, fetchStudentsMetricsData } from 'features/Students/data/thunks'; +import { daysWeek } from 'features/constants'; import 'features/Students/StudentsMetrics/index.scss'; const StudentsMetrics = () => { const dispatch = useDispatch(); - const stateMetrics = useSelector((state) => state.students.metrics.data); + const institution = useSelector((state) => state.main.selectedInstitution); + const studentsMetrics = useSelector((state) => state.students.studentsMetrics.data); + const classesMetrics = useSelector((state) => state.students.classesMetrics.data); useEffect(() => { - dispatch(fetchMetricsData()); - }, [dispatch]); + if (Object.keys(institution).length > 0) { + dispatch(fetchStudentsMetricsData(institution.id, daysWeek)); + dispatch(fetchClassesMetricsData(institution.id, daysWeek)); + } + }, [institution, dispatch]); return (
@@ -20,10 +26,10 @@ const StudentsMetrics = () => { This week - + {/* Temporarily disabled */} Next week - + {/* Temporarily disabled */} Next month @@ -39,7 +45,9 @@ const StudentsMetrics = () => { title="New students registered" /> -
{stateMetrics.newStudentsRegistered}
+

+ {studentsMetrics.numberOfEnrollments ? studentsMetrics.numberOfEnrollments : '-'} +

@@ -47,7 +55,9 @@ const StudentsMetrics = () => { title="Classes scheduled" /> -
{stateMetrics.classesScheduled}
+

+ {classesMetrics.numberOfClassesCreated ? classesMetrics.numberOfClassesCreated : '-'} +

diff --git a/src/features/Students/StudentsPage/_test_/index.test.jsx b/src/features/Students/StudentsPage/_test_/index.test.jsx index 59a090a4..06cbfc8f 100644 --- a/src/features/Students/StudentsPage/_test_/index.test.jsx +++ b/src/features/Students/StudentsPage/_test_/index.test.jsx @@ -45,8 +45,19 @@ const mockStore = { num_pages: 1, current_page: 1, }, - metrics: { - data: [], + classesMetrics: { + data: [ + { + numberOfClassesCreated: 10, + }, + ], + }, + studentsMetrics: { + data: [ + { + numberOfEnrollments: 2, + }, + ], }, classes: { data: [], @@ -75,6 +86,8 @@ describe('StudentsPage', () => { expect(component.container).toHaveTextContent('Instructor 2'); expect(component.container).toHaveTextContent('active'); expect(component.container).toHaveTextContent('pending'); + expect(component.container).toHaveTextContent('10'); + expect(component.container).toHaveTextContent('2'); }); }); }); diff --git a/src/features/Students/data/_test_/redux.test.js b/src/features/Students/data/_test_/redux.test.js index 9f232a40..50347425 100644 --- a/src/features/Students/data/_test_/redux.test.js +++ b/src/features/Students/data/_test_/redux.test.js @@ -5,7 +5,8 @@ import { fetchStudentsData, fetchCoursesData, fetchClassesData, - fetchMetricsData, + fetchClassesMetricsData, + fetchStudentsMetricsData, } from 'features/Students/data/thunks'; import { updateCurrentPage, updateFilters } from 'features/Students/data/slice'; import { executeThunk } from 'test-utils'; @@ -170,24 +171,78 @@ describe('Students redux tests', () => { .toEqual('error'); }); - test('successful fetch metrics data', async () => { - const mockResponse = { - classesScheduled: '71%', - newStudentsRegistered: '367', - }; + test('successful fetch classes metrics data', async () => { + const classesMetricsApiUrl = `${process.env.COURSE_OPERATIONS_API_METRICS_BASE_URL}/classes-number/`; + const mockResponse = { numberOfClassesCreated: 71 }; + axiosMock.onGet(classesMetricsApiUrl) + .reply(200, mockResponse); - expect(store.getState().students.metrics.status) + expect(store.getState().students.classesMetrics.status) .toEqual('loading'); - await executeThunk(fetchMetricsData(), store.dispatch, store.getState); + await executeThunk(fetchClassesMetricsData(1, 2), store.dispatch, store.getState); - expect(store.getState().students.metrics.data) + expect(store.getState().students.classesMetrics.data) .toEqual(mockResponse); - expect(store.getState().students.metrics.status) + expect(store.getState().students.classesMetrics.status) .toEqual('success'); }); + test('failed fetch classes metrics data', async () => { + const classesMetricsApiUrl = `${process.env.COURSE_OPERATIONS_API_METRICS_BASE_URL}/classes-number/`; + + axiosMock.onGet(classesMetricsApiUrl) + .reply(500); + + expect(store.getState().students.classesMetrics.status) + .toEqual('loading'); + + await executeThunk(fetchClassesMetricsData(1, 2), store.dispatch, store.getState); + + expect(store.getState().students.classesMetrics.data) + .toEqual([]); + + expect(store.getState().students.classesMetrics.status) + .toEqual('error'); + }); + + test('successful fetch students metrics data', async () => { + const studentsMetricsApiUrl = `${process.env.COURSE_OPERATIONS_API_METRICS_BASE_URL}/students-number/`; + const mockResponse = { numberOfEnrollments: 20 }; + axiosMock.onGet(studentsMetricsApiUrl) + .reply(200, mockResponse); + + expect(store.getState().students.studentsMetrics.status) + .toEqual('loading'); + + await executeThunk(fetchStudentsMetricsData(1, 2), store.dispatch, store.getState); + + expect(store.getState().students.studentsMetrics.data) + .toEqual(mockResponse); + + expect(store.getState().students.studentsMetrics.status) + .toEqual('success'); + }); + + test('failed fetch students metrics data', async () => { + const studentsMetricsApiUrl = `${process.env.COURSE_OPERATIONS_API_METRICS_BASE_URL}/students-number/`; + + axiosMock.onGet(studentsMetricsApiUrl) + .reply(500); + + expect(store.getState().students.studentsMetrics.status) + .toEqual('loading'); + + await executeThunk(fetchStudentsMetricsData(1, 2), store.dispatch, store.getState); + + expect(store.getState().students.studentsMetrics.data) + .toEqual([]); + + expect(store.getState().students.studentsMetrics.status) + .toEqual('error'); + }); + test('update current page', () => { const newPage = 2; const intialState = store.getState().students.table; diff --git a/src/features/Students/data/api.js b/src/features/Students/data/api.js index 1d277935..a425a1cc 100644 --- a/src/features/Students/data/api.js +++ b/src/features/Students/data/api.js @@ -24,18 +24,33 @@ function handleEnrollments(data, courseId) { ); } -function getMetricsStudents() { - const metricsData = { - data: { - new_students_registered: '367', - classes_scheduled: '71%', - }, +function getStudentsMetrics(institutionId, days) { + const params = { + institution_id: institutionId, + days, }; - return metricsData; + + return getAuthenticatedHttpClient().get( + `${getConfig().COURSE_OPERATIONS_API_METRICS_BASE_URL}/students-number/`, + { params }, + ); +} + +function getClassesMetrics(institutionId, days) { + const params = { + institution_id: institutionId, + days, + }; + + return getAuthenticatedHttpClient().get( + `${getConfig().COURSE_OPERATIONS_API_METRICS_BASE_URL}/classes-number/`, + { params }, + ); } export { getStudentbyInstitutionAdmin, handleEnrollments, - getMetricsStudents, + getStudentsMetrics, + getClassesMetrics, }; diff --git a/src/features/Students/data/slice.js b/src/features/Students/data/slice.js index 4234404d..973134be 100644 --- a/src/features/Students/data/slice.js +++ b/src/features/Students/data/slice.js @@ -17,7 +17,10 @@ const initialState = { classes: { ...initialStateService, }, - metrics: { + classesMetrics: { + ...initialStateService, + }, + studentsMetrics: { ...initialStateService, }, filters: {}, @@ -66,15 +69,25 @@ export const studentsSlice = createSlice({ fetchClassesDataFailed: (state) => { state.classes.status = RequestStatus.ERROR; }, - fetchMetricsDataRequest: (state) => { - state.metrics.status = RequestStatus.LOADING; + fetchClassesMetricsDataRequest: (state) => { + state.classesMetrics.status = RequestStatus.LOADING; + }, + fetchClassesMetricsDataSuccess: (state, { payload }) => { + state.classesMetrics.status = RequestStatus.SUCCESS; + state.classesMetrics.data = payload; + }, + fetchClassesMetricsDataFailed: (state) => { + state.classesMetrics.status = RequestStatus.ERROR; + }, + fetchStudentsMetricsDataRequest: (state) => { + state.studentsMetrics.status = RequestStatus.LOADING; }, - fetchMetricsDataSuccess: (state, { payload }) => { - state.metrics.status = RequestStatus.SUCCESS; - state.metrics.data = payload; + fetchStudentsMetricsDataSuccess: (state, { payload }) => { + state.studentsMetrics.status = RequestStatus.SUCCESS; + state.studentsMetrics.data = payload; }, - fetchMetricsDataFailed: (state) => { - state.metrics.status = RequestStatus.ERROR; + fetchStudentsMetricsDataFailed: (state) => { + state.studentsMetrics.status = RequestStatus.ERROR; }, }, }); @@ -91,9 +104,12 @@ export const { fetchClassesDataRequest, fetchClassesDataSuccess, fetchClassesDataFailed, - fetchMetricsDataRequest, - fetchMetricsDataSuccess, - fetchMetricsDataFailed, + fetchClassesMetricsDataRequest, + fetchClassesMetricsDataSuccess, + fetchClassesMetricsDataFailed, + fetchStudentsMetricsDataRequest, + fetchStudentsMetricsDataSuccess, + fetchStudentsMetricsDataFailed, } = studentsSlice.actions; export const { reducer } = studentsSlice; diff --git a/src/features/Students/data/thunks.js b/src/features/Students/data/thunks.js index db8510cd..9507901a 100644 --- a/src/features/Students/data/thunks.js +++ b/src/features/Students/data/thunks.js @@ -10,11 +10,18 @@ import { fetchClassesDataRequest, fetchClassesDataSuccess, fetchClassesDataFailed, - fetchMetricsDataRequest, - fetchMetricsDataSuccess, - fetchMetricsDataFailed, + fetchClassesMetricsDataRequest, + fetchClassesMetricsDataSuccess, + fetchClassesMetricsDataFailed, + fetchStudentsMetricsDataRequest, + fetchStudentsMetricsDataSuccess, + fetchStudentsMetricsDataFailed, } from 'features/Students/data/slice'; -import { getStudentbyInstitutionAdmin, getMetricsStudents } from 'features/Students/data/api'; +import { + getClassesMetrics, + getStudentsMetrics, + getStudentbyInstitutionAdmin, +} from 'features/Students/data/api'; import { getCoursesByInstitution, getClassesByInstitution } from 'features/Common/data/api'; function fetchStudentsData(id, currentPage, filtersData) { @@ -59,15 +66,29 @@ function fetchClassesData(id, courseName) { }; } -function fetchMetricsData() { +function fetchClassesMetricsData(institutionId, days) { + return async (dispatch) => { + dispatch(fetchClassesMetricsDataRequest()); + + try { + const response = camelCaseObject(await getClassesMetrics(institutionId, days)); + dispatch(fetchClassesMetricsDataSuccess(response.data)); + } catch (error) { + dispatch(fetchClassesMetricsDataFailed()); + logError(error); + } + }; +} + +function fetchStudentsMetricsData(institutionId, days) { return async (dispatch) => { - dispatch(fetchMetricsDataRequest()); + dispatch(fetchStudentsMetricsDataRequest()); try { - const response = camelCaseObject(await getMetricsStudents()); - dispatch(fetchMetricsDataSuccess(response.data)); + const response = camelCaseObject(await getStudentsMetrics(institutionId, days)); + dispatch(fetchStudentsMetricsDataSuccess(response.data)); } catch (error) { - dispatch(fetchMetricsDataFailed()); + dispatch(fetchStudentsMetricsDataFailed()); logError(error); } }; @@ -77,5 +98,6 @@ export { fetchStudentsData, fetchCoursesData, fetchClassesData, - fetchMetricsData, + fetchClassesMetricsData, + fetchStudentsMetricsData, };