diff --git a/src/components/Admin/Admin.test.jsx b/src/components/Admin/Admin.test.jsx index 2a209b5c5e..24eef1a2d5 100644 --- a/src/components/Admin/Admin.test.jsx +++ b/src/components/Admin/Admin.test.jsx @@ -163,7 +163,7 @@ describe('', () => { /> )) .toJSON(); - expect(mockFetchDashboardAnalytics).toHaveBeenCalled(); + waitFor(() => expect(mockFetchDashboardAnalytics).toHaveBeenCalled()); expect(tree).toMatchSnapshot(); }); diff --git a/src/components/Admin/AdminCards.jsx b/src/components/Admin/AdminCards.jsx index 1766a03b56..3b717c0adf 100644 --- a/src/components/Admin/AdminCards.jsx +++ b/src/components/Admin/AdminCards.jsx @@ -1,106 +1,108 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { Award, Check, Groups, RemoveRedEye, } from '@openedx/paragon/icons'; import NumberCard from '../NumberCard'; -class AdminCards extends React.Component { - constructor(props) { - super(props); - const { intl } = this.props; +const AdminCards = ({ + activeLearners, + numberOfUsers, + courseCompletions, + enrolledLearners, +}) => { + const intl = useIntl(); - this.cards = { - numberOfUsers: { - ref: React.createRef(), - description: intl.formatMessage({ - id: 'adminPortal.cards.registeredLearners', - defaultMessage: 'total number of learners registered', + const cards = { + numberOfUsers: { + ref: React.createRef(), + description: intl.formatMessage({ + id: 'adminPortal.cards.registeredLearners', + defaultMessage: 'total number of learners registered', + }), + icon: Groups, + actions: [{ + label: intl.formatMessage({ + id: 'adminPortal.cards.registeredUnenrolledLearners', + defaultMessage: 'Which learners are registered but not yet enrolled in any courses?', }), - icon: Groups, - actions: [{ - label: intl.formatMessage({ - id: 'adminPortal.cards.registeredUnenrolledLearners', - defaultMessage: 'Which learners are registered but not yet enrolled in any courses?', - }), - slug: 'registered-unenrolled-learners', - }], - }, - enrolledLearners: { - id: 'enrolled-learners', - ref: React.createRef(), - description: intl.formatMessage({ - id: 'adminPortal.cards.enrolledOneCourse', - defaultMessage: 'learners enrolled in at least one course', + slug: 'registered-unenrolled-learners', + }], + }, + enrolledLearners: { + id: 'enrolled-learners', + ref: React.createRef(), + description: intl.formatMessage({ + id: 'adminPortal.cards.enrolledOneCourse', + defaultMessage: 'learners enrolled in at least one course', + }), + icon: Check, + actions: [{ + label: intl.formatMessage({ + id: 'adminPortal.cards.enrolledLearners', + defaultMessage: 'How many courses are learners enrolled in?', }), - icon: Check, - actions: [{ - label: intl.formatMessage({ - id: 'adminPortal.cards.enrolledLearners', - defaultMessage: 'How many courses are learners enrolled in?', - }), - slug: 'enrolled-learners', - }, { - label: intl.formatMessage({ - id: 'adminPortal.cards.enrolledLearnersInactiveCourses', - defaultMessage: 'Who is no longer enrolled in a current course?', - }), - slug: 'enrolled-learners-inactive-courses', - }], - }, - activeLearners: { - ref: React.createRef(), - description: intl.formatMessage({ - id: 'adminPortal.cards.activeLearnersPastWeek', - defaultMessage: 'active learners in the past week', + slug: 'enrolled-learners', + }, { + label: intl.formatMessage({ + id: 'adminPortal.cards.enrolledLearnersInactiveCourses', + defaultMessage: 'Who is no longer enrolled in a current course?', }), - icon: RemoveRedEye, - actions: [{ - label: intl.formatMessage({ - id: 'adminPortal.cards.learnersActiveWeek', - defaultMessage: 'Who are my top active learners?', - }), - slug: 'learners-active-week', - }, { - label: intl.formatMessage({ - id: 'adminPortal.cards.learnersInactiveWeek', - defaultMessage: 'Who has not been active for over a week?', - }), - slug: 'learners-inactive-week', - }, { - label: intl.formatMessage({ - id: 'adminPortal.cards.learnersInactiveMonth', - defaultMessage: 'Who has not been active for over a month?', - }), - slug: 'learners-inactive-month', - }], - }, - courseCompletions: { - ref: React.createRef(), - description: 'course completions', - icon: Award, - actions: [{ - label: intl.formatMessage({ - id: 'adminPortal.cards.completedLearners', - defaultMessage: 'How many courses have been completed by learners?', - }), - slug: 'completed-learners', - }, { - label: intl.formatMessage({ - id: 'adminPortal.cards.completedLearnersWeek', - defaultMessage: 'Who completed a course in the past week?', - }), - slug: 'completed-learners-week', - }], - }, - }; - } + slug: 'enrolled-learners-inactive-courses', + }], + }, + activeLearners: { + ref: React.createRef(), + description: intl.formatMessage({ + id: 'adminPortal.cards.activeLearnersPastWeek', + defaultMessage: 'active learners in the past week', + }), + icon: RemoveRedEye, + actions: [{ + label: intl.formatMessage({ + id: 'adminPortal.cards.learnersActiveWeek', + defaultMessage: 'Who are my top active learners?', + }), + slug: 'learners-active-week', + }, { + label: intl.formatMessage({ + id: 'adminPortal.cards.learnersInactiveWeek', + defaultMessage: 'Who has not been active for over a week?', + }), + slug: 'learners-inactive-week', + }, { + label: intl.formatMessage({ + id: 'adminPortal.cards.learnersInactiveMonth', + defaultMessage: 'Who has not been active for over a month?', + }), + slug: 'learners-inactive-month', + }], + }, + courseCompletions: { + ref: React.createRef(), + description: 'course completions', + icon: Award, + actions: [{ + label: intl.formatMessage({ + id: 'adminPortal.cards.completedLearners', + defaultMessage: 'How many courses have been completed by learners?', + }), + slug: 'completed-learners', + }, { + label: intl.formatMessage({ + id: 'adminPortal.cards.completedLearnersWeek', + defaultMessage: 'Who completed a course in the past week?', + }), + slug: 'completed-learners-week', + }], + }, + }; - renderCard({ title, cardKey }) { - const card = this.cards[cardKey]; + const renderCard = ({ title, cardKey }) => { + const card = cards[cardKey]; return (
); - } - - render() { - const { - activeLearners, - numberOfUsers, - courseCompletions, - enrolledLearners, - } = this.props; + }; - const data = { - activeLearners: activeLearners.past_week, - numberOfUsers, - courseCompletions, - enrolledLearners, - }; + const data = { + activeLearners: activeLearners.past_week, + numberOfUsers, + courseCompletions, + enrolledLearners, + }; - return Object.keys(this.cards).map(cardKey => ( - this.renderCard({ - title: data[cardKey], - cardKey, - }) - )); - } -} + return Object.keys(cards).map(cardKey => ( + renderCard({ + title: data[cardKey], + cardKey, + }) + )); +}; AdminCards.propTypes = { activeLearners: PropTypes.shape({ @@ -151,8 +144,6 @@ AdminCards.propTypes = { numberOfUsers: PropTypes.number.isRequired, courseCompletions: PropTypes.number.isRequired, enrolledLearners: PropTypes.number.isRequired, - // injected - intl: intlShape.isRequired, }; -export default injectIntl(AdminCards); +export default AdminCards; diff --git a/src/components/Admin/AdminSearchForm.jsx b/src/components/Admin/AdminSearchForm.jsx index fc784eb0ab..a756c41662 100644 --- a/src/components/Admin/AdminSearchForm.jsx +++ b/src/components/Admin/AdminSearchForm.jsx @@ -1,12 +1,12 @@ /* eslint-disable camelcase */ -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { Form } from '@openedx/paragon'; import { Info } from '@openedx/paragon/icons'; -import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils'; import SearchBar from '../SearchBar'; @@ -15,36 +15,30 @@ import IconWithTooltip from '../IconWithTooltip'; import { withLocation, withNavigate } from '../../hoc'; import EVENT_NAMES from '../../eventTracking'; -class AdminSearchForm extends React.Component { - componentDidUpdate(prevProps) { - const { - searchParams: { - searchQuery, searchCourseQuery, searchDateQuery, searchBudgetQuery, searchGroupQuery, - }, - } = this.props; - const { - searchParams: { - searchQuery: prevSearchQuery, - searchCourseQuery: prevSearchCourseQuery, - searchDateQuery: prevSearchDateQuery, - searchBudgetQuery: prevSearchBudgetQuery, - searchGroupQuery: prevSearchGroupQuery, - }, - } = prevProps; +const AdminSearchForm = ({ + searchEnrollmentsList, + searchParams: { + searchQuery, searchCourseQuery, searchDateQuery, searchBudgetQuery, searchGroupQuery, + }, + tableData = [], + budgets, + groups, + navigate, + location, + enterpriseId, +}) => { + const intl = useIntl(); + const isFirstRender = useRef(true); - if (searchQuery !== prevSearchQuery || searchCourseQuery !== prevSearchCourseQuery - || searchDateQuery !== prevSearchDateQuery || searchBudgetQuery !== prevSearchBudgetQuery - || searchGroupQuery !== prevSearchGroupQuery) { - this.handleSearch(); + useEffect(() => { + if (isFirstRender.current) { + isFirstRender.current = false; + return; } - } + searchEnrollmentsList(); + }, [searchEnrollmentsList, searchQuery, searchCourseQuery, searchDateQuery, searchBudgetQuery, searchGroupQuery]); - handleSearch() { - this.props.searchEnrollmentsList(); - } - - onCourseSelect(event) { - const { navigate, location } = this.props; + const onCourseSelect = (event) => { const updateParams = { search_course: event.target.value, page: 1, @@ -53,248 +47,234 @@ class AdminSearchForm extends React.Component { updateParams.search_start_date = ''; } updateUrl(navigate, location.pathname, updateParams); - } + }; - onBudgetSelect(event) { - const { navigate, location } = this.props; + const onBudgetSelect = (event) => { const updateParams = { budget_uuid: event.target.value, page: 1, }; updateUrl(navigate, location.pathname, updateParams); - } + }; - onGroupSelect(event) { - const { navigate, location } = this.props; + const onGroupSelect = (event) => { const updateParams = { group_uuid: event.target.value, page: 1, }; updateUrl(navigate, location.pathname, updateParams); sendEnterpriseTrackEvent( - this.props.enterpriseId, + enterpriseId, EVENT_NAMES.LEARNER_PROGRESS_REPORT.FILTER_BY_GROUP_DROPDOWN, { group: event.target.value }, ); - } - - render() { - const { - intl, - tableData, - budgets, - groups, - searchParams: { - searchCourseQuery, searchDateQuery, searchQuery, searchBudgetQuery, searchGroupQuery, - }, - } = this.props; + }; - const courseTitles = Array.from(new Set(tableData.map(en => en.course_title).sort())); - const courseDates = Array.from(new Set(tableData.map(en => en.course_start_date).sort().reverse())); - const columnWidth = (budgets?.length || groups?.length) ? 'col-md-3' : 'col-md-6'; + const courseTitles = Array.from(new Set(tableData.map(en => en.course_title).sort())); + const courseDates = Array.from(new Set(tableData.map(en => en.course_start_date).sort().reverse())); + const columnWidth = (budgets?.length || groups?.length) ? 'col-md-3' : 'col-md-6'; - return ( -
-
-
- {groups?.length ? ( -
- - - - - this.onGroupSelect(e)} - > - - {groups.map(group => ( - - ))} - - -
- ) : null} -
+ return ( +
+
+
+ {groups?.length ? ( +
this.onCourseSelect(e)} + value={searchGroupQuery} + onChange={e => onGroupSelect(e)} > - {courseTitles.map(title => ( + {groups.map(group => ( ))}
-
- - - - - - + + + + + onCourseSelect(e)} + > + + {courseTitles.map(title => ( + + ))} + + +
+
+ + + + + + + + updateUrl(navigate, location.pathname, { + search_start_date: event.target.value, + page: 1, + })} + disabled={!searchCourseQuery} + > + + {searchCourseQuery && courseDates.map(date => ( + + ))} + + +
+ {budgets?.length ? ( +
+ + + updateUrl(this.props.navigate, this.props.location.pathname, { - search_start_date: event.target.value, - page: 1, - })} - disabled={!searchCourseQuery} + value={searchBudgetQuery} + onChange={e => onBudgetSelect(e)} > - {searchCourseQuery && courseDates.map(date => ( + {budgets.map(budget => ( ))}
- {budgets?.length ? ( -
- - - - - this.onBudgetSelect(e)} - > - - {budgets.map(budget => ( - - ))} - - -
- ) : null } -
- - - - updateUrl(this.props.navigate, this.props.location.pathname, { - search: query, - page: 1, - })} - onClear={() => updateUrl(this.props.navigate, this.props.location.pathname, { search: undefined })} - value={searchQuery} - aria-labelledby="search-email-label" - className="py-0" - inputProps={{ 'data-hj-suppress': true }} + ) : null } +
+ + -
+ + updateUrl(navigate, location.pathname, { + search: query, + page: 1, + })} + onClear={() => updateUrl(navigate, location.pathname, { search: undefined })} + value={searchQuery} + aria-labelledby="search-email-label" + className="py-0" + inputProps={{ 'data-hj-suppress': true }} + />
- ); - } -} +
+ ); +}; AdminSearchForm.defaultProps = { tableData: [], @@ -317,8 +297,6 @@ AdminSearchForm.propTypes = { pathname: PropTypes.string, }), enterpriseId: PropTypes.string, - // injected - intl: intlShape.isRequired, }; -export default withLocation(withNavigate(injectIntl(AdminSearchForm))); +export default withLocation(withNavigate(AdminSearchForm)); diff --git a/src/components/Admin/__snapshots__/Admin.test.jsx.snap b/src/components/Admin/__snapshots__/Admin.test.jsx.snap index 801d4c95e7..6afddaf057 100644 --- a/src/components/Admin/__snapshots__/Admin.test.jsx.snap +++ b/src/components/Admin/__snapshots__/Admin.test.jsx.snap @@ -1047,7 +1047,7 @@ exports[` renders correctly with dashboard analytics data renders # cou > @@ -1056,7 +1056,7 @@ exports[` renders correctly with dashboard analytics data renders # cou > renders correctly with dashboard analytics data renders # cou >