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 (#320)
  • Loading branch information
alex-sheehan-edx authored Jun 21, 2023
1 parent c26f42c commit 7ede119
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 150 deletions.
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
SEGMENT_KEY=''
SITE_NAME='edX'
USER_INFO_COOKIE_NAME='edx-user-info'
CATALOG_SERVICE_BASE_URL='foobar.com'
62 changes: 28 additions & 34 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 { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { logError } from '@edx/frontend-platform/logging';
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import { connectStateResults } from 'react-instantsearch-dom';

import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Alert, CardView, DataTable } from '@edx/paragon';

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));
57 changes: 18 additions & 39 deletions src/components/catalogNoResultsDeck/CatalogNoResultsDeck.test.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,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 { render, screen, waitFor } from '@testing-library/react';

import { IntlProvider } from '@edx/frontend-platform/i18n';

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

const TEST_COURSE_NAME = 'test course';
Expand All @@ -16,9 +15,19 @@ const TEST_COURSE_NAME_2 = 'test course 2';
const TEST_PARTNER_2 = 'edx 2';
const TEST_CATALOGS_2 = ['baz', 'ayylmao'];

const csvData = {
default_content: [
{
// fetching catalog from query params mock
jest.mock('../../utils/common', () => ({
...jest.requireActual('../../utils/common'),
getSelectedCatalogFromURL: jest.fn(),
}));

const defaultProps = {
setCardView: jest.fn(),
columns: [],
renderCardComponent: jest.fn(),
contentType: 'course',
searchResults: {
hits: [{
title: TEST_COURSE_NAME,
partners: [{ name: TEST_PARTNER, logo_image_url: '' }],
enterprise_catalog_query_titles: TEST_CATALOGS,
Expand All @@ -35,25 +44,8 @@ const csvData = {
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'),
getSelectedCatalogFromURL: jest.fn(),
}));

const defaultProps = {
setCardView: jest.fn(),
columns: [],
renderCardComponent: jest.fn(),
contentType: 'course',
}],
},
};

const execEdProps = {
Expand All @@ -65,7 +57,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 +81,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
39 changes: 30 additions & 9 deletions src/components/catalogSearchResults/CatalogSearchResults.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,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 @@ -30,8 +35,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 @@ -40,7 +47,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 @@ -49,7 +56,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 @@ -82,6 +89,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 @@ -455,12 +463,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
31 changes: 8 additions & 23 deletions src/components/catalogSearchResults/CatalogSearchResults.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@ import {
EXEC_ED_TITLE,
HIDE_PRICE_REFINEMENT,
} from '../../constants';
import EnterpriseCatalogApiService from '../../data/services/EnterpriseCatalogAPIService';

// Mocking this connected component so as not to have to mock the algolia Api
const PAGINATE_ME = 'PAGINATE ME :)';
const PaginationComponent = () => <div>{PAGINATE_ME}</div>;

const csvData = [{ csv_data: 'foobar' }];
jest
.spyOn(EnterpriseCatalogApiService, 'fetchDefaultCoursesInCatalogWithFacets')
.mockResolvedValue(csvData);
// 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 DEFAULT_SEARCH_CONTEXT_VALUE = { refinements: {} };

Expand Down Expand Up @@ -523,23 +524,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 +537,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
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ describe('Download button', () => {
const input = screen.getByText('Download results');
userEvent.click(input);
});
// The query, query param should not have an `&` in it.
// TODO: figure out why the process env for catalog base service can't be set in the test
const expectedWindowLocation = 'undefined/api/v1/enterprise-catalogs/catalog_workbook?availability=Available'
const expectedWindowLocation = `${process.env.CATALOG_SERVICE_BASE_URL}/api/v1/enterprise-catalogs/catalog_workbook?availability=Available`
+ '+Now&availability=Upcoming&query=math%20%26%20science';
expect(window.location.href).toEqual(expectedWindowLocation);
});
Expand Down
Loading

0 comments on commit 7ede119

Please sign in to comment.