Skip to content

Commit

Permalink
squash: address comments
Browse files Browse the repository at this point in the history
  • Loading branch information
pwnage101 committed Aug 20, 2024
1 parent 3a39614 commit 3a10a7b
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 25 deletions.
21 changes: 19 additions & 2 deletions src/components/app/data/hooks/useCourseMetadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import {
determineAllocatedCourseRunAssignmentsForCourse,
getAvailableCourseRuns,
transformCourseMetadataByAllocatedCourseRunAssignments,
isRunUnrestrictedForCustomer,
} from '../utils';
import useLateEnrollmentBufferDays from './useLateEnrollmentBufferDays';
import useRedeemablePolicies from './useRedeemablePolicies';
import useEnterpriseCustomerContainsContent from './useEnterpriseCustomerContainsContent';

/**
* Retrieves the course metadata for the given enterprise customer and course key.
Expand All @@ -27,6 +29,11 @@ export default function useCourseMetadata(queryOptions = {}) {
courseKey,
redeemableLearnerCreditPolicies,
});
const {
data: {
restrictedRunsAllowed,
},
} = useEnterpriseCustomerContainsContent([courseKey]);
// `requestUrl.searchParams` uses `URLSearchParams`, which decodes `+` as a space, so we
// need to replace it with `+` again to be a valid course run key.
let courseRunKey = searchParams.get('course_run_key')?.replaceAll(' ', '+');
Expand All @@ -45,10 +52,20 @@ export default function useCourseMetadata(queryOptions = {}) {
if (!data) {
return data;
}
const availableCourseRuns = getAvailableCourseRuns({ course: data, lateEnrollmentBufferDays });
// First stage filters out any runs that are unavailable for universal reasons, such as enrollment windows and
// published states.
const basicAvailableCourseRuns = getAvailableCourseRuns({ course: data, lateEnrollmentBufferDays });
// Second stage filters out any *restricted* runs that are certainly not available to the current customer. The
// result may still include runs that are restricted for the subsidy types actually applicable for the learner, so
// consumers of useCourseMetadata() should perform additional subsidy-specific filtering.
const availableAndUnrestrictedCourseRuns = basicAvailableCourseRuns.filter(r => isRunUnrestrictedForCustomer({
restrictedRunsAllowed,
courseKey,
courseRunMetadata: r,
}));
let transformedData = {
...data,
availableCourseRuns,
availableCourseRuns: availableAndUnrestrictedCourseRuns,
};
// This logic should appropriately handle multiple course runs being assigned, and return the appropriate metadata
transformedData = transformCourseMetadataByAllocatedCourseRunAssignments({
Expand Down
3 changes: 3 additions & 0 deletions src/components/app/data/hooks/useCourseMetadata.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import { fetchCourseMetadata } from '../services';
import useLateEnrollmentBufferDays from './useLateEnrollmentBufferDays';
import useCourseMetadata from './useCourseMetadata';
import useRedeemablePolicies from './useRedeemablePolicies';
import useEnterpriseCustomerContainsContent from './useEnterpriseCustomerContainsContent';

jest.mock('./useEnterpriseCustomer');
jest.mock('./useLateEnrollmentBufferDays');
jest.mock('./useRedeemablePolicies');
jest.mock('./useEnterpriseCustomerContainsContent');

jest.mock('../services', () => ({
...jest.requireActual('../services'),
Expand Down Expand Up @@ -69,6 +71,7 @@ describe('useCourseMetadata', () => {
useLateEnrollmentBufferDays.mockReturnValue(undefined);
useSearchParams.mockReturnValue([new URLSearchParams({ course_run_key: 'course-v1:edX+DemoX+2T2024' })]);
useRedeemablePolicies.mockReturnValue({ data: mockBaseRedeemablePolicies });
useEnterpriseCustomerContainsContent.mockReturnValue({ data: {} });
});
it('should handle resolved value correctly with no select function passed', async () => {
const { result, waitForNextUpdate } = renderHook(() => useCourseMetadata(), { wrapper: Wrapper });
Expand Down
10 changes: 5 additions & 5 deletions src/components/app/data/hooks/useCourseRedemptionEligibility.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useParams } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';

import { isRunUnrestricted } from '../utils';
import { isRunUnrestrictedForCatalog } from '../utils';
import useCourseMetadata from './useCourseMetadata';
import { queryCanRedeem } from '../queries';
import useEnterpriseCustomer from './useEnterpriseCustomer';
Expand All @@ -17,11 +17,11 @@ export function transformCourseRedemptionEligibility({
// Begin by excluding restricted runs that should not be visible to the requester.
// This filtering does not control visibility of individual course runs, but
// it does serve as input to the determination of redemption eligiblity.
const unrestrictedCanRedeemData = canRedeemData.filter(r => isRunUnrestricted({
const unrestrictedCanRedeemData = canRedeemData.filter(canRedeemRun => isRunUnrestrictedForCatalog({
restrictedRunsAllowed,
courseMetadata,
courseRunKey: r.contentKey,
catalogUuid: r.redeemableSubsidyAccessPolicy?.catalogUuid,
courseKey: courseMetadata.key,
courseRunMetadata: courseMetadata.availableCourseRuns.find(r => r.key === canRedeemRun.contentKey),
catalogUuid: canRedeemRun.redeemableSubsidyAccessPolicy?.catalogUuid,
}));
const redeemabilityForActiveCourseRun = unrestrictedCanRedeemData.find(
r => r.contentKey === courseMetadata.activeCourseRun?.key,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const mockExpectedUseCouseRedemptionEligibilityReturn = transformCourseRedemptio
courseMetadata: mockCourseMetadata,
courseRunKey: mockCourseRunKey,
canRedeemData: mockCanRedeemData,
restrictedRunsAllowed: {},
restrictedRunsAllowed: undefined,
});

describe('useCourseRedemptionEligibility', () => {
Expand Down
70 changes: 59 additions & 11 deletions src/components/app/data/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,7 @@ export const filterPoliciesByExpirationAndActive = (policies) => {
* @param applicableCouponCode
* @param applicableEnterpriseOffer
* @param applicableSubsidyAccessPolicy
* @returns {{perLearnerSpendLimit: (number|null|Number|*), policyRedemptionUrl: (string|string|*), discountType: string, discountValue: number, subsidyType: string, perLearnerEnrollmentLimit: (null|*)}|{subsidyId, discountType: string, discountValue: number, startDate, subsidyType: string, expirationDate, status}|undefined|{maxUserApplications: (null|*), endDate: (string|*), subsidyType: string, offerType: *, isCurrent, remainingApplications: (number|null|*), remainingApplicationsForUser: (number|null|*), discountType: string, remainingBalance, remainingBalanceForUser, discountValue, startDate: (string|*), maxUserDiscount}|{code, endDate: (string|*), discountType: (string|*), discountValue: (number|*), startDate: (string|*), subsidyType: string}}
* @returns {{perLearnerSpendLimit: (number|null|Number|*), policyRedemptionUrl: (string|string|*), discountType: string, discountValue: number, subsidyType: string, perLearnerEnrollmentLimit: (null|*)}|{subsidyId, discountType: string, discountValue: number, startDate, subsidyType: string, expirationDate, status}|undefined|{maxUserApplications: (null|*), endDate: (string|*), subsidyType: string, offerType: *, isCurrent, remainingApplications: (number|null|*), remainingApplicationsForUser: (number|null|*), discountType: string, remainingBalance, remainingBalanceForUser, discountValue, startDate: (string|*), maxUserDiscount}|{code, endDate: (string|*), discountType: (string|*), discountValue: (number|*), startDate: (string|*), subsidyType: string, catalogUuid: (string|*)}}
*/
/* eslint-enable max-len */
export const getSubsidyToApplyForCourse = ({
Expand All @@ -689,6 +689,7 @@ export const getSubsidyToApplyForCourse = ({
expirationDate: applicableSubscriptionLicense.subscriptionPlan.expirationDate,
status: applicableSubscriptionLicense.status,
subsidyId: applicableSubscriptionLicense.uuid,
catalogUuid: applicableSubscriptionLicense.subscriptionPlan.enterpriseCatalogUuid,
};
}

Expand All @@ -700,6 +701,7 @@ export const getSubsidyToApplyForCourse = ({
endDate: applicableCouponCode.couponEndDate,
code: applicableCouponCode.code,
subsidyType: COUPON_CODE_SUBSIDY_TYPE,
catalogUuid: applicableCouponCode.catalog,
};
}

Expand All @@ -712,6 +714,7 @@ export const getSubsidyToApplyForCourse = ({
perLearnerEnrollmentLimit: redeemableSubsidyAccessPolicy?.perLearnerEnrollmentLimit,
perLearnerSpendLimit: redeemableSubsidyAccessPolicy?.perLearnerSpendLimit,
policyRedemptionUrl: redeemableSubsidyAccessPolicy?.policyRedemptionUrl,
catalogUuid: redeemableSubsidyAccessPolicy?.catalogUuid,
};
}

Expand All @@ -730,6 +733,7 @@ export const getSubsidyToApplyForCourse = ({
remainingApplications: applicableEnterpriseOffer.remainingApplications,
remainingApplicationsForUser: applicableEnterpriseOffer.remainingApplicationsForUser,
isCurrent: applicableEnterpriseOffer.isCurrent,
catalogUuid: applicableEnterpriseOffer.enterpriseCatalogUuid,
};
}

Expand Down Expand Up @@ -834,19 +838,63 @@ export function transformCourseMetadataByAllocatedCourseRunAssignments({
}

/*
* A centralized helper to tell us if a given run is unrestricted for the given catalog.
* Centralized logic to get all the catalogs (for one customer) that have access to a specific restricted run.
*
* The hope is that we strive to limit any additional code that accesses restrictedRunsAllowed so that there is only one
* place to edit/fix it.
*/
export function isRunUnrestricted({
export function getAllowedCatalogsForRestrictedRun({
restrictedRunsAllowed,
courseMetadata,
courseRunKey,
catalogUuid,
courseKey,
courseRunMetadata,
}) {
const courseRunMetadata = courseMetadata.availableCourseRuns.find(r => r.contentKey === courseRunKey);
if (courseRunMetadata?.restrictionType === 'custom-b2b-enterprise') {
// If the run is restricted for enterprise, make sure the catalog of interest explicitly allows it.
return restrictedRunsAllowed[courseMetadata.key][courseRunKey].includes(catalogUuid);
return {
allowedCatalogs: restrictedRunsAllowed?.[courseKey]?.[courseRunMetadata.key]?.catalogUuids,
isRestricted: true,
};
}
// Otherwise, only allow completely unrestricted runs.
return !courseRunMetadata?.restrictionType;
return {
allowedCatalogs: undefined,
isRestricted: !!courseRunMetadata?.restrictionType,
};
}

/*
* Determine if a given run is unrestricted for ANY CATALOG for the current customer.
*/
export function isRunUnrestrictedForCustomer({
restrictedRunsAllowed,
courseKey,
courseRunMetadata,
}) {
const {
allowedCatalogs,
isRestricted,
} = getAllowedCatalogsForRestrictedRun({
restrictedRunsAllowed,
courseKey,
courseRunMetadata,
});

Check warning on line 878 in src/components/app/data/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/app/data/utils.js#L878

Added line #L878 was not covered by tests
return !isRestricted || allowedCatalogs?.length > 0;
}

/*
* Determine if a given run is unrestricted for the given catalog.
*/
export function isRunUnrestrictedForCatalog({
restrictedRunsAllowed,
courseKey,
courseRunMetadata,
catalogUuid,
}) {
const {
allowedCatalogs,
isRestricted,
} = getAllowedCatalogsForRestrictedRun({
restrictedRunsAllowed,
courseKey,
courseRunMetadata,
});
return !isRestricted || allowedCatalogs?.includes(catalogUuid);
}
12 changes: 6 additions & 6 deletions src/components/course/course-header/CourseRunCards.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
useEnterpriseCourseEnrollments,
useEnterpriseCustomerContainsContent,
useUserEntitlements,
isRunUnrestricted,
isRunUnrestrictedForCatalog,
} from '../../app/data';

/**
Expand All @@ -22,7 +22,7 @@ const CourseRunCards = () => {
const {
userSubsidyApplicableToCourse,
missingUserSubsidyReason,
applicableCatalogUuid,
catalogUuid,
} = useUserSubsidyApplicableToCourse();
const {
data: {
Expand All @@ -33,11 +33,11 @@ const CourseRunCards = () => {
const { data: courseMetadata } = useCourseMetadata();
const { data: { enterpriseCourseEnrollments } } = useEnterpriseCourseEnrollments();
const { data: userEntitlements } = useUserEntitlements();
const availableCourseRuns = courseMetadata.availableCourseRuns.filter(r => isRunUnrestricted({
const availableCourseRuns = courseMetadata.availableCourseRuns.filter(r => isRunUnrestrictedForCatalog({
restrictedRunsAllowed,
courseMetadata,
courseRunKey: r.key,
applicableCatalogUuid,
courseKey: courseMetadata.key,
courseRunMetadata: r,
catalogUuid,
}));

return (
Expand Down

0 comments on commit 3a10a7b

Please sign in to comment.