From 0c5823a70041d1d391d4ea1937df1b4939daa1fb Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Fri, 1 Dec 2023 11:29:50 -0500 Subject: [PATCH 01/11] feat: modifies logic for setting cookies on dashboard page load --- src/components/dashboard/DashboardPage.jsx | 13 ++----------- .../course-enrollments/CourseEnrollments.jsx | 11 ++++++++++- .../EnterpriseLearnerFirstVisitRedirect.jsx | 17 +++++++++-------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/components/dashboard/DashboardPage.jsx b/src/components/dashboard/DashboardPage.jsx index b9c1a20af..17a4da10a 100644 --- a/src/components/dashboard/DashboardPage.jsx +++ b/src/components/dashboard/DashboardPage.jsx @@ -1,13 +1,7 @@ -import React, { - useContext, useEffect, useMemo, -} from 'react'; +import React, { useContext, useEffect, useMemo } from 'react'; import { Helmet } from 'react-helmet'; import { useHistory, useLocation } from 'react-router-dom'; -import { - Container, - Tabs, - Tab, -} from '@edx/paragon'; +import { Container, Tab, Tabs } from '@edx/paragon'; import { AppContext } from '@edx/frontend-platform/react'; import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils'; import { ProgramListingPage } from '../program-progress'; @@ -18,7 +12,6 @@ import { useLearnerProgramsListData } from '../program-progress/data/hooks'; import { useInProgressPathwaysData } from '../pathway-progress/data/hooks'; import CoursesTabComponent from './main-content/CoursesTabComponent'; import { MyCareerTab } from '../my-career'; -import EnterpriseLearnerFirstVisitRedirect from '../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'; import { UserSubsidyContext } from '../enterprise-user-subsidy'; import { IntegrationWarningModal } from '../integration-warning-modal'; import SubscriptionExpirationModal from './SubscriptionExpirationModal'; @@ -37,7 +30,6 @@ const DashboardPage = () => { canOnlyViewHighlightSets, }, } = useEnterpriseCuration(enterpriseConfig.uuid); - const onSelectHandler = (key) => { if (key === 'my-career') { sendEnterpriseTrackEvent( @@ -94,7 +86,6 @@ const DashboardPage = () => {

{userFirstName ? `Welcome, ${userFirstName}!` : 'Welcome!'}

- onSelectHandler(k)}>{allTabs.filter(tab => tab)} {enterpriseConfig.showIntegrationWarning && } {subscriptionPlan && showExpirationNotifications && } diff --git a/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx b/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx index f27a1c618..74fa44014 100644 --- a/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx +++ b/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx @@ -10,11 +10,16 @@ import CourseEnrollmentsAlert from './CourseEnrollmentsAlert'; import CourseAssignmentAlert from './CourseAssignmentAlert'; import { CourseEnrollmentsContext } from './CourseEnrollmentsContextProvider'; import { - getTransformedAllocatedAssignments, sortedEnrollmentsByEnrollmentDate, sortAssignmentsByAssignmentStatus, + getTransformedAllocatedAssignments, isAssignmentExpired, + sortAssignmentsByAssignmentStatus, + sortedEnrollmentsByEnrollmentDate, } from './data/utils'; import { UserSubsidyContext } from '../../../enterprise-user-subsidy'; import { features } from '../../../../config'; +import EnterpriseLearnerFirstVisitRedirect, { + isFirstDashboardPageVisit, +} from '../../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'; export const COURSE_SECTION_TITLES = { current: 'My courses', @@ -115,6 +120,10 @@ const CourseEnrollments = ({ children }) => { const hasCourseEnrollments = Object.values(courseEnrollmentsByStatus).flat().length > 0; const hasCourseAssignments = filteredAssignments?.length > 0; + if (hasCourseAssignments && isFirstDashboardPageVisit()) { + return ; + } + return ( <> {showCancelledAssignmentsAlert && ( diff --git a/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx b/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx index 7e7cd165b..2c17e096e 100644 --- a/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx +++ b/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx @@ -2,23 +2,24 @@ import React, { useEffect } from 'react'; import { Redirect, useParams } from 'react-router-dom'; import Cookies from 'universal-cookie'; +export const isFirstDashboardPageVisit = () => { + const cookies = new Cookies(); + + const hasUserVisitedDashboard = cookies.get('has-user-visited-learner-dashboard'); + return !hasUserVisitedDashboard; +}; + const EnterpriseLearnerFirstVisitRedirect = () => { const { enterpriseSlug } = useParams(); - const cookies = new Cookies(); - const isFirstVisit = () => { - const hasUserVisitedDashboard = cookies.get('has-user-visited-learner-dashboard'); - return !hasUserVisitedDashboard; - }; - useEffect(() => { - if (isFirstVisit()) { + if (isFirstDashboardPageVisit()) { cookies.set('has-user-visited-learner-dashboard', true, { path: '/' }); } }); - if (isFirstVisit()) { + if (isFirstDashboardPageVisit()) { return ; } From 98001a84a8eeae24a374c7c851819993fbeb2ad1 Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Mon, 4 Dec 2023 10:12:40 -0500 Subject: [PATCH 02/11] chore: tests --- src/components/dashboard/DashboardPage.jsx | 26 ++++- .../course-enrollments/CourseEnrollments.jsx | 8 -- .../course-enrollments/data/hooks.js | 6 +- .../dashboard/tests/DashboardPage.test.jsx | 105 +++++++++++++++--- 4 files changed, 114 insertions(+), 31 deletions(-) diff --git a/src/components/dashboard/DashboardPage.jsx b/src/components/dashboard/DashboardPage.jsx index 17a4da10a..a18561495 100644 --- a/src/components/dashboard/DashboardPage.jsx +++ b/src/components/dashboard/DashboardPage.jsx @@ -15,13 +15,21 @@ import { MyCareerTab } from '../my-career'; import { UserSubsidyContext } from '../enterprise-user-subsidy'; import { IntegrationWarningModal } from '../integration-warning-modal'; import SubscriptionExpirationModal from './SubscriptionExpirationModal'; +import { ASSIGNMENT_TYPES } from './main-content/course-enrollments/CourseEnrollments'; +import EnterpriseLearnerFirstVisitRedirect, { + isFirstDashboardPageVisit, +} from '../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'; const DashboardPage = () => { const { state } = useLocation(); const history = useHistory(); const { enterpriseConfig, authenticatedUser } = useContext(AppContext); const { username } = authenticatedUser; - const { subscriptionPlan, showExpirationNotifications } = useContext(UserSubsidyContext); + const { + subscriptionPlan, + showExpirationNotifications, + redeemableLearnerCreditPolicies, + } = useContext(UserSubsidyContext); // TODO: Create a context provider containing these 2 data fetch hooks to future proof when we need to use this data const [learnerProgramsListData, programsFetchError] = useLearnerProgramsListData(enterpriseConfig.uuid); const [pathwayProgressData, pathwayFetchError] = useInProgressPathwaysData(enterpriseConfig.uuid); @@ -30,6 +38,18 @@ const DashboardPage = () => { canOnlyViewHighlightSets, }, } = useEnterpriseCuration(enterpriseConfig.uuid); + + const hasActiveCourseAssignments = (learnerCreditPolicies) => { + const learnerContentAssignmentsArray = learnerCreditPolicies?.flatMap( + item => item?.learnerContentAssignments || [], + ); + // looks for some course assignments that are either 'allocated' or 'accepted' + const hasActiveAssignments = learnerContentAssignmentsArray.filter( + assignment => assignment.state !== ASSIGNMENT_TYPES.cancelled, + ); + return hasActiveAssignments.length > 0; + }; + const onSelectHandler = (key) => { if (key === 'my-career') { sendEnterpriseTrackEvent( @@ -79,6 +99,10 @@ const DashboardPage = () => { ), ]; + if (!hasActiveCourseAssignments(redeemableLearnerCreditPolicies) && isFirstDashboardPageVisit()) { + return ; + } + return ( <> diff --git a/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx b/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx index 74fa44014..69b7c4cea 100644 --- a/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx +++ b/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx @@ -17,9 +17,6 @@ import { } from './data/utils'; import { UserSubsidyContext } from '../../../enterprise-user-subsidy'; import { features } from '../../../../config'; -import EnterpriseLearnerFirstVisitRedirect, { - isFirstDashboardPageVisit, -} from '../../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'; export const COURSE_SECTION_TITLES = { current: 'My courses', @@ -68,7 +65,6 @@ const CourseEnrollments = ({ children }) => { setShowCancelledAssignmentsAlert(hasCancelledAssignments); setShowExpiredAssignmentsAlert(hasExpiredAssignments); }, [redeemableLearnerCreditPolicies]); - const filteredAssignments = assignments?.filter((assignment) => assignment?.state === ASSIGNMENT_TYPES.allocated || assignment?.state === ASSIGNMENT_TYPES.cancelled); const assignedCourses = getTransformedAllocatedAssignments(filteredAssignments, slug); @@ -120,10 +116,6 @@ const CourseEnrollments = ({ children }) => { const hasCourseEnrollments = Object.values(courseEnrollmentsByStatus).flat().length > 0; const hasCourseAssignments = filteredAssignments?.length > 0; - if (hasCourseAssignments && isFirstDashboardPageVisit()) { - return ; - } - return ( <> {showCancelledAssignmentsAlert && ( diff --git a/src/components/dashboard/main-content/course-enrollments/data/hooks.js b/src/components/dashboard/main-content/course-enrollments/data/hooks.js index bfb434a91..534dcac63 100644 --- a/src/components/dashboard/main-content/course-enrollments/data/hooks.js +++ b/src/components/dashboard/main-content/course-enrollments/data/hooks.js @@ -1,6 +1,4 @@ -import { - useState, useEffect, useCallback, -} from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { camelCaseObject } from '@edx/frontend-platform/utils'; import { logError } from '@edx/frontend-platform/logging'; import _camelCase from 'lodash.camelcase'; @@ -11,8 +9,8 @@ import { groupCourseEnrollmentsByStatus, transformCourseEnrollment } from './uti import { COURSE_STATUSES } from './constants'; import CourseService from '../../../../course/data/service'; import { - createEnrollWithLicenseUrl, createEnrollWithCouponCodeUrl, + createEnrollWithLicenseUrl, findCouponCodeForCourse, findHighestLevelSeatSku, getSubsidyToApplyForCourse, diff --git a/src/components/dashboard/tests/DashboardPage.test.jsx b/src/components/dashboard/tests/DashboardPage.test.jsx index 2921eea55..f62197b12 100644 --- a/src/components/dashboard/tests/DashboardPage.test.jsx +++ b/src/components/dashboard/tests/DashboardPage.test.jsx @@ -7,21 +7,15 @@ import { breakpoints } from '@edx/paragon'; import Cookies from 'universal-cookie'; import userEvent from '@testing-library/user-event'; +import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils'; import { UserSubsidyContext } from '../../enterprise-user-subsidy'; import { CourseContextProvider } from '../../course/CourseContextProvider'; -import { - SUBSCRIPTION_EXPIRED_MODAL_TITLE, - SUBSCRIPTION_EXPIRING_MODAL_TITLE, -} from '../SubscriptionExpirationModal'; -import { - SEEN_SUBSCRIPTION_EXPIRATION_MODAL_COOKIE_PREFIX, -} from '../../../config/constants'; +import { SUBSCRIPTION_EXPIRED_MODAL_TITLE, SUBSCRIPTION_EXPIRING_MODAL_TITLE } from '../SubscriptionExpirationModal'; +import { SEEN_SUBSCRIPTION_EXPIRATION_MODAL_COOKIE_PREFIX } from '../../../config/constants'; import { features } from '../../../config'; import * as hooks from '../main-content/course-enrollments/data/hooks'; -import { - renderWithRouter, -} from '../../../utils/tests'; +import { renderWithRouter } from '../../../utils/tests'; import DashboardPage from '../DashboardPage'; import { LICENSE_ACTIVATION_MESSAGE } from '../data/constants'; @@ -31,6 +25,9 @@ import { LICENSE_STATUS } from '../../enterprise-user-subsidy/data/constants'; import { SubsidyRequestsContext } from '../../enterprise-subsidy-requests'; import { SUBSIDY_TYPE } from '../../enterprise-subsidy-requests/constants'; import { sortAssignmentsByAssignmentStatus } from '../main-content/course-enrollments/data/utils'; +import EnterpriseLearnerFirstVisitRedirect, { + isFirstDashboardPageVisit, +} from '../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'; const defaultCouponCodesState = { couponCodes: [], @@ -40,6 +37,11 @@ const defaultCouponCodesState = { const mockAuthenticatedUser = { username: 'myspace-tom', name: 'John Doe' }; +jest.mock('@edx/frontend-enterprise-utils', () => ({ + ...jest.requireActual('@edx/frontend-enterprise-utils'), + sendEnterpriseTrackEvent: jest.fn(), +})); + jest.mock('../../../config', () => ({ features: { FEATURE_ENABLE_PATHWAY_PROGRESS: jest.fn(), @@ -52,6 +54,14 @@ jest.mock('../main-content/course-enrollments/data/utils', () => ({ sortAssignmentsByAssignmentStatus: jest.fn(), })); +jest.mock('../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'); + +// jest.mock('../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect', () => ( +// { +// ...jest.requireActual('../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'), +// isFirstDashboardPageVisit: jest.fn(), +// })); + const defaultAppState = { enterpriseConfig: { name: 'BearsRUs', @@ -68,6 +78,7 @@ const defaultAppState = { const defaultUserSubsidyState = { couponCodes: defaultCouponCodesState, enterpriseOffers: [], + redeemableLearnerCreditPolicies: [], }; const defaultCourseState = { @@ -176,12 +187,13 @@ describe('', () => { }); beforeEach(() => { + jest.clearAllMocks(); sortAssignmentsByAssignmentStatus.mockReturnValue([]); }); it('renders user first name if available', () => { renderWithRouter(); - expect(screen.getByText('Welcome, John!')); + expect(screen.getByText('Welcome, John!')).toBeInTheDocument(); }); it('does not render user first name if not available', () => { @@ -193,7 +205,7 @@ describe('', () => { }, }; renderWithRouter(); - expect(screen.getByText('Welcome!')); + expect(screen.getByText('Welcome!')).toBeInTheDocument(); }); it('renders license activation alert on activation success', () => { @@ -201,7 +213,7 @@ describe('', () => { , { route: '/?activationSuccess=true' }, ); - expect(screen.getByText(LICENSE_ACTIVATION_MESSAGE)); + expect(screen.getByText(LICENSE_ACTIVATION_MESSAGE)).toBeInTheDocument(); }); it('does not render license activation alert without activation success', () => { @@ -218,7 +230,7 @@ describe('', () => { renderWithRouter( , ); - expect(screen.getByTestId('sidebar')); + expect(screen.getByTestId('sidebar')).toBeInTheDocument(); }); it('renders subsidies summary on a small screen', () => { @@ -233,14 +245,14 @@ describe('', () => { }} />, ); - expect(screen.getByTestId('subsidies-summary')); + expect(screen.getByTestId('subsidies-summary')).toBeInTheDocument(); }); it('renders "Find a course" when search is enabled for the customer', () => { renderWithRouter( , ); - expect(screen.getByText('Find a course')); + expect(screen.getByText('Find a course')).toBeInTheDocument(); }); it('renders Pathways when feature is enabled', () => { @@ -258,7 +270,7 @@ describe('', () => { renderWithRouter( , ); - expect(screen.getByText('Pathways')); + expect(screen.getByText('Pathways')).toBeInTheDocument(); }); it('renders My Career when feature is enabled', () => { @@ -266,7 +278,7 @@ describe('', () => { renderWithRouter( , ); - expect(screen.getByText('My Career')); + expect(screen.getByText('My Career')).toBeInTheDocument(); }); it('does not render "Find a course" when search is disabled for the customer', () => { @@ -331,6 +343,63 @@ describe('', () => { expect(programsTab).toHaveAttribute('aria-selected', 'false'); }); + it('should send track event when "my-craeer" tab selected', () => { + renderWithRouter(); + + const myCareerTab = screen.getByText('My Career'); + userEvent.click(myCareerTab); + + expect(sendEnterpriseTrackEvent).toHaveBeenCalledTimes(1); + }); + + it('should render redirect component if no cookie and no courseAssignments exist', () => { + isFirstDashboardPageVisit.mockReturnValueOnce(true); + EnterpriseLearnerFirstVisitRedirect.mockImplementation(() => ( + +
search-page
+
+ )); + + const noActiveCourseAssignmentUserSubsidyState = { + ...defaultUserSubsidyState, + redeemableLearnerCreditPolicies: [{ + learnerContentAssignments: [{ + state: 'cancelled', + }, + ], + }], + }; + renderWithRouter(); + + expect(isFirstDashboardPageVisit).toHaveBeenCalled(); + expect(screen.getByText('search-page')).toBeInTheDocument(); + }); + + it('should not render redirect component if active learnerContentAssignment exist', () => { + isFirstDashboardPageVisit.mockReturnValueOnce(true); + EnterpriseLearnerFirstVisitRedirect.mockImplementation(() => ( + +
search-page
+
+ )); + + const noActiveCourseAssignmentUserSubsidyState = { + ...defaultUserSubsidyState, + redeemableLearnerCreditPolicies: [{ + learnerContentAssignments: [{ + state: 'allocated', + }, + { + state: 'accepted', + }, + ], + }], + }; + renderWithRouter(); + + expect(screen.queryByText('search-page')).not.toBeInTheDocument(); + }); + describe('SubscriptionExpirationModal', () => { it('should not render when > 60 days of access remain', () => { renderWithRouter( From 4fb6173dc78f0d61c4f3075069a72ef6381afa00 Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Mon, 4 Dec 2023 10:18:52 -0500 Subject: [PATCH 03/11] chore: cleanup --- src/components/dashboard/DashboardPage.jsx | 2 +- src/components/dashboard/tests/DashboardPage.test.jsx | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/components/dashboard/DashboardPage.jsx b/src/components/dashboard/DashboardPage.jsx index a18561495..49a4688be 100644 --- a/src/components/dashboard/DashboardPage.jsx +++ b/src/components/dashboard/DashboardPage.jsx @@ -43,7 +43,7 @@ const DashboardPage = () => { const learnerContentAssignmentsArray = learnerCreditPolicies?.flatMap( item => item?.learnerContentAssignments || [], ); - // looks for some course assignments that are either 'allocated' or 'accepted' + // filters out course assignments that are not considered active const hasActiveAssignments = learnerContentAssignmentsArray.filter( assignment => assignment.state !== ASSIGNMENT_TYPES.cancelled, ); diff --git a/src/components/dashboard/tests/DashboardPage.test.jsx b/src/components/dashboard/tests/DashboardPage.test.jsx index f62197b12..ead33b58a 100644 --- a/src/components/dashboard/tests/DashboardPage.test.jsx +++ b/src/components/dashboard/tests/DashboardPage.test.jsx @@ -56,12 +56,6 @@ jest.mock('../main-content/course-enrollments/data/utils', () => ({ jest.mock('../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'); -// jest.mock('../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect', () => ( -// { -// ...jest.requireActual('../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'), -// isFirstDashboardPageVisit: jest.fn(), -// })); - const defaultAppState = { enterpriseConfig: { name: 'BearsRUs', From 59074f72a5a8431eff0504c83825117c0b04a3f2 Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Mon, 4 Dec 2023 10:28:25 -0500 Subject: [PATCH 04/11] fix: typo in test name --- src/components/dashboard/tests/DashboardPage.test.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/dashboard/tests/DashboardPage.test.jsx b/src/components/dashboard/tests/DashboardPage.test.jsx index ead33b58a..fbc3c3d0e 100644 --- a/src/components/dashboard/tests/DashboardPage.test.jsx +++ b/src/components/dashboard/tests/DashboardPage.test.jsx @@ -337,7 +337,7 @@ describe('', () => { expect(programsTab).toHaveAttribute('aria-selected', 'false'); }); - it('should send track event when "my-craeer" tab selected', () => { + it('should send track event when "my-career" tab selected', () => { renderWithRouter(); const myCareerTab = screen.getByText('My Career'); From 0c1018f5c84ed6269e7a92e8877e78bc96b54ca3 Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Mon, 4 Dec 2023 14:34:07 -0500 Subject: [PATCH 05/11] chore: PR fixes with corresponding tests --- src/components/dashboard/DashboardPage.jsx | 22 +------ .../course-enrollments/CourseEnrollments.jsx | 1 + .../dashboard/tests/DashboardPage.test.jsx | 54 +++++----------- .../EnterpriseLearnerFirstVisitRedirect.jsx | 26 ++++++-- ...terpriseLearnerFirstVisitRedirect.test.jsx | 61 ++++++++++++++++++- 5 files changed, 96 insertions(+), 68 deletions(-) diff --git a/src/components/dashboard/DashboardPage.jsx b/src/components/dashboard/DashboardPage.jsx index 49a4688be..57c39cb5b 100644 --- a/src/components/dashboard/DashboardPage.jsx +++ b/src/components/dashboard/DashboardPage.jsx @@ -15,10 +15,7 @@ import { MyCareerTab } from '../my-career'; import { UserSubsidyContext } from '../enterprise-user-subsidy'; import { IntegrationWarningModal } from '../integration-warning-modal'; import SubscriptionExpirationModal from './SubscriptionExpirationModal'; -import { ASSIGNMENT_TYPES } from './main-content/course-enrollments/CourseEnrollments'; -import EnterpriseLearnerFirstVisitRedirect, { - isFirstDashboardPageVisit, -} from '../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'; +import EnterpriseLearnerFirstVisitRedirect from '../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'; const DashboardPage = () => { const { state } = useLocation(); @@ -28,7 +25,6 @@ const DashboardPage = () => { const { subscriptionPlan, showExpirationNotifications, - redeemableLearnerCreditPolicies, } = useContext(UserSubsidyContext); // TODO: Create a context provider containing these 2 data fetch hooks to future proof when we need to use this data const [learnerProgramsListData, programsFetchError] = useLearnerProgramsListData(enterpriseConfig.uuid); @@ -39,17 +35,6 @@ const DashboardPage = () => { }, } = useEnterpriseCuration(enterpriseConfig.uuid); - const hasActiveCourseAssignments = (learnerCreditPolicies) => { - const learnerContentAssignmentsArray = learnerCreditPolicies?.flatMap( - item => item?.learnerContentAssignments || [], - ); - // filters out course assignments that are not considered active - const hasActiveAssignments = learnerContentAssignmentsArray.filter( - assignment => assignment.state !== ASSIGNMENT_TYPES.cancelled, - ); - return hasActiveAssignments.length > 0; - }; - const onSelectHandler = (key) => { if (key === 'my-career') { sendEnterpriseTrackEvent( @@ -99,10 +84,6 @@ const DashboardPage = () => { ), ]; - if (!hasActiveCourseAssignments(redeemableLearnerCreditPolicies) && isFirstDashboardPageVisit()) { - return ; - } - return ( <> @@ -110,6 +91,7 @@ const DashboardPage = () => {

{userFirstName ? `Welcome, ${userFirstName}!` : 'Welcome!'}

+ onSelectHandler(k)}>{allTabs.filter(tab => tab)} {enterpriseConfig.showIntegrationWarning && } {subscriptionPlan && showExpirationNotifications && } diff --git a/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx b/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx index 69b7c4cea..a5004d135 100644 --- a/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx +++ b/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx @@ -53,6 +53,7 @@ const CourseEnrollments = ({ children }) => { const [showExpiredAssignmentsAlert, setShowExpiredAssignmentsAlert] = useState(false); useEffect(() => { + // TODO: Refactor to DRY up code for redeemableLearnerCreditPolicies const data = redeemableLearnerCreditPolicies?.flatMap(item => item?.learnerContentAssignments || []); const assignmentsData = sortAssignmentsByAssignmentStatus(data); setAssignments(assignmentsData); diff --git a/src/components/dashboard/tests/DashboardPage.test.jsx b/src/components/dashboard/tests/DashboardPage.test.jsx index fbc3c3d0e..4ee732df8 100644 --- a/src/components/dashboard/tests/DashboardPage.test.jsx +++ b/src/components/dashboard/tests/DashboardPage.test.jsx @@ -25,9 +25,7 @@ import { LICENSE_STATUS } from '../../enterprise-user-subsidy/data/constants'; import { SubsidyRequestsContext } from '../../enterprise-subsidy-requests'; import { SUBSIDY_TYPE } from '../../enterprise-subsidy-requests/constants'; import { sortAssignmentsByAssignmentStatus } from '../main-content/course-enrollments/data/utils'; -import EnterpriseLearnerFirstVisitRedirect, { - isFirstDashboardPageVisit, -} from '../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'; +import * as EnterpriseLearnerFirstVisitRedirect from '../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'; const defaultCouponCodesState = { couponCodes: [], @@ -54,7 +52,7 @@ jest.mock('../main-content/course-enrollments/data/utils', () => ({ sortAssignmentsByAssignmentStatus: jest.fn(), })); -jest.mock('../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'); +// jest.mock('../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'); const defaultAppState = { enterpriseConfig: { @@ -72,7 +70,16 @@ const defaultAppState = { const defaultUserSubsidyState = { couponCodes: defaultCouponCodesState, enterpriseOffers: [], - redeemableLearnerCreditPolicies: [], + redeemableLearnerCreditPolicies: [{ + learnerContentAssignments: { + state: 'allocated', + }, + }, + { + learnerContentAssignments: { + state: 'accepted', + }, + }], }; const defaultCourseState = { @@ -347,13 +354,6 @@ describe('', () => { }); it('should render redirect component if no cookie and no courseAssignments exist', () => { - isFirstDashboardPageVisit.mockReturnValueOnce(true); - EnterpriseLearnerFirstVisitRedirect.mockImplementation(() => ( - -
search-page
-
- )); - const noActiveCourseAssignmentUserSubsidyState = { ...defaultUserSubsidyState, redeemableLearnerCreditPolicies: [{ @@ -363,35 +363,9 @@ describe('', () => { ], }], }; + jest.spyOn(EnterpriseLearnerFirstVisitRedirect, 'default').mockReturnValueOnce(search-page); renderWithRouter(); - - expect(isFirstDashboardPageVisit).toHaveBeenCalled(); - expect(screen.getByText('search-page')).toBeInTheDocument(); - }); - - it('should not render redirect component if active learnerContentAssignment exist', () => { - isFirstDashboardPageVisit.mockReturnValueOnce(true); - EnterpriseLearnerFirstVisitRedirect.mockImplementation(() => ( - -
search-page
-
- )); - - const noActiveCourseAssignmentUserSubsidyState = { - ...defaultUserSubsidyState, - redeemableLearnerCreditPolicies: [{ - learnerContentAssignments: [{ - state: 'allocated', - }, - { - state: 'accepted', - }, - ], - }], - }; - renderWithRouter(); - - expect(screen.queryByText('search-page')).not.toBeInTheDocument(); + expect(screen.queryByText('search-page')).toBeTruthy(); }); describe('SubscriptionExpirationModal', () => { diff --git a/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx b/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx index 2c17e096e..8c73ef234 100644 --- a/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx +++ b/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx @@ -1,6 +1,8 @@ -import React, { useEffect } from 'react'; +import React, { useContext, useEffect } from 'react'; import { Redirect, useParams } from 'react-router-dom'; import Cookies from 'universal-cookie'; +import { ASSIGNMENT_TYPES } from '../dashboard/main-content/course-enrollments/CourseEnrollments'; +import { UserSubsidyContext } from '../enterprise-user-subsidy'; export const isFirstDashboardPageVisit = () => { const cookies = new Cookies(); @@ -11,15 +13,29 @@ export const isFirstDashboardPageVisit = () => { const EnterpriseLearnerFirstVisitRedirect = () => { const { enterpriseSlug } = useParams(); - const cookies = new Cookies(); + const { + redeemableLearnerCreditPolicies, + } = useContext(UserSubsidyContext); + // TODO: Refactor to DRY up code for redeemableLearnerCreditPolicies + const hasActiveCourseAssignments = (learnerCreditPolicies) => { + const learnerContentAssignmentsArray = learnerCreditPolicies?.flatMap( + item => item?.learnerContentAssignments || [], + ); + // filters out course assignments that are not considered active + const hasActiveAssignments = learnerContentAssignmentsArray.filter( + assignment => assignment.state !== ASSIGNMENT_TYPES.cancelled, + ); + return hasActiveAssignments.length > 0; + }; useEffect(() => { + const cookies = new Cookies(); + if (isFirstDashboardPageVisit()) { cookies.set('has-user-visited-learner-dashboard', true, { path: '/' }); } - }); - - if (isFirstDashboardPageVisit()) { + }, []); + if (!hasActiveCourseAssignments(redeemableLearnerCreditPolicies) && isFirstDashboardPageVisit()) { return ; } diff --git a/src/components/enterprise-redirects/tests/EnterpriseLearnerFirstVisitRedirect.test.jsx b/src/components/enterprise-redirects/tests/EnterpriseLearnerFirstVisitRedirect.test.jsx index b00dde859..a6db96906 100644 --- a/src/components/enterprise-redirects/tests/EnterpriseLearnerFirstVisitRedirect.test.jsx +++ b/src/components/enterprise-redirects/tests/EnterpriseLearnerFirstVisitRedirect.test.jsx @@ -2,8 +2,10 @@ import React from 'react'; import '@testing-library/jest-dom/extend-expect'; import Cookies from 'universal-cookie'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; import { renderWithRouter } from '../../../utils/tests'; import EnterpriseLearnerFirstVisitRedirect from '../EnterpriseLearnerFirstVisitRedirect'; +import { UserSubsidyContext } from '../../enterprise-user-subsidy'; const COOKIE_NAME = 'has-user-visited-learner-dashboard'; const TEST_ENTERPRISE = { @@ -19,6 +21,37 @@ jest.mock('react-router-dom', () => ({ }), })); +const defaultCouponCodesState = { + couponCodes: [], + loading: false, + couponCodesCount: 0, +}; + +const defaultUserSubsidyState = { + couponCodes: defaultCouponCodesState, + enterpriseOffers: [], + redeemableLearnerCreditPolicies: [{ + learnerContentAssignments: { + state: 'allocated', + }, + }, + { + learnerContentAssignments: { + state: 'accepted', + }, + }], +}; + +const EnterpriseLearnerFirstVisitRedirectWrapper = ({ + initialUserSubsidyState = defaultUserSubsidyState, +}) => ( + + + + + +); + describe('', () => { beforeEach(() => { const cookies = new Cookies(); @@ -26,7 +59,29 @@ describe('', () => { }); test('redirects to search if user is visiting for the first time.', async () => { - const { history } = renderWithRouter(, { route: `/${TEST_ENTERPRISE.slug}` }); + const noActiveCourseAssignmentUserSubsidyState = { + ...defaultUserSubsidyState, + redeemableLearnerCreditPolicies: [{ + learnerContentAssignments: [], + }], + }; + + const { history } = renderWithRouter(, { route: `/${TEST_ENTERPRISE.slug}` }); + expect(history.location.pathname).toEqual(`/${TEST_ENTERPRISE.slug}/search`); + }); + + test('redirects to search if the course assigned is not active.', async () => { + const noActiveCourseAssignmentUserSubsidyState = { + ...defaultUserSubsidyState, + redeemableLearnerCreditPolicies: [{ + learnerContentAssignments: [{ + state: 'cancelled', + }, + ], + }], + }; + + const { history } = renderWithRouter(, { route: `/${TEST_ENTERPRISE.slug}` }); expect(history.location.pathname).toEqual(`/${TEST_ENTERPRISE.slug}/search`); }); @@ -35,7 +90,7 @@ describe('', () => { const cookies = new Cookies(); cookies.set(COOKIE_NAME, true); - const { history } = renderWithRouter(, { route: `/${TEST_ENTERPRISE.slug}` }); + const { history } = renderWithRouter(, { route: `/${TEST_ENTERPRISE.slug}` }); expect(history.location.pathname).toEqual(`/${TEST_ENTERPRISE.slug}`); }); @@ -44,7 +99,7 @@ describe('', () => { const cookies = new Cookies(); cookies.set(COOKIE_NAME, true); - const { history } = renderWithRouter(, { route: `/${TEST_ENTERPRISE.slug}` }); + const { history } = renderWithRouter(, { route: `/${TEST_ENTERPRISE.slug}` }); expect(history.location.pathname).toEqual(`/${TEST_ENTERPRISE.slug}`); }); }); From fe1da5010a2e9b08ce8fdbd5bddb42b076de26f8 Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Mon, 4 Dec 2023 14:38:06 -0500 Subject: [PATCH 06/11] chore: pr cleanup --- src/components/dashboard/tests/DashboardPage.test.jsx | 2 -- .../tests/EnterpriseLearnerFirstVisitRedirect.test.jsx | 8 -------- 2 files changed, 10 deletions(-) diff --git a/src/components/dashboard/tests/DashboardPage.test.jsx b/src/components/dashboard/tests/DashboardPage.test.jsx index 4ee732df8..5401944ab 100644 --- a/src/components/dashboard/tests/DashboardPage.test.jsx +++ b/src/components/dashboard/tests/DashboardPage.test.jsx @@ -52,8 +52,6 @@ jest.mock('../main-content/course-enrollments/data/utils', () => ({ sortAssignmentsByAssignmentStatus: jest.fn(), })); -// jest.mock('../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'); - const defaultAppState = { enterpriseConfig: { name: 'BearsRUs', diff --git a/src/components/enterprise-redirects/tests/EnterpriseLearnerFirstVisitRedirect.test.jsx b/src/components/enterprise-redirects/tests/EnterpriseLearnerFirstVisitRedirect.test.jsx index a6db96906..d0bc76b9f 100644 --- a/src/components/enterprise-redirects/tests/EnterpriseLearnerFirstVisitRedirect.test.jsx +++ b/src/components/enterprise-redirects/tests/EnterpriseLearnerFirstVisitRedirect.test.jsx @@ -21,15 +21,7 @@ jest.mock('react-router-dom', () => ({ }), })); -const defaultCouponCodesState = { - couponCodes: [], - loading: false, - couponCodesCount: 0, -}; - const defaultUserSubsidyState = { - couponCodes: defaultCouponCodesState, - enterpriseOffers: [], redeemableLearnerCreditPolicies: [{ learnerContentAssignments: { state: 'allocated', From c1f3141c248f36e90b15a2430f783c21e9843fe6 Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Mon, 4 Dec 2023 16:31:33 -0500 Subject: [PATCH 07/11] chore: PR fixes --- src/components/dashboard/data/utils.js | 11 +++++++++ .../course-enrollments/CourseEnrollments.jsx | 14 ++++------- .../dashboard/tests/DashboardPage.test.jsx | 17 ++++++-------- .../EnterpriseLearnerFirstVisitRedirect.jsx | 12 ++++------ ...terpriseLearnerFirstVisitRedirect.test.jsx | 23 +++++-------------- .../enterprise-offers/data/constants.js | 1 + 6 files changed, 35 insertions(+), 43 deletions(-) create mode 100644 src/components/dashboard/data/utils.js diff --git a/src/components/dashboard/data/utils.js b/src/components/dashboard/data/utils.js new file mode 100644 index 000000000..fd1515650 --- /dev/null +++ b/src/components/dashboard/data/utils.js @@ -0,0 +1,11 @@ +import { ASSIGNMENT_TYPES } from '../../enterprise-user-subsidy/enterprise-offers/data/constants'; + +export default function getActiveAssignments(assignments) { + const filteredAssignments = assignments?.filter((assignment) => assignment?.state === ASSIGNMENT_TYPES.ALLOCATED + || assignment?.state === ASSIGNMENT_TYPES.CANCELLED); + const isActiveAssignments = () => filteredAssignments.length > 0; + return { + filteredAssignments, + isActiveAssignments, + }; +} diff --git a/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx b/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx index a5004d135..744874093 100644 --- a/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx +++ b/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx @@ -17,6 +17,8 @@ import { } from './data/utils'; import { UserSubsidyContext } from '../../../enterprise-user-subsidy'; import { features } from '../../../../config'; +import getActiveAssignments from '../../data/utils'; +import { ASSIGNMENT_TYPES } from '../../../enterprise-user-subsidy/enterprise-offers/data/constants'; export const COURSE_SECTION_TITLES = { current: 'My courses', @@ -24,11 +26,6 @@ export const COURSE_SECTION_TITLES = { savedForLater: 'Saved for later', assigned: 'Assigned Courses', }; -export const ASSIGNMENT_TYPES = { - accepted: 'accepted', - allocated: 'allocated', - cancelled: 'cancelled', -}; const CourseEnrollments = ({ children }) => { const { @@ -59,22 +56,21 @@ const CourseEnrollments = ({ children }) => { setAssignments(assignmentsData); const hasCancelledAssignments = assignmentsData?.some( - assignment => assignment.state === ASSIGNMENT_TYPES.cancelled, + assignment => assignment.state === ASSIGNMENT_TYPES.CANCELLED, ); const hasExpiredAssignments = assignmentsData?.some(assignment => isAssignmentExpired(assignment)); setShowCancelledAssignmentsAlert(hasCancelledAssignments); setShowExpiredAssignmentsAlert(hasExpiredAssignments); }, [redeemableLearnerCreditPolicies]); - const filteredAssignments = assignments?.filter((assignment) => assignment?.state === ASSIGNMENT_TYPES.allocated - || assignment?.state === ASSIGNMENT_TYPES.cancelled); + const { filteredAssignments } = getActiveAssignments(assignments); const assignedCourses = getTransformedAllocatedAssignments(filteredAssignments, slug); const currentCourseEnrollments = useMemo( () => { Object.keys(courseEnrollmentsByStatus).forEach((status) => { courseEnrollmentsByStatus[status] = courseEnrollmentsByStatus[status].map((course) => { - const isAssigned = assignments?.some(assignment => (assignment?.state === ASSIGNMENT_TYPES.accepted + const isAssigned = assignments?.some(assignment => (assignment?.state === ASSIGNMENT_TYPES.ACCEPTED && course.courseRunId.includes(assignment?.contentKey))); if (isAssigned) { return { ...course, isCourseAssigned: true }; diff --git a/src/components/dashboard/tests/DashboardPage.test.jsx b/src/components/dashboard/tests/DashboardPage.test.jsx index 5401944ab..d47643d02 100644 --- a/src/components/dashboard/tests/DashboardPage.test.jsx +++ b/src/components/dashboard/tests/DashboardPage.test.jsx @@ -25,7 +25,6 @@ import { LICENSE_STATUS } from '../../enterprise-user-subsidy/data/constants'; import { SubsidyRequestsContext } from '../../enterprise-subsidy-requests'; import { SUBSIDY_TYPE } from '../../enterprise-subsidy-requests/constants'; import { sortAssignmentsByAssignmentStatus } from '../main-content/course-enrollments/data/utils'; -import * as EnterpriseLearnerFirstVisitRedirect from '../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect'; const defaultCouponCodesState = { couponCodes: [], @@ -52,6 +51,10 @@ jest.mock('../main-content/course-enrollments/data/utils', () => ({ sortAssignmentsByAssignmentStatus: jest.fn(), })); +jest.mock('../../enterprise-redirects/EnterpriseLearnerFirstVisitRedirect', () => jest.fn( + () => (
enterprise-learner-first-visit-redirect
), +)); + const defaultAppState = { enterpriseConfig: { name: 'BearsRUs', @@ -75,7 +78,7 @@ const defaultUserSubsidyState = { }, { learnerContentAssignments: { - state: 'accepted', + state: 'cancelled', }, }], }; @@ -354,16 +357,10 @@ describe('', () => { it('should render redirect component if no cookie and no courseAssignments exist', () => { const noActiveCourseAssignmentUserSubsidyState = { ...defaultUserSubsidyState, - redeemableLearnerCreditPolicies: [{ - learnerContentAssignments: [{ - state: 'cancelled', - }, - ], - }], + redeemableLearnerCreditPolicies: [], }; - jest.spyOn(EnterpriseLearnerFirstVisitRedirect, 'default').mockReturnValueOnce(search-page); renderWithRouter(); - expect(screen.queryByText('search-page')).toBeTruthy(); + expect(screen.queryByText('enterprise-learner-first-visit-redirect')).toBeTruthy(); }); describe('SubscriptionExpirationModal', () => { diff --git a/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx b/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx index 8c73ef234..44b998a29 100644 --- a/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx +++ b/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx @@ -1,8 +1,8 @@ import React, { useContext, useEffect } from 'react'; import { Redirect, useParams } from 'react-router-dom'; import Cookies from 'universal-cookie'; -import { ASSIGNMENT_TYPES } from '../dashboard/main-content/course-enrollments/CourseEnrollments'; import { UserSubsidyContext } from '../enterprise-user-subsidy'; +import getActiveAssignments from '../dashboard/data/utils'; export const isFirstDashboardPageVisit = () => { const cookies = new Cookies(); @@ -18,15 +18,12 @@ const EnterpriseLearnerFirstVisitRedirect = () => { } = useContext(UserSubsidyContext); // TODO: Refactor to DRY up code for redeemableLearnerCreditPolicies - const hasActiveCourseAssignments = (learnerCreditPolicies) => { + const hasActiveContentAssignments = (learnerCreditPolicies) => { const learnerContentAssignmentsArray = learnerCreditPolicies?.flatMap( item => item?.learnerContentAssignments || [], ); // filters out course assignments that are not considered active - const hasActiveAssignments = learnerContentAssignmentsArray.filter( - assignment => assignment.state !== ASSIGNMENT_TYPES.cancelled, - ); - return hasActiveAssignments.length > 0; + return getActiveAssignments(learnerContentAssignmentsArray).isActiveAssignments(); }; useEffect(() => { const cookies = new Cookies(); @@ -35,7 +32,8 @@ const EnterpriseLearnerFirstVisitRedirect = () => { cookies.set('has-user-visited-learner-dashboard', true, { path: '/' }); } }, []); - if (!hasActiveCourseAssignments(redeemableLearnerCreditPolicies) && isFirstDashboardPageVisit()) { + + if (!hasActiveContentAssignments(redeemableLearnerCreditPolicies) && isFirstDashboardPageVisit()) { return ; } diff --git a/src/components/enterprise-redirects/tests/EnterpriseLearnerFirstVisitRedirect.test.jsx b/src/components/enterprise-redirects/tests/EnterpriseLearnerFirstVisitRedirect.test.jsx index d0bc76b9f..05e6bc980 100644 --- a/src/components/enterprise-redirects/tests/EnterpriseLearnerFirstVisitRedirect.test.jsx +++ b/src/components/enterprise-redirects/tests/EnterpriseLearnerFirstVisitRedirect.test.jsx @@ -1,8 +1,6 @@ import React from 'react'; import '@testing-library/jest-dom/extend-expect'; import Cookies from 'universal-cookie'; - -import { IntlProvider } from '@edx/frontend-platform/i18n'; import { renderWithRouter } from '../../../utils/tests'; import EnterpriseLearnerFirstVisitRedirect from '../EnterpriseLearnerFirstVisitRedirect'; import { UserSubsidyContext } from '../../enterprise-user-subsidy'; @@ -29,7 +27,7 @@ const defaultUserSubsidyState = { }, { learnerContentAssignments: { - state: 'accepted', + state: 'cancelled', }, }], }; @@ -37,11 +35,9 @@ const defaultUserSubsidyState = { const EnterpriseLearnerFirstVisitRedirectWrapper = ({ initialUserSubsidyState = defaultUserSubsidyState, }) => ( - - - - - + + + ); describe('', () => { @@ -53,9 +49,7 @@ describe('', () => { test('redirects to search if user is visiting for the first time.', async () => { const noActiveCourseAssignmentUserSubsidyState = { ...defaultUserSubsidyState, - redeemableLearnerCreditPolicies: [{ - learnerContentAssignments: [], - }], + redeemableLearnerCreditPolicies: [], }; const { history } = renderWithRouter(, { route: `/${TEST_ENTERPRISE.slug}` }); @@ -65,12 +59,7 @@ describe('', () => { test('redirects to search if the course assigned is not active.', async () => { const noActiveCourseAssignmentUserSubsidyState = { ...defaultUserSubsidyState, - redeemableLearnerCreditPolicies: [{ - learnerContentAssignments: [{ - state: 'cancelled', - }, - ], - }], + redeemableLearnerCreditPolicies: [], }; const { history } = renderWithRouter(, { route: `/${TEST_ENTERPRISE.slug}` }); diff --git a/src/components/enterprise-user-subsidy/enterprise-offers/data/constants.js b/src/components/enterprise-user-subsidy/enterprise-offers/data/constants.js index 3075de4fd..ff47fed63 100644 --- a/src/components/enterprise-user-subsidy/enterprise-offers/data/constants.js +++ b/src/components/enterprise-user-subsidy/enterprise-offers/data/constants.js @@ -34,4 +34,5 @@ export const ASSIGNMENT_TYPES = { ACCEPTED: 'accepted', ALLOCATED: 'allocated', CANCELLED: 'cancelled', + ERRORED: 'errored', }; From 88213cebc7308d70fb3c27e1a8e02970f9c06ca2 Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Mon, 4 Dec 2023 16:38:56 -0500 Subject: [PATCH 08/11] chore: PR cleanup --- src/components/dashboard/data/utils.js | 12 +++++++++--- .../course-enrollments/CourseEnrollments.jsx | 6 +++--- .../EnterpriseLearnerFirstVisitRedirect.jsx | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/dashboard/data/utils.js b/src/components/dashboard/data/utils.js index fd1515650..9096fec07 100644 --- a/src/components/dashboard/data/utils.js +++ b/src/components/dashboard/data/utils.js @@ -1,11 +1,17 @@ import { ASSIGNMENT_TYPES } from '../../enterprise-user-subsidy/enterprise-offers/data/constants'; +/** + * Takes the flattened array from redeemableLearnerCreditPolicies and returns the options of + * the array of filteredActiveAssignments, along with a method to return the boolean value + * @param assignments - flatMap'ed object from redeemableLearnerCreditPolicies for learnerContentAssignments + * @returns {{isActiveAssignments: (function(): boolean), filteredActiveAssignments: *}} + */ export default function getActiveAssignments(assignments) { - const filteredAssignments = assignments?.filter((assignment) => assignment?.state === ASSIGNMENT_TYPES.ALLOCATED + const filteredActiveAssignments = assignments?.filter((assignment) => assignment?.state === ASSIGNMENT_TYPES.ALLOCATED || assignment?.state === ASSIGNMENT_TYPES.CANCELLED); - const isActiveAssignments = () => filteredAssignments.length > 0; + const isActiveAssignments = () => filteredActiveAssignments.length > 0; return { - filteredAssignments, + filteredActiveAssignments, isActiveAssignments, }; } diff --git a/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx b/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx index 744874093..63414812b 100644 --- a/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx +++ b/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx @@ -63,8 +63,8 @@ const CourseEnrollments = ({ children }) => { setShowCancelledAssignmentsAlert(hasCancelledAssignments); setShowExpiredAssignmentsAlert(hasExpiredAssignments); }, [redeemableLearnerCreditPolicies]); - const { filteredAssignments } = getActiveAssignments(assignments); - const assignedCourses = getTransformedAllocatedAssignments(filteredAssignments, slug); + const { filteredActiveAssignments } = getActiveAssignments(assignments); + const assignedCourses = getTransformedAllocatedAssignments(filteredActiveAssignments, slug); const currentCourseEnrollments = useMemo( () => { @@ -111,7 +111,7 @@ const CourseEnrollments = ({ children }) => { } const hasCourseEnrollments = Object.values(courseEnrollmentsByStatus).flat().length > 0; - const hasCourseAssignments = filteredAssignments?.length > 0; + const hasCourseAssignments = filteredActiveAssignments?.length > 0; return ( <> diff --git a/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx b/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx index 44b998a29..54af4fc54 100644 --- a/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx +++ b/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx @@ -22,7 +22,7 @@ const EnterpriseLearnerFirstVisitRedirect = () => { const learnerContentAssignmentsArray = learnerCreditPolicies?.flatMap( item => item?.learnerContentAssignments || [], ); - // filters out course assignments that are not considered active + // filters out course assignments that are not considered active and returns a boolean value return getActiveAssignments(learnerContentAssignmentsArray).isActiveAssignments(); }; useEffect(() => { From 9727e9fcc5b0308acdf5efaf04b81e4a2f72cbd7 Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Mon, 4 Dec 2023 17:19:52 -0500 Subject: [PATCH 09/11] chore: more PR fixes --- src/components/dashboard/data/utils.js | 15 ++++++++------- .../course-enrollments/CourseEnrollments.jsx | 6 +++--- .../EnterpriseLearnerFirstVisitRedirect.jsx | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/components/dashboard/data/utils.js b/src/components/dashboard/data/utils.js index 9096fec07..4c5c8a2ca 100644 --- a/src/components/dashboard/data/utils.js +++ b/src/components/dashboard/data/utils.js @@ -2,16 +2,17 @@ import { ASSIGNMENT_TYPES } from '../../enterprise-user-subsidy/enterprise-offer /** * Takes the flattened array from redeemableLearnerCreditPolicies and returns the options of - * the array of filteredActiveAssignments, along with a method to return the boolean value + * the array of activeAssignments, or hasActiveAssignments which returns a boolean value * @param assignments - flatMap'ed object from redeemableLearnerCreditPolicies for learnerContentAssignments - * @returns {{isActiveAssignments: (function(): boolean), filteredActiveAssignments: *}} + * @returns {{hasActiveAssignments: boolean, activeAssignments: Array}} */ export default function getActiveAssignments(assignments) { - const filteredActiveAssignments = assignments?.filter((assignment) => assignment?.state === ASSIGNMENT_TYPES.ALLOCATED - || assignment?.state === ASSIGNMENT_TYPES.CANCELLED); - const isActiveAssignments = () => filteredActiveAssignments.length > 0; + const activeAssignments = assignments?.filter((assignment) => [ + ASSIGNMENT_TYPES.CANCELLED, ASSIGNMENT_TYPES.ALLOCATED, + ].includes(assignment.state)); + const hasActiveAssignments = activeAssignments.length > 0; return { - filteredActiveAssignments, - isActiveAssignments, + activeAssignments, + hasActiveAssignments, }; } diff --git a/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx b/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx index 63414812b..789f5238e 100644 --- a/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx +++ b/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx @@ -63,8 +63,8 @@ const CourseEnrollments = ({ children }) => { setShowCancelledAssignmentsAlert(hasCancelledAssignments); setShowExpiredAssignmentsAlert(hasExpiredAssignments); }, [redeemableLearnerCreditPolicies]); - const { filteredActiveAssignments } = getActiveAssignments(assignments); - const assignedCourses = getTransformedAllocatedAssignments(filteredActiveAssignments, slug); + const { activeAssigments } = getActiveAssignments(assignments); + const assignedCourses = getTransformedAllocatedAssignments(activeAssigments, slug); const currentCourseEnrollments = useMemo( () => { @@ -111,7 +111,7 @@ const CourseEnrollments = ({ children }) => { } const hasCourseEnrollments = Object.values(courseEnrollmentsByStatus).flat().length > 0; - const hasCourseAssignments = filteredActiveAssignments?.length > 0; + const hasCourseAssignments = activeAssigments?.length > 0; return ( <> diff --git a/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx b/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx index 54af4fc54..d8dfc96cb 100644 --- a/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx +++ b/src/components/enterprise-redirects/EnterpriseLearnerFirstVisitRedirect.jsx @@ -23,7 +23,7 @@ const EnterpriseLearnerFirstVisitRedirect = () => { item => item?.learnerContentAssignments || [], ); // filters out course assignments that are not considered active and returns a boolean value - return getActiveAssignments(learnerContentAssignmentsArray).isActiveAssignments(); + return getActiveAssignments(learnerContentAssignmentsArray).hasActiveAssignments; }; useEffect(() => { const cookies = new Cookies(); From 0ceba9a4ad14c0cd0eae13e62dd3a2ee1337781f Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Mon, 4 Dec 2023 17:28:08 -0500 Subject: [PATCH 10/11] fix: added failsafe default value for undefined 'assignments' --- src/components/dashboard/data/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/dashboard/data/utils.js b/src/components/dashboard/data/utils.js index 4c5c8a2ca..0a90ed832 100644 --- a/src/components/dashboard/data/utils.js +++ b/src/components/dashboard/data/utils.js @@ -6,8 +6,8 @@ import { ASSIGNMENT_TYPES } from '../../enterprise-user-subsidy/enterprise-offer * @param assignments - flatMap'ed object from redeemableLearnerCreditPolicies for learnerContentAssignments * @returns {{hasActiveAssignments: boolean, activeAssignments: Array}} */ -export default function getActiveAssignments(assignments) { - const activeAssignments = assignments?.filter((assignment) => [ +export default function getActiveAssignments(assignments = []) { + const activeAssignments = assignments.filter((assignment) => [ ASSIGNMENT_TYPES.CANCELLED, ASSIGNMENT_TYPES.ALLOCATED, ].includes(assignment.state)); const hasActiveAssignments = activeAssignments.length > 0; From 20e3c7d338e7273bb3254daa34039a59a5f00560 Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Mon, 4 Dec 2023 17:33:54 -0500 Subject: [PATCH 11/11] chore: redundant logic removed --- .../main-content/course-enrollments/CourseEnrollments.jsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx b/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx index 789f5238e..8a22eb131 100644 --- a/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx +++ b/src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx @@ -63,7 +63,7 @@ const CourseEnrollments = ({ children }) => { setShowCancelledAssignmentsAlert(hasCancelledAssignments); setShowExpiredAssignmentsAlert(hasExpiredAssignments); }, [redeemableLearnerCreditPolicies]); - const { activeAssigments } = getActiveAssignments(assignments); + const { activeAssigments, hasActiveAssignments } = getActiveAssignments(assignments); const assignedCourses = getTransformedAllocatedAssignments(activeAssigments, slug); const currentCourseEnrollments = useMemo( @@ -111,8 +111,6 @@ const CourseEnrollments = ({ children }) => { } const hasCourseEnrollments = Object.values(courseEnrollmentsByStatus).flat().length > 0; - const hasCourseAssignments = activeAssigments?.length > 0; - return ( <> {showCancelledAssignmentsAlert && ( @@ -136,7 +134,7 @@ const CourseEnrollments = ({ children }) => { This allows the parent component to customize what gets displayed if the user does not have any course enrollments. */} - {(!hasCourseEnrollments && !hasCourseAssignments) && children} + {(!hasCourseEnrollments && !hasActiveAssignments) && children} <> {features.FEATURE_ENABLE_TOP_DOWN_ASSIGNMENT && (