diff --git a/src/components/LearnerActivityTable/LearnerActivityTable.test.jsx b/src/components/LearnerActivityTable/LearnerActivityTable.test.jsx index 3d8a62b94b..f14a16b1ed 100644 --- a/src/components/LearnerActivityTable/LearnerActivityTable.test.jsx +++ b/src/components/LearnerActivityTable/LearnerActivityTable.test.jsx @@ -8,96 +8,26 @@ import { Provider } from 'react-redux'; import { mount } from 'enzyme'; import LearnerActivityTable from '.'; +import useCourseEnrollments from './data/hooks/useCourseEnrollments'; +import mockUseCourseEnrollments from './data/tests/constants'; const enterpriseId = 'test-enterprise'; const mockStore = configureMockStore([thunk]); -const learnerActivityEmptyStore = mockStore({ - portalConfiguration: { - enterpriseId, - }, - table: { - 'active-week': { - data: { - results: [], - current_page: 1, - num_pages: 1, - }, - ordering: null, - loading: false, - error: null, - }, - }, -}); -const tableMockData = { - data: { - count: 2, - num_pages: 1, - current_page: 1, - results: [ - { - id: 1, - passed_date: '2018-09-23T16:27:34.690065Z', - course_title: 'Dive into ReactJS', - course_key: 'edX/ReactJS', - user_email: 'awesome.me@example.com', - course_list_price: '200', - course_start_date: '2017-10-21T23:47:32.738Z', - course_end_date: '2018-05-13T12:47:27.534Z', - current_grade: '0.66', - progress_status: 'Failed', - last_activity_date: '2018-09-22T10:59:28.628Z', - }, - { - id: 5, - passed_date: '2018-09-22T16:27:34.690065Z', - course_title: 'Redux with ReactJS', - course_key: 'edX/Redux_ReactJS', - user_email: 'new@example.com', - course_list_price: '200', - course_start_date: '2017-10-21T23:47:32.738Z', - course_end_date: '2018-05-13T12:47:27.534Z', - current_grade: '0.80', - progress_status: 'Passed', - last_activity_date: '2018-09-25T10:59:28.628Z', - }, - ], - next: null, - start: 0, - previous: null, - }, - ordering: null, - loading: false, - error: null, -}; +jest.mock('./data/hooks/useCourseEnrollments', () => ( + jest.fn().mockReturnValue({}) +)); -const learnerActivityStore = mockStore({ +const store = mockStore({ portalConfiguration: { enterpriseId, }, - table: { - 'active-week': tableMockData, - 'inactive-week': tableMockData, - 'inactive-month': tableMockData, - }, }); -const LearnerActivityEmptyTableWrapper = props => ( - - - - - - - -); - const LearnerActivityTableWrapper = props => ( - + @@ -107,33 +37,53 @@ const LearnerActivityTableWrapper = props => ( ); const verifyLearnerActivityTableRendered = (tableId, activity, columnTitles, rowsData) => { - const wrapper = mount(( - - )); - // Verify that table has correct number of columns - expect(wrapper.find(`.${tableId} thead th`).length).toEqual(columnTitles.length); + const wrapper = mount(); + + const table = wrapper.find('[role="table"]'); + const headerColumns = table.find('thead th'); + const tableRows = table.find('tbody tr'); - // Verify only expected columns are shown - wrapper.find(`.${tableId} thead th`).forEach((column, index) => { + // Verify the number of columns + expect(headerColumns).toHaveLength(columnTitles.length); + + // Verify column titles + headerColumns.forEach((column, index) => { expect(column.text()).toContain(columnTitles[index]); }); - // Verify that table has correct number of rows - expect(wrapper.find(`.${tableId} tbody tr`).length).toEqual(2); + // Verify the number of rows + expect(tableRows).toHaveLength(rowsData.length); - // Verify each row in table has correct data - wrapper.find(`.${tableId} tbody tr`).forEach((row, rowIndex) => { - row.find('td').forEach((cell, colIndex) => { + // Verify row data + tableRows.forEach((row, rowIndex) => { + const cells = row.find('td'); + cells.forEach((cell, colIndex) => { expect(cell.text()).toEqual(rowsData[rowIndex][colIndex]); }); }); }; describe('LearnerActivityTable', () => { + beforeEach(() => { + useCourseEnrollments.mockReturnValue(mockUseCourseEnrollments); + }); + + afterEach(() => jest.clearAllMocks()); + it('renders empty state correctly', () => { + useCourseEnrollments.mockReturnValue( + { + isLoading: false, + courseEnrollments: { + itemCount: 0, + pageCount: 0, + results: [], + }, + }, + ); const tree = renderer .create(( - + )) .toJSON(); expect(tree).toMatchSnapshot(); diff --git a/src/components/LearnerActivityTable/__snapshots__/LearnerActivityTable.test.jsx.snap b/src/components/LearnerActivityTable/__snapshots__/LearnerActivityTable.test.jsx.snap index 9ff48db13a..e4761765e0 100644 --- a/src/components/LearnerActivityTable/__snapshots__/LearnerActivityTable.test.jsx.snap +++ b/src/components/LearnerActivityTable/__snapshots__/LearnerActivityTable.test.jsx.snap @@ -2,280 +2,456 @@ exports[`LearnerActivityTable renders active learners table correctly 1`] = `
- - +
+ Showing 1 - 2 of 2. +
+ +
+
+
+
+
+
+
+
+ @@ -397,101 +574,105 @@ exports[`LearnerActivityTable renders active learners table correctly 1`] = `
- + - + - + - + - + - + - + - + - +
Dive into ReactJS $200 October 21, 2017 May 13, 2018 September 23, 2018 66% Failed September 22, 2018
Redux with ReactJS $200 October 21, 2017 May 13, 2018 September 22, 2018 80% Passed September 25, 2018
-
-
-
-
-
- - -
  • -
  • +
  • -
    - Next +
    - -
  • - - + + + + +
    @@ -499,35 +680,543 @@ exports[`LearnerActivityTable renders active learners table correctly 1`] = ` exports[`LearnerActivityTable renders empty state correctly 1`] = `
    - - - - -
    - There are no results. +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + +
    + + + Email + + + + + + + + + + + Course Title + + + + + + + + + + + Course Price + + + + + + + + + + + Start Date + + + + + + + + + + + End Date + + + + + + + + + + + Passed Date + + + + + + + + + + + Current Grade + + + + + + + + + + + Progress Status + + + + + + + + + + + Last Activity Date + + + + + + + +
    +
    +
    + No results found +
    +
    + +
    @@ -535,253 +1224,412 @@ exports[`LearnerActivityTable renders empty state correctly 1`] = ` exports[`LearnerActivityTable renders inactive past month learners table correctly 1`] = `
    - - +
    + Showing 1 - 2 of 2. +
    + +
    +
    +
    +
    +
    +
    +
    +
    + @@ -891,101 +1740,105 @@ exports[`LearnerActivityTable renders inactive past month learners table correct
    - + - + - + - + - + - + - + - +
    Dive into ReactJS $200 October 21, 2017 May 13, 2018 66% Failed September 22, 2018
    Redux with ReactJS $200 October 21, 2017 May 13, 2018 80% Passed September 25, 2018
    -
    -
    -
    -
    -
    +
      -
    - - -
  • -
  • +
  • -
    - Next +
    - -
  • - - + + + + +
    @@ -993,253 +1846,412 @@ exports[`LearnerActivityTable renders inactive past month learners table correct exports[`LearnerActivityTable renders inactive past week learners table correctly 1`] = `
    - - +
    + Showing 1 - 2 of 2. +
    + +
    +
    +
    +
    +
    +
    +
    +
    + @@ -1349,101 +2362,105 @@ exports[`LearnerActivityTable renders inactive past week learners table correctl
    - + - + - + - + - + - + - + - +
    Dive into ReactJS $200 October 21, 2017 May 13, 2018 66% Failed September 22, 2018
    Redux with ReactJS $200 October 21, 2017 May 13, 2018 80% Passed September 25, 2018
    -
    -
    -
    -
    -
    - - -
  • -
  • +
  • -
    - Next +
    - -
  • - - + + + + +
    diff --git a/src/components/LearnerActivityTable/data/hooks/useCourseEnrollments.js b/src/components/LearnerActivityTable/data/hooks/useCourseEnrollments.js new file mode 100644 index 0000000000..e9d67c3e14 --- /dev/null +++ b/src/components/LearnerActivityTable/data/hooks/useCourseEnrollments.js @@ -0,0 +1,99 @@ +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' }, + courseListPrice: { key: 'course_list_price' }, + courseStartDate: { key: 'course_start_date' }, + courseEndDate: { key: 'course_end_date' }, + passedDate: { key: 'passed_date' }, + currentGrade: { key: 'current_grade' }, + progressStatus: { key: 'progress_status' }, + lastActivityDate: { key: 'last_activity_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 useCourseEnrollments = (enterpriseId, tableId) => { + const shouldTrackFetchEvents = useRef(false); + const [isLoading, setIsLoading] = useState(true); + const [courseEnrollments, setCourseEnrollments] = useState({ + itemCount: 0, + pageCount: 0, + results: [], + }); + + const fetchCourseEnrollments = useCallback(async (args) => { + try { + setIsLoading(true); + const options = { + page: args.pageIndex + 1, + pageSize: args.pageSize, + }; + applySortByToOptions(args.sortBy, options); + + const response = await EnterpriseDataApiService.fetchCourseEnrollments(enterpriseId, options); + const data = camelCaseObject(response.data); + setCourseEnrollments({ + 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, + ...options, + }, + ); + } else { + // set to true to enable tracking events on future API queries + shouldTrackFetchEvents.current = true; + } + } catch (error) { + logError(error); + } finally { + setIsLoading(false); + } + }, [enterpriseId, tableId]); + + const debouncedFetchCourseEnrollments = useMemo( + () => debounce(fetchCourseEnrollments, 300), + [fetchCourseEnrollments], + ); + + return { + isLoading, + courseEnrollments, + fetchCourseEnrollments: debouncedFetchCourseEnrollments, + }; +}; + +export default useCourseEnrollments; diff --git a/src/components/LearnerActivityTable/data/tests/constants.js b/src/components/LearnerActivityTable/data/tests/constants.js new file mode 100644 index 0000000000..7928440642 --- /dev/null +++ b/src/components/LearnerActivityTable/data/tests/constants.js @@ -0,0 +1,38 @@ +const mockUseCourseEnrollments = { + isLoading: false, + courseEnrollments: { + itemCount: 2, + pageCount: 1, + results: [ + { + id: 1, + passedDate: '2018-09-23', + courseTitle: 'Dive into ReactJS', + courseKey: 'edX/ReactJS', + userEmail: 'awesome.me@example.com', + courseListPrice: '200', + courseStartDate: '2017-10-21', + courseEndDate: '2018-05-13', + currentGrade: '0.66', + progressStatus: 'Failed', + lastActivityDate: '2018-09-22', + }, + { + id: 5, + passedDate: '2018-09-22', + courseTitle: 'Redux with ReactJS', + courseKey: 'edX/Redux_ReactJS', + userEmail: 'new@example.com', + courseListPrice: '200', + courseStartDate: '2017-10-21', + courseEndDate: '2018-05-13', + currentGrade: '0.80', + progressStatus: 'Passed', + lastActivityDate: '2018-09-25', + }, + ], + }, + fetchCourseEnrollments: jest.fn(), +}; + +export default mockUseCourseEnrollments; diff --git a/src/components/LearnerActivityTable/index.jsx b/src/components/LearnerActivityTable/index.jsx index 114763aaa7..318beb468d 100644 --- a/src/components/LearnerActivityTable/index.jsx +++ b/src/components/LearnerActivityTable/index.jsx @@ -1,153 +1,159 @@ import React from 'react'; import PropTypes from 'prop-types'; - -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; - -import TableContainer from '../../containers/TableContainer'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { DataTable, TextFilter } from '@openedx/paragon'; +import { connect } from 'react-redux'; +import useCourseEnrollments from './data/hooks/useCourseEnrollments'; +import { DEFAULT_PAGE, PAGE_SIZE } from '../../data/constants/table'; import { - i18nFormatTimestamp, i18nFormatPassedTimestamp, i18nFormatProgressStatus, formatPercentage, + i18nFormatTimestamp, + i18nFormatPassedTimestamp, + i18nFormatProgressStatus, + formatPercentage, } from '../../utils'; -import EnterpriseDataApiService from '../../data/services/EnterpriseDataApiService'; +import { formatPrice } from '../learner-credit-management/data'; -class LearnerActivityTable extends React.Component { - getTableColumns() { - const { activity, intl } = this.props; - const tableColumns = [ - { - label: intl.formatMessage({ - id: 'admin.portal.lpr.learner.activity.table.user_email.column.heading', - defaultMessage: 'Email', - description: 'Column heading for the user email column in the learner activity table', - }), - key: 'user_email', - columnSortable: true, - }, - { - label: intl.formatMessage({ - id: 'admin.portal.lpr.learner.activity.table.course_title.column.heading', - defaultMessage: 'Course Title', - description: 'Column heading for the course title column in the learner activity table', - }), - key: 'course_title', - columnSortable: true, - }, - { - label: intl.formatMessage({ - id: 'admin.portal.lpr.learner.activity.table.course_list_price.column.heading', - defaultMessage: 'Course Price', - description: 'Column heading for the course price column in the learner activity table', - }), - key: 'course_list_price', - columnSortable: true, - }, - { - label: intl.formatMessage({ - id: 'admin.portal.lpr.learner.activity.table.course_start_date.column.heading', - defaultMessage: 'Start Date', - description: 'Column heading for the course start date column in the learner activity table', - }), - key: 'course_start_date', - columnSortable: true, - }, - { - label: intl.formatMessage({ - id: 'admin.portal.lpr.learner.activity.table.course_end_date.column.heading', - defaultMessage: 'End Date', - description: 'Column heading for the course end date column in the learner activity table', - }), - key: 'course_end_date', - columnSortable: true, - }, - { - label: intl.formatMessage({ - id: 'admin.portal.lpr.learner.activity.table.passed_date.column.heading', - defaultMessage: 'Passed Date', - description: 'Column heading for the passed date column in the learner activity table', - }), - key: 'passed_date', - columnSortable: true, - }, - { - label: intl.formatMessage({ - id: 'admin.portal.lpr.learner.activity.table.current_grade.column.heading', - defaultMessage: 'Current Grade', - description: 'Column heading for the current grade column in the learner activity table', - }), - key: 'current_grade', - columnSortable: true, - }, - { - label: intl.formatMessage({ - id: 'admin.portal.lpr.learner.activity.table.progress_status.column.heading', - defaultMessage: 'Progress Status', - description: 'Column heading for the progress status column in the learner activity table', - }), - key: 'progress_status', - columnSortable: true, - }, - { - label: intl.formatMessage({ - id: 'admin.portal.lpr.learner.activity.table.enrollment_date.column.heading', - defaultMessage: 'Last Activity Date', - description: 'Column heading for the last activity date column in the learner activity table', - }), - key: 'last_activity_date', - columnSortable: true, - }, - ]; +const FilterStatus = (rest) => ; - if (activity !== 'active_past_week') { - return tableColumns.filter(column => column.key !== 'passed_date'); - } - return tableColumns; - } +const UserEmail = ({ row }) => ( + {row.original.userEmail} +); + +UserEmail.propTypes = { + row: PropTypes.shape({ + original: PropTypes.shape({ + userEmail: PropTypes.string.isRequired, + }).isRequired, + }).isRequired, +}; - formatTableData = enrollments => enrollments.map(enrollment => ({ - ...enrollment, - user_email: {enrollment.user_email}, - last_activity_date: i18nFormatTimestamp({ intl: this.props.intl, timestamp: enrollment.last_activity_date }), - course_start_date: i18nFormatTimestamp({ intl: this.props.intl, timestamp: enrollment.course_start_date }), - course_end_date: i18nFormatTimestamp({ intl: this.props.intl, timestamp: enrollment.course_end_date }), - enrollment_date: i18nFormatTimestamp({ - intl: this.props.intl, timestamp: enrollment.enrollment_date, - }), - passed_date: i18nFormatPassedTimestamp({ intl: this.props.intl, timestamp: enrollment.passed_date }), - user_account_creation_date: i18nFormatTimestamp({ - intl: this.props.intl, timestamp: enrollment.user_account_creation_date, - }), - progress_status: i18nFormatProgressStatus({ intl: this.props.intl, progressStatus: enrollment.progress_status }), - course_list_price: enrollment.course_list_price ? `$${enrollment.course_list_price}` : '', - current_grade: formatPercentage({ decimal: enrollment.current_grade }), - })); +const LearnerActivityTable = ({ id, enterpriseId, activity }) => { + const intl = useIntl(); + const { + isLoading, + courseEnrollments: tableData, + fetchCourseEnrollments: fetchTableData, + } = useCourseEnrollments(enterpriseId, id); - render() { - const { activity, id } = this.props; + const columns = [ + { + Header: intl.formatMessage({ + id: 'admin.portal.lpr.learner.activity.table.user_email.column.heading', + defaultMessage: 'Email', + description: 'Column heading for the user email column in the learner activity table', + }), + accessor: 'userEmail', + Cell: UserEmail, + }, + { + Header: intl.formatMessage({ + id: 'admin.portal.lpr.learner.activity.table.course_title.column.heading', + defaultMessage: 'Course Title', + description: 'Column heading for the course title column in the learner activity table', + }), + accessor: 'courseTitle', + }, + { + Header: intl.formatMessage({ + id: 'admin.portal.lpr.learner.activity.table.course_list_price.column.heading', + defaultMessage: 'Course Price', + description: 'Column heading for the course price column in the learner activity table', + }), + accessor: 'courseListPrice', + Cell: ({ row }) => formatPrice(row.values.courseListPrice), + }, + { + Header: intl.formatMessage({ + id: 'admin.portal.lpr.learner.activity.table.course_start_date.column.heading', + defaultMessage: 'Start Date', + description: 'Column heading for the course start date column in the learner activity table', + }), + accessor: 'courseStartDate', + Cell: ({ row }) => i18nFormatTimestamp({ intl, timestamp: row.values.courseStartDate }), + }, + { + Header: intl.formatMessage({ + id: 'admin.portal.lpr.learner.activity.table.course_end_date.column.heading', + defaultMessage: 'End Date', + description: 'Column heading for the course end date column in the learner activity table', + }), + accessor: 'courseEndDate', + Cell: ({ row }) => i18nFormatTimestamp({ intl, timestamp: row.values.courseEndDate }), + }, + { + Header: intl.formatMessage({ + id: 'admin.portal.lpr.learner.activity.table.passed_date.column.heading', + defaultMessage: 'Passed Date', + description: 'Column heading for the passed date column in the learner activity table', + }), + accessor: 'passedDate', + Cell: ({ row }) => i18nFormatPassedTimestamp({ intl, timestamp: row.values.passedDate }), + }, + { + Header: intl.formatMessage({ + id: 'admin.portal.lpr.learner.activity.table.current_grade.column.heading', + defaultMessage: 'Current Grade', + description: 'Column heading for the current grade column in the learner activity table', + }), + accessor: 'currentGrade', + Cell: ({ row }) => formatPercentage({ decimal: row.values.currentGrade }), + }, + { + Header: intl.formatMessage({ + id: 'admin.portal.lpr.learner.activity.table.progress_status.column.heading', + defaultMessage: 'Progress Status', + description: 'Column heading for the progress status column in the learner activity table', + }), + accessor: 'progressStatus', + Cell: ({ row }) => i18nFormatProgressStatus({ intl, progressStatus: row.values.progressStatus }), + }, + { + Header: intl.formatMessage({ + id: 'admin.portal.lpr.learner.activity.table.enrollment_date.column.heading', + defaultMessage: 'Last Activity Date', + description: 'Column heading for the last activity date column in the learner activity table', + }), + accessor: 'lastActivityDate', + Cell: ({ row }) => i18nFormatTimestamp({ intl, timestamp: row.values.lastActivityDate }), + }, + ]; - return ( - EnterpriseDataApiService.fetchCourseEnrollments( - enterpriseId, - { - learnerActivity: activity, - ...options, - }, - )} - columns={this.getTableColumns()} - formatData={this.formatTableData} - tableSortable - /> - ); + if (activity !== 'active_past_week') { + columns.splice(columns.findIndex(col => col.accessor === 'passedDate'), 1); } -} + + return ( + + ); +}; + +const mapStateToProps = state => ({ + enterpriseId: state.portalConfiguration.enterpriseId, +}); LearnerActivityTable.propTypes = { id: PropTypes.string.isRequired, + enterpriseId: PropTypes.string.isRequired, activity: PropTypes.string.isRequired, - // injected - intl: intlShape.isRequired, }; -export default injectIntl(LearnerActivityTable); +export default connect(mapStateToProps)(LearnerActivityTable); diff --git a/src/data/constants/table.js b/src/data/constants/table.js index aeb1588897..d83ac45f3d 100644 --- a/src/data/constants/table.js +++ b/src/data/constants/table.js @@ -5,6 +5,8 @@ const SORT_REQUEST = 'SORT_REQUEST'; const SORT_SUCCESS = 'SORT_SUCCESS'; const SORT_FAILURE = 'SORT_FAILURE'; const CLEAR_TABLE = 'CLEAR_TABLE'; +const PAGE_SIZE = 20; +const DEFAULT_PAGE = 0; export { PAGINATION_REQUEST, @@ -14,4 +16,6 @@ export { SORT_SUCCESS, SORT_FAILURE, CLEAR_TABLE, + PAGE_SIZE, + DEFAULT_PAGE, }; 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;