Skip to content

Commit

Permalink
feat: rely on API to determine expired state and acknowledge assignme…
Browse files Browse the repository at this point in the history
…nts (#932)
  • Loading branch information
adamstankiewicz authored Feb 1, 2024
1 parent c299e4d commit 7ae824b
Show file tree
Hide file tree
Showing 20 changed files with 444 additions and 497 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const useCourseRunCardAction = ({

const handleRedemptionSuccess = (transaction) => {
if (!isUserEnrolled && !externalCourseEnrollmentUrl) {
toasts?.addToast(`You Enrolled in ${course.title}.`);
toasts?.addToast(`You enrolled in ${course.title}.`);
}
handleRedeemSuccess(transaction);
};
Expand Down
183 changes: 7 additions & 176 deletions src/components/dashboard/data/utils.js
Original file line number Diff line number Diff line change
@@ -1,77 +1,4 @@
import dayjs from 'dayjs';
import { ASSIGNMENT_TYPES, ASSIGNMENT_ACTION_TYPES } from '../../enterprise-user-subsidy/enterprise-offers/data/constants';
import {
LEARNER_ACKNOWLEDGED_ASSIGNMENT_CANCELLATION_ALERT,
LEARNER_ACKNOWLEDGED_ASSIGNMENT_EXPIRATION_ALERT,
} from '../main-content/course-enrollments/data/constants';

/**
* Checks if an assignment has expired based on following conditions:
* - 90 days have passed since the "created" date.
* - The course enrollment deadline has passed.
* - The subsidy expiration date has passed.
* @param {object} assignment - Information about the assignment.
* @returns {boolean} - Returns true if the assignment has expired, otherwise false.
*/
export const isAssignmentExpired = (assignment) => {
if (!assignment) {
return {
isExpired: false,
enrollByDeadline: undefined,
};
}

const currentDate = dayjs();
// Note: `created` is not currently present in the API response for assignments. In the future,
// the enroll by deadline will be returned by API instead of calculating it here.
const allocationDate = assignment.created ? dayjs(assignment.created) : undefined;
const enrollmentEndDate = assignment.contentMetadata.enrollByDate
? dayjs(assignment.contentMetadata.enrollByDate)
: undefined;
const subsidyExpirationDate = dayjs(assignment.subsidyExpirationDate);

const hasExceededAssignmentDeadline = allocationDate && currentDate.diff(allocationDate, 'day') > 90;
const isEnrollmentDeadlineExpired = enrollmentEndDate && currentDate.isAfter(enrollmentEndDate);

const isExpired = (
hasExceededAssignmentDeadline || isEnrollmentDeadlineExpired || currentDate.isAfter(subsidyExpirationDate)
);

const assignmentExpiryDates = [subsidyExpirationDate];
if (enrollmentEndDate) {
assignmentExpiryDates.push(enrollmentEndDate);
}
if (allocationDate) {
assignmentExpiryDates.push(dayjs(allocationDate).add(90, 'day'));
}
const earliestAssignmentExpiryDate = assignmentExpiryDates.sort((a, b) => (dayjs(a).isAfter(b) ? 1 : -1))[0].toDate();

return {
isExpired,
enrollByDeadline: earliestAssignmentExpiryDate,
};
};

/**
* Determines whether an assignment record is expired and/or the expiration has been acknowledged by the learner.
*
* @param {Object} assignment - Metadata about the assignment.
* @returns {Object} - Returns an object with the following properties:
* - isExpired: Boolean indicating whether the assignment has expired.
* - hasDismissedExpiration: Boolean indicating whether the learner has acknowledged the assignment expiration.
*/
export function isExpiredAssignmentAcknowledged(assignment) {
const lastExpiredAlertDismissedTime = global.localStorage.getItem(
LEARNER_ACKNOWLEDGED_ASSIGNMENT_EXPIRATION_ALERT,
);
const { isExpired, enrollByDeadline } = isAssignmentExpired(assignment);
const isAcknowledged = dayjs(enrollByDeadline).isBefore(new Date(lastExpiredAlertDismissedTime));
const hasDismissedExpiration = isExpired && isAcknowledged;
return {
isExpired,
hasDismissedExpiration,
};
}
import { ASSIGNMENT_TYPES } from '../../enterprise-user-subsidy/enterprise-offers/data/constants';

/**
* Determines whether there are any unacknowledged expired assignments.
Expand All @@ -80,37 +7,9 @@ export function isExpiredAssignmentAcknowledged(assignment) {
* @returns {Boolean} - Returns true if there are any unacknowledged expired assignments, otherwise false.
*/
export function getHasUnacknowledgedExpiredAssignments(assignments) {
return assignments.some((assignment) => {
const { isExpired, hasDismissedExpiration } = isExpiredAssignmentAcknowledged(assignment);
return isExpired && !hasDismissedExpiration;
});
}

/**
* Determines whether an assignment has been canceled and/or the cancelaton has been acknowledged by the learner.
*
* @param {Object} assignment - Metadata about the assignment.
* @returns {Object} - Returns an object with the following properties:
* - isCanceled: Boolean indicating whether the assignment has been canceled.
* - hasDismissedCancellation: Boolean indicating whether the learner has acknowledged the assignment cancellation.
*/
export function isCanceledAssignmentAcknowledged(assignment) {
const lastCanceledAlertDismissedTime = global.localStorage.getItem(
LEARNER_ACKNOWLEDGED_ASSIGNMENT_CANCELLATION_ALERT,
);
const isCanceled = assignment.state === ASSIGNMENT_TYPES.CANCELED;
const hasDismissedCancelation = assignment.actions.some((action) => {
const isCanceledNoticationAction = [
ASSIGNMENT_ACTION_TYPES.CANCELED,
ASSIGNMENT_ACTION_TYPES.AUTOMATIC_CANCELATION,
].includes(action.actionType);
const isAcknowledged = dayjs(action.completedAt).isBefore(new Date(lastCanceledAlertDismissedTime));
return isCanceled && isCanceledNoticationAction && isAcknowledged;
});
return {
isCanceled,
hasDismissedCancelation,
};
return assignments.some((assignment) => (
assignment.state === ASSIGNMENT_TYPES.EXPIRED && !assignment.learnerAcknowledged
));
}

/**
Expand All @@ -120,75 +19,7 @@ export function isCanceledAssignmentAcknowledged(assignment) {
* @returns {Boolean} - Returns true if there are any unacknowledged canceled assignments, otherwise false.
*/
export function getHasUnacknowledgedCanceledAssignments(assignments) {
return assignments.some((assignment) => {
const { isCanceled, hasDismissedCancelation } = isCanceledAssignmentAcknowledged(assignment);
return isCanceled && !hasDismissedCancelation;
});
}

/**
* Takes a flattened array of assignments and returns an object containing
* lists of assignments for each assignment state.
*
* @param {Array} assignments - List of content assignments.
* @returns {{
* assignments: Array,
* hasAssignments: Boolean,
* allocatedAssignments: Array,
* hasAllocatedAssignments: Boolean,
* canceledAssignments: Array,
* hasCanceledAssignments: Boolean,
* acceptedAssignments: Array,
* hasAcceptedAssignments: Boolean,
* }}
*/
export function getAssignmentsByState(assignments = []) {
const allAssignments = [];
const allocatedAssignments = [];
const canceledAssignments = [];
const acceptedAssignments = [];
const erroredAssignments = [];
const assignmentsForDisplay = [];

assignments.forEach((assignment) => {
allAssignments.push(assignment);
if (assignment.state === ASSIGNMENT_TYPES.ALLOCATED) {
allocatedAssignments.push(assignment);
}
if (assignment.state === ASSIGNMENT_TYPES.CANCELED) {
canceledAssignments.push(assignment);
}
if (assignment.state === ASSIGNMENT_TYPES.ACCEPTED) {
acceptedAssignments.push(assignment);
}
if (assignment.state === ASSIGNMENT_TYPES.ERRORED) {
erroredAssignments.push(assignment);
}
});

const hasAssignments = allAssignments.length > 0;
const hasAllocatedAssignments = allocatedAssignments.length > 0;
const hasCanceledAssignments = canceledAssignments.length > 0;
const hasAcceptedAssignments = acceptedAssignments.length > 0;
const hasErroredAssignments = erroredAssignments.length > 0;

// Concatenate all assignments for display (includes allocated and canceled assignments)
assignmentsForDisplay.push(...allocatedAssignments);
assignmentsForDisplay.push(...canceledAssignments);
const hasAssignmentsForDisplay = assignmentsForDisplay.length > 0;

return {
assignments,
hasAssignments,
allocatedAssignments,
hasAllocatedAssignments,
canceledAssignments,
hasCanceledAssignments,
acceptedAssignments,
hasAcceptedAssignments,
erroredAssignments,
hasErroredAssignments,
assignmentsForDisplay,
hasAssignmentsForDisplay,
};
return assignments.some((assignment) => (
assignment.state === ASSIGNMENT_TYPES.CANCELED && !assignment.learnerAcknowledged
));
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import React, { useContext } from 'react';
import { AppContext } from '@edx/frontend-platform/react';
import PropTypes from 'prop-types';
import { AppContext } from '@edx/frontend-platform/react';
import { Alert, Button, MailtoLink } from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';

import { getContactEmail } from '../../../../utils/common';
import { ASSIGNMENT_TYPES } from '../../../enterprise-user-subsidy/enterprise-offers/data/constants';

const CourseAssignmentAlert = ({
showAlert,
onClose,
variant,
}) => {
const intl = useIntl();
const heading = variant === 'canceled' ? (
const heading = variant === ASSIGNMENT_TYPES.CANCELED ? (
<FormattedMessage
id="enterprise.dashboard.course.assignment.cancelled.alert.heading"
defaultMessage="Course assignment canceled"
Expand All @@ -26,7 +28,7 @@ const CourseAssignmentAlert = ({
/>
);

const text = variant === 'canceled' ? (
const text = variant === ASSIGNMENT_TYPES.CANCELED ? (
<FormattedMessage
id="enterprise.dashboard.course.assignment.cancelled.alert.text"
defaultMessage="Your learning administrator canceled one or more course assignments below."
Expand Down Expand Up @@ -73,7 +75,7 @@ const CourseAssignmentAlert = ({

CourseAssignmentAlert.propTypes = {
onClose: PropTypes.func,
variant: PropTypes.string,
variant: PropTypes.oneOf([ASSIGNMENT_TYPES.CANCELED, ASSIGNMENT_TYPES.EXPIRED]),
showAlert: PropTypes.bool,
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import React, {
useContext, useEffect, useState,
} from 'react';
import PropTypes from 'prop-types';
import Cookies from 'universal-cookie';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';

import PropTypes from 'prop-types';

import CourseSection from './CourseSection';
import CourseEnrollmentsAlert from './CourseEnrollmentsAlert';
import CourseAssignmentAlert from './CourseAssignmentAlert';
import { CourseEnrollmentsContext } from './CourseEnrollmentsContextProvider';
import { features } from '../../../../config';
import { useCourseEnrollmentsBySection, useContentAssignments } from './data';
import { UserSubsidyContext } from '../../../enterprise-user-subsidy';
import { ASSIGNMENT_TYPES } from '../../../enterprise-user-subsidy/enterprise-offers/data/constants';

const CourseEnrollments = ({ children }) => {
const { redeemableLearnerCreditPolicies } = useContext(UserSubsidyContext);
Expand All @@ -28,8 +28,7 @@ const CourseEnrollments = ({ children }) => {
assignments,
showCanceledAssignmentsAlert,
showExpiredAssignmentsAlert,
handleOnCloseCancelAlert,
handleOnCloseExpiredAlert,
handleAcknowledgeAssignments,
} = useContentAssignments(redeemableLearnerCreditPolicies);
const {
hasCourseEnrollments,
Expand Down Expand Up @@ -67,10 +66,22 @@ const CourseEnrollments = ({ children }) => {
return (
<>
{features.FEATURE_ENABLE_TOP_DOWN_ASSIGNMENT && (
<CourseAssignmentAlert showAlert={showCanceledAssignmentsAlert} variant="canceled" onClose={handleOnCloseCancelAlert}> </CourseAssignmentAlert>
<CourseAssignmentAlert
showAlert={showCanceledAssignmentsAlert}
variant={ASSIGNMENT_TYPES.CANCELED}
onClose={() => handleAcknowledgeAssignments({
assignmentState: ASSIGNMENT_TYPES.CANCELED,
})}
/>
)}
{features.FEATURE_ENABLE_TOP_DOWN_ASSIGNMENT && (
<CourseAssignmentAlert showAlert={showExpiredAssignmentsAlert} variant="expired" onClose={handleOnCloseExpiredAlert}> </CourseAssignmentAlert>
<CourseAssignmentAlert
showAlert={showExpiredAssignmentsAlert}
variant={ASSIGNMENT_TYPES.EXPIRED}
onClose={() => handleAcknowledgeAssignments({
assignmentState: ASSIGNMENT_TYPES.EXPIRED,
})}
/>
)}
{showMarkCourseCompleteSuccess && (
<CourseEnrollmentsAlert variant="success" onClose={() => setShowMarkCourseCompleteSuccess(false)}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,3 @@ export const COURSE_STATUSES = {
};

export const GETSMARTER_BASE_URL = 'https://www.getsmarter.com';

export const LEARNER_ACKNOWLEDGED_ASSIGNMENT_CANCELLATION_ALERT = 'learnerAcknowledgedCancellationAt';
export const LEARNER_ACKNOWLEDGED_ASSIGNMENT_EXPIRATION_ALERT = 'learnerAcknowledgedExpirationAt';
Loading

0 comments on commit 7ae824b

Please sign in to comment.