Skip to content

Commit

Permalink
fix: Update threshold dates for displaying course runs (#1317)
Browse files Browse the repository at this point in the history
\
  • Loading branch information
brobro10000 authored Sep 24, 2024
1 parent b8cd0c7 commit 9aef684
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 42 deletions.
13 changes: 13 additions & 0 deletions __mocks__/react-instantsearch-dom.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ const mockEndDate = dayjs().add(2, 'years').toISOString();
const mockEnrollByDate = dayjs().add(9, 'months').toISOString();
const mockEnrollByTimestamp = dayjs(mockEnrollByDate).unix();
const mockUpgradeDeadlineTimestamp = dayjs().add(3, 'months').unix();
const mockEnrollStartDate = dayjs().add(2, 'months').toISOString();
const mockEnrollStartTimestamp = dayjs(mockEnrollStartDate).unix();

const mockNormalizedData = {
start_date: mockCurrentStartDate,
end_date: mockEndDate,
enroll_by_date: mockEnrollByDate,
enroll_start_date: mockEnrollStartDate,
};

/* eslint-disable camelcase */
Expand All @@ -36,6 +39,8 @@ const fakeHits = [
end: mockEndDate,
enroll_by: mockEnrollByTimestamp,
has_enroll_by: true,
enroll_start: mockEnrollStartTimestamp,
has_enroll_start: true,
is_active: true,
max_effort: 5,
min_effort: 1,
Expand All @@ -53,6 +58,8 @@ const fakeHits = [
end: mockEndDate,
enroll_by: mockEnrollByTimestamp,
has_enroll_by: true,
enroll_start: mockEnrollStartTimestamp,
has_enroll_start: true,
is_active: true,
max_effort: 5,
min_effort: 1,
Expand All @@ -67,6 +74,8 @@ const fakeHits = [
end: dayjs('2020-09-09T04:00:00Z').add(1, 'year').toISOString(),
enroll_by: mockEnrollByTimestamp,
has_enroll_by: true,
enroll_start: mockEnrollStartTimestamp,
has_enroll_start: true,
is_active: true,
max_effort: 5,
min_effort: 1,
Expand All @@ -90,6 +99,8 @@ const fakeHits = [
end: mockEndDate,
enroll_by: mockEnrollByTimestamp,
has_enroll_by: true,
enroll_start: mockEnrollStartTimestamp,
has_enroll_start: true,
is_active: true,
max_effort: 5,
min_effort: 1,
Expand All @@ -107,6 +118,8 @@ const fakeHits = [
end: dayjs('2022-10-09T04:00:00Z').add(1, 'year').toISOString(),
enroll_by: mockEnrollByTimestamp,
has_enroll_by: true,
enroll_start: mockEnrollStartTimestamp,
has_enroll_start: true,
is_active: true,
max_effort: 5,
min_effort: 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
import React, { useCallback, useContext, useState } from 'react';
import PropTypes from 'prop-types';
import { generatePath, useNavigate, useParams } from 'react-router-dom';
import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';
import { getConfig } from '@edx/frontend-platform/config';
import { camelCaseObject, snakeCaseObject } from '@edx/frontend-platform/utils';
import {
ActionRow, Button, FullscreenModal, Hyperlink, StatefulButton, useToggle,
} from '@openedx/paragon';
import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { camelCaseObject, snakeCaseObject } from '@edx/frontend-platform/utils';
import PropTypes from 'prop-types';
import React, { useCallback, useContext, useState } from 'react';
import { connect } from 'react-redux';
import { getConfig } from '@edx/frontend-platform/config';
import { generatePath, useNavigate, useParams } from 'react-router-dom';

import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { logError } from '@edx/frontend-platform/logging';
import AssignmentModalContent from './AssignmentModalContent';
import EnterpriseAccessApiService from '../../../data/services/EnterpriseAccessApiService';
import EVENT_NAMES from '../../../eventTracking';
import { BudgetDetailPageContext } from '../BudgetDetailPageWrapper';
import {
getAssignableCourseRuns,
LEARNER_CREDIT_ROUTE,
learnerCreditManagementQueryKeys,
useBudgetId,
getAssignableCourseRuns, learnerCreditManagementQueryKeys, LEARNER_CREDIT_ROUTE, useBudgetId,
useSubsidyAccessPolicy,
} from '../data';
import AssignmentModalContent from './AssignmentModalContent';
import CreateAllocationErrorAlertModals from './CreateAllocationErrorAlertModals';
import { BudgetDetailPageContext } from '../BudgetDetailPageWrapper';
import EVENT_NAMES from '../../../eventTracking';
import NewAssignmentModalDropdown from './NewAssignmentModalDropdown';

const useAllocateContentAssignments = () => useMutation({
Expand Down Expand Up @@ -88,7 +85,7 @@ const NewAssignmentModalButton = ({ enterpriseId, course, children }) => {
setAssignmentRun(selectedCourseRun);
if (!selectedCourseRun) {
logError(`[handleOpenAssignmentModal]: Unable to open learner credit management allocation modal,
selectedCourseRun: ${selectedCourseRun},
selectedCourseRun: ${selectedCourseRun},
parentContentKey: ${course.key},
contentKey: ${selectedCourseRun.key},
enterpriseUuid: ${enterpriseId},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineMessages, useIntl } from '@edx/frontend-platform/i18n';
import { Dropdown, Stack } from '@openedx/paragon';
import dayjs from 'dayjs';
import PropTypes from 'prop-types';
import { defineMessages, useIntl } from '@edx/frontend-platform/i18n';
import { useState } from 'react';
import { SHORT_MONTH_DATE_FORMAT } from '../data';

Expand Down Expand Up @@ -29,7 +29,10 @@ const messages = defineMessages({
});

const NewAssignmentModalDropdown = ({
id: courseKey, onClick: openAssignmentModal, courseRuns, children,
id: courseKey,
onClick: openAssignmentModal,
courseRuns,
children,
}) => {
const intl = useIntl();
const [clickedDropdownItem, setClickedDropdownItem] = useState(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ jest.mock('../../data', () => ({
}));
jest.mock('../../../../data/services/EnterpriseAccessApiService');

const futureStartDate = dayjs().add(5, 'days').toISOString();
const pastStartDate = dayjs().subtract(5, 'days').toISOString();
const enrollByTimestamp = dayjs().add(2, 'days').unix();
const futureStartDate = dayjs().add(10, 'days').toISOString();
const pastStartDate = dayjs().subtract(10, 'days').toISOString();
const enrollStartDate = dayjs().add(3, 'days').toISOString();
const enrollStartTimestamp = dayjs(enrollStartDate).unix();
const enrollByTimestamp = dayjs().add(5, 'days').unix();
const enrollByDropdownText = `Enroll by ${dayjs.unix(enrollByTimestamp).format(SHORT_MONTH_DATE_FORMAT)}`;

const originalData = {
Expand All @@ -64,6 +66,7 @@ const originalData = {
normalized_metadata: {
enroll_by_date: dayjs.unix(1892678399).toISOString(),
start_date: futureStartDate,
enroll_start_date: enrollStartDate,
content_price: 100,
},
original_image_url: '',
Expand All @@ -77,6 +80,8 @@ const originalData = {
pacing_type: 'self_paced',
enroll_by: enrollByTimestamp,
has_enroll_by: true,
enroll_start: enrollStartTimestamp,
has_enroll_start: true,
is_active: true,
weeks_to_complete: 60,
end: dayjs().add(1, 'years').toISOString(),
Expand All @@ -90,6 +95,8 @@ const originalData = {
pacing_type: 'self_paced',
enroll_by: enrollByTimestamp,
has_enroll_by: true,
enroll_start: enrollStartTimestamp,
has_enroll_start: true,
is_active: true,
weeks_to_complete: 60,
end: dayjs().add(1, 'year').toISOString(),
Expand Down Expand Up @@ -123,6 +130,8 @@ const execEdData = {
pacing_type: 'instructor_paced',
enroll_by: enrollByTimestamp,
has_enroll_by: true,
enroll_start: enrollStartTimestamp,
has_enroll_start: true,
is_active: true,
weeks_to_complete: 60,
end: dayjs().add(1, 'year').toISOString(),
Expand All @@ -136,6 +145,8 @@ const execEdData = {
pacing_type: 'instructor_paced',
enroll_by: enrollByTimestamp,
has_enroll_by: true,
enroll_start: enrollStartTimestamp,
has_enroll_start: true,
is_active: true,
weeks_to_complete: 60,
end: dayjs().add(1, 'year').toISOString(),
Expand Down
2 changes: 2 additions & 0 deletions src/components/learner-credit-management/data/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ export const EMPTY_CONTENT_PRICE_VALUE = 0;
// Late enrollments feature
export const LATE_ENROLLMENTS_BUFFER_DAYS = 30;

export const MAX_MILLISECONDS = 8640000000000000;

// Query Key factory for the learner credit management module, intended to be used with `@tanstack/react-query`.
// Inspired by https://tkdodo.eu/blog/effective-react-query-keys#use-query-key-factories.
export const learnerCreditManagementQueryKeys = {
Expand Down
72 changes: 49 additions & 23 deletions src/components/learner-credit-management/data/utils.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { v4 as uuidv4 } from 'uuid';
import dayjs from 'dayjs';
import { camelCaseObject } from '@edx/frontend-platform/utils';
import { logInfo } from '@edx/frontend-platform/logging';
import { camelCaseObject } from '@edx/frontend-platform/utils';
import dayjs from 'dayjs';
import { v4 as uuidv4 } from 'uuid';

import EnterpriseAccessApiService from '../../../data/services/EnterpriseAccessApiService';
import EnterpriseDataApiService from '../../../data/services/EnterpriseDataApiService';
import SubsidyApiService from '../../../data/services/EnterpriseSubsidyApiService';
import { isPlanApproachingExpiry } from '../../BudgetExpiryAlertAndModal/data/utils';
import { BUDGET_STATUSES } from '../../EnterpriseApp/data/constants';
import {
ASSIGNMENT_ENROLLMENT_DEADLINE,
COURSE_PACING_MAP,
DAYS_UNTIL_ASSIGNMENT_ALLOCATION_EXPIRATION,
LATE_ENROLLMENTS_BUFFER_DAYS,
LOW_REMAINING_BALANCE_PERCENT_THRESHOLD,
MAX_ALLOWABLE_REFUND_THRESHOLD_DAYS,
MAX_MILLISECONDS,
NO_BALANCE_REMAINING_DOLLAR_THRESHOLD,
START_DATE_DEFAULT_TO_TODAY_THRESHOLD_DAYS,
} from './constants';
import { BUDGET_STATUSES } from '../../EnterpriseApp/data/constants';
import EnterpriseAccessApiService from '../../../data/services/EnterpriseAccessApiService';
import EnterpriseDataApiService from '../../../data/services/EnterpriseDataApiService';
import SubsidyApiService from '../../../data/services/EnterpriseSubsidyApiService';
import { isPlanApproachingExpiry } from '../../BudgetExpiryAlertAndModal/data/utils';

/**
* Transforms subsidy (offer or Subsidy) summary from API for display in the UI, guarding
Expand Down Expand Up @@ -570,6 +571,29 @@ export const minimumEnrollByDateFromToday = ({ subsidyExpirationDatetime }) => M
dayjs(subsidyExpirationDatetime).subtract(MAX_ALLOWABLE_REFUND_THRESHOLD_DAYS, 'days').toDate(),
);

const subsidyExpirationRefundCutoffDate = ({ subsidyExpirationDatetime }) => dayjs(subsidyExpirationDatetime).subtract(MAX_ALLOWABLE_REFUND_THRESHOLD_DAYS, 'days').toDate();

const isStartDateWithinThreshold = ({ enrollStart, start, subsidyExpirationDatetime }) => {
if (!start && !enrollStart) {
return true;
}
const timeStampStartDate = dayjs(start).unix() || MAX_MILLISECONDS;
const timeStampEnrollStartDate = dayjs(enrollStart).unix() || MAX_MILLISECONDS;
const earliestStartDate = Math.min(timeStampStartDate, timeStampEnrollStartDate);
return dayjs(
earliestStartDate,
).isBefore(subsidyExpirationRefundCutoffDate({ subsidyExpirationDatetime }), 'seconds');
};

const isEnrollByDateWithinThreshold = ({ enrollBy, isLateRedemptionAllowed = false }) => {
if (!enrollBy) { return true; }
let enrollmentEffectiveDate = dayjs();
if (isLateRedemptionAllowed) {
enrollmentEffectiveDate = enrollmentEffectiveDate.subtract(LATE_ENROLLMENTS_BUFFER_DAYS, 'days');
}
return dayjs(enrollBy).isAfter(enrollmentEffectiveDate, 'seconds');
};

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

export const hasTimeToComplete = ({ end, weeksToComplete }) => {
Expand Down Expand Up @@ -653,25 +677,27 @@ export const getAssignableCourseRuns = ({ courseRuns, subsidyExpirationDatetime,
const clonedCourseRuns = courseRuns.map(courseRun => ({
...courseRun,
enrollBy: courseRun.hasEnrollBy ? dayjs.unix(courseRun.enrollBy).toISOString() : null,
enrollStart: courseRun.hasEnrollStart ? dayjs.unix(courseRun.enrollStart).toISOString() : null,
upgradeDeadline: dayjs.unix(courseRun.upgradeDeadline).toISOString(),
}));
const assignableCourseRunsFilter = ({
enrollBy, isActive, hasEnrollBy, isLateEnrollmentEligible,
enrollBy, enrollStart, start, isActive, hasEnrollBy, isLateEnrollmentEligible,
}) => {
let isEligibleForEnrollment = true;
if (hasEnrollBy) {
const enrollByDate = dayjs(enrollBy);
// Determine eligibility based on the provided enrollBy is and the subsidy expiration date - refund threshold
isEligibleForEnrollment = (
!isDateBeforeToday(enrollByDate)
&& enrollByDate.isBefore(minimumEnrollByDateFromToday({ subsidyExpirationDatetime }))
);
// Late redemption filter
if (isDateBeforeToday(enrollBy) && isLateRedemptionAllowed) {
const lateEnrollmentCutoff = dayjs().subtract(LATE_ENROLLMENTS_BUFFER_DAYS, 'days');
isEligibleForEnrollment = enrollByDate.isAfter(lateEnrollmentCutoff);
return isLateEnrollmentEligible && isEligibleForEnrollment;
}
// Determine eligibility based on the provided enrollBy, start, and enrollStart date
const isEligibleForEnrollment = isEnrollByDateWithinThreshold(
{
enrollBy,
isLateRedemptionAllowed,
},
) && isStartDateWithinThreshold(
{
enrollStart,
start,
subsidyExpirationDatetime,
},
);
if (hasEnrollBy && isLateRedemptionAllowed && isDateBeforeToday(enrollBy)) {
return isLateEnrollmentEligible && isEligibleForEnrollment;
}
// General courseware filter
return isActive && isEligibleForEnrollment;
Expand Down

0 comments on commit 9aef684

Please sign in to comment.