Skip to content

Commit

Permalink
chore: refine enrollby and start date logic
Browse files Browse the repository at this point in the history
  • Loading branch information
brobro10000 committed Sep 17, 2024
1 parent 027fee7 commit eba4828
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,7 @@ import { defineMessages, useIntl } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';

import dayjs from 'dayjs';
import {
DATETIME_FORMAT,
getNormalizedEnrollByDate,
getNormalizedStartDate,
isDateBeforeToday,
SHORT_MONTH_DATE_FORMAT,
} from '../data';
import { DATETIME_FORMAT, isDateBeforeToday, SHORT_MONTH_DATE_FORMAT } from '../data';

const messages = defineMessages({
importantDates: {
Expand Down Expand Up @@ -53,19 +47,18 @@ AssignmentModalImportantDate.propTypes = {

const AssignmentModalImportantDates = ({ courseRun }) => {
const intl = useIntl();
const normalizedEnrollByDate = getNormalizedEnrollByDate(courseRun);
const enrollByDate = normalizedEnrollByDate
? dayjs(normalizedEnrollByDate).format(DATETIME_FORMAT)
: null;
const courseStartDate = getNormalizedStartDate(courseRun);
const courseHasStartedLabel = isDateBeforeToday(courseStartDate)
? intl.formatMessage(messages.courseStarted)
: intl.formatMessage(messages.courseStarts);
const enrollByDate = dayjs(courseRun.enrollBy).format(DATETIME_FORMAT);
const courseStartDate = courseRun.start;

// This is an edge case that the user should never enter but covered nonetheless
if (!enrollByDate && !courseStartDate) {
return null;

Check warning on line 55 in src/components/learner-credit-management/assignment-modal/AssignmentModalmportantDates.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/learner-credit-management/assignment-modal/AssignmentModalmportantDates.jsx#L55

Added line #L55 was not covered by tests
}

const courseHasStartedLabel = isDateBeforeToday(courseStartDate)
? intl.formatMessage(messages.courseStarted)
: intl.formatMessage(messages.courseStarts);

return (
<section className="assignments-important-dates small">
<Stack direction="vertical" gap={1}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ import dayjs from 'dayjs';
import PropTypes from 'prop-types';
import { defineMessages, useIntl } from '@edx/frontend-platform/i18n';
import { useState } from 'react';
import {
getNormalizedEnrollByDate,
getNormalizedStartDate,
SHORT_MONTH_DATE_FORMAT,
} from '../data';
import { SHORT_MONTH_DATE_FORMAT } from '../data';

const messages = defineMessages({
byDate: {
Expand Down Expand Up @@ -68,12 +64,12 @@ const NewAssignmentModalDropdown = ({
>
<Stack>
{intl.formatMessage(messages.enrollBy, {
enrollByDate: dayjs(getNormalizedEnrollByDate(courseRun)).format(SHORT_MONTH_DATE_FORMAT),
enrollByDate: dayjs(courseRun.enrollBy).format(SHORT_MONTH_DATE_FORMAT),
})}
<span className={`small ${getDropdownItemClassName(courseRun)}`}>
{intl.formatMessage(messages.startDate, {
startLabel: startLabel(courseRun),
startDate: dayjs(getNormalizedStartDate(courseRun)).format(SHORT_MONTH_DATE_FORMAT),
startDate: dayjs(courseRun.start).format(SHORT_MONTH_DATE_FORMAT),
})}
</span>
</Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ describe('Course card works as expected', () => {
});

test('executive education card renders', () => {
const enrollByDate = getNormalizedEnrollByDate({ enrollBy: dayjs.unix(enrollByTimestamp).toISOString() });
const enrollByDate = getNormalizedEnrollByDate(dayjs.unix(enrollByTimestamp).toISOString());
const formattedEnrollBy = dayjs(enrollByDate).format(SHORT_MONTH_DATE_FORMAT);
renderWithRouter(<CourseCardWrapper {...execEdProps} />);
expect(screen.queryByText('$999')).toBeInTheDocument();
Expand Down
126 changes: 79 additions & 47 deletions src/components/learner-credit-management/data/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -565,52 +565,10 @@ export const isLmsBudget = (
*/
export const isDateBeforeToday = date => dayjs(date).isBefore(dayjs());

/**
* Filters assignable course runs based on the following criteria:
* - If hasEnrollBy, we return the soonest of two dates: The subsidy expiration date - refund threshold OR today
* offset by the 90-day allocation threshold for an assignment denoted as isEligibleForEnrollment
* - If isLateRedemptionEnabled, we consider only the isLateEnrollmentEligible field returned by Algolia for
* each run.
*
* Based on the above criteria, if isLateRedemptionAllowed is false, filter on if the course run isActive AND
* isEligibleForEnrollment
*
* Furthermore, we return assignable course runs sorted by the enrollBy date (soonest to latest)
*
* @param courseRuns
* @param subsidyExpirationDatetime
* @param isLateRedemptionAllowed
* @returns {*}
*/
export const getAssignableCourseRuns = ({ courseRuns, subsidyExpirationDatetime, isLateRedemptionAllowed }) => {
const clonedCourseRuns = courseRuns.map(courseRun => ({
...courseRun,
enrollBy: courseRun.hasEnrollBy ? dayjs.unix(courseRun.enrollBy).toISOString() : null,
upgradeDeadline: dayjs.unix(courseRun.upgradeDeadline).toISOString(),
}));
const assignableCourseRunsFilter = ({
enrollBy, isActive, hasEnrollBy = false, isLateEnrollmentEligible = false,
}) => {
let isEligibleForEnrollment = true;
if (hasEnrollBy) {
isEligibleForEnrollment = dayjs(enrollBy).isBefore(
Math.min(
dayjs(subsidyExpirationDatetime).subtract(MAX_ALLOWABLE_REFUND_THRESHOLD_DAYS, 'days').toDate(),
dayjs().add(DAYS_UNTIL_ASSIGNMENT_ALLOCATION_EXPIRATION, 'days').toDate(),
),
);
}
if (isDateBeforeToday(enrollBy) && isLateRedemptionAllowed) {
const lateEnrollmentCutoff = dayjs().subtract(LATE_ENROLLMENTS_BUFFER_DAYS, 'days');
isEligibleForEnrollment = dayjs(enrollBy).isAfter(lateEnrollmentCutoff);
return isLateEnrollmentEligible && isEligibleForEnrollment;
}
return isActive && isEligibleForEnrollment;
};
const assignableCourseRuns = clonedCourseRuns.filter(assignableCourseRunsFilter);
const sortedAssignableCourseRuns = assignableCourseRuns.sort((a, b) => a.enrollBy - b.enrollBy);
return sortedAssignableCourseRuns;
};
export const minimumEnrollByDateFromToday = ({ today = dayjs(), subsidyExpirationDatetime }) => Math.min(
dayjs(subsidyExpirationDatetime).subtract(MAX_ALLOWABLE_REFUND_THRESHOLD_DAYS, 'days').toDate(),
today.add(DAYS_UNTIL_ASSIGNMENT_ALLOCATION_EXPIRATION, 'days').toDate(),
);

export const isCourseSelfPaced = ({ pacingType }) => pacingType === COURSE_PACING_MAP.SELF_PACED;

Expand Down Expand Up @@ -654,7 +612,7 @@ export const getNormalizedStartDate = ({
return startDateIso;
};

export const getNormalizedEnrollByDate = ({ enrollBy }) => {
export const getNormalizedEnrollByDate = (enrollBy) => {
if (!enrollBy) {
return null;

Check warning on line 617 in src/components/learner-credit-management/data/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/learner-credit-management/data/utils.js#L617

Added line #L617 was not covered by tests
}
Expand All @@ -664,3 +622,77 @@ export const getNormalizedEnrollByDate = ({ enrollBy }) => {
}
return enrollBy;
};

/**
* Filters assignable course runs based on the following criteria:
* - If hasEnrollBy, we return assignments with enroll before the soonest of the two date: The subsidy expiration
* date - refund threshold OR today offset by the 90-day allocation threshold for an assignment denoted as
* isEligibleForEnrollment
* - If isLateRedemptionEnabled, we consider only the isLateEnrollmentEligible field returned by Algolia for
* each run.
*
* Based on the above criteria, if isLateRedemptionAllowed is false, filter on if the course run isActive AND
* isEligibleForEnrollment
*
* We transform the assignedCourseRuns data to normalize the start and enrollby dates based on the functions
*
* Furthermore, we return assignable course runs sorted by the enrollBy date (soonest to latest). If the enrollby dates
* are equivalent, sort by the start date.
*
* @param courseRuns
* @param subsidyExpirationDatetime
* @param isLateRedemptionAllowed
* @returns {*}
*/
export const getAssignableCourseRuns = ({ courseRuns, subsidyExpirationDatetime, isLateRedemptionAllowed }) => {
const today = dayjs();
const clonedCourseRuns = courseRuns.map(courseRun => ({
...courseRun,
enrollBy: courseRun.hasEnrollBy ? dayjs.unix(courseRun.enrollBy).toISOString() : null,
upgradeDeadline: dayjs.unix(courseRun.upgradeDeadline).toISOString(),
}));
const assignableCourseRunsFilter = ({
enrollBy, isActive, hasEnrollBy = false, isLateEnrollmentEligible = false,
}) => {
let isEligibleForEnrollment = true;
if (hasEnrollBy) {
isEligibleForEnrollment = dayjs(enrollBy).isBefore(
minimumEnrollByDateFromToday({ today, subsidyExpirationDatetime }),
);
}
// Late redemption filter
if (isDateBeforeToday(enrollBy) && isLateRedemptionAllowed) {
const lateEnrollmentCutoff = dayjs().subtract(LATE_ENROLLMENTS_BUFFER_DAYS, 'days');
isEligibleForEnrollment = dayjs(enrollBy).isAfter(lateEnrollmentCutoff);

Check warning on line 666 in src/components/learner-credit-management/data/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/learner-credit-management/data/utils.js#L665-L666

Added lines #L665 - L666 were not covered by tests
return isLateEnrollmentEligible && isEligibleForEnrollment;
}
// General courseware filter
return isActive && isEligibleForEnrollment;
};
// Main function that transforms the cloned course runs to the normalizedStart and normalizedEnrollBy dates
const assignableCourseRuns = clonedCourseRuns.filter(assignableCourseRunsFilter).map(courseRun => {
if (!courseRun.hasEnrollBy) {
return {

Check warning on line 675 in src/components/learner-credit-management/data/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/learner-credit-management/data/utils.js#L675

Added line #L675 was not covered by tests
...courseRun,
start: getNormalizedStartDate(courseRun),
enrollBy: getNormalizedEnrollByDate(
minimumEnrollByDateFromToday({ today, subsidyExpirationDatetime }),
),
hasEnrollBy: true,
};
}
return {
...courseRun,
start: getNormalizedStartDate(courseRun),
enrollBy: getNormalizedEnrollByDate(courseRun.enrollBy),
};
});
// Sorts by the enrollBy date. If enrollby is equivalent, sort by start.
const sortedAssignableCourseRuns = assignableCourseRuns.sort((a, b) => {
if (a.enrollBy === b.enrollBy) {
return dayjs(a.start).unix() - dayjs(b.start).unix();

Check warning on line 693 in src/components/learner-credit-management/data/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/learner-credit-management/data/utils.js#L693

Added line #L693 was not covered by tests
}
return a.enrollBy - b.enrollBy;

Check warning on line 695 in src/components/learner-credit-management/data/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/learner-credit-management/data/utils.js#L695

Added line #L695 was not covered by tests
});
return sortedAssignableCourseRuns;
};

0 comments on commit eba4828

Please sign in to comment.