Skip to content

Commit

Permalink
feat: removing public catalog's dependency on enterprise catalog defa…
Browse files Browse the repository at this point in the history
…ult results
  • Loading branch information
alex-sheehan-edx committed Jun 20, 2023
1 parent f3d063a commit 96a62f3
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 110 deletions.
58 changes: 26 additions & 32 deletions src/components/catalogNoResultsDeck/CatalogNoResultsDeck.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { Alert, CardView, DataTable } from '@edx/paragon';
import React, { useEffect, useState } from 'react';
import React, { useMemo } from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { logError } from '@edx/frontend-platform/logging';
import PropTypes from 'prop-types';

import { connectStateResults } from 'react-instantsearch-dom';

import {
CONTENT_TYPE_COURSE,
CONTENT_TYPE_PROGRAM,
EXEC_ED_TITLE,
LEARNING_TYPE_REFINEMENT,
NO_RESULTS_DECK_ITEM_COUNT,
NO_RESULTS_PAGE_SIZE,
NO_RESULTS_PAGE_ITEM_COUNT,
} from '../../constants';
import messages from './CatalogNoResultsDeck.messages';
import EnterpriseCatalogApiService from '../../data/services/EnterpriseCatalogAPIService';
import { getSelectedCatalogFromURL } from '../../utils/common';

const BASE_APP_URL = process.env.BASE_URL;
Expand All @@ -25,9 +24,12 @@ const CatalogNoResultsDeck = ({
columns,
renderCardComponent,
contentType,
searchResults,
}) => {
const [defaultData, setDefaultData] = useState([]);
const [apiError, setApiError] = useState(false);
const tableData = useMemo(
() => searchResults?.hits || [],
[searchResults?.hits],
);

const selectedCatalog = getSelectedCatalogFromURL();
let redirect;
Expand All @@ -39,25 +41,6 @@ const CatalogNoResultsDeck = ({
redirect = BASE_APP_URL;
}

useEffect(() => {
const defaultCoursesRefinements = {
enterprise_catalog_query_titles: selectedCatalog,
[LEARNING_TYPE_REFINEMENT]: contentType,
};

EnterpriseCatalogApiService.fetchDefaultCoursesInCatalogWithFacets(
defaultCoursesRefinements,
)
.then((response) => {
setDefaultData(response.default_content || []);
setApiError(false);
})
.catch((err) => {
setApiError(true);
logError(err);
});
}, [selectedCatalog, contentType]);

let defaultDeckTitle;
let alertText;
if (contentType === CONTENT_TYPE_COURSE) {
Expand Down Expand Up @@ -97,11 +80,10 @@ const CatalogNoResultsDeck = ({
)}
</Alert.Link>
</Alert>
{!apiError && (
<h3 className="mt-4.5 mb-3.5" data-testid="noResultsDeckTitleTestId">
{defaultDeckTitle}
</h3>
)}

<h3 className="mt-4.5 mb-3.5" data-testid="noResultsDeckTitleTestId">
{defaultDeckTitle}
</h3>
<DataTable
dataViewToggleOptions={{
isDataViewToggleEnabled: true,
Expand All @@ -110,7 +92,7 @@ const CatalogNoResultsDeck = ({
defaultActiveStateValue: 'card',
}}
columns={columns}
data={defaultData}
data={tableData}
itemCount={NO_RESULTS_DECK_ITEM_COUNT}
pageCount={NO_RESULTS_PAGE_ITEM_COUNT}
pageSize={NO_RESULTS_PAGE_SIZE}
Expand All @@ -135,14 +117,26 @@ CatalogNoResultsDeck.defaultProps = {
renderCardComponent: () => {},
columns: [],
contentType: '',
searchResults: {},
};

CatalogNoResultsDeck.propTypes = {
searchResults: PropTypes.shape({
_state: PropTypes.shape({
disjunctiveFacetsRefinements: PropTypes.shape({}),
}),
disjunctiveFacetsRefinements: PropTypes.arrayOf(PropTypes.shape({})),
nbHits: PropTypes.number,
hits: PropTypes.arrayOf(PropTypes.shape({})),
nbPages: PropTypes.number,
hitsPerPage: PropTypes.number,
page: PropTypes.number,
}),
contentType: PropTypes.string,
intl: intlShape.isRequired,
setCardView: PropTypes.func,
renderCardComponent: PropTypes.func,
columns: PropTypes.arrayOf(PropTypes.shape({})),
};

export default injectIntl(CatalogNoResultsDeck);
export default connectStateResults(injectIntl(CatalogNoResultsDeck));
53 changes: 3 additions & 50 deletions src/components/catalogNoResultsDeck/CatalogNoResultsDeck.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,10 @@ import { IntlProvider } from '@edx/frontend-platform/i18n';
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { logError } from '@edx/frontend-platform/logging';

import CatalogNoResultsDeck from './CatalogNoResultsDeck';
import EnterpriseCatalogApiService from '../../data/services/EnterpriseCatalogAPIService';
import { getSelectedCatalogFromURL } from '../../utils/common';

const TEST_COURSE_NAME = 'test course';
const TEST_PARTNER = 'edx';
const TEST_CATALOGS = ['baz'];

const TEST_COURSE_NAME_2 = 'test course 2';
const TEST_PARTNER_2 = 'edx 2';
const TEST_CATALOGS_2 = ['baz', 'ayylmao'];

const csvData = {
default_content: [
{
title: TEST_COURSE_NAME,
partners: [{ name: TEST_PARTNER, logo_image_url: '' }],
enterprise_catalog_query_titles: TEST_CATALOGS,
card_image_url: 'http://url.test.location',
first_enrollable_paid_seat_price: 100,
original_image_url: '',
availability: ['Available Now'],
},
{
title: TEST_COURSE_NAME_2,
partners: [{ name: TEST_PARTNER_2, logo_image_url: '' }],
enterprise_catalog_query_titles: TEST_CATALOGS_2,
card_image_url: 'http://url.test2.location',
first_enrollable_paid_seat_price: 99,
original_image_url: '',
availability: ['Available Now'],
},
],
};
// Enterprise catalog API mock
const mockCatalogApiService = jest.spyOn(
EnterpriseCatalogApiService,
'fetchDefaultCoursesInCatalogWithFacets',
);
// fetching catalog from query params mock
jest.mock('../../utils/common', () => ({
...jest.requireActual('../../utils/common'),
Expand All @@ -54,6 +17,9 @@ const defaultProps = {
columns: [],
renderCardComponent: jest.fn(),
contentType: 'course',
searchResults: {
hits: [],
},
};

const execEdProps = {
Expand All @@ -65,7 +31,6 @@ const execEdProps = {

describe('catalog no results deck works as expected', () => {
test('it displays no results alert text', async () => {
mockCatalogApiService.mockResolvedValue(csvData);
render(
<IntlProvider locale="en">
<CatalogNoResultsDeck {...defaultProps} />
Expand All @@ -90,18 +55,6 @@ describe('catalog no results deck works as expected', () => {
);
});
});
test('API error responses will hide content deck', async () => {
mockCatalogApiService.mockRejectedValue(new Error('Async error'));
render(
<IntlProvider locale="en">
<CatalogNoResultsDeck {...defaultProps} />
</IntlProvider>,
);
expect(
await screen.findByTestId('noResultsDeckTitleTestId'),
).not.toBeInTheDocument();
expect(logError).toBeCalled();
});
test('shows executive education alert text', async () => {
render(
<IntlProvider locale="en">
Expand Down
41 changes: 31 additions & 10 deletions src/components/catalogSearchResults/CatalogSearchResults.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ import React, {
useMemo,
useState,
} from 'react';
import { connectStateResults } from 'react-instantsearch-dom';
import {
Configure,
connectStateResults,
Index,
InstantSearch,
} from 'react-instantsearch-dom';
import Skeleton from 'react-loading-skeleton';
import {
CONTENT_TYPE_COURSE,
Expand All @@ -31,8 +36,10 @@ import {
EXEC_ED_TITLE,
HIDE_PRICE_REFINEMENT,
LEARNING_TYPE_REFINEMENT,
NO_RESULTS_PAGE_SIZE,
PROGRAM_TITLE,
PROGRAM_TITLE_DESC,
QUERY_TITLE_REFINEMENT,
TWOU_EXEC_ED_TITLE_DESC,
} from '../../constants';
import {
Expand All @@ -41,7 +48,7 @@ import {
mapAlgoliaObjectToProgram,
} from '../../utils/algoliaUtils';
import CatalogInfoModal from '../catalogInfoModal/CatalogInfoModal';
import { useSelectedCourse } from '../catalogs/data/hooks';
import { useSelectedCourse, useAlgoliaIndex } from '../catalogs/data/hooks';
import CourseCard from '../courseCard/CourseCard';
import ProgramCard from '../programCard/ProgramCard';
import CatalogBadges from './associatedComponents/catalogBadges/CatalogBadges';
Expand All @@ -50,7 +57,7 @@ import DownloadCsvButton from './associatedComponents/downloadCsvButton/Download
import messages from './CatalogSearchResults.messages';

import CatalogNoResultsDeck from '../catalogNoResultsDeck/CatalogNoResultsDeck';
import { formatDate, makePlural } from '../../utils/common';
import { formatDate, makePlural, getSelectedCatalogFromURL } from '../../utils/common';

export const ERROR_MESSAGE = 'An error occured while retrieving data';

Expand Down Expand Up @@ -83,6 +90,7 @@ export const BaseCatalogSearchResults = ({
setNoContent,
preview,
}) => {
const { algoliaIndexName, searchClient } = useAlgoliaIndex();
const [isProgramType, setIsProgramType] = useState();
const [isCourseType, setIsCourseType] = useState();
const [isExecEdType, setIsExecEdType] = useState();
Expand Down Expand Up @@ -456,12 +464,25 @@ export const BaseCatalogSearchResults = ({
)}
<div className="mb-5">
{searchResults?.nbHits === 0 && (
<CatalogNoResultsDeck
setCardView={setCardView}
columns={chosenColumn}
renderCardComponent={renderCardComponent}
contentType={contentType}
/>
<InstantSearch indexName={algoliaIndexName} searchClient={searchClient}>
<Index
indexName={algoliaIndexName}
indexId={`search-${contentType}`}
key={`search-${contentType}`}
>
<Configure
filters={`${LEARNING_TYPE_REFINEMENT}:"${contentType}" AND ${QUERY_TITLE_REFINEMENT}:"${getSelectedCatalogFromURL()}"`}
hitsPerPage={NO_RESULTS_PAGE_SIZE}
facetingAfterDistinct
/>
<CatalogNoResultsDeck
setCardView={setCardView}
columns={chosenColumn}
renderCardComponent={renderCardComponent}
contentType={contentType}
/>
</Index>
</InstantSearch>
)}
{searchResults?.nbHits !== 0 && (
<DataTable
Expand Down Expand Up @@ -508,7 +529,7 @@ BaseCatalogSearchResults.defaultProps = {
paginationComponent: SearchPagination,
row: null,
preview: false,
setNoContent: () => {},
setNoContent: () => { },
courseType: null,
};

Expand Down
27 changes: 9 additions & 18 deletions src/components/catalogSearchResults/CatalogSearchResults.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ import EnterpriseCatalogApiService from '../../data/services/EnterpriseCatalogAP
const PAGINATE_ME = 'PAGINATE ME :)';
const PaginationComponent = () => <div>{PAGINATE_ME}</div>;

// all we are testing is routes, we don't need InstantSearch to work here
jest.mock('react-instantsearch-dom', () => ({
...jest.requireActual('react-instantsearch-dom'),
InstantSearch: () => <div>Popular Courses</div>,
Index: () => <div>Popular Courses</div>,
}));

const csvData = [{ csv_data: 'foobar' }];
jest
.spyOn(EnterpriseCatalogApiService, 'fetchDefaultCoursesInCatalogWithFacets')
Expand Down Expand Up @@ -523,23 +530,8 @@ describe('Main Catalogs view works as expected', () => {
),
).toBeInTheDocument();
});
test('no program search results displays popular programs text', async () => {
const emptySearchResults = { ...searchResults, nbHits: 0 };
renderWithRouter(
<IntlProvider locale="en">
<SearchDataWrapper>
<BaseCatalogSearchResults
{...programProps}
searchResults={emptySearchResults}
/>
</SearchDataWrapper>
</IntlProvider>,
);
expect(screen.getByTestId('noResultsAlertTestId')).toBeInTheDocument();
await act(() => screen.findByText('Popular Programs'));
expect(screen.getByText('Popular Programs')).toBeInTheDocument();
});
test('no course search results displays popular programs text', async () => {

test('no course search results displays popular course text', async () => {
const emptySearchResults = { ...searchResults, nbHits: 0 };
renderWithRouter(
<IntlProvider locale="en">
Expand All @@ -551,7 +543,6 @@ describe('Main Catalogs view works as expected', () => {
</SearchDataWrapper>
</IntlProvider>,
);
expect(screen.getByTestId('noResultsAlertTestId')).toBeInTheDocument();
await act(() => screen.findByText('Popular Courses'));
expect(screen.getByText('Popular Courses')).toBeInTheDocument();
});
Expand Down

0 comments on commit 96a62f3

Please sign in to comment.