Skip to content

Commit

Permalink
feat: default stale start dates to today
Browse files Browse the repository at this point in the history
  • Loading branch information
brobro10000 committed Sep 13, 2024
1 parent bd4087e commit 2366eab
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 20 deletions.
3 changes: 3 additions & 0 deletions src/components/app/data/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,6 @@ export const ASSIGNMENT_TYPES = {
ERRORED: 'errored',
EXPIRING: 'expiring',
};

// Start date threshold to default to today days, sets start date to today if course start date is beyond this value
export const START_DATE_DEFAULT_TO_TODAY_THRESHOLD_DAYS = 14;
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export default function useEnterpriseCourseEnrollments(queryOptions = {}) {
},
enabled: isEnabled,
});

// TODO: Talk about how we don't have access to weeksToComplete on the dashboard page.
const allEnrollmentsByStatus = useMemo(() => transformAllEnrollmentsByStatus({
enterpriseCourseEnrollments,
requests,
Expand Down
4 changes: 1 addition & 3 deletions src/components/app/data/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,6 @@ export const canUnenrollCourseEnrollment = (courseEnrollment) => {
*/
export const transformCourseEnrollment = (rawCourseEnrollment) => {
const courseEnrollment = { ...rawCourseEnrollment };

// Return the fields expected by the component(s)
courseEnrollment.title = courseEnrollment.displayName;
courseEnrollment.microMastersTitle = courseEnrollment.micromastersTitle;
Expand Down Expand Up @@ -409,7 +408,6 @@ export const transformLearnerContentAssignment = (learnerContentAssignment, ente
courseKey = parentContentKey;
courseRunId = contentKey;
}

return {
linkToCourse: `/${enterpriseSlug}/course/${courseKey}`,
courseRunId,
Expand Down Expand Up @@ -861,7 +859,7 @@ export function transformCourseMetadataByAllocatedCourseRunAssignments({
courseRuns: courseMetadata.courseRuns.filter(
courseRun => allocatedCourseRunAssignmentKeys.includes(courseRun.key),
),
availableCourseRuns: courseMetadata.courseRuns.filter(
availableCourseRuns: courseMetadata.availableCourseRuns.filter(
courseRun => allocatedCourseRunAssignmentKeys.includes(courseRun.key),
),
};
Expand Down
13 changes: 8 additions & 5 deletions src/components/course/course-header/CourseImportantDates.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { useParams, useSearchParams } from 'react-router-dom';
import { defineMessages, useIntl } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import {
DATE_FORMAT, DATETIME_FORMAT, getSoonestEarliestPossibleExpirationData, hasCourseStarted,
DATE_FORMAT,
DATETIME_FORMAT,
getNormalizedStartDate,
getSoonestEarliestPossibleExpirationData,
hasCourseStarted,
} from '../data';
import {
determineAllocatedCourseRunAssignmentsForCourse,
Expand Down Expand Up @@ -73,7 +77,6 @@ const CourseImportantDates = () => {
redeemableLearnerCreditPolicies,
courseKey,
});

const [searchParams] = useSearchParams();
const courseRunKey = searchParams.get('course_run_key')?.replaceAll(' ', '+');
// Check if the corresponding course run key from query parameters matches an allocated assignment course run key
Expand All @@ -93,11 +96,11 @@ const CourseImportantDates = () => {
// Match soonest expiring assignment to the corresponding course start date from course metadata
let soonestExpiringAllocatedAssignmentCourseStartDate = null;
if (soonestExpiringAssignment) {
soonestExpiringAllocatedAssignmentCourseStartDate = courseMetadata.availableCourseRuns.find(
const soonestExpiringAllocatedAssignment = courseMetadata.availableCourseRuns.find(
(courseRun) => courseRun.key === soonestExpiringAssignment?.contentKey,
)?.start;
);
soonestExpiringAllocatedAssignmentCourseStartDate = getNormalizedStartDate(soonestExpiringAllocatedAssignment);
}

// Parse logic of date existence and labels
const enrollByDate = soonestExpirationDate ?? null;
const courseStartDate = soonestExpiringAllocatedAssignmentCourseStartDate
Expand Down
4 changes: 3 additions & 1 deletion src/components/course/data/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import GetSmarterLogo from '../../../assets/icons/getsmarter-header-icon.svg';
export const COURSE_PACING_MAP = {
SELF_PACED: 'self_paced',
INSTRUCTOR_PACED: 'instructor_paced',
INSTRUCTOR: 'instructor',
SELF: 'self',
};

export const SUBSIDY_DISCOUNT_TYPE_MAP = {
Expand Down Expand Up @@ -122,5 +124,5 @@ export const DISABLED_ENROLL_USER_MESSAGES = {
/* eslint-enable max-len */

export const DATE_FORMAT = 'MMM D, YYYY';
export const DATETIME_FORMAT = 'MMM D, YYYY h:mm, a';
export const DATETIME_FORMAT = 'MMM D, YYYY h:mm a';
export const ZERO_PRICE = 0.00;
72 changes: 65 additions & 7 deletions src/components/course/data/utils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,20 @@ import {
findHighestLevelEntitlementSku,
findHighestLevelSkuByEntityModeType,
isEnrollmentUpgradeable,
START_DATE_DEFAULT_TO_TODAY_THRESHOLD_DAYS,
} from '../../app/data';

export function hasCourseStarted(start) {
return dayjs(start).isBefore(dayjs());
}
/**
* Determines if the course has already started. Mostly used around text formatting for tense
* Introduces 'jitter' in the form of a 30 second offset to take into account any additional
* formatting that takes place down stream related to setting values to today's date through dayjs()
*
* This should also help with reducing 'flaky' tests.
*
* @param start
* @returns {boolean}
*/
export const hasCourseStarted = (start) => dayjs(start).isBefore(dayjs().subtract(30, 'seconds'));

export function findUserEnrollmentForCourseRun({ userEnrollments, key }) {
return userEnrollments.find(
Expand Down Expand Up @@ -59,13 +68,43 @@ export function hasTimeToComplete(courseRun) {
}

export function isCourseSelfPaced(pacingType) {
return pacingType === COURSE_PACING_MAP.SELF_PACED;
return pacingType === COURSE_PACING_MAP.SELF_PACED || pacingType === COURSE_PACING_MAP.SELF;
}

export function isCourseInstructorPaced(pacingType) {
return pacingType === COURSE_PACING_MAP.INSTRUCTOR_PACED;
return pacingType === COURSE_PACING_MAP.INSTRUCTOR_PACED || pacingType === COURSE_PACING_MAP.INSTRUCTOR;
}

const isWithinMinimumStartDateThreshold = ({ start }) => dayjs(start).isBefore(dayjs().subtract(START_DATE_DEFAULT_TO_TODAY_THRESHOLD_DAYS, 'days'));

/**
* If the start date of the course is before today offset by the START_DATE_DEFAULT_TO_TODAY_THRESHOLD_DAYS
* then return today's formatted date. Otherwise, pass-through the start date in ISO format.
*
* @param start
* @param pacingType
* @param end
* @param weeksToComplete
* @returns {string}
*/
export const getNormalizedStartDate = ({
start, pacingType, end, weeksToComplete,
}) => {
const todayToIso = dayjs().toISOString();
if (!start) {
return todayToIso;
}
const startDateIso = dayjs(start).toISOString();
if (isCourseSelfPaced({ pacingType })) {
if (hasTimeToComplete({ end, weeksToComplete }) || isWithinMinimumStartDateThreshold({ start })) {
// always today's date (incentives enrollment)
return todayToIso;
}
return startDateIso;
}
return startDateIso;
};

export function getDefaultProgram(programs = []) {
if (programs.length === 0) {
return undefined;
Expand Down Expand Up @@ -928,15 +967,34 @@ export function getSoonestEarliestPossibleExpirationData({
const sortedByExpirationDate = assignmentsWithExpiration.sort(
(a, b) => (dayjs(a.earliestPossibleExpiration.date).isBefore(b.earliestPossibleExpiration.date) ? -1 : 1),
);
let { date: soonestExpirationDate } = sortedByExpirationDate[0].earliestPossibleExpiration.date;
let soonestExpirationDate = sortedByExpirationDate[0].earliestPossibleExpiration.date;
if (dateFormat) {
soonestExpirationDate = dayjs(soonestExpirationDate).format(dateFormat);
}

return {
soonestExpirationDate,
soonestExpirationReason: sortedByExpirationDate[0].earliestPossibleExpiration.reason,
soonestExpiringAssignment: sortedByExpirationDate[0],
sortedExpirationAssignments: sortedByExpirationDate,
};
}

/**
* If the start date of the course is before today offset by the START_DATE_DEFAULT_TO_TODAY_THRESHOLD_DAYS
* then return today's formatted date. Otherwise, pass-through the start date in ISO format.
*
* For cases where a start date does not exist, just return today's date.
*
* @param start
* @param format
* @returns {string}
*/
export const setStaleCourseStartDates = ({ start }) => {
if (!start) {
return dayjs().toISOString();
}
if (dayjs(start).isBefore(dayjs().subtract(START_DATE_DEFAULT_TO_TODAY_THRESHOLD_DAYS, 'days'))) {
return dayjs().toISOString();
}
return dayjs(start).toISOString();
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
useEnterpriseCustomer,
} from '../../../../app/data';
import { isCourseEnded, isDefinedAndNotNull, isTodayWithinDateThreshold } from '../../../../../utils/common';
import { getNormalizedStartDate } from '../../../../course/data';

const messages = defineMessages({
statusBadgeLabelInProgress: {
Expand Down Expand Up @@ -401,8 +402,15 @@ const BaseCourseCard = ({
};

const renderStartDate = () => {
const formattedStartDate = startDate ? dayjs(startDate).format('MMMM Do, YYYY') : null;
const isCourseStarted = dayjs(startDate) <= dayjs();
// TODO: Determine if its worth exposing weeks_to_complete in assignments to utilize this function effectively
const courseStartDate = getNormalizedStartDate({
start: startDate,
pacingType: pacing,
end: endDate,
weeksToComplete: null,
});
const formattedStartDate = dayjs(courseStartDate).format('MMMM Do, YYYY');
const isCourseStarted = dayjs(courseStartDate).isBefore(dayjs());
if (formattedStartDate && !isCourseStarted) {
return <span className="font-weight-light">Starts {formattedStartDate}</span>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,6 @@ export function useCourseEnrollmentsBySection(courseEnrollmentsByStatus) {
]),
[courseEnrollmentsByStatus],
);

const completedCourseEnrollments = useMemo(
() => sortedEnrollmentsByEnrollmentDate(courseEnrollmentsByStatus.completed),
[courseEnrollmentsByStatus.completed],
Expand Down

0 comments on commit 2366eab

Please sign in to comment.