From 3df1d800bc2af2437d4a45a65f3129fe028e916f Mon Sep 17 00:00:00 2001 From: Bryan Florkiewicz Date: Mon, 9 Oct 2023 18:44:33 -0400 Subject: [PATCH] RHCLOUD-28295 - update chrome to use autosuggest search API --- src/components/Search/SearchDescription.tsx | 6 +- src/components/Search/SearchGroup.tsx | 27 +++++---- src/components/Search/SearchInput.scss | 3 + src/components/Search/SearchInput.tsx | 67 ++++++++++----------- src/components/Search/SearchTitle.tsx | 13 ++-- src/components/Search/SearchTypes.ts | 29 ++++----- src/components/Search/parseHighlight.ts | 4 -- 7 files changed, 73 insertions(+), 76 deletions(-) delete mode 100644 src/components/Search/parseHighlight.ts diff --git a/src/components/Search/SearchDescription.tsx b/src/components/Search/SearchDescription.tsx index d6d5a6e63..a76e4cd71 100644 --- a/src/components/Search/SearchDescription.tsx +++ b/src/components/Search/SearchDescription.tsx @@ -1,17 +1,15 @@ import React from 'react'; import { Text, TextContent } from '@patternfly/react-core/dist/dynamic/components/Text'; -import parseHighlights from './parseHighlight'; import './SearchDescription.scss'; -const SearchDescription = ({ description, highlight = [] }: { highlight?: string[]; description: string }) => { - const parsedDescription = parseHighlights(description, highlight); +const SearchDescription = ({ description }: { description: string }) => { return ( ); diff --git a/src/components/Search/SearchGroup.tsx b/src/components/Search/SearchGroup.tsx index d6f646bc4..6a3b135df 100644 --- a/src/components/Search/SearchGroup.tsx +++ b/src/components/Search/SearchGroup.tsx @@ -3,21 +3,24 @@ import React from 'react'; import ChromeLink from '../ChromeLink'; import SearchDescription from './SearchDescription'; import SearchTitle from './SearchTitle'; -import { HighlightingResponseType, SearchResultItem } from './SearchTypes'; +import { AUTOSUGGEST_TERM_DELIMITER, SearchResultItem } from './SearchTypes'; -const SearchGroup = ({ items, highlighting }: { items: SearchResultItem[]; highlighting: HighlightingResponseType }) => { +const SearchGroup = ({ items }: { items: SearchResultItem[] }) => { return items.length > 0 ? ( - {items.map(({ id, allTitle, bundle_title, abstract, relative_uri }) => ( - } - description={} - key={id} - > - - - ))} + {items.map(({ term, payload }) => { + const [allTitle, bundle_title, abstract] = term.split(AUTOSUGGEST_TERM_DELIMITER); + return ( + } + description={} + key={crypto.randomUUID()} + > + + + ); + })} ) : null; }; diff --git a/src/components/Search/SearchInput.scss b/src/components/Search/SearchInput.scss index 077ec00fe..c57481b62 100644 --- a/src/components/Search/SearchInput.scss +++ b/src/components/Search/SearchInput.scss @@ -37,6 +37,9 @@ display: none; } } + small { + display: inline-block; + } } &__empty-state { .pf-v5-c-empty-state__icon { diff --git a/src/components/Search/SearchInput.tsx b/src/components/Search/SearchInput.tsx index 7c0d3d982..e55bf8316 100644 --- a/src/components/Search/SearchInput.tsx +++ b/src/components/Search/SearchInput.tsx @@ -9,7 +9,7 @@ import debounce from 'lodash/debounce'; import './SearchInput.scss'; import SearchGroup from './SearchGroup'; -import { HighlightingResponseType, SearchResponseType, SearchResultItem } from './SearchTypes'; +import { AUTOSUGGEST_HIGHLIGHT_TAG, AUTOSUGGEST_TERM_DELIMITER, SearchResponseType, SearchResultItem } from './SearchTypes'; import EmptySearchState from './EmptySearchState'; import { isProd } from '../../utils/common'; import { useSegment } from '../../analytics/useSegment'; @@ -36,22 +36,20 @@ const FUZZY_RANGE_TAG = 'FUZZY_RANGE_TAG'; */ const BASE_SEARCH = new URLSearchParams(); -BASE_SEARCH.append( - 'q', - `${REPLACE_TAG} OR *${REPLACE_TAG}~${FUZZY_RANGE_TAG} OR ${REPLACE_TAG}*~${FUZZY_RANGE_TAG} OR ${REPLACE_TAG}~${FUZZY_RANGE_TAG}` -); // add query replacement tag and enable fuzzy search with ~ and wildcards -BASE_SEARCH.append('fq', 'documentKind:ModuleDefinition'); // search for ModuleDefinition documents -BASE_SEARCH.append('rows', '10'); // request 10 results -BASE_SEARCH.append('hl', 'true'); // enable highlight -BASE_SEARCH.append('hl.method', 'original'); // choose highlight method -BASE_SEARCH.append('hl.fl', 'abstract'); // highlight description -BASE_SEARCH.append('hl.fl', 'allTitle'); // highlight title -BASE_SEARCH.append('hl.fl', 'bundle_title'); // highlight bundle title -BASE_SEARCH.append('hl.fl', 'bundle'); // highlight bundle id -BASE_SEARCH.append('hl.snippets', '3'); // enable up to 3 highlights in a single string -BASE_SEARCH.append('hl.mergeContiguous', 'true'); // Use only one highlight attribute to simply tag replacement. - -const BASE_URL = new URL(`https://access.${IS_PROD ? '' : 'stage.'}redhat.com/hydra/rest/search/platform/console/`); +BASE_SEARCH.append('redhat_client', 'console'); // required client id +BASE_SEARCH.append('q', REPLACE_TAG); // add query replacement tag and enable fuzzy search with ~ and wildcards +// BASE_SEARCH.append('fq', 'documentKind:ModuleDefinition'); // search for ModuleDefinition documents +BASE_SEARCH.append('suggest.count', '10'); // request 10 results +// BASE_SEARCH.append('hl', 'true'); // enable highlight +// BASE_SEARCH.append('hl.method', 'original'); // choose highlight method +// BASE_SEARCH.append('hl.fl', 'abstract'); // highlight description +// BASE_SEARCH.append('hl.fl', 'allTitle'); // highlight title +// BASE_SEARCH.append('hl.fl', 'bundle_title'); // highlight bundle title +// BASE_SEARCH.append('hl.fl', 'bundle'); // highlight bundle id +// BASE_SEARCH.append('hl.snippets', '3'); // enable up to 3 highlights in a single string +// BASE_SEARCH.append('hl.mergeContiguous', 'true'); // Use only one highlight attribute to simply tag replacement. + +const BASE_URL = new URL(`https://access.${IS_PROD ? '' : 'stage.'}redhat.com/hydra/proxy/gss-diag/rs/search/autosuggest`); // search API stopped receiving encoded search string BASE_URL.search = decodeURIComponent(BASE_SEARCH.toString()); const SEARCH_QUERY = BASE_URL.toString(); @@ -74,10 +72,9 @@ type SearchCategories = { }; const initialSearchState: SearchResponseType = { - docs: [], - maxScore: 0, - numFound: 0, - start: 0, + suggest: { + default: {}, + }, }; type SearchInputListener = { @@ -89,7 +86,6 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { const [searchValue, setSearchValue] = useState(''); const [isFetching, setIsFetching] = useState(false); const [searchResults, setSearchResults] = useState(initialSearchState); - const [highlighting, setHighlighting] = useState({}); const { ready, analytics } = useSegment(); const blockCloseEvent = useRef(false); @@ -99,19 +95,22 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { const containerRef = useRef(null); const { md } = useWindowWidth(); + const resultCount = searchResults?.suggest?.default[searchValue]?.numFound || 0; + // sort result items based on matched field and its priority const resultCategories = useMemo( () => - searchResults.docs.reduce( + (searchResults?.suggest?.default[searchValue]?.suggestions || []).reduce( (acc, curr) => { - if (highlighting[curr.id]?.allTitle) { + const [allTitle, , abstract] = curr.term.split(AUTOSUGGEST_TERM_DELIMITER); + if (allTitle.includes(AUTOSUGGEST_HIGHLIGHT_TAG)) { return { ...acc, highLevel: [...acc.highLevel, curr], }; } - if (highlighting[curr.id]?.abstract) { + if (abstract.includes(AUTOSUGGEST_HIGHLIGHT_TAG)) { return { ...acc, midLevel: [...acc.midLevel, curr], @@ -129,7 +128,7 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { lowLevel: [], } ), - [searchResults.docs, highlighting] + [searchResults, searchValue] ); const handleMenuKeys = (event: KeyboardEvent) => { @@ -155,7 +154,7 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { }; const onInputClick: SearchInputProps['onClick'] = () => { - if (!isOpen && searchResults.numFound > 0) { + if (!isOpen && resultCount > 0) { if (!md && isExpanded && searchValue !== '') { setIsOpen(true); onStateChange(true); @@ -216,10 +215,9 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { const handleFetch = (value = '') => { return fetch(SEARCH_QUERY.replaceAll(REPLACE_TAG, value).replaceAll(FUZZY_RANGE_TAG, value.length > 3 ? '2' : '1')) .then((r) => r.json()) - .then(({ response, highlighting }: { highlighting: HighlightingResponseType; response: SearchResponseType }) => { + .then((response: SearchResponseType) => { if (isMounted.current) { setSearchResults(response); - setHighlighting(highlighting); // make sure to calculate resize when switching from loading to sucess state handleWindowResize(); } @@ -280,7 +278,6 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { className={isExpanded ? 'pf-u-flex-grow-1' : 'chr-c-search__collapsed'} /> ); - const menu = ( @@ -291,14 +288,14 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { ) : ( <> - 0 ? `Top ${searchResults.docs.length} results` : undefined}> - - - + 0 ? `Top ${resultCount} results` : undefined}> + + + )} - {searchResults.numFound === 0 && !isFetching && } + {resultCount === 0 && !isFetching && } diff --git a/src/components/Search/SearchTitle.tsx b/src/components/Search/SearchTitle.tsx index 729ea9684..445e42f81 100644 --- a/src/components/Search/SearchTitle.tsx +++ b/src/components/Search/SearchTitle.tsx @@ -2,13 +2,16 @@ import React from 'react'; import { Text, TextContent } from '@patternfly/react-core/dist/dynamic/components/Text'; const SearchTitle = ({ title, bundleTitle }: { title: string; bundleTitle: string }) => { + const showBundleTitle = bundleTitle.replace(/\s/g, '').length > 0; return ( - - {title} - | - {bundleTitle} - + + {showBundleTitle && ( + + | + + )} + {showBundleTitle && } ); }; diff --git a/src/components/Search/SearchTypes.ts b/src/components/Search/SearchTypes.ts index 82f7738db..a60bf092e 100644 --- a/src/components/Search/SearchTypes.ts +++ b/src/components/Search/SearchTypes.ts @@ -1,23 +1,20 @@ export type SearchResultItem = { - abstract: string; - allTitle: string; - bundle: string[]; - bundle_title: string[]; - documentKind: string; - id: string; - relative_uri: string; - view_uri: string; + term: string; + weight: string; + payload: string; }; export type SearchResponseType = { - docs: SearchResultItem[]; - start: number; - numFound: number; - maxScore: number; + suggest: { + default: { + [recordId: string]: { + numFound: number; + suggestions: SearchResultItem[]; + }; + }; + }; }; -export type SearchHighlight = { allTitle?: string[]; abstract?: string[]; bundle_title?: string[]; bundle?: string[] }; +export const AUTOSUGGEST_HIGHLIGHT_TAG = ''; -export type HighlightingResponseType = { - [recordId: string]: SearchHighlight; -}; +export const AUTOSUGGEST_TERM_DELIMITER = '|'; diff --git a/src/components/Search/parseHighlight.ts b/src/components/Search/parseHighlight.ts deleted file mode 100644 index b52297901..000000000 --- a/src/components/Search/parseHighlight.ts +++ /dev/null @@ -1,4 +0,0 @@ -// merge multiple highlight marks into single string -const parseHighlights = (base: string, highlights: string[] = []) => (highlights.length === 0 ? base : highlights.join(' ')); - -export default parseHighlights;