From 756aaa91db49adcaaa4f01cc4326e081c518ac44 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Wed, 17 Apr 2024 20:58:18 -0700 Subject: [PATCH] fix: show an error when the search connection isn't working --- src/search-modal/EmptyStates.jsx | 16 ++++++++++++++-- src/search-modal/SearchModal.test.jsx | 10 +--------- src/search-modal/data/apiHooks.js | 1 + src/search-modal/manager/SearchManager.js | 4 +++- src/search-modal/messages.js | 5 +++++ 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/search-modal/EmptyStates.jsx b/src/search-modal/EmptyStates.jsx index eeb61365a2..fa5c77b227 100644 --- a/src/search-modal/EmptyStates.jsx +++ b/src/search-modal/EmptyStates.jsx @@ -2,7 +2,7 @@ // @ts-check import React from 'react'; import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import { Stack } from '@openedx/paragon'; +import { Alert, Stack } from '@openedx/paragon'; import { useSearchContext } from './manager/SearchManager'; import EmptySearchImage from './images/empty-search.svg'; @@ -24,9 +24,21 @@ const InfoMessage = ({ title, subtitle, image }) => ( * @type {React.FC<{children: React.ReactElement}>} */ const EmptyStates = ({ children }) => { - const { canClearFilters: hasFiltersApplied, totalHits, searchKeywords } = useSearchContext(); + const { + canClearFilters: hasFiltersApplied, + totalHits, + searchKeywords, + hasError, + } = useSearchContext(); const hasQuery = !!searchKeywords; + if (hasError) { + return ( + + + + ); + } if (!hasQuery && !hasFiltersApplied) { // We haven't started the search yet. Display the "start your search" empty state return ( diff --git a/src/search-modal/SearchModal.test.jsx b/src/search-modal/SearchModal.test.jsx index 6c77043958..a14bb0fc2d 100644 --- a/src/search-modal/SearchModal.test.jsx +++ b/src/search-modal/SearchModal.test.jsx @@ -67,17 +67,9 @@ describe('', () => { expect(await findByText('Start searching to find content')).toBeInTheDocument(); }); - it('should render the spinner while the config is loading', () => { - axiosMock.onGet(getContentSearchConfigUrl()).replyOnce(200, new Promise(() => {})); // never resolves - const { getByRole } = render(); - - const spinner = getByRole('status'); - expect(spinner.textContent).toEqual('Loading...'); - }); - it('should render the error message if the api call throws', async () => { axiosMock.onGet(getContentSearchConfigUrl()).networkError(); const { findByText } = render(); - expect(await findByText('Network Error')).toBeInTheDocument(); + expect(await findByText('An error occurred. Unable to load search results.')).toBeInTheDocument(); }); }); diff --git a/src/search-modal/data/apiHooks.js b/src/search-modal/data/apiHooks.js index eae5ded8af..06867e0ebe 100644 --- a/src/search-modal/data/apiHooks.js +++ b/src/search-modal/data/apiHooks.js @@ -91,6 +91,7 @@ export const useContentSearchResults = ({ blockTypes: pages?.[0]?.blockTypes ?? {}, status: query.status, isFetching: query.isFetching, + isError: query.isError, isFetchingNextPage: query.isFetchingNextPage, // Call this to load more pages. We include some "safety" features recommended by the docs: this should never be // called while already fetching a page, and parameters (like 'event') should not be passed into fetchNextPage(). diff --git a/src/search-modal/manager/SearchManager.js b/src/search-modal/manager/SearchManager.js index 6cbf17864d..bbed2e08e5 100644 --- a/src/search-modal/manager/SearchManager.js +++ b/src/search-modal/manager/SearchManager.js @@ -32,6 +32,7 @@ import { useContentSearchConnection, useContentSearchResults } from '../data/api * isFetchingNextPage: boolean, * fetchNextPage: () => void, * closeSearchModal: () => void, + * hasError: boolean, * }>} */ const SearchContext = /** @type {any} */(React.createContext(undefined)); @@ -55,7 +56,7 @@ export const SearchContextProvider = ({ extraFilter, children, closeSearchModal }, []); // Initialize a connection to Meilisearch: - const { data: connectionDetails } = useContentSearchConnection(); + const { data: connectionDetails, isError: hasConnectionError } = useContentSearchConnection(); const indexName = connectionDetails?.indexName; const client = React.useMemo(() => { if (connectionDetails?.apiKey === undefined || connectionDetails?.url === undefined) { @@ -88,6 +89,7 @@ export const SearchContextProvider = ({ extraFilter, children, closeSearchModal canClearFilters, clearFilters, closeSearchModal: closeSearchModal ?? (() => {}), + hasError: hasConnectionError || result.isError, ...result, }, }, children); diff --git a/src/search-modal/messages.js b/src/search-modal/messages.js index 8f50fe255d..13a23138c4 100644 --- a/src/search-modal/messages.js +++ b/src/search-modal/messages.js @@ -175,6 +175,11 @@ const messages = defineMessages({ defaultMessage: 'Open in new window', description: 'Alt text for the button that opens the search result in a new window', }, + searchError: { + id: 'course-authoring.course-search.searchError', + defaultMessage: 'An error occurred. Unable to load search results.', + description: 'Error message shown when search is not working.', + }, }); export default messages;