From fe79b4e8515cd28d30c9460503689458773fb606 Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Thu, 26 Sep 2024 12:51:39 -0400 Subject: [PATCH] feat: read from normalized metadata content_price in course cards --- .env.development | 6 +++-- package.json | 3 ++- .../CatalogSearchResults.jsx | 21 ++++++----------- .../CatalogSearchResults.test.jsx | 9 ++++++++ src/components/courseCard/CourseCard.jsx | 23 ++++++------------- src/components/courseCard/CourseCard.test.jsx | 6 +++++ src/utils/algoliaUtils.js | 13 ++++++----- src/utils/common.js | 10 ++++++++ 8 files changed, 52 insertions(+), 39 deletions(-) diff --git a/.env.development b/.env.development index ccee18b3..104e2332 100644 --- a/.env.development +++ b/.env.development @@ -25,8 +25,10 @@ ALGOLIA_SEARCH_API_KEY='' ALGOLIA_INDEX_NAME='' AUTHN_MINIMAL_HEADER=true MINIMAL_HEADER=true -EDX_FOR_SUBSCRIPTION_TITLE='' -EDX_ENTERPRISE_ALACARTE_TITLE='' +EDX_FOR_BUSINESS_TITLE:'Business' +EDX_FOR_SUBSCRIPTION_TITLE:'Subscription' +EDX_ENTERPRISE_ALACARTE_TITLE:'A la carte' +EDX_FOR_ONLINE_EDU_TITLE:'Education' FEATURE_LANGUAGE_FACET=true HOTJAR_APP_ID='' HOTJAR_VERSION=6 diff --git a/package.json b/package.json index c2184eae..fceb9725 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "install-theme": "npm install \"@edx/brand@${THEME}\" --no-save", "start": "fedx-scripts webpack-dev-server --progress", "start:with-theme": "THEME=npm:@edx/brand-edx.org@latest npm run install-theme && npm run start", - "test": "fedx-scripts jest --transformIgnorePatterns \"node_modules/(?!@edx/frontend-app-enterprise-public-catalog)/\" --env=jsdom --coverage --passWithNoTests" + "test": "fedx-scripts jest --transformIgnorePatterns \"node_modules/(?!@edx/frontend-app-enterprise-public-catalog)/\" --env=jsdom --coverage --passWithNoTests", + "test:watch": "npm run test -- --watch" }, "husky": { "hooks": { diff --git a/src/components/catalogSearchResults/CatalogSearchResults.jsx b/src/components/catalogSearchResults/CatalogSearchResults.jsx index 987f3657..116cd840 100644 --- a/src/components/catalogSearchResults/CatalogSearchResults.jsx +++ b/src/components/catalogSearchResults/CatalogSearchResults.jsx @@ -55,7 +55,7 @@ import DownloadCsvButton from './associatedComponents/downloadCsvButton/Download import messages from './CatalogSearchResults.messages'; import CatalogNoResultsDeck from '../catalogNoResultsDeck/CatalogNoResultsDeck'; -import { formatDate, makePlural, getSelectedCatalogFromURL } from '../../utils/common'; +import { formatDate, makePlural, getSelectedCatalogFromURL, formatPrice } from '../../utils/common'; export const ERROR_MESSAGE = 'An error occured while retrieving data'; @@ -184,11 +184,8 @@ export const BaseCatalogSearchResults = ({ }; const renderCardComponent = (props) => { - if (contentType === CONTENT_TYPE_COURSE) { - return ; - } - if (contentType === EXEC_ED_TITLE) { - return ; + if ([CONTENT_TYPE_COURSE, EXEC_ED_TITLE].includes(contentType)) { + return ; } return ; }; @@ -225,10 +222,8 @@ export const BaseCatalogSearchResults = ({ }, { Header: TABLE_HEADERS.price, - accessor: 'first_enrollable_paid_seat_price', - Cell: ({ row }) => (row.values.first_enrollable_paid_seat_price - ? `$${row.values.first_enrollable_paid_seat_price}` - : null), + accessor: 'normalized_metadata', + Cell: ({ row }) => formatPrice(row.values.normalized_metadata.content_price), }, { Header: TABLE_HEADERS.catalogs, @@ -252,10 +247,8 @@ export const BaseCatalogSearchResults = ({ }, { Header: TABLE_HEADERS.price, - accessor: 'entitlements', - Cell: ({ row }) => (row.values.entitlements[0].price - ? `$${Math.trunc(row.values.entitlements[0].price)}` - : null), + accessor: 'normalized_metadata', + Cell: ({ row }) => formatPrice(row.values.normalized_metadata.content_price), }, { Header: TABLE_HEADERS.catalogs, diff --git a/src/components/catalogSearchResults/CatalogSearchResults.test.jsx b/src/components/catalogSearchResults/CatalogSearchResults.test.jsx index 6737090b..7bd29b9a 100644 --- a/src/components/catalogSearchResults/CatalogSearchResults.test.jsx +++ b/src/components/catalogSearchResults/CatalogSearchResults.test.jsx @@ -89,6 +89,9 @@ const searchResults = { upgrade_deadline: 1892678399, pacing_type: 'self_paced', }, + normalized_metadata: { + content_price: 100, + }, }, { title: TEST_COURSE_NAME_2, @@ -105,6 +108,9 @@ const searchResults = { upgrade_deadline: 1892678399, pacing_type: 'self_paced', }, + normalized_metadata: { + content_price: 99, + }, }, ], page: 1, @@ -159,6 +165,9 @@ const searchResultsExecEd = { start_date: '2020-01-24T05:00:00Z', end_date: '2080-01-01T17:00:00Z', }, + normalized_metadata: { + content_price: 100, + }, }, ], page: 1, diff --git a/src/components/courseCard/CourseCard.jsx b/src/components/courseCard/CourseCard.jsx index 386276f1..521adb4c 100644 --- a/src/components/courseCard/CourseCard.jsx +++ b/src/components/courseCard/CourseCard.jsx @@ -6,32 +6,21 @@ import PropTypes from 'prop-types'; import { Badge, Card } from '@openedx/paragon'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import messages from './CourseCard.messages'; -import { CONTENT_TYPE_COURSE } from '../../constants'; import defaultCardHeader from '../../static/default-card-header-light.png'; +import { formatPrice } from '../../utils/common'; const CourseCard = ({ - intl, onClick, original, learningType, + intl, onClick, original, }) => { const { title, card_image_url, partners, - first_enrollable_paid_seat_price, + normalized_metadata, enterprise_catalog_query_titles, - entitlements, advertised_course_run, } = original; - let rowPrice; - let priceText; - - if (learningType === CONTENT_TYPE_COURSE) { - rowPrice = first_enrollable_paid_seat_price; - priceText = rowPrice != null ? `$${rowPrice.toString()}` : 'N/A'; - } else { - [rowPrice] = entitlements || [null]; - priceText = rowPrice != null ? `$${Math.trunc(rowPrice.price)?.toString()}` : 'N/A'; - } - + const priceText = formatPrice(normalized_metadata.content_price); let pacingType = 'NA'; if (advertised_course_run) { pacingType = advertised_course_run.pacing_type === 'self_paced' ? 'Self paced' : 'Instructor led'; @@ -90,7 +79,6 @@ CourseCard.defaultProps = { CourseCard.propTypes = { intl: intlShape.isRequired, onClick: PropTypes.func, - learningType: PropTypes.string.isRequired, original: PropTypes.shape({ title: PropTypes.string, card_image_url: PropTypes.string, @@ -102,6 +90,9 @@ CourseCard.propTypes = { logo_image_url: PropTypes.string, }), ), + normalized_metadata: PropTypes.shape({ + content_price: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + }), first_enrollable_paid_seat_price: PropTypes.number, enterprise_catalog_query_titles: PropTypes.arrayOf(PropTypes.string), original_image_url: PropTypes.string, diff --git a/src/components/courseCard/CourseCard.test.jsx b/src/components/courseCard/CourseCard.test.jsx index b9655b9e..1646d39d 100644 --- a/src/components/courseCard/CourseCard.test.jsx +++ b/src/components/courseCard/CourseCard.test.jsx @@ -21,6 +21,9 @@ const originalData = { original_image_url: '', enterprise_catalog_query_titles: TEST_CATALOG, advertised_course_run: { pacing_type: 'self_paced' }, + normalized_metadata: { + content_price: 100, + }, }; const defaultProps = { @@ -37,6 +40,9 @@ const execEdData = { enterprise_catalog_query_titles: TEST_CATALOG, advertised_course_run: { pacing_type: 'instructor_paced' }, entitlements: [{ price: '999.00' }], + normalized_metadata: { + content_price: 999, + }, }; const execEdProps = { diff --git a/src/utils/algoliaUtils.js b/src/utils/algoliaUtils.js index 3475c3ad..3ba98f75 100644 --- a/src/utils/algoliaUtils.js +++ b/src/utils/algoliaUtils.js @@ -1,4 +1,5 @@ import { CONTENT_TYPE_COURSE, CONTENT_TYPE_PROGRAM } from '../constants'; +import { formatPrice } from './common'; const extractUuid = (aggregationKey) => aggregationKey.split(':')[1]; @@ -9,7 +10,7 @@ function mapAlgoliaObjectToCourse(algoliaCourseObject, intl, messages) { const { title: courseTitle, partners, - first_enrollable_paid_seat_price: coursePrice, // todo + normalized_metadata: normalizedMetadata, enterprise_catalog_query_titles: courseAssociatedCatalogs, full_description: courseDescription, original_image_url: bannerImageUrl, @@ -19,8 +20,8 @@ function mapAlgoliaObjectToCourse(algoliaCourseObject, intl, messages) { skill_names: skillNames, } = algoliaCourseObject; const { start: startDate, end: endDate } = courseRun; - const priceText = coursePrice != null - ? `$${coursePrice.toString()}` + const priceText = normalizedMetadata.content_price != null + ? formatPrice(normalizedMetadata.content_price) : intl.formatMessage( messages['catalogSearchResult.table.priceNotAvailable'], ); @@ -48,7 +49,7 @@ function mapAlgoliaObjectToExecEd(algoliaCourseObject, intl, messages) { const { title: courseTitle, partners, - entitlements: coursePrice, // todo + normalized_metadata: normalizedMetadata, enterprise_catalog_query_titles: courseAssociatedCatalogs, full_description: courseDescription, original_image_url: bannerImageUrl, @@ -58,8 +59,8 @@ function mapAlgoliaObjectToExecEd(algoliaCourseObject, intl, messages) { additional_metadata: additionalMetadata, } = algoliaCourseObject; const { start_date: startDate, end_date: endDate } = additionalMetadata; - const priceText = coursePrice != null - ? `$${coursePrice[0].price.toString()}` + const priceText = normalizedMetadata.content_price != null + ? formatPrice(normalizedMetadata.content_price) : intl.formatMessage( messages['catalogSearchResult.table.priceNotAvailable'], ); diff --git a/src/utils/common.js b/src/utils/common.js index d6157d12..9bf21f0b 100644 --- a/src/utils/common.js +++ b/src/utils/common.js @@ -46,3 +46,13 @@ export function getCourses(numCourses, string) { export function hasNonEmptyValues(data) { return Object.values(data).some(item => Array.isArray(item) && item.length > 0); } + +export const formatPrice = (price, options = {}) => { + const USDollar = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 0, + ...options, + }); + return USDollar.format(Math.abs(price)); +};