From af7103f12388e52a1802c812b20880ff409c9718 Mon Sep 17 00:00:00 2001 From: Cyril Nxumalo Date: Fri, 30 Aug 2024 11:46:22 +0200 Subject: [PATCH] chore: Migrate deprecated Table to DataTable for PastWeekPassedLearnersTable --- .../data/hooks/usePastWeekPassedLearners.js | 95 ++++++++++++++ .../PastWeekPassedLearnersTable/index.jsx | 117 ++++++++++-------- src/eventTracking.js | 6 + 3 files changed, 167 insertions(+), 51 deletions(-) create mode 100644 src/components/PastWeekPassedLearnersTable/data/hooks/usePastWeekPassedLearners.js diff --git a/src/components/PastWeekPassedLearnersTable/data/hooks/usePastWeekPassedLearners.js b/src/components/PastWeekPassedLearnersTable/data/hooks/usePastWeekPassedLearners.js new file mode 100644 index 0000000000..579df42b3a --- /dev/null +++ b/src/components/PastWeekPassedLearnersTable/data/hooks/usePastWeekPassedLearners.js @@ -0,0 +1,95 @@ +import { + useCallback, useMemo, useRef, useState, +} from 'react'; +import { camelCaseObject } from '@edx/frontend-platform/utils'; +import debounce from 'lodash.debounce'; +import { logError } from '@edx/frontend-platform/logging'; +import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils'; +import EnterpriseDataApiService from '../../../../data/services/EnterpriseDataApiService'; +import EVENT_NAMES from '../../../../eventTracking'; + +const applySortByToOptions = (sortBy, options) => { + if (!sortBy || sortBy.length === 0) { + return; + } + const apiFieldsForColumnAccessor = { + userEmail: { key: 'user_email' }, + CourseTitle: { key: 'course_title' }, + PassedDate: { key: 'passed_date' }, + }; + const orderingStrings = sortBy.map(({ id, desc }) => { + const apiFieldForColumnAccessor = apiFieldsForColumnAccessor[id]; + if (!apiFieldForColumnAccessor) { + return undefined; + } + const apiFieldKey = apiFieldForColumnAccessor.key; + return desc ? `-${apiFieldKey}` : apiFieldKey; + }).filter(orderingString => !!orderingString); + Object.assign(options, { + ordering: orderingStrings.join(','), + }); +}; + +const usePastWeekPassedLearners = (enterpriseId) => { + const shouldTrackFetchEvents = useRef(false); + const [isLoading, setIsLoading] = useState(true); + const [pastWeekPassedLearners, setPastWeekPassedLearners] = useState({ + itemCount: 0, + pageCount: 0, + results: [], + }); + + const fetchPastWeekPassedLearners = useCallback(async (args) => { + try { + setIsLoading(true); + const options = { + page: args.pageIndex + 1, + pageSize: args.pageSize, + passedDate: 'last_week', + }; + applySortByToOptions(args.sortBy, options); + + const response = await EnterpriseDataApiService.fetchCourseEnrollments(enterpriseId, options); + const data = camelCaseObject(response.data); + setPastWeekPassedLearners({ + itemCount: data.count, + pageCount: data.numPages ?? Math.floor(data.count / options.pageSize), + results: data.results, + }); + + if (shouldTrackFetchEvents.current) { + // track event only after original API query to avoid sending event on initial page load. instead, + // only track event when user performs manual data operation (e.g., pagination, sort, filter) and + // send all table state as event properties. + sendEnterpriseTrackEvent( + enterpriseId, + EVENT_NAMES.PROGRESS_REPORT.DATATABLE_SORT_BY_OR_FILTER, + { + tableId: 'completed-learners-week', + ...options, + }, + ); + } else { + // set to true to enable tracking events on future API queries + shouldTrackFetchEvents.current = true; + } + } catch (error) { + logError(error); + } finally { + setIsLoading(false); + } + }, [enterpriseId]); + + const debouncedFetchPastWeekPassedLearners = useMemo( + () => debounce(fetchPastWeekPassedLearners, 300), + [fetchPastWeekPassedLearners], + ); + + return { + isLoading, + pastWeekPassedLearners, + fetchPastWeekPassedLearners: debouncedFetchPastWeekPassedLearners, + }; +}; + +export default usePastWeekPassedLearners; diff --git a/src/components/PastWeekPassedLearnersTable/index.jsx b/src/components/PastWeekPassedLearnersTable/index.jsx index 2fa60f8b62..1ef4152463 100644 --- a/src/components/PastWeekPassedLearnersTable/index.jsx +++ b/src/components/PastWeekPassedLearnersTable/index.jsx @@ -1,66 +1,81 @@ import React from 'react'; - +import PropTypes from 'prop-types'; import { useIntl } from '@edx/frontend-platform/i18n'; - -import TableContainer from '../../containers/TableContainer'; -import EnterpriseDataApiService from '../../data/services/EnterpriseDataApiService'; +import { DataTable } from '@openedx/paragon'; +import usePastWeekPassedLearners from './data/hooks/usePastWeekPassedLearners'; import { i18nFormatTimestamp } from '../../utils'; -const PastWeekPassedLearnersTable = () => { - const intl = useIntl(); +const UserEmail = ({ row }) => ( + {row.original.userEmail} +); - const tableColumns = [ - { - label: intl.formatMessage({ - id: 'admin.portal.lpr.past.week.passed.learners.table.user_email.column.heading', - defaultMessage: 'Email', - description: 'Column heading for the user email column in the past week passed learners table', - }), - key: 'user_email', - columnSortable: true, - }, - { - label: intl.formatMessage({ - id: 'admin.portal.lpr.past.week.passed.learners.table.course_title.column.heading', - defaultMessage: 'Course Title', - description: 'Column heading for the course title column in the past week passed learners table', - }), - key: 'course_title', - columnSortable: true, - }, - { - label: intl.formatMessage({ - id: 'admin.portal.lpr.past.week.passed.learners.table.passed_date.column.heading', - defaultMessage: 'Passed Date', - description: 'Column heading for the passed date column in the past week passed learners table', - }), - key: 'passed_date', - columnSortable: true, - }, - ]; +UserEmail.propTypes = { + row: PropTypes.shape({ + original: PropTypes.shape({ + userEmail: PropTypes.string.isRequired, + }).isRequired, + }).isRequired, +}; - const formatLearnerData = learners => learners.map(learner => ({ - ...learner, - user_email: {learner.user_email}, - passed_date: i18nFormatTimestamp({ intl, timestamp: learner.passed_date }), - })); +const PastWeekPassedLearnersTable = ({ enterpriseId }) => { + const intl = useIntl(); + const { + isLoading, + pastWeekPassedLearners: tableData, + fetchPastWeekPassedLearners: fetchTableData, + } = usePastWeekPassedLearners(enterpriseId); return ( - EnterpriseDataApiService.fetchCourseEnrollments( - enterpriseId, + i18nFormatTimestamp({ intl, timestamp: row.values.passedDate }), + }, + ]} + initialState={{ + pageSize: 20, // Set this according to your requirements + pageIndex: 0, + sortBy: [{ id: 'passedDate', desc: true }], + selectedRowsOrdered: [], + }} + fetchData={fetchTableData} + data={tableData.results} + itemCount={tableData.itemCount} + pageCount={tableData.pageCount} /> ); }; +PastWeekPassedLearnersTable.propTypes = { + enterpriseId: PropTypes.string.isRequired, +}; + export default PastWeekPassedLearnersTable; diff --git a/src/eventTracking.js b/src/eventTracking.js index f7dfd9872f..7525d89a36 100644 --- a/src/eventTracking.js +++ b/src/eventTracking.js @@ -17,6 +17,7 @@ const SUBSCRIPTION_PREFIX = `${PROJECT_NAME}.subscriptions`; const SETTINGS_PREFIX = `${PROJECT_NAME}.settings`; const CONTENT_HIGHLIGHTS_PREFIX = `${PROJECT_NAME}.content_highlights`; const LEARNER_CREDIT_MANAGEMENT_PREFIX = `${PROJECT_NAME}.learner_credit_management`; +const PROGRESS_REPORT_PREFIX = `${PROJECT_NAME}.progress_report`; // Sub-prefixes // Subscriptions @@ -95,6 +96,10 @@ export const CONTENT_HIGHLIGHTS_EVENTS = { const SETTINGS_ACCESS_PREFIX = `${SETTINGS_PREFIX}.ACCESS`; +export const PROGRESS_REPORT_EVENTS = { + DATATABLE_SORT_BY_OR_FILTER: `${PROGRESS_REPORT_PREFIX}.datatable.sort_by_or_filter.changed`, +}; + export const SETTINGS_ACCESS_EVENTS = { UNIVERSAL_LINK_TOGGLE: `${SETTINGS_ACCESS_PREFIX}.universal-link.toggle.clicked`, UNIVERSAL_LINK_GENERATE: `${SETTINGS_ACCESS_PREFIX}.universal-link.generate.clicked`, @@ -184,6 +189,7 @@ const EVENT_NAMES = { SUBSCRIPTIONS: SUBSCRIPTION_EVENTS, CONTENT_HIGHLIGHTS: CONTENT_HIGHLIGHTS_EVENTS, LEARNER_CREDIT_MANAGEMENT: LEARNER_CREDIT_MANAGEMENT_EVENTS, + PROGRESS_REPORT: PROGRESS_REPORT_EVENTS, }; export default EVENT_NAMES;