Skip to content

Commit

Permalink
feat: default stale start dates to today (#1180)
Browse files Browse the repository at this point in the history
  • Loading branch information
brobro10000 authored Sep 16, 2024
1 parent 4c45493 commit f1887e4
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 18 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',
};

// When the start date is before this number of days before today, display the alternate start date (fixed to today).
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
5 changes: 1 addition & 4 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 @@ -510,7 +508,6 @@ export function getAvailableCourseRuns({ course, lateEnrollmentBufferDays }) {
if (!course?.courseRuns) {
return [];
}

// These are the standard rules used for determining whether a run is "available".
const standardAvailableCourseRunsFilter = (courseRun) => (
courseRun.isMarketable && !isArchived(courseRun) && courseRun.isEnrollable
Expand Down Expand Up @@ -879,7 +876,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
8 changes: 5 additions & 3 deletions src/components/course/course-header/CourseImportantDates.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import PropTypes from 'prop-types';
import {
DATE_FORMAT,
DATETIME_FORMAT,
getNormalizedStartDate,
getSoonestEarliestPossibleExpirationData,
hasCourseStarted,
useIsCourseAssigned,
Expand Down Expand Up @@ -88,11 +89,12 @@ 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 = soonestExpiringAllocatedAssignment
&& getNormalizedStartDate(soonestExpiringAllocatedAssignment);
}

// Parse logic of date existence and labels
const enrollByDate = soonestExpirationDate ?? null;
const courseStartDate = soonestExpiringAllocatedAssignmentCourseStartDate
Expand Down
6 changes: 5 additions & 1 deletion src/components/course/data/constants.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import GetSmarterLogo from '../../../assets/icons/getsmarter-header-icon.svg';

// The SELF and INSTRUCTOR values are keys/value pairs used specifically for pacing sourced from the
// enterprise_course_enrollments API.
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 +126,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:mma';
export const ZERO_PRICE = 0.00;
46 changes: 40 additions & 6 deletions src/components/course/data/utils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@ 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
*
* @param start
* @returns {boolean}
*/
export const hasCourseStarted = (start) => dayjs(start).isBefore(dayjs());

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

export function isCourseSelfPaced(pacingType) {
return pacingType === COURSE_PACING_MAP.SELF_PACED;
return [COURSE_PACING_MAP.SELF_PACED, COURSE_PACING_MAP.SELF].includes(pacingType);
}

export function isCourseInstructorPaced(pacingType) {
return pacingType === COURSE_PACING_MAP.INSTRUCTOR_PACED;
return [COURSE_PACING_MAP.INSTRUCTOR_PACED, COURSE_PACING_MAP.INSTRUCTOR].includes(pacingType);
}

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 {String} start
* @param {String} pacingType
* @param {String} end
* @param {Number} 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 @@ -932,7 +967,6 @@ export function getSoonestEarliestPossibleExpirationData({
if (dateFormat) {
soonestExpirationDate = dayjs(soonestExpirationDate).format(dateFormat);
}

return {
soonestExpirationDate,
soonestExpirationReason: sortedByExpirationDate[0].earliestPossibleExpiration.reason,
Expand Down
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 f1887e4

Please sign in to comment.