Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dismiss alert modal and cancelled assignments #900

Closed
wants to merge 9 commits into from
Closed
33 changes: 31 additions & 2 deletions src/components/dashboard/data/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,33 @@
import { ASSIGNMENT_TYPES } from '../../enterprise-user-subsidy/enterprise-offers/data/constants';
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';

export function getIsActiveExpiredAssignment(assignments) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: getHasActiveExpiredAssignments as we're working with plural assignments here?

const lastExpiredAlertDismissedTime = global.localStorage.getItem(LEARNER_ACKNOWLEDGED_ASSIGNMENT_EXPIRATION_ALERT);

Check warning on line 8 in src/components/dashboard/data/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/dashboard/data/utils.js#L7-L8

Added lines #L7 - L8 were not covered by tests

const activeExpiredAssignments = assignments.filter((assignment) => (
assignment?.actions.some((action) => (

Check warning on line 11 in src/components/dashboard/data/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/dashboard/data/utils.js#L10-L11

Added lines #L10 - L11 were not covered by tests
action.actionType === ASSIGNMENT_ACTION_TYPES.AUTOMATIC_CANCELLATION_NOTIFICATION
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: the cron job that runs to automatically cancel assignments will result in the assignment(s) being marked as state=cancelled, which means those assignment cards would actually render as the canceled assignment card variant instead of the expired variant for assignments where the cron job already ran.

I believe at this point, the handing of expired assignments is for the edge case where a learner is viewing an assignment in the UI that hasn't yet been canceled by the cron job. That said, I think the logic here may need to be based on the determined enrollment deadline as depicted by isAssignmentExpired, i.e. are there expired assignments that haven't been dismissed with an assignment enrollment deadline that is more recent than the last dismissed expired assignments?

[idea] Refactor isAssignmentExpired slightly to return both an isExpired boolean but also the enrollment deadline for the assignment and then check the localStorage date against this returned enrollment deadline.

export const isAssignmentExpired = (assignment) => {
  // Assignment is not in an allocated state, so it cannot be expired.
  if (!assignment) {
    return false;
  }

  const currentDate = dayjs();
  const allocationDate = dayjs(assignment.created);
  const enrollmentEndDate = dayjs(assignment.contentMetadata.enrollByDate);
  const subsidyExpirationDate = dayjs(assignment.subsidyExpirationDate);

  const isExpired = (
    currentDate.diff(allocationDate, 'day') > 90
    || currentDate.isAfter(enrollmentEndDate)
    || currentDate.isAfter(subsidyExpirationDate)
  );

  const earliestAssignmentExpiryDate = [
    dayjs(allocationDate).add(90, 'day'),
    enrollmentEndDate,
    subsidyExpirationDate,
  ].sort()[0]?.toDate();

  return {
    isExpired,
    enrollByDeadline: earliestAssignmentExpiryDate,
  };
};
export function getHasActiveExpiredAssignment(assignments) {
  const lastExpiredAlertDismissedTime = global.localStorage.getItem(LEARNER_ACKNOWLEDGED_ASSIGNMENT_EXPIRATION_ALERT);

  const activeExpiredAssignments = assignments.filter((assignment) => {
    const {
      isExpired,
      enrollByDeadline,
    } = isAssignmentExpired(assignment);

    if (!isExpired) {
      return false;
    }

    return dayjs(enrollByDeadline).isAfter(new Date(lastExpiredAlertDismissedTime));
  });

  return activeExpiredAssignments.length > 0;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good idea for now, but really we should introduce an expired state on the backend - and then the frontend could determine if an assignment is expired by checking assignment.state === 'expired'.

&& new Date(action.completedAt) > new Date(lastExpiredAlertDismissedTime)

Check warning on line 13 in src/components/dashboard/data/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/dashboard/data/utils.js#L13

Added line #L13 was not covered by tests
))
));
return activeExpiredAssignments.length > 0;

Check warning on line 16 in src/components/dashboard/data/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/dashboard/data/utils.js#L16

Added line #L16 was not covered by tests
}

export function getIsActiveCancelledAssignment(assignments) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: getHasActiveCancelledAssignments as we're working with plural assignments here?

const lastCancelledAlertDismissedTime = global.localStorage.getItem(

Check warning on line 20 in src/components/dashboard/data/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/dashboard/data/utils.js#L19-L20

Added lines #L19 - L20 were not covered by tests
LEARNER_ACKNOWLEDGED_ASSIGNMENT_CANCELLATION_ALERT,
);
const activeCancelledAssignments = assignments.filter((assignment) => (
assignment?.actions.some((action) => (

Check warning on line 24 in src/components/dashboard/data/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/dashboard/data/utils.js#L23-L24

Added lines #L23 - L24 were not covered by tests
action.actionType === ASSIGNMENT_ACTION_TYPES.CANCELLED_NOTIFICATION
&& new Date(action.completedAt) > new Date(lastCancelledAlertDismissedTime)

Check warning on line 26 in src/components/dashboard/data/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/dashboard/data/utils.js#L26

Added line #L26 was not covered by tests
))
));
return activeCancelledAssignments.length > 0;

Check warning on line 29 in src/components/dashboard/data/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/dashboard/data/utils.js#L29

Added line #L29 was not covered by tests
}

/**
* Takes a flattened array of assignments and returns an object containing:
Expand All @@ -17,7 +46,7 @@
*/
export default function getActiveAssignments(assignments = []) {
const activeAssignments = assignments.filter((assignment) => [
ASSIGNMENT_TYPES.CANCELLED, ASSIGNMENT_TYPES.ALLOCATED,
ASSIGNMENT_TYPES.ALLOCATED,
].includes(assignment.state));
Comment on lines 47 to 50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamstankiewicz any concerns with changing this definition of getActiveAssignments()?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iloveagent57 @katrinan029 Yes, by removing ASSIGNMENT_TYPES.CANCELLED here, the CourseEnrollments components that check against "cancelled" assignment states would no longer be provided any canceled assignments to process (i.e., I don't believe the util functions (e.g., getIsActiveCancelledAssignment) would be executed? 🤔 For example, trying to run this locally, I couldn't get the canceled alert to appear unless canceled assignments were considered here.

I might recommend modifying getActiveAssignments to split out arrays for each assignment lifecycle state that's relevant, which would enable more granular control over which types of assignments you're working with in CourseEnrollments, i.e.:

// Renamed `getActiveAssignments` to `getAssignmentsByState`

export function getAssignmentsByState(assignments = []) {
  const allAssignments = [];
  const allocatedAssignments = [];
  const canceledAssignments = [];
  const acceptedAssignments = [];

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

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

  return {
    assignments,
    hasAssignments,
    allocatedAssignments,
    hasAllocatedAssignments,
    canceledAssignments,
    hasCanceledAssignments,
    acceptedAssignments,
    hasAcceptedAssignments,
};

Note: This function should really only be called within getRedeemablePoliciesData at this point (as it is on master) as that logic has already been executed further up in the component tree, without needing to run again to get similar results.

const hasAssignments = assignments.length > 0;
const hasActiveAssignments = activeAssignments.length > 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Info } from '@edx/paragon/icons';
import { getContactEmail } from '../../../../utils/common';

const CourseAssignmentAlert = ({
showAlert,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call to make this alert component act as a controlled component instead of uncontrolled.

onClose,
variant,
}) => {
Expand All @@ -20,6 +21,7 @@ const CourseAssignmentAlert = ({
return (
<Alert
variant="danger"
show={showAlert}
icon={Info}
dismissible
actions={[
Expand All @@ -38,11 +40,13 @@ const CourseAssignmentAlert = ({
CourseAssignmentAlert.propTypes = {
onClose: PropTypes.func,
variant: PropTypes.string,
showAlert: PropTypes.bool,
};

CourseAssignmentAlert.defaultProps = {
onClose: null,
variant: null,
showAlert: null,
};

export default CourseAssignmentAlert;
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
} from './data/utils';
import { UserSubsidyContext } from '../../../enterprise-user-subsidy';
import { features } from '../../../../config';
import getActiveAssignments from '../../data/utils';
import getActiveAssignments, { getIsActiveCancelledAssignment, getIsActiveExpiredAssignment } from '../../data/utils';
import { ASSIGNMENT_TYPES } from '../../../enterprise-user-subsidy/enterprise-offers/data/constants';
import { LEARNER_ACKNOWLEDGED_ASSIGNMENT_CANCELLATION_ALERT, LEARNER_ACKNOWLEDGED_ASSIGNMENT_EXPIRATION_ALERT } from './data';

export const COURSE_SECTION_TITLES = {
current: 'My courses',
Expand All @@ -44,7 +45,10 @@
const { redeemableLearnerCreditPolicies } = useContext(UserSubsidyContext);

const [assignments, setAssignments] = useState([]);
const [showCancelledAssignmentsAlert, setShowCancelledAssignmentsAlert] = useState(false);
const [
showCancelledAssignmentsAlert,
setShowCancelledAssignmentsAlert,
] = useState(false);
const [showExpiredAssignmentsAlert, setShowExpiredAssignmentsAlert] = useState(false);

useEffect(() => {
Expand All @@ -53,12 +57,12 @@
);
setAssignments(assignmentsData);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] I might propose a bit of a further refactor to this useEffect to clean it up a bit:

useEffect(() => {
    if (!redeemableLearnerCreditPolicies) {
      return;
    }

    const {
      allocatedAssignments,
      canceledAssignments,
      hasCanceledAssignments,
    } = redeemableLearnerCreditPolicies.learnerContentAssignments;

    const sortedAllocatedAssignments = sortAssignmentsByAssignmentStatus(allocatedAssignments);
    const transformedAllocatedAssignments = getTransformedAllocatedAssignments(
      sortedAllocatedAssignments,
      slug,
    );
    setAssignments(transformedAllocatedAssignments);

    const hasActiveCancelledAssignments = (
      hasCanceledAssignments && getHasActiveCancelledAssignments(canceledAssignments)
    );
    setShowCancelledAssignmentsAlert(hasActiveCancelledAssignments);

    const hasActiveExpiredAssignments = getHasActiveExpiredAssignment(allocatedAssignments);
    setShowExpiredAssignmentsAlert(hasActiveExpiredAssignments);
}, [redeemableLearnerCreditPolicies, slug]);

By doing so, we could rely on the state variable assignments instead of assignedCourses when passing them as the course runs to the assigned CourseSection component (i.e., the call to getTransformedAllocatedAssignments was moved within the useEffect).


const hasCancelledAssignments = assignmentsData?.some(
assignment => assignment.state === ASSIGNMENT_TYPES.CANCELLED,
);
const hasExpiredAssignments = assignmentsData?.some(assignment => isAssignmentExpired(assignment));
const hasActiveCancelledAssignments = assignmentsData?.some((assignment) => (
assignment.state === ASSIGNMENT_TYPES.CANCELLED)) && getIsActiveCancelledAssignment(assignmentsData);
iloveagent57 marked this conversation as resolved.
Show resolved Hide resolved
setShowCancelledAssignmentsAlert(hasActiveCancelledAssignments);

setShowCancelledAssignmentsAlert(hasCancelledAssignments);
const hasExpiredAssignments = assignmentsData?.some(assignment => isAssignmentExpired(assignment))
&& getIsActiveExpiredAssignment(assignmentsData);
Comment on lines +64 to +65
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to another comment, I believe you could move the call to the (refactored) isAssignmentExpired into getIsActiveExpiredAssignment itself such that it has access to the proposed enrollByDeadline returned by isAssignmentExpired to compare against the localStorage timestamp value.

setShowExpiredAssignmentsAlert(hasExpiredAssignments);
}, [redeemableLearnerCreditPolicies]);

Expand All @@ -70,9 +74,9 @@
Object.keys(courseEnrollmentsByStatus).forEach((status) => {
courseEnrollmentsByStatus[status] = courseEnrollmentsByStatus[status].map((course) => {
const isAssigned = assignments?.some(assignment => (assignment?.state === ASSIGNMENT_TYPES.ACCEPTED
&& course.courseRunId.includes(assignment?.contentKey)));

Check warning on line 77 in src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx#L77

Added line #L77 was not covered by tests
if (isAssigned) {
return { ...course, isCourseAssigned: true };

Check warning on line 79 in src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx#L79

Added line #L79 was not covered by tests
}
return course;
});
Expand Down Expand Up @@ -108,15 +112,24 @@
</CourseEnrollmentsAlert>
);
}
const handleOnCloseCancelAlert = () => {
setShowCancelledAssignmentsAlert(false);
global.localStorage.setItem(LEARNER_ACKNOWLEDGED_ASSIGNMENT_CANCELLATION_ALERT, new Date());
};

const handleOnCloseExpiredAlert = () => {
setShowCancelledAssignmentsAlert(false);
global.localStorage.setItem(LEARNER_ACKNOWLEDGED_ASSIGNMENT_EXPIRATION_ALERT, new Date());

Check warning on line 122 in src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/dashboard/main-content/course-enrollments/CourseEnrollments.jsx#L121-L122

Added lines #L121 - L122 were not covered by tests
};

const hasCourseEnrollments = Object.values(courseEnrollmentsByStatus).flat().length > 0;
return (
<>
{features.FEATURE_ENABLE_TOP_DOWN_ASSIGNMENT && showCancelledAssignmentsAlert && (
<CourseAssignmentAlert variant="cancelled" onClose={() => setShowCancelledAssignmentsAlert(false)}> </CourseAssignmentAlert>
{features.FEATURE_ENABLE_TOP_DOWN_ASSIGNMENT && (
<CourseAssignmentAlert showAlert={showCancelledAssignmentsAlert} variant="cancelled" onClose={handleOnCloseCancelAlert}> </CourseAssignmentAlert>
)}
{features.FEATURE_ENABLE_TOP_DOWN_ASSIGNMENT && showExpiredAssignmentsAlert && (
<CourseAssignmentAlert variant="expired" onClose={() => setShowExpiredAssignmentsAlert(false)}> </CourseAssignmentAlert>
{features.FEATURE_ENABLE_TOP_DOWN_ASSIGNMENT && (
<CourseAssignmentAlert showAlert={showExpiredAssignmentsAlert} variant="expired" onClose={handleOnCloseExpiredAlert}> </CourseAssignmentAlert>
)}
{showMarkCourseCompleteSuccess && (
<CourseEnrollmentsAlert variant="success" onClose={() => setShowMarkCourseCompleteSuccess(false)}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ 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';
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import '@testing-library/jest-dom/extend-expect';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppContext } from '@edx/frontend-platform/react';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { renderWithRouter } from '@edx/frontend-enterprise-utils';

import dayjs from 'dayjs';
import userEvent from '@testing-library/user-event';
import {
createCourseEnrollmentWithStatus,
} from './enrollment-testutils';
import CourseEnrollments, { COURSE_SECTION_TITLES } from '../CourseEnrollments';
import CourseEnrollments, {
COURSE_SECTION_TITLES,
} from '../CourseEnrollments';
import { MARK_MOVE_TO_IN_PROGRESS_DEFAULT_LABEL } from '../course-cards/move-to-in-progress-modal/MoveToInProgressModal';
import { MARK_SAVED_FOR_LATER_DEFAULT_LABEL } from '../course-cards/mark-complete-modal/MarkCompleteModal';
import { updateCourseCompleteStatusRequest } from '../course-cards/mark-complete-modal/data/service';
Expand All @@ -24,15 +27,28 @@ import * as hooks from '../data/hooks';
import { SubsidyRequestsContext } from '../../../../enterprise-subsidy-requests';
import { UserSubsidyContext } from '../../../../enterprise-user-subsidy';
import { sortAssignmentsByAssignmentStatus } from '../data/utils';
import getActiveAssignments, { getIsActiveCancelledAssignment } from '../../../data/utils';

jest.mock('@edx/frontend-platform/auth');
jest.mock('@edx/frontend-enterprise-utils');
getAuthenticatedUser.mockReturnValue({ username: 'test-username' });

jest.mock('../course-cards/mark-complete-modal/data/service');

jest.mock('../../../data/utils', () => ({
__esModule: true,
default: jest.fn(),
getIsActiveCancelledAssignment: jest.fn(),
getIsActiveExpiredAssignment: jest.fn(),
}));

jest.mock('../data/service');
jest.mock('../data/hooks');
jest.mock('../../../../../config', () => ({
features: {
FEATURE_ENABLE_TOP_DOWN_ASSIGNMENT: true,
},
}));

const enterpriseConfig = {
uuid: 'test-enterprise-uuid',
Expand All @@ -42,6 +58,10 @@ const inProgCourseRun = createCourseEnrollmentWithStatus({ status: COURSE_STATUS
const upcomingCourseRun = createCourseEnrollmentWithStatus({ status: COURSE_STATUSES.upcoming });
const completedCourseRun = createCourseEnrollmentWithStatus({ status: COURSE_STATUSES.completed });
const savedForLaterCourseRun = createCourseEnrollmentWithStatus({ status: COURSE_STATUSES.savedForLater });
const cancelledAssignedCourseRun = createCourseEnrollmentWithStatus({
status: COURSE_STATUSES.assigned,
isCancelledAssignment: true,
});

const transformedLicenseRequest = {
created: '2017-02-05T05:00:00Z',
Expand All @@ -52,11 +72,25 @@ const transformedLicenseRequest = {
notifications: [],
};

const assignmentData = {
contentKey: 'test-contentKey',
contentTitle: 'test-title',
contentMetadata: {
endDate: '2018-08-18T05:00:00Z',
startDate: '2017-02-05T05:00:00Z',
courseType: 'test-course-type',
enrollByDate: '2017-02-05T05:00:00Z',
partners: [{ name: 'test-partner' }],
},
state: 'cancelled',
};

hooks.useCourseEnrollments.mockReturnValue({
courseEnrollmentsByStatus: {
inProgress: [inProgCourseRun],
upcoming: [upcomingCourseRun],
completed: [completedCourseRun],
assigned: [cancelledAssignedCourseRun],
savedForLater: [savedForLaterCourseRun],
requested: [transformedLicenseRequest],
},
Expand Down Expand Up @@ -96,7 +130,11 @@ jest.mock('../data/utils', () => ({
describe('Course enrollments', () => {
beforeEach(() => {
updateCourseCompleteStatusRequest.mockImplementation(() => ({ data: {} }));
sortAssignmentsByAssignmentStatus.mockReturnValue([]);
sortAssignmentsByAssignmentStatus.mockReturnValue([assignmentData]);
getActiveAssignments.mockReturnValue({
activeAssignments: [],
hasActiveAssignments: true,
});
});

afterEach(() => {
Expand All @@ -111,6 +149,29 @@ describe('Course enrollments', () => {
expect(screen.getAllByText(inProgCourseRun.title).length).toBeGreaterThanOrEqual(1);
});

it('does not render cancelled assignment and renders cancelled alert', async () => {
getIsActiveCancelledAssignment.mockReturnValue(true);
renderWithRouter(renderEnrollmentsComponent());
expect(screen.queryByText('Your learning administrator canceled this assignment.')).toBeFalsy();
expect(screen.getByText('Course assignment cancelled')).toBeInTheDocument();
expect(screen.queryByText('test-title')).toBeFalsy();
const dismissButton = screen.getByText('Dismiss');
userEvent.click(dismissButton);
await waitFor(() => expect(screen.queryByText('Course assignment cancelled')).toBeFalsy());
});

it('if there are active cancelled assignments, cancelled alert is rendered', () => {
getIsActiveCancelledAssignment.mockReturnValue(true);
renderEnrollmentsComponent();
expect(screen.queryByText('Course assignment cancelled')).toBeTruthy();
});

it('if there are no active cancelled assignments, cancelled alert is hidden', () => {
getIsActiveCancelledAssignment.mockReturnValue(false);
renderEnrollmentsComponent();
expect(screen.queryByText('Course assignment cancelled')).toBeFalsy();
});

it('generates course status update on move to in progress action', async () => {
const { getByText } = renderEnrollmentsComponent();
await act(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { COURSE_STATUSES } from '../data/constants';
* Generate an enrollment with given status.
* Can be used as a baseline to override and generate new courseRuns.
*/
const createCourseEnrollmentWithStatus = ({ status = COURSE_STATUSES.inProgress, mode = 'verified' }) => {
const createCourseEnrollmentWithStatus = ({ status = COURSE_STATUSES.inProgress, mode = 'verified', isCancelledAssignment = false }) => {
const randomNumber = Math.random();
return ({
courseRunId: `$course-v1:edX+DemoX+Demo_Course-${randomNumber}`,
Expand All @@ -18,6 +18,7 @@ const createCourseEnrollmentWithStatus = ({ status = COURSE_STATUSES.inProgress,
hasEmailsEnabled: true,
isRevoked: false,
mode,
isCancelledAssignment,
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ export const ASSIGNMENT_TYPES = {
ERRORED: 'errored',
};

export const ASSIGNMENT_ACTION_TYPES = {
CANCELLED_NOTIFICATION: 'cancelled',
AUTOMATIC_CANCELLATION_NOTIFICATION: 'automatic_cancellation',
};

export const POLICY_TYPES = {
ASSIGNED_CREDIT: 'AssignedLearnerCreditAccessPolicy',
PER_LEARNER_CREDIT: 'PerLearnerSpendCreditAccessPolicy',
Expand Down
Loading