diff --git a/src/components/app/data/hooks/useCourseMetadata.js b/src/components/app/data/hooks/useCourseMetadata.js index c3c047756e..c323020fa3 100644 --- a/src/components/app/data/hooks/useCourseMetadata.js +++ b/src/components/app/data/hooks/useCourseMetadata.js @@ -6,6 +6,7 @@ import { determineAllocatedCourseRunAssignmentsForCourse, getAvailableCourseRuns, transformCourseMetadataByAllocatedCourseRunAssignments, + isRunUnrestrictedForCatalog, isRunUnrestrictedForCustomer, } from '../utils'; import useLateEnrollmentBufferDays from './useLateEnrollmentBufferDays'; @@ -16,7 +17,7 @@ import useEnterpriseCustomerContainsContent from './useEnterpriseCustomerContain * Retrieves the course metadata for the given enterprise customer and course key. * @returns {Types.UseQueryResult}} The query results for the course metadata. */ -export default function useCourseMetadata(queryOptions = {}) { +export default function useCourseMetadata(queryOptions = {}, catalogUuid = undefined) { const { select, ...queryOptionsRest } = queryOptions; const { courseKey } = useParams(); const [searchParams] = useSearchParams(); @@ -55,14 +56,26 @@ export default function useCourseMetadata(queryOptions = {}) { // 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, - })); + // Second stage filters out any *restricted* runs. + let restrictedRunFilter; + if (catalogUuid) { + // We have all the info we need to filter out restricted runs that are not redeemable via a specific catalog. + restrictedRunFilter = courseRunMetadata => isRunUnrestrictedForCatalog({ + restrictedRunsAllowed, + courseKey, + courseRunMetadata, + catalogUuid, + }); + } else { + // Fallback to only filtering out runs that are not available to the current customer under ANY catalog. The + // result may still include runs that are restricted for the subsidy types actually applicable for the learner. + restrictedRunFilter = courseRunMetadata => isRunUnrestrictedForCustomer({ + restrictedRunsAllowed, + courseKey, + courseRunMetadata, + }); + } + const availableAndUnrestrictedCourseRuns = basicAvailableCourseRuns.filter(restrictedRunFilter); let transformedData = { ...data, availableCourseRuns: availableAndUnrestrictedCourseRuns, diff --git a/src/components/app/data/services/course.js b/src/components/app/data/services/course.js index f917dd6e01..58917d9332 100644 --- a/src/components/app/data/services/course.js +++ b/src/components/app/data/services/course.js @@ -4,6 +4,7 @@ import { logError } from '@edx/frontend-platform/logging'; import { getErrorResponseStatusCode } from '../../../../utils/common'; import { findHighestLevelEntitlementSku, getActiveCourseRun } from '../utils'; +import { ENTERPRISE_RESTRICTION_TYPE } from '../../../../constants'; /** * TODO @@ -17,7 +18,7 @@ export async function fetchCourseMetadata(courseKey, courseRunKey) { const contentMetadataUrl = `${getConfig().DISCOVERY_API_BASE_URL}/api/v1/courses/${courseKey}/`; const queryParams = new URLSearchParams(); // Always include restricted/custom-b2b-enterprise runs in case one has been requested. - queryParams.append('include_restricted', 'custom-b2b-enterprise'); + queryParams.append('include_restricted', ENTERPRISE_RESTRICTION_TYPE); const url = `${contentMetadataUrl}?${queryParams.toString()}`; try { const response = await getAuthenticatedHttpClient().get(url); diff --git a/src/components/app/data/services/course.test.js b/src/components/app/data/services/course.test.js index 80e7cb22d2..8401378713 100644 --- a/src/components/app/data/services/course.test.js +++ b/src/components/app/data/services/course.test.js @@ -7,6 +7,7 @@ import { fetchCanRedeem, fetchCourseMetadata, fetchCourseRunMetadata } from './c import { findHighestLevelEntitlementSku, getActiveCourseRun } from '../utils'; import { getErrorResponseStatusCode } from '../../../../utils/common'; import { COURSE_MODES_MAP } from '../constants'; +import { ENTERPRISE_RESTRICTION_TYPE } from '../../../../constants'; const axiosMock = new MockAdapter(axios); getAuthenticatedHttpClient.mockReturnValue(axios); @@ -35,7 +36,7 @@ jest.mock('@edx/frontend-platform/auth', () => ({ })); describe('fetchCourseMetadata', () => { - const params = 'include_restricted=custom-b2b-enterprise'; + const params = `include_restricted=${ENTERPRISE_RESTRICTION_TYPE}`; const CONTENT_METADATA_URL = `${APP_CONFIG.DISCOVERY_API_BASE_URL}/api/v1/courses/${mockCourseKey}/?${params}`; const courseMetadata = { key: mockCourseKey, @@ -77,7 +78,7 @@ describe('fetchCourseMetadata', () => { }); describe('fetchCourseRunMetadata', () => { - const params = 'include_restricted=custom-b2b-enterprise'; + const params = `include_restricted=${ENTERPRISE_RESTRICTION_TYPE}`; const COURSE_RUN_METADATA = `${APP_CONFIG.DISCOVERY_API_BASE_URL}/api/v1/course_runs/${mockCourseRunKey}/?${params}`; const courseRunMetadata = { key: mockCourseRunKey, diff --git a/src/components/app/data/utils.js b/src/components/app/data/utils.js index 17ed2d80a6..60f59ab846 100644 --- a/src/components/app/data/utils.js +++ b/src/components/app/data/utils.js @@ -4,7 +4,7 @@ import { logError } from '@edx/frontend-platform/logging'; import { ASSIGNMENT_TYPES, POLICY_TYPES } from '../../enterprise-user-subsidy/enterprise-offers/data/constants'; import { LICENSE_STATUS } from '../../enterprise-user-subsidy/data/constants'; import { getBrandColorsFromCSSVariables, isDefinedAndNotNull, isTodayWithinDateThreshold } from '../../../utils/common'; -import { COURSE_STATUSES, SUBSIDY_TYPE } from '../../../constants'; +import { COURSE_STATUSES, SUBSIDY_TYPE, ENTERPRISE_RESTRICTION_TYPE } from '../../../constants'; import { LATE_ENROLLMENTS_BUFFER_DAYS } from '../../../config/constants'; import { COUPON_CODE_SUBSIDY_TYPE, @@ -848,7 +848,7 @@ export function getAllowedCatalogsForRestrictedRun({ courseKey, courseRunMetadata, }) { - if (courseRunMetadata?.restrictionType === 'custom-b2b-enterprise') { + if (courseRunMetadata?.restrictionType === ENTERPRISE_RESTRICTION_TYPE) { return { allowedCatalogs: restrictedRunsAllowed?.[courseKey]?.[courseRunMetadata.key]?.catalogUuids, isRestricted: true, diff --git a/src/components/course/course-header/CourseRunCards.jsx b/src/components/course/course-header/CourseRunCards.jsx index 9dc3484749..c7fa7df6f1 100644 --- a/src/components/course/course-header/CourseRunCards.jsx +++ b/src/components/course/course-header/CourseRunCards.jsx @@ -10,7 +10,6 @@ import { useEnterpriseCourseEnrollments, useEnterpriseCustomerContainsContent, useUserEntitlements, - isRunUnrestrictedForCatalog, } from '../../app/data'; /** @@ -24,28 +23,17 @@ const CourseRunCards = () => { missingUserSubsidyReason, catalogUuid, } = useUserSubsidyApplicableToCourse(); - const { - data: { - catalogList, - restrictedRunsAllowed, - }, - } = useEnterpriseCustomerContainsContent([courseKey]); - const { data: courseMetadata } = useCourseMetadata(); + const { data: courseMetadata } = useCourseMetadata({}, catalogUuid); + const { data: { catalogList } } = useEnterpriseCustomerContainsContent([courseKey]); const { data: { enterpriseCourseEnrollments } } = useEnterpriseCourseEnrollments(); const { data: userEntitlements } = useUserEntitlements(); - const availableCourseRuns = courseMetadata.availableCourseRuns.filter(r => isRunUnrestrictedForCatalog({ - restrictedRunsAllowed, - courseKey: courseMetadata.key, - courseRunMetadata: r, - catalogUuid, - })); return ( - {availableCourseRuns.map((courseRun) => { + {courseMetadata.availableCourseRuns.map((courseRun) => { const hasRedeemablePolicy = userSubsidyApplicableToCourse?.subsidyType === LEARNER_CREDIT_SUBSIDY_TYPE; // Render the newer `CourseRunCard` component when the user's subsidy, if any, is diff --git a/src/constants/course.js b/src/constants/course.js index bde5d42885..e693f0aea5 100644 --- a/src/constants/course.js +++ b/src/constants/course.js @@ -14,3 +14,8 @@ export const COURSE_PACING = { INSTRUCTOR: 'instructor', SELF: 'self', }; + +// [ENT-9360] Restricted runs feature. Pass this slug to discovery API endpoints (param key is "include_restricted") to +// retrieve restricted runs. Find the same restriction type encoded in course run metadata under the `restriction_type` +// metadata key. +export const ENTERPRISE_RESTRICTION_TYPE = 'custom-b2b-enterprise';