diff --git a/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.jsx b/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.jsx index 6a221cf1..50883fb9 100644 --- a/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.jsx +++ b/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.jsx @@ -7,7 +7,8 @@ import PropTypes from 'prop-types'; import { CONTENT_TYPE_COURSE, CONTENT_TYPE_PROGRAM, - EXECUTIVE_EDUCATION_2U_COURSE_TYPE, + EXEC_ED_TITLE, + LEARNING_TYPE_REFINEMENT, NO_RESULTS_DECK_ITEM_COUNT, NO_RESULTS_PAGE_SIZE, NO_RESULTS_PAGE_ITEM_COUNT, @@ -41,7 +42,7 @@ function CatalogNoResultsDeck({ useEffect(() => { const defaultCoursesRefinements = { enterprise_catalog_query_titles: selectedCatalog, - learning_type: contentType, + [LEARNING_TYPE_REFINEMENT]: contentType, }; EnterpriseCatalogApiService.fetchDefaultCoursesInCatalogWithFacets( @@ -66,7 +67,7 @@ function CatalogNoResultsDeck({ defaultDeckTitle = intl.formatMessage( messages['catalogSearchResults.DefaultCourseDeckTitle'], ); - } else if (contentType === EXECUTIVE_EDUCATION_2U_COURSE_TYPE) { + } else if (contentType === EXEC_ED_TITLE) { alertText = intl.formatMessage( messages['catalogSearchResults.NoResultsExecEdCourseBannerText'], ); diff --git a/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.test.jsx b/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.test.jsx index 1326c8e5..cc1d656d 100644 --- a/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.test.jsx +++ b/src/components/catalogNoResultsDeck/CatalogNoResultsDeck.test.jsx @@ -60,7 +60,7 @@ const execEdProps = { setCardView: jest.fn(), columns: [], renderCardComponent: jest.fn(), - contentType: 'executive-education-2u', + contentType: 'Executive Education', }; describe('catalog no results deck works as expected', () => { diff --git a/src/components/catalogPage/CatalogPage.jsx b/src/components/catalogPage/CatalogPage.jsx index 261ce494..6bf63f9b 100644 --- a/src/components/catalogPage/CatalogPage.jsx +++ b/src/components/catalogPage/CatalogPage.jsx @@ -17,20 +17,20 @@ import CatalogSelectionDeck from '../catalogSelectionDeck/CatalogSelectionDeck'; import { AVAILABILITY_REFINEMENT, AVAILABILITY_REFINEMENT_DEFAULTS, - EXECUTIVE_EDUCATION_2U_COURSE_TYPE, + EXEC_ED_TITLE, + LEARNING_TYPE_REFINEMENT, QUERY_TITLE_REFINEMENT, HIDE_CARDS_REFINEMENT, TRACKING_APP_NAME, } from '../../constants'; -const LEARNING_TYPE_REFINEMENT = 'learning_type'; const learningType = { - attribute: 'learning_type', + attribute: LEARNING_TYPE_REFINEMENT, title: 'Learning Type', }; // Add learning_type to the search facet filters if it doesn't exist in the list yet. if ( - !SEARCH_FACET_FILTERS.some((filter) => filter.attribute === 'learning_type') + !SEARCH_FACET_FILTERS.some((filter) => filter.attribute === LEARNING_TYPE_REFINEMENT) ) { SEARCH_FACET_FILTERS.push(learningType); } @@ -63,7 +63,7 @@ function CatalogPage({ intl }) { === config.EDX_ENTERPRISE_ALACARTE_TITLE )) && loadedSearchParams.get(LEARNING_TYPE_REFINEMENT) - === EXECUTIVE_EDUCATION_2U_COURSE_TYPE + === EXEC_ED_TITLE ) { const loadedLearningTypes = loadedSearchParams.getAll( LEARNING_TYPE_REFINEMENT, @@ -71,7 +71,7 @@ function CatalogPage({ intl }) { if (loadedLearningTypes.length) { loadedSearchParams.delete(LEARNING_TYPE_REFINEMENT); loadedLearningTypes.forEach((type) => { - if (type !== EXECUTIVE_EDUCATION_2U_COURSE_TYPE) { + if (type !== EXEC_ED_TITLE) { loadedSearchParams.append(LEARNING_TYPE_REFINEMENT, type); } }); @@ -84,7 +84,6 @@ function CatalogPage({ intl }) { // the `a la carte` catalog if ( config.EDX_ENTERPRISE_ALACARTE_TITLE - && !loadedSearchParams.get(LEARNING_TYPE_REFINEMENT) && !loadedSearchParams.get(QUERY_TITLE_REFINEMENT) ) { loadedSearchParams.set( @@ -97,7 +96,6 @@ function CatalogPage({ intl }) { // Ensure we have availability refinement(s) set by default if ( !loadedSearchParams.get(AVAILABILITY_REFINEMENT) - && !loadedSearchParams.get(LEARNING_TYPE_REFINEMENT) ) { AVAILABILITY_REFINEMENT_DEFAULTS.map((a) => loadedSearchParams.append(AVAILABILITY_REFINEMENT, a)); reloadPage = true; diff --git a/src/components/catalogPage/CatalogPage.test.jsx b/src/components/catalogPage/CatalogPage.test.jsx index 8d25ba46..875ed942 100644 --- a/src/components/catalogPage/CatalogPage.test.jsx +++ b/src/components/catalogPage/CatalogPage.test.jsx @@ -4,6 +4,7 @@ import React from 'react'; import { mockWindowLocations, renderWithRouter } from '../tests/testUtils'; import CatalogPage from './CatalogPage'; import selectionCardMessage from '../catalogSelectionDeck/CatalogSelectionDeck.messages'; +import { LEARNING_TYPE_REFINEMENT } from '../../constants'; // all we are testing is routes, we don't need InstantSearch to work here jest.mock('react-instantsearch-dom', () => ({ @@ -72,17 +73,17 @@ describe('CatalogPage', () => { it('accounts for exec ed disclusion when not a la carte is selected', () => { const location = { ...window.location, - search: '?learning_type=executive-education-2u&learning_type=ayylmao&enterprise_catalog_query_titles=foobar', + search: `?${LEARNING_TYPE_REFINEMENT}=Executive Education&${LEARNING_TYPE_REFINEMENT}=ayylmao&enterprise_catalog_query_titles=foobar`, }; Object.defineProperty(window, 'location', { writable: true, value: location, }); - expect(window.location.search).toEqual('?learning_type=executive-education-2u&learning_type=ayylmao&enterprise_catalog_query_titles=foobar'); + expect(window.location.search).toEqual(`?${LEARNING_TYPE_REFINEMENT}=Executive Education&${LEARNING_TYPE_REFINEMENT}=ayylmao&enterprise_catalog_query_titles=foobar`); renderWithRouter(); // Assert learning type: exec ed has been removed but not learning type `ayylmao` expect(window.location.search).toEqual( - 'enterprise_catalog_query_titles=foobar&learning_type=ayylmao', + `enterprise_catalog_query_titles=foobar&${LEARNING_TYPE_REFINEMENT}=ayylmao&availability=Available+Now&availability=Starting+Soon&availability=Upcoming`, ); }); }); diff --git a/src/components/catalogSearchResults/CatalogSearchResults.jsx b/src/components/catalogSearchResults/CatalogSearchResults.jsx index 0c6789cb..80a6f2e6 100644 --- a/src/components/catalogSearchResults/CatalogSearchResults.jsx +++ b/src/components/catalogSearchResults/CatalogSearchResults.jsx @@ -29,7 +29,6 @@ import { COURSE_TITLE, EDX_COURSE_TITLE_DESC, EXEC_ED_TITLE, - EXECUTIVE_EDUCATION_2U_COURSE_TYPE, HIDE_PRICE_REFINEMENT, LEARNING_TYPE_REFINEMENT, PROGRAM_TITLE, @@ -91,7 +90,7 @@ export function BaseCatalogSearchResults({ useEffect(() => { setIsProgramType(contentType === CONTENT_TYPE_PROGRAM); setIsCourseType(contentType === CONTENT_TYPE_COURSE); - setIsExecEdType(contentType === EXECUTIVE_EDUCATION_2U_COURSE_TYPE); + setIsExecEdType(contentType === EXEC_ED_TITLE); }, [contentType]); const TABLE_HEADERS = useMemo( @@ -168,7 +167,7 @@ export function BaseCatalogSearchResults({ } else if (isExecEdType) { dispatch( setRefinementAction(LEARNING_TYPE_REFINEMENT, [ - EXECUTIVE_EDUCATION_2U_COURSE_TYPE, + EXEC_ED_TITLE, ]), ); } else { @@ -182,7 +181,7 @@ export function BaseCatalogSearchResults({ if (contentType === CONTENT_TYPE_COURSE) { return ; } - if (contentType === EXECUTIVE_EDUCATION_2U_COURSE_TYPE) { + if (contentType === EXEC_ED_TITLE) { return ; } return ; diff --git a/src/components/catalogSearchResults/CatalogSearchResults.test.jsx b/src/components/catalogSearchResults/CatalogSearchResults.test.jsx index 2dac0057..1f2c7ddb 100644 --- a/src/components/catalogSearchResults/CatalogSearchResults.test.jsx +++ b/src/components/catalogSearchResults/CatalogSearchResults.test.jsx @@ -15,7 +15,7 @@ import messages from './CatalogSearchResults.messages'; import { CONTENT_TYPE_COURSE, CONTENT_TYPE_PROGRAM, - EXECUTIVE_EDUCATION_2U_COURSE_TYPE, + EXEC_ED_TITLE, HIDE_PRICE_REFINEMENT, } from '../../constants'; import EnterpriseCatalogApiService from '../../data/services/EnterpriseCatalogAPIService'; @@ -150,7 +150,7 @@ const searchResultsExecEd = { card_image_url: 'http://url.test2.location', availability: ['Available Now'], course_keys: [], - content_type: EXECUTIVE_EDUCATION_2U_COURSE_TYPE, + content_type: EXEC_ED_TITLE, entitlements: [{ price: '100.00' }], advertised_course_run: { start: '2020-01-24T05:00:00Z', @@ -214,7 +214,7 @@ const execEdProps = { isSearchStalled: false, searchState: { page: 1 }, error: null, - contentType: EXECUTIVE_EDUCATION_2U_COURSE_TYPE, + contentType: EXEC_ED_TITLE, // mock i18n requirements intl: { formatMessage: (header) => header.defaultMessage, diff --git a/src/components/catalogs/CatalogSearch.jsx b/src/components/catalogs/CatalogSearch.jsx index 10109ffa..60d39f12 100644 --- a/src/components/catalogs/CatalogSearch.jsx +++ b/src/components/catalogs/CatalogSearch.jsx @@ -20,7 +20,8 @@ import PageWrapper from '../PageWrapper'; import { CONTENT_TYPE_COURSE, CONTENT_TYPE_PROGRAM, - EXECUTIVE_EDUCATION_2U_COURSE_TYPE, + EXEC_ED_TITLE, + LEARNING_TYPE_REFINEMENT, NUM_RESULTS_COURSE, NUM_RESULTS_PROGRAM, NUM_RESULTS_PER_PAGE, @@ -32,19 +33,20 @@ import { mapAlgoliaObjectToCourse, mapAlgoliaObjectToExecEd, } from '../../utils/algoliaUtils'; +import { convertLearningTypesToFilters } from '../../utils/catalogUtils'; import messages from '../catalogSearchResults/CatalogSearchResults.messages'; function CatalogSearch(intl) { const { refinements: { - learning_type: learningType, + [LEARNING_TYPE_REFINEMENT]: learningType, enterprise_catalog_query_titles: enterpriseCatalogQueryTitles, }, } = useContext(SearchContext); const { algoliaIndexName, searchClient } = useAlgoliaIndex(); - const courseFilter = `learning_type:${CONTENT_TYPE_COURSE}`; - const execEdFilter = `learning_type:${EXECUTIVE_EDUCATION_2U_COURSE_TYPE}`; - const programFilter = `learning_type:${CONTENT_TYPE_PROGRAM}`; + const courseFilter = `${LEARNING_TYPE_REFINEMENT}:${CONTENT_TYPE_COURSE}`; + const execEdFilter = `${LEARNING_TYPE_REFINEMENT}:"${EXEC_ED_TITLE}"`; + const programFilter = `${LEARNING_TYPE_REFINEMENT}:${CONTENT_TYPE_PROGRAM}`; const [noCourseResults, setNoCourseResults] = useState(false); const [noProgramResults, setNoProgramResults] = useState(false); const [noExecEdResults, setNoExecEdResults] = useState(false); @@ -67,7 +69,7 @@ function CatalogSearch(intl) { setNoResults: setNoCourseResults, numResults: NUM_RESULTS_COURSE, }, - [EXECUTIVE_EDUCATION_2U_COURSE_TYPE]: { + [EXEC_ED_TITLE]: { filter: execEdFilter, noResults: noExecEdResults, setNoResults: setNoExecEdResults, @@ -93,7 +95,7 @@ function CatalogSearch(intl) { useEffect(() => { contentData[CONTENT_TYPE_COURSE].noResults = noCourseResults; contentData[CONTENT_TYPE_PROGRAM].noResults = noProgramResults; - contentData[EXECUTIVE_EDUCATION_2U_COURSE_TYPE].noResults = noExecEdResults; + contentData[EXEC_ED_TITLE].noResults = noExecEdResults; }, [noCourseResults, noProgramResults, noExecEdResults, contentData]); // set specified content types & suggested search content types @@ -103,7 +105,7 @@ function CatalogSearch(intl) { setSpecifiedContentType(learningType); } setSuggestedSearchContentTypeFilter( - learningType.map((item) => `learning_type:${item}`).join(' OR '), + convertLearningTypesToFilters(learningType), ); } else { setSpecifiedContentType(undefined); @@ -111,9 +113,9 @@ function CatalogSearch(intl) { [ CONTENT_TYPE_COURSE, CONTENT_TYPE_PROGRAM, - EXECUTIVE_EDUCATION_2U_COURSE_TYPE, + `"${EXEC_ED_TITLE}"`, ] - .map((item) => `learning_type:${item}`) + .map((item) => `${LEARNING_TYPE_REFINEMENT}:${item}`) .join(' OR '), ); } @@ -126,12 +128,12 @@ function CatalogSearch(intl) { }, [config.ALGOLIA_INDEX_NAME, searchClient]); const suggestedCourseOnClick = (hit) => { - if (hit.learning_type === CONTENT_TYPE_PROGRAM) { + if (hit[LEARNING_TYPE_REFINEMENT] === CONTENT_TYPE_PROGRAM) { setSelectedSuggestedCourse(mapAlgoliaObjectToProgram(hit)); setSelectedSuggestedCourseType(CONTENT_TYPE_PROGRAM); - } else if (hit.learning_type === EXECUTIVE_EDUCATION_2U_COURSE_TYPE) { + } else if (hit[LEARNING_TYPE_REFINEMENT] === EXEC_ED_TITLE) { setSelectedSuggestedCourse(mapAlgoliaObjectToExecEd(hit)); - setSelectedSuggestedCourseType(EXECUTIVE_EDUCATION_2U_COURSE_TYPE); + setSelectedSuggestedCourseType(EXEC_ED_TITLE); } else { setSelectedSuggestedCourse(mapAlgoliaObjectToCourse(hit, intl, messages)); setSelectedSuggestedCourseType(CONTENT_TYPE_COURSE); @@ -144,7 +146,7 @@ function CatalogSearch(intl) { useEffect(() => { const defaultTypes = [ CONTENT_TYPE_COURSE, - EXECUTIVE_EDUCATION_2U_COURSE_TYPE, + EXEC_ED_TITLE, CONTENT_TYPE_PROGRAM, ]; // Grab content type(s) to use @@ -159,9 +161,9 @@ function CatalogSearch(intl) { config.EDX_ENTERPRISE_ALACARTE_TITLE, )) ) { - if (contentToDisplay.indexOf(EXECUTIVE_EDUCATION_2U_COURSE_TYPE) > 0) { + if (contentToDisplay.indexOf(EXEC_ED_TITLE) > 0) { contentToDisplay.splice( - contentToDisplay.indexOf(EXECUTIVE_EDUCATION_2U_COURSE_TYPE), + contentToDisplay.indexOf(EXEC_ED_TITLE), 1, ); } @@ -215,7 +217,7 @@ function CatalogSearch(intl) { return itemsWithResultsList; }; - const defaultInstantSearchFilter = `learning_type:${CONTENT_TYPE_COURSE} OR learning_type:${CONTENT_TYPE_PROGRAM} OR learning_type:${EXECUTIVE_EDUCATION_2U_COURSE_TYPE}`; + const defaultInstantSearchFilter = `${LEARNING_TYPE_REFINEMENT}:${CONTENT_TYPE_COURSE} OR ${LEARNING_TYPE_REFINEMENT}:${CONTENT_TYPE_PROGRAM} OR ${LEARNING_TYPE_REFINEMENT}:"${EXEC_ED_TITLE}"`; return ( diff --git a/src/components/courseCard/CourseCard.jsx b/src/components/courseCard/CourseCard.jsx index 49eb3e8a..33eda7a7 100644 --- a/src/components/courseCard/CourseCard.jsx +++ b/src/components/courseCard/CourseCard.jsx @@ -60,14 +60,14 @@ function CourseCard({ {priceText} • {pacingType}

- {enterprise_catalog_query_titles.includes( + {enterprise_catalog_query_titles?.includes( process.env.EDX_ENTERPRISE_ALACARTE_TITLE, ) && ( {intl.formatMessage(messages['courseCard.aLaCarteBadge'])} )} - {enterprise_catalog_query_titles.includes( + {enterprise_catalog_query_titles?.includes( process.env.EDX_FOR_BUSINESS_TITLE, ) && ( )} - {enterprise_catalog_query_titles.includes( + {enterprise_catalog_query_titles?.includes( process.env.EDX_FOR_ONLINE_EDU_TITLE, ) && ( diff --git a/src/components/courseCard/CourseCard.test.jsx b/src/components/courseCard/CourseCard.test.jsx index aee1b495..235dd536 100644 --- a/src/components/courseCard/CourseCard.test.jsx +++ b/src/components/courseCard/CourseCard.test.jsx @@ -4,7 +4,7 @@ import '@testing-library/jest-dom/extend-expect'; import { IntlProvider } from '@edx/frontend-platform/i18n'; import CourseCard from './CourseCard'; -import { CONTENT_TYPE_COURSE, EXECUTIVE_EDUCATION_2U_COURSE_TYPE } from '../../constants'; +import { CONTENT_TYPE_COURSE, EXEC_ED_TITLE } from '../../constants'; jest.mock('@edx/frontend-platform', () => ({ ...jest.requireActual('@edx/frontend-platform'), @@ -40,7 +40,7 @@ const execEdData = { const execEdProps = { original: execEdData, - learningType: EXECUTIVE_EDUCATION_2U_COURSE_TYPE, + learningType: EXEC_ED_TITLE, }; describe('Course card works as expected', () => { diff --git a/src/constants.js b/src/constants.js index 32f1a88b..5fe17a21 100644 --- a/src/constants.js +++ b/src/constants.js @@ -18,34 +18,33 @@ export const AVAILABILITY_REFINEMENT_DEFAULTS = [ 'Upcoming', ]; +// Facet filters export const CONTENT_TYPE_REFINEMENT = 'content_type'; export const COURSE_TYPE_REFINEMENT = 'course_type'; -export const LEARNING_TYPE_REFINEMENT = 'learning_type'; +export const LEARNING_TYPE_REFINEMENT = 'learning_type_v2'; + +// Page refinement settings export const HIDE_CARDS_REFINEMENT = 'hide_cards'; export const HIDE_PRICE_REFINEMENT = 'hide_price'; export const NUM_RESULTS_PER_PAGE = 40; + +// Learning types export const CONTENT_TYPE_COURSE = 'course'; export const CONTENT_TYPE_PROGRAM = 'program'; +export const EXEC_ED_TITLE = 'Executive Education'; + +// Page metric settings export const NUM_RESULTS_PROGRAM = 4; export const NUM_RESULTS_COURSE = 8; + export const COURSE_TITLE = 'Courses'; export const PROGRAM_TITLE = 'Programs'; -export const EXEC_ED_TITLE = 'Executive Education'; + export const NO_RESULTS_DECK_ITEM_COUNT = 4; export const NO_RESULTS_PAGE_ITEM_COUNT = 1; export const NO_RESULTS_PAGE_SIZE = 4; -const AUDIT_COURSE_TYPE = 'audit'; -const VERIFIED_AUDIT_COURSE_TYPE = 'verified-audit'; -const PROFESSIONAL_COURSE_TYPE = 'professional'; -const CREDIT_VERIFIED_AUDIT_COURSE_TYPE = 'credit-verified-audit'; -export const EXECUTIVE_EDUCATION_2U_COURSE_TYPE = 'executive-education-2u'; -export const EDX_COURSES_COURSE_TYPES = [ - AUDIT_COURSE_TYPE, - VERIFIED_AUDIT_COURSE_TYPE, - PROFESSIONAL_COURSE_TYPE, - CREDIT_VERIFIED_AUDIT_COURSE_TYPE, -]; +// Descriptions export const EDX_COURSE_TITLE_DESC = 'Self paced online learning from world-class academic institutions and corporate partners.'; export const TWOU_EXEC_ED_TITLE_DESC = 'Immersive, instructor led online short courses designed to develop interpersonal, analytical, and critical thinking skills.'; export const PROGRAM_TITLE_DESC = 'Multi-course bundled learning for skills mastery and to earn credentials such as Professional Certificates, MicroBachelors™, MicroMasters®, and Master’s Degrees.'; diff --git a/src/utils/catalogUtils.js b/src/utils/catalogUtils.js index 3b20d2b2..128a5144 100644 --- a/src/utils/catalogUtils.js +++ b/src/utils/catalogUtils.js @@ -1,3 +1,5 @@ +import { EXEC_ED_TITLE } from '../constants'; + /* eslint-disable import/prefer-default-export */ const nowDate = new Date(Date.now()); @@ -32,4 +34,15 @@ function checkAvailability(start, end) { return ''; } -export { checkAvailability, checkSubscriptions }; +function convertLearningTypesToFilters(types) { + return types.reduce((learningFacets, type) => { + if (type === EXEC_ED_TITLE) { + learningFacets.push(`"${type}"`); + } else { + learningFacets.push(type); + } + return learningFacets; + }, []).join(' OR '); +} + +export { checkAvailability, checkSubscriptions, convertLearningTypesToFilters }; diff --git a/src/utils/catalogUtils.test.js b/src/utils/catalogUtils.test.js new file mode 100644 index 00000000..91b29975 --- /dev/null +++ b/src/utils/catalogUtils.test.js @@ -0,0 +1,9 @@ +import { EXEC_ED_TITLE } from '../constants'; +import { convertLearningTypesToFilters } from './catalogUtils'; + +describe('catalogUtils', () => { + it('converts lists of learning types to algolia filters', () => { + const algoliaFilter = convertLearningTypesToFilters(['a', 'b', EXEC_ED_TITLE]); + expect(algoliaFilter).toEqual('a OR b OR "Executive Education"'); + }); +});