From ab2a50c6401131b7b5aeb16c6a9bc635583fbdf6 Mon Sep 17 00:00:00 2001 From: Troy Sankey Date: Mon, 25 Nov 2024 21:59:01 -0800 Subject: [PATCH] feat: enable allocation of restricted runs ENT-9411 --- .../cards/BaseCourseCard.jsx | 8 +++- .../cards/data/useCourseCardMetadata.jsx | 11 ++++++ .../data/constants.js | 6 +++ .../data/hooks/index.js | 1 + ...alogContainsContentItemsMultipleQueries.js | 37 +++++++++++++++++++ .../services/EnterpriseCatalogApiServiceV2.js | 34 +++++++++++++++++ 6 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 src/components/learner-credit-management/data/hooks/useCatalogContainsContentItemsMultipleQueries.js create mode 100644 src/data/services/EnterpriseCatalogApiServiceV2.js diff --git a/src/components/learner-credit-management/cards/BaseCourseCard.jsx b/src/components/learner-credit-management/cards/BaseCourseCard.jsx index a9545d8e82..c01c255914 100644 --- a/src/components/learner-credit-management/cards/BaseCourseCard.jsx +++ b/src/components/learner-credit-management/cards/BaseCourseCard.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { - Badge, breakpoints, Card, Stack, useMediaQuery, + Badge, breakpoints, Card, Skeleton, Stack, useMediaQuery, } from '@openedx/paragon'; import { camelCaseObject } from '@edx/frontend-platform/utils'; @@ -35,7 +35,11 @@ const BaseCourseCard = ({ formattedPrice, isExecEdCourseType, footerText, + isLoadingRestrictedRuns, } = courseCardMetadata; + const runPrice = formatPrice(courseRun.contentPrice); + const coursePrice = isLoadingRestrictedRuns ? : formattedPrice; + const cardPrice = courseRun ? runPrice : coursePrice; return ( -
{courseRun ? formatPrice(courseRun.contentPrice) : formattedPrice}
+
{cardPrice}
run.restrictionType === 'custom-e2e-enterprise'), // TODO: replace with constant + ); + const assignableCourseRuns = getAssignableCourseRuns({ courseRuns, subsidyExpirationDatetime: subsidyAccessPolicy.subsidyExpirationDatetime, isLateRedemptionAllowed: subsidyAccessPolicy.isLateRedemptionAllowed, + catalogContainsRestrictedRunsData, }); // Extracts the content price from assignable course runs @@ -98,6 +108,7 @@ const useCourseCardMetadata = ({ linkToCourse, isExecEdCourseType, footerText, + isLoading: isLoadingcatalogContainsRestrictedRuns, }; }; diff --git a/src/components/learner-credit-management/data/constants.js b/src/components/learner-credit-management/data/constants.js index abc7e43c4a..24126510fb 100644 --- a/src/components/learner-credit-management/data/constants.js +++ b/src/components/learner-credit-management/data/constants.js @@ -125,6 +125,12 @@ export const learnerCreditManagementQueryKeys = { budgetGroupLearners: (budgetId) => [...learnerCreditManagementQueryKeys.budget(budgetId), 'group learners'], enterpriseCustomer: (enterpriseId) => [...learnerCreditManagementQueryKeys.all, 'enterpriseCustomer', enterpriseId], flexGroup: (enterpriseId) => [...learnerCreditManagementQueryKeys.enterpriseCustomer(enterpriseId), 'flexGroup'], + catalog: (catalog) => [...learnerCreditManagementQueryKeys.all, 'catalog', catalog], + catalogContainsContentItem: (catalogUuid, contentKey) => [ + ...learnerCreditManagementQueryKeys.catalog(catalogUuid), + 'containsContentItem', + contentKey, + ], }; // Route to learner credit diff --git a/src/components/learner-credit-management/data/hooks/index.js b/src/components/learner-credit-management/data/hooks/index.js index 6f1842953f..93413391be 100644 --- a/src/components/learner-credit-management/data/hooks/index.js +++ b/src/components/learner-credit-management/data/hooks/index.js @@ -23,3 +23,4 @@ export { default as useContentMetadata } from './useContentMetadata'; export { default as useEnterpriseRemovedGroupMembers } from './useEnterpriseRemovedGroupMembers'; export { default as useEnterpriseFlexGroups } from './useEnterpriseFlexGroups'; export { default as useGroupDropdownToggle } from './useGroupDropdownToggle'; +export { default as useCatalogContainsContentItemsMultipleQueries } from './useCatalogContainsContentItemsMultipleQueries'; diff --git a/src/components/learner-credit-management/data/hooks/useCatalogContainsContentItemsMultipleQueries.js b/src/components/learner-credit-management/data/hooks/useCatalogContainsContentItemsMultipleQueries.js new file mode 100644 index 0000000000..321ffe4c92 --- /dev/null +++ b/src/components/learner-credit-management/data/hooks/useCatalogContainsContentItemsMultipleQueries.js @@ -0,0 +1,37 @@ +import { useQueries } from '@tanstack/react-query'; +import { camelCaseObject } from '@edx/frontend-platform/utils'; + +import EnterpriseCatalogApiServiceV2 from '../../../../data/services/EnterpriseCatalogApiServiceV2'; +import { learnerCreditManagementQueryKeys } from '../constants'; + +/** + * Retrieves a response from the following enterprise-catalog endpoint for a SINGLE content key: + * + * /api/v2/enterprise-catalogs/{uuid}/contains_content_items/?course_run_ids={content_key} + * + * @param {*} queryKey The queryKey from the associated `useQuery` call. + * @returns The contains_content_items response. + */ +const getCatalogContainsContentItem = async ({ queryKey }) => { + const catalogUuid = queryKey[2]; + const contentKey = queryKey[4]; + const response = await EnterpriseCatalogApiServiceV2.retrieveContainsContentItems(catalogUuid, contentKey); + return camelCaseObject(response.data); +}; + +const useCatalogContainsContentItemsMultipleQueries = (catalogUuid, contentKeys, { queryOptions } = {}) => { + const multipleResults = useQueries({ + queries: contentKeys.map((contentKey) => ({ + queryKey: learnerCreditManagementQueryKeys.catalogContainsContentItem(catalogUuid, contentKey), + queryFn: getCatalogContainsContentItem, + ...queryOptions, + })), + }); + return { + data: multipleResults, + // If at least one query is still loading, then this whole hook is considered to be still loading. + isLoading: multipleResults.some(result => result.isLoading), + }; +}; + +export default useCatalogContainsContentItemsMultipleQueries; diff --git a/src/data/services/EnterpriseCatalogApiServiceV2.js b/src/data/services/EnterpriseCatalogApiServiceV2.js new file mode 100644 index 0000000000..ae7199355d --- /dev/null +++ b/src/data/services/EnterpriseCatalogApiServiceV2.js @@ -0,0 +1,34 @@ +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; + +import { configuration } from '../../config'; + +class EnterpriseCatalogApiServiceV2 { + static baseUrl = `${configuration.ENTERPRISE_CATALOG_BASE_URL}/api/v2`; + + static apiClient = getAuthenticatedHttpClient; + + static enterpriseCatalogsUrl = `${EnterpriseCatalogApiServiceV2.baseUrl}/enterprise-catalogs/`; + + /** + * Retrieves the enterprise-catalog based contains_content_items endpoint for + * ONE content key: + * + * /api/v2/enterprise-catalogs/{uuid}/contains_content_items/?course_run_ids={content_key} + * + * This endpoint technically supports an arbitrary number of content keys, + * but this function only supports one. + * + * @param {*} catalogUuid The catalog to check for content inclusion. + * @param {*} contentKey The content to check for inclusion in the requested catalog. + */ + static retrieveContainsContentItems(catalogUuid, contentKey) { + const queryParams = new URLSearchParams(); + queryParams.append('course_run_ids', contentKey); + const baseCatalogUrl = `${EnterpriseCatalogApiServiceV2.enterpriseCatalogsUrl}${catalogUuid}`; + return EnterpriseCatalogApiServiceV2.apiClient().get( + `${baseCatalogUrl}/contains_content_items/?${queryParams.toString()}`, + ); + } +} + +export default EnterpriseCatalogApiServiceV2;