From 3df1d800bc2af2437d4a45a65f3129fe028e916f Mon Sep 17 00:00:00 2001 From: Bryan Florkiewicz Date: Mon, 9 Oct 2023 18:44:33 -0400 Subject: [PATCH 1/4] 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; From ec2e2f59d25608fc18f4e176ef51e61644b77462 Mon Sep 17 00:00:00 2001 From: Bryan Florkiewicz Date: Thu, 19 Oct 2023 15:52:02 -0400 Subject: [PATCH 2/4] Use both APIs and remove wildcard matching and fuzzy matching --- src/components/Search/SearchDescription.tsx | 6 +- src/components/Search/SearchGroup.tsx | 34 ++++- src/components/Search/SearchInput.tsx | 147 +++++++++++++------- src/components/Search/SearchTypes.ts | 32 ++++- src/components/Search/parseHighlight.ts | 4 + 5 files changed, 159 insertions(+), 64 deletions(-) create mode 100644 src/components/Search/parseHighlight.ts diff --git a/src/components/Search/SearchDescription.tsx b/src/components/Search/SearchDescription.tsx index a76e4cd71..a530e30eb 100644 --- a/src/components/Search/SearchDescription.tsx +++ b/src/components/Search/SearchDescription.tsx @@ -2,14 +2,16 @@ import React from 'react'; import { Text, TextContent } from '@patternfly/react-core/dist/dynamic/components/Text'; import './SearchDescription.scss'; +import parseHighlights from './parseHighlight'; -const SearchDescription = ({ description }: { description: string }) => { +const SearchDescription = ({ description, highlight = [] }: { highlight?: string[]; description: string }) => { + const parsedDescription = parseHighlights(description, highlight); return ( ); diff --git a/src/components/Search/SearchGroup.tsx b/src/components/Search/SearchGroup.tsx index 6a3b135df..44bbdce93 100644 --- a/src/components/Search/SearchGroup.tsx +++ b/src/components/Search/SearchGroup.tsx @@ -3,19 +3,39 @@ import React from 'react'; import ChromeLink from '../ChromeLink'; import SearchDescription from './SearchDescription'; import SearchTitle from './SearchTitle'; -import { AUTOSUGGEST_TERM_DELIMITER, SearchResultItem } from './SearchTypes'; +import { + AUTOSUGGEST_TERM_DELIMITER, + HighlightingResponseType, + SearchAutoSuggestionResultItem, + SearchResultItem, + SearchResultItemAggregate, +} from './SearchTypes'; -const SearchGroup = ({ items }: { items: SearchResultItem[] }) => { +const SearchGroup = ({ items, highlighting }: { items: SearchResultItemAggregate[]; highlighting: HighlightingResponseType }) => { return items.length > 0 ? ( - {items.map(({ term, payload }) => { - const [allTitle, bundle_title, abstract] = term.split(AUTOSUGGEST_TERM_DELIMITER); + {items.map((item) => { + if (item.term) { + const { term, payload } = item as SearchAutoSuggestionResultItem; + const [allTitle, bundle_title, abstract] = term.split(AUTOSUGGEST_TERM_DELIMITER); + return ( + } + description={} + key={crypto.randomUUID()} + > + + + ); + } + const { id, allTitle, bundle_title, abstract, relative_uri } = item as SearchResultItem; return ( } - description={} - key={crypto.randomUUID()} + component={(props) => } + description={} + key={id} > diff --git a/src/components/Search/SearchInput.tsx b/src/components/Search/SearchInput.tsx index e55bf8316..84d901699 100644 --- a/src/components/Search/SearchInput.tsx +++ b/src/components/Search/SearchInput.tsx @@ -9,7 +9,15 @@ import debounce from 'lodash/debounce'; import './SearchInput.scss'; import SearchGroup from './SearchGroup'; -import { AUTOSUGGEST_HIGHLIGHT_TAG, AUTOSUGGEST_TERM_DELIMITER, SearchResponseType, SearchResultItem } from './SearchTypes'; +import { + AUTOSUGGEST_HIGHLIGHT_TAG, + AUTOSUGGEST_TERM_DELIMITER, + HighlightingResponseType, + SearchAutoSuggestionResponseType, + SearchResponseAggregate, + SearchResponseType, + SearchResultItemAggregate, +} from './SearchTypes'; import EmptySearchState from './EmptySearchState'; import { isProd } from '../../utils/common'; import { useSegment } from '../../analytics/useSegment'; @@ -21,7 +29,6 @@ export type SearchInputprops = { const IS_PROD = isProd(); const REPLACE_TAG = 'REPLACE_TAG'; -const FUZZY_RANGE_TAG = 'FUZZY_RANGE_TAG'; /** * The ?q is the search term. * ------ @@ -36,24 +43,33 @@ const FUZZY_RANGE_TAG = 'FUZZY_RANGE_TAG'; */ const BASE_SEARCH = new URLSearchParams(); -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`); +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('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/`); // search API stopped receiving encoded search string BASE_URL.search = decodeURIComponent(BASE_SEARCH.toString()); const SEARCH_QUERY = BASE_URL.toString(); +const SUGGEST_SEARCH = new URLSearchParams(); +SUGGEST_SEARCH.append('redhat_client', 'console'); // required client id +SUGGEST_SEARCH.append('q', REPLACE_TAG); // add query replacement tag and enable fuzzy search with ~ and wildcards +SUGGEST_SEARCH.append('suggest.count', '10'); // request 10 results + +const SUGGEST_URL = new URL(`https://access.${IS_PROD ? '' : 'stage.'}redhat.com/hydra/proxy/gss-diag/rs/search/autosuggest`); +// search API stopped receiving encoded search string +SUGGEST_URL.search = decodeURIComponent(SUGGEST_SEARCH.toString()); +const SUGGEST_SEARCH_QUERY = SUGGEST_URL.toString(); + const getMaxMenuHeight = (menuElement?: HTMLDivElement | null) => { if (!menuElement) { return 0; @@ -66,12 +82,16 @@ const getMaxMenuHeight = (menuElement?: HTMLDivElement | null) => { }; type SearchCategories = { - highLevel: SearchResultItem[]; - midLevel: SearchResultItem[]; - lowLevel: SearchResultItem[]; + highLevel: SearchResultItemAggregate[]; + midLevel: SearchResultItemAggregate[]; + lowLevel: SearchResultItemAggregate[]; }; -const initialSearchState: SearchResponseType = { +const initialSearchState: SearchResponseAggregate = { + docs: [], + maxScore: 0, + numFound: 0, + start: 0, suggest: { default: {}, }, @@ -85,7 +105,8 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { const [isOpen, setIsOpen] = useState(false); const [searchValue, setSearchValue] = useState(''); const [isFetching, setIsFetching] = useState(false); - const [searchResults, setSearchResults] = useState(initialSearchState); + const [highlighting, setHighlighting] = useState({}); + const [searchResults, setSearchResults] = useState(initialSearchState); const { ready, analytics } = useSegment(); const blockCloseEvent = useRef(false); @@ -98,38 +119,48 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { const resultCount = searchResults?.suggest?.default[searchValue]?.numFound || 0; // sort result items based on matched field and its priority - const resultCategories = useMemo( - () => - (searchResults?.suggest?.default[searchValue]?.suggestions || []).reduce( - (acc, curr) => { - const [allTitle, , abstract] = curr.term.split(AUTOSUGGEST_TERM_DELIMITER); - if (allTitle.includes(AUTOSUGGEST_HIGHLIGHT_TAG)) { - return { - ...acc, - highLevel: [...acc.highLevel, curr], - }; - } - - if (abstract.includes(AUTOSUGGEST_HIGHLIGHT_TAG)) { - return { - ...acc, - midLevel: [...acc.midLevel, curr], - }; - } + const resultCategories = useMemo(() => { + const categories = (searchResults?.suggest?.default[searchValue]?.suggestions || []).reduce( + (acc, curr) => { + const [allTitle, , abstract] = curr.term.split(AUTOSUGGEST_TERM_DELIMITER); + if (allTitle.includes(AUTOSUGGEST_HIGHLIGHT_TAG)) { + return { + ...acc, + highLevel: [...acc.highLevel, curr], + }; + } + if (abstract.includes(AUTOSUGGEST_HIGHLIGHT_TAG)) { return { ...acc, - lowLevel: [...acc.lowLevel, curr], + midLevel: [...acc.midLevel, curr], }; - }, - { - highLevel: [], - midLevel: [], - lowLevel: [], } - ), - [searchResults, searchValue] - ); + + return { + ...acc, + lowLevel: [...acc.lowLevel, curr], + }; + }, + { + highLevel: [], + midLevel: [], + lowLevel: [], + } + ); + searchResults.docs.forEach((doc) => { + if (highlighting[doc.id]?.allTitle) { + categories.highLevel.push(doc); + } + + if (highlighting[doc.id]?.abstract) { + categories.midLevel.push(doc); + } + + categories.lowLevel.push(doc); + }); + return categories; + }, [searchResults, searchValue]); const handleMenuKeys = (event: KeyboardEvent) => { if (!isOpen) { @@ -213,11 +244,19 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { }, [isOpen, menuRef]); const handleFetch = (value = '') => { - return fetch(SEARCH_QUERY.replaceAll(REPLACE_TAG, value).replaceAll(FUZZY_RANGE_TAG, value.length > 3 ? '2' : '1')) + let results: SearchResponseAggregate = initialSearchState; + return fetch(SUGGEST_SEARCH_QUERY.replaceAll(REPLACE_TAG, value)) + .then((r) => r.json()) + .then((response: SearchAutoSuggestionResponseType) => { + results = { ...results, ...response }; + return fetch(SEARCH_QUERY.replaceAll(REPLACE_TAG, value)); + }) .then((r) => r.json()) - .then((response: SearchResponseType) => { + .then(({ response, highlighting }: { highlighting: HighlightingResponseType; response: SearchResponseType }) => { + results = { ...results, ...response }; if (isMounted.current) { - setSearchResults(response); + setSearchResults(results); + setHighlighting(highlighting); // make sure to calculate resize when switching from loading to sucess state handleWindowResize(); } @@ -289,9 +328,9 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { ) : ( <> 0 ? `Top ${resultCount} results` : undefined}> - - - + + + )} diff --git a/src/components/Search/SearchTypes.ts b/src/components/Search/SearchTypes.ts index a60bf092e..7476e2d0f 100644 --- a/src/components/Search/SearchTypes.ts +++ b/src/components/Search/SearchTypes.ts @@ -1,20 +1,50 @@ +import { EitherNotBoth } from '@openshift/dynamic-plugin-sdk'; + export type SearchResultItem = { + abstract: string; + allTitle: string; + bundle: string[]; + bundle_title: string[]; + documentKind: string; + id: string; + relative_uri: string; + view_uri: string; +}; + +export type SearchAutoSuggestionResultItem = { term: string; weight: string; payload: string; }; +export type SearchResultItemAggregate = EitherNotBoth; + export type SearchResponseType = { + docs: SearchResultItem[]; + start: number; + numFound: number; + maxScore: number; +}; + +export type SearchAutoSuggestionResponseType = { suggest: { default: { [recordId: string]: { numFound: number; - suggestions: SearchResultItem[]; + suggestions: SearchAutoSuggestionResultItem[]; }; }; }; }; +export type SearchResponseAggregate = SearchResponseType & SearchAutoSuggestionResponseType; + +export type SearchHighlight = { allTitle?: string[]; abstract?: string[]; bundle_title?: string[]; bundle?: string[] }; + +export type HighlightingResponseType = { + [recordId: string]: SearchHighlight; +}; + export const AUTOSUGGEST_HIGHLIGHT_TAG = ''; export const AUTOSUGGEST_TERM_DELIMITER = '|'; diff --git a/src/components/Search/parseHighlight.ts b/src/components/Search/parseHighlight.ts new file mode 100644 index 000000000..b52297901 --- /dev/null +++ b/src/components/Search/parseHighlight.ts @@ -0,0 +1,4 @@ +// merge multiple highlight marks into single string +const parseHighlights = (base: string, highlights: string[] = []) => (highlights.length === 0 ? base : highlights.join(' ')); + +export default parseHighlights; From 2dbda741d155615a2b2c02c906e718c91a8cce1e Mon Sep 17 00:00:00 2001 From: Bryan Florkiewicz Date: Thu, 26 Oct 2023 20:14:30 -0400 Subject: [PATCH 3/4] Attempt to pass autosuggest results into search solr endpoint --- src/components/Search/SearchInput.tsx | 40 ++++++++++++++++++++++----- src/components/Search/SearchTypes.ts | 8 +++++- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/components/Search/SearchInput.tsx b/src/components/Search/SearchInput.tsx index 84d901699..7c99a7368 100644 --- a/src/components/Search/SearchInput.tsx +++ b/src/components/Search/SearchInput.tsx @@ -6,11 +6,15 @@ import { Spinner } from '@patternfly/react-core/dist/dynamic/components/Spinner' import { Popper } from '@patternfly/react-core/dist/dynamic/helpers/Popper/Popper'; import debounce from 'lodash/debounce'; +import uniq from 'lodash/uniq'; import './SearchInput.scss'; import SearchGroup from './SearchGroup'; import { - AUTOSUGGEST_HIGHLIGHT_TAG, + AUTOSUGGEST_BUNDLE_CLOSE_TAG, + AUTOSUGGEST_BUNDLE_OPEN_TAG, + AUTOSUGGEST_HIGHLIGHT_CLOSE_TAG, + AUTOSUGGEST_HIGHLIGHT_OPEN_TAG, AUTOSUGGEST_TERM_DELIMITER, HighlightingResponseType, SearchAutoSuggestionResponseType, @@ -123,14 +127,14 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { const categories = (searchResults?.suggest?.default[searchValue]?.suggestions || []).reduce( (acc, curr) => { const [allTitle, , abstract] = curr.term.split(AUTOSUGGEST_TERM_DELIMITER); - if (allTitle.includes(AUTOSUGGEST_HIGHLIGHT_TAG)) { + if (allTitle.includes(AUTOSUGGEST_HIGHLIGHT_OPEN_TAG)) { return { ...acc, highLevel: [...acc.highLevel, curr], }; } - if (abstract.includes(AUTOSUGGEST_HIGHLIGHT_TAG)) { + if (abstract.includes(AUTOSUGGEST_HIGHLIGHT_OPEN_TAG)) { return { ...acc, midLevel: [...acc.midLevel, curr], @@ -244,16 +248,38 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { }, [isOpen, menuRef]); const handleFetch = (value = '') => { - let results: SearchResponseAggregate = initialSearchState; return fetch(SUGGEST_SEARCH_QUERY.replaceAll(REPLACE_TAG, value)) .then((r) => r.json()) .then((response: SearchAutoSuggestionResponseType) => { - results = { ...results, ...response }; - return fetch(SEARCH_QUERY.replaceAll(REPLACE_TAG, value)); + let valueToSearch = value; + const autoSuggestions = (response?.suggest?.default[value]?.suggestions || []).map((suggestion) => { + const [allTitle, bundle_title, abstract] = suggestion.term.split(AUTOSUGGEST_TERM_DELIMITER); + // TODO it is not safe to assume only one field will have a highlight. Need to revisit this and above logic + let matched; + if (allTitle.includes(AUTOSUGGEST_HIGHLIGHT_OPEN_TAG)) { + matched = allTitle; + } else if (bundle_title.includes(AUTOSUGGEST_HIGHLIGHT_OPEN_TAG)) { + matched = bundle_title; + } else { + matched = abstract; + } + const result = matched + .replaceAll(AUTOSUGGEST_HIGHLIGHT_OPEN_TAG, '') + .replaceAll(AUTOSUGGEST_HIGHLIGHT_CLOSE_TAG, '') + .replaceAll(AUTOSUGGEST_BUNDLE_OPEN_TAG, '') + .replaceAll(AUTOSUGGEST_BUNDLE_CLOSE_TAG, '') + .trim(); + // wrap multiple terms in quotes - otherwise search treats each as an individual term to search + return `"${result}"`; + }); + if (autoSuggestions.length > 0) { + valueToSearch = uniq(autoSuggestions).join('+OR+'); + } + return fetch(SEARCH_QUERY.replaceAll(REPLACE_TAG, valueToSearch)); }) .then((r) => r.json()) .then(({ response, highlighting }: { highlighting: HighlightingResponseType; response: SearchResponseType }) => { - results = { ...results, ...response }; + const results: SearchResponseAggregate = { ...initialSearchState, ...response }; if (isMounted.current) { setSearchResults(results); setHighlighting(highlighting); diff --git a/src/components/Search/SearchTypes.ts b/src/components/Search/SearchTypes.ts index 7476e2d0f..c2684daa4 100644 --- a/src/components/Search/SearchTypes.ts +++ b/src/components/Search/SearchTypes.ts @@ -45,6 +45,12 @@ export type HighlightingResponseType = { [recordId: string]: SearchHighlight; }; -export const AUTOSUGGEST_HIGHLIGHT_TAG = ''; +export const AUTOSUGGEST_HIGHLIGHT_OPEN_TAG = ''; + +export const AUTOSUGGEST_HIGHLIGHT_CLOSE_TAG = ''; + +export const AUTOSUGGEST_BUNDLE_OPEN_TAG = '['; + +export const AUTOSUGGEST_BUNDLE_CLOSE_TAG = ']'; export const AUTOSUGGEST_TERM_DELIMITER = '|'; From 66ad46acdc385c08d0dd6da94312785a2298630a Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Wed, 1 Nov 2023 15:31:06 +0100 Subject: [PATCH 4/4] Use autosuggest search API. --- cypress/e2e/release-gate/search.cy.tsx | 69 -------- src/components/Search/SearchGroup.tsx | 48 ------ src/components/Search/SearchInput.tsx | 208 ++++++++----------------- src/components/Search/SearchTypes.ts | 20 --- 4 files changed, 68 insertions(+), 277 deletions(-) delete mode 100644 cypress/e2e/release-gate/search.cy.tsx delete mode 100644 src/components/Search/SearchGroup.tsx diff --git a/cypress/e2e/release-gate/search.cy.tsx b/cypress/e2e/release-gate/search.cy.tsx deleted file mode 100644 index b88347640..000000000 --- a/cypress/e2e/release-gate/search.cy.tsx +++ /dev/null @@ -1,69 +0,0 @@ -const searchResponse = { - response: { - numFound: 2, - start: 0, - maxScore: 10.0, - docs: [ - { - id: 'hcc-module-/openshift/create-OPENSHIFT.cluster.create.azure', - view_uri: 'https://console.redhat.com/openshift/create', - documentKind: 'ModuleDefinition', - allTitle: 'Azure Red Hat OpenShift', - bundle: ['openshift'], - bundle_title: ['OpenShift'], - relative_uri: '/openshift/create', - alt_titles: ['ARO', 'Azure', 'OpenShift on Azure'], - abstract: 'https://console.redhat.com/openshift/create', - timestamp: '2023-08-22T17:01:31.717Z', - _version_: 1774949404248113152, - }, - { - id: 'hcc-module-/openshift/releases-openshift.releases', - view_uri: 'https://console.redhat.com/openshift/releases', - documentKind: 'ModuleDefinition', - allTitle: 'Releases', - bundle: ['openshift'], - bundle_title: ['OpenShift'], - relative_uri: '/openshift/releases', - icons: 'InfrastructureIcon', - abstract: 'View general information on the most recent OpenShift Container Platform release versions that you can install.', - timestamp: '2023-08-15T10:55:46.769Z', - _version_: 1774949404248113152, - }, - ], - }, - highlighting: { - 'hcc-module-/openshift/create-OPENSHIFT.cluster.create.azure': { - abstract: ['https://console.redhat.com/openshift/create'], - allTitle: ['Azure Red Hat OpenShift'], - bundle: ['openshift'], - }, - 'hcc-module-/openshift/releases-openshift.releases': { - abstract: ['View general information on the most recent OpenShift Container Platform release versions that you can install.'], - allTitle: ['Releases'], - bundle: ['openshift'], - }, - }, -}; - -describe('Search', () => { - it('search for openshift services', () => { - cy.login(); - cy.visit('/'); - cy.intercept( - { - method: 'GET', - url: '**/hydra/rest/search/**', - }, - searchResponse - ).as('search'); - cy.get('.chr-c-search__input').click().type('openshift'); - cy.wait('@search').its('response.statusCode').should('equal', 200); - cy.get('@search.all').should('have.length', 1); - cy.screenshot(); - cy.get('.chr-c-search__input').should('contain', 'Top 2 results'); - cy.get('.chr-c-search__input li').first().should('contain', 'Azure'); - cy.get('.chr-c-search__input li').last().should('contain', 'Releases').click(); - cy.url().should('contain', '/openshift/releases'); - }); -}); diff --git a/src/components/Search/SearchGroup.tsx b/src/components/Search/SearchGroup.tsx deleted file mode 100644 index 44bbdce93..000000000 --- a/src/components/Search/SearchGroup.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { MenuGroup, MenuItem } from '@patternfly/react-core/dist/dynamic/components/Menu'; -import React from 'react'; -import ChromeLink from '../ChromeLink'; -import SearchDescription from './SearchDescription'; -import SearchTitle from './SearchTitle'; -import { - AUTOSUGGEST_TERM_DELIMITER, - HighlightingResponseType, - SearchAutoSuggestionResultItem, - SearchResultItem, - SearchResultItemAggregate, -} from './SearchTypes'; - -const SearchGroup = ({ items, highlighting }: { items: SearchResultItemAggregate[]; highlighting: HighlightingResponseType }) => { - return items.length > 0 ? ( - - {items.map((item) => { - if (item.term) { - const { term, payload } = item as SearchAutoSuggestionResultItem; - const [allTitle, bundle_title, abstract] = term.split(AUTOSUGGEST_TERM_DELIMITER); - return ( - } - description={} - key={crypto.randomUUID()} - > - - - ); - } - const { id, allTitle, bundle_title, abstract, relative_uri } = item as SearchResultItem; - return ( - } - description={} - key={id} - > - - - ); - })} - - ) : null; -}; - -export default SearchGroup; diff --git a/src/components/Search/SearchInput.tsx b/src/components/Search/SearchInput.tsx index 7c99a7368..5fd7dbfb9 100644 --- a/src/components/Search/SearchInput.tsx +++ b/src/components/Search/SearchInput.tsx @@ -1,31 +1,23 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Bullseye } from '@patternfly/react-core/dist/dynamic/layouts/Bullseye'; -import { Menu, MenuContent, MenuGroup, MenuList } from '@patternfly/react-core/dist/dynamic/components/Menu'; +import { Menu, MenuContent, MenuGroup, MenuItem, MenuList } from '@patternfly/react-core/dist/dynamic/components/Menu'; import { SearchInput as PFSearchInput, SearchInputProps } from '@patternfly/react-core/dist/dynamic/components/SearchInput'; import { Spinner } from '@patternfly/react-core/dist/dynamic/components/Spinner'; import { Popper } from '@patternfly/react-core/dist/dynamic/helpers/Popper/Popper'; import debounce from 'lodash/debounce'; import uniq from 'lodash/uniq'; +import uniqWith from 'lodash/uniqWith'; import './SearchInput.scss'; -import SearchGroup from './SearchGroup'; -import { - AUTOSUGGEST_BUNDLE_CLOSE_TAG, - AUTOSUGGEST_BUNDLE_OPEN_TAG, - AUTOSUGGEST_HIGHLIGHT_CLOSE_TAG, - AUTOSUGGEST_HIGHLIGHT_OPEN_TAG, - AUTOSUGGEST_TERM_DELIMITER, - HighlightingResponseType, - SearchAutoSuggestionResponseType, - SearchResponseAggregate, - SearchResponseType, - SearchResultItemAggregate, -} from './SearchTypes'; +import { AUTOSUGGEST_TERM_DELIMITER, SearchAutoSuggestionResponseType, SearchResponseType } from './SearchTypes'; import EmptySearchState from './EmptySearchState'; import { isProd } from '../../utils/common'; import { useSegment } from '../../analytics/useSegment'; import useWindowWidth from '../../hooks/useWindowWidth'; +import ChromeLink from '../ChromeLink'; +import SearchTitle from './SearchTitle'; +import SearchDescription from './SearchDescription'; export type SearchInputprops = { isExpanded?: boolean; @@ -33,6 +25,7 @@ export type SearchInputprops = { const IS_PROD = isProd(); const REPLACE_TAG = 'REPLACE_TAG'; +const REPLACE_COUNT_TAG = 'REPLACE_COUNT_TAG'; /** * The ?q is the search term. * ------ @@ -47,22 +40,13 @@ const REPLACE_TAG = 'REPLACE_TAG'; */ const BASE_SEARCH = new URLSearchParams(); -BASE_SEARCH.append('q', `${REPLACE_TAG}`); // add query replacement tag and enable fuzzy search with ~ and wildcards +BASE_SEARCH.append('q', `alt_titles:${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('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. +BASE_SEARCH.append('rows', `${REPLACE_COUNT_TAG}`); // request 10 results const BASE_URL = new URL(`https://access.${IS_PROD ? '' : 'stage.'}redhat.com/hydra/rest/search/platform/console/`); // search API stopped receiving encoded search string BASE_URL.search = decodeURIComponent(BASE_SEARCH.toString()); -const SEARCH_QUERY = BASE_URL.toString(); const SUGGEST_SEARCH = new URLSearchParams(); SUGGEST_SEARCH.append('redhat_client', 'console'); // required client id @@ -85,20 +69,11 @@ const getMaxMenuHeight = (menuElement?: HTMLDivElement | null) => { return bodyHeight - menuTopOffset - 4; }; -type SearchCategories = { - highLevel: SearchResultItemAggregate[]; - midLevel: SearchResultItemAggregate[]; - lowLevel: SearchResultItemAggregate[]; -}; - -const initialSearchState: SearchResponseAggregate = { - docs: [], - maxScore: 0, - numFound: 0, - start: 0, - suggest: { - default: {}, - }, +type SearchItem = { + title: string; + bundleTitle: string; + description: string; + pathname: string; }; type SearchInputListener = { @@ -109,8 +84,7 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { const [isOpen, setIsOpen] = useState(false); const [searchValue, setSearchValue] = useState(''); const [isFetching, setIsFetching] = useState(false); - const [highlighting, setHighlighting] = useState({}); - const [searchResults, setSearchResults] = useState(initialSearchState); + const [searchItems, setSearchItems] = useState([]); const { ready, analytics } = useSegment(); const blockCloseEvent = useRef(false); @@ -120,51 +94,7 @@ 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(() => { - const categories = (searchResults?.suggest?.default[searchValue]?.suggestions || []).reduce( - (acc, curr) => { - const [allTitle, , abstract] = curr.term.split(AUTOSUGGEST_TERM_DELIMITER); - if (allTitle.includes(AUTOSUGGEST_HIGHLIGHT_OPEN_TAG)) { - return { - ...acc, - highLevel: [...acc.highLevel, curr], - }; - } - - if (abstract.includes(AUTOSUGGEST_HIGHLIGHT_OPEN_TAG)) { - return { - ...acc, - midLevel: [...acc.midLevel, curr], - }; - } - - return { - ...acc, - lowLevel: [...acc.lowLevel, curr], - }; - }, - { - highLevel: [], - midLevel: [], - lowLevel: [], - } - ); - searchResults.docs.forEach((doc) => { - if (highlighting[doc.id]?.allTitle) { - categories.highLevel.push(doc); - } - - if (highlighting[doc.id]?.abstract) { - categories.midLevel.push(doc); - } - - categories.lowLevel.push(doc); - }); - return categories; - }, [searchResults, searchValue]); + const resultCount = searchItems.length; const handleMenuKeys = (event: KeyboardEvent) => { if (!isOpen) { @@ -247,57 +177,52 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { }; }, [isOpen, menuRef]); - const handleFetch = (value = '') => { - return fetch(SUGGEST_SEARCH_QUERY.replaceAll(REPLACE_TAG, value)) - .then((r) => r.json()) - .then((response: SearchAutoSuggestionResponseType) => { - let valueToSearch = value; - const autoSuggestions = (response?.suggest?.default[value]?.suggestions || []).map((suggestion) => { - const [allTitle, bundle_title, abstract] = suggestion.term.split(AUTOSUGGEST_TERM_DELIMITER); - // TODO it is not safe to assume only one field will have a highlight. Need to revisit this and above logic - let matched; - if (allTitle.includes(AUTOSUGGEST_HIGHLIGHT_OPEN_TAG)) { - matched = allTitle; - } else if (bundle_title.includes(AUTOSUGGEST_HIGHLIGHT_OPEN_TAG)) { - matched = bundle_title; - } else { - matched = abstract; - } - const result = matched - .replaceAll(AUTOSUGGEST_HIGHLIGHT_OPEN_TAG, '') - .replaceAll(AUTOSUGGEST_HIGHLIGHT_CLOSE_TAG, '') - .replaceAll(AUTOSUGGEST_BUNDLE_OPEN_TAG, '') - .replaceAll(AUTOSUGGEST_BUNDLE_CLOSE_TAG, '') - .trim(); - // wrap multiple terms in quotes - otherwise search treats each as an individual term to search - return `"${result}"`; - }); - if (autoSuggestions.length > 0) { - valueToSearch = uniq(autoSuggestions).join('+OR+'); - } - return fetch(SEARCH_QUERY.replaceAll(REPLACE_TAG, valueToSearch)); - }) - .then((r) => r.json()) - .then(({ response, highlighting }: { highlighting: HighlightingResponseType; response: SearchResponseType }) => { - const results: SearchResponseAggregate = { ...initialSearchState, ...response }; - if (isMounted.current) { - setSearchResults(results); - setHighlighting(highlighting); - // make sure to calculate resize when switching from loading to sucess state - handleWindowResize(); - } - if (ready && analytics) { - analytics.track('chrome.search-query', { query: value }); - } - }) - .finally(() => { - isMounted.current && setIsFetching(false); - }); + const handleFetch = async (value = '') => { + const response = (await fetch(SUGGEST_SEARCH_QUERY.replaceAll(REPLACE_TAG, value)).then((r) => r.json())) as SearchAutoSuggestionResponseType; + + const items = (response?.suggest?.default[value]?.suggestions || []).map((suggestion) => { + const [allTitle, bundleTitle, abstract] = suggestion.term.split(AUTOSUGGEST_TERM_DELIMITER); + const url = new URL(suggestion.payload); + const pathname = url.pathname; + const item = { + title: allTitle, + bundleTitle, + description: abstract, + pathname, + }; + // wrap multiple terms in quotes - otherwise search treats each as an individual term to search + return { item, allTitle }; + }); + const suggests = uniq(items.map(({ allTitle }) => allTitle.replace(/(|<\/b>)/gm, '').trim())); + let searchItems = items.map(({ item }) => item); + console.log(suggests); + if (items.length < 10) { + console.log({ value }); + const altTitleResults = (await fetch( + BASE_URL.toString() + .replaceAll(REPLACE_TAG, `(${suggests.join(' OR ')} OR ${value})`) + .replaceAll(REPLACE_COUNT_TAG, '10') + ).then((r) => r.json())) as { response: SearchResponseType }; + searchItems = searchItems.concat( + altTitleResults.response.docs.map((doc) => ({ + pathname: doc.relative_uri, + bundleTitle: doc.bundle_title[0], + title: doc.allTitle, + description: doc.abstract, + })) + ); + } + searchItems = uniqWith(searchItems, (a, b) => a.title.replace(/(|<\/b>)/gm, '').trim() === b.title.replace(/(|<\/b>)/gm, '').trim()); + setSearchItems(searchItems.slice(0, 10)); + isMounted.current && setIsFetching(false); + if (ready && analytics) { + analytics.track('chrome.search-query', { query: value }); + } }; const debouncedFetch = useCallback(debounce(handleFetch, 500), []); - const handleChange = (_e: any, value: string) => { + const handleChange: SearchInputProps['onChange'] = (_e, value) => { setSearchValue(value); setIsFetching(true); debouncedFetch(value); @@ -325,7 +250,7 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { onChange={handleChange} onClear={(ev) => { setSearchValue(''); - setSearchResults(initialSearchState); + setSearchItems([]); ev.stopPropagation(); setIsOpen(false); onStateChange(false); @@ -353,14 +278,17 @@ const SearchInput = ({ onStateChange }: SearchInputListener) => { ) : ( <> - 0 ? `Top ${resultCount} results` : undefined}> - - - + 0 ? `Top ${searchItems.length} results` : undefined}> + {searchItems.map((item, index) => ( + }> + + + + ))} )} - {resultCount === 0 && !isFetching && } + {searchItems.length === 0 && !isFetching && } diff --git a/src/components/Search/SearchTypes.ts b/src/components/Search/SearchTypes.ts index c2684daa4..8eb286720 100644 --- a/src/components/Search/SearchTypes.ts +++ b/src/components/Search/SearchTypes.ts @@ -1,5 +1,3 @@ -import { EitherNotBoth } from '@openshift/dynamic-plugin-sdk'; - export type SearchResultItem = { abstract: string; allTitle: string; @@ -17,8 +15,6 @@ export type SearchAutoSuggestionResultItem = { payload: string; }; -export type SearchResultItemAggregate = EitherNotBoth; - export type SearchResponseType = { docs: SearchResultItem[]; start: number; @@ -37,20 +33,4 @@ export type SearchAutoSuggestionResponseType = { }; }; -export type SearchResponseAggregate = SearchResponseType & SearchAutoSuggestionResponseType; - -export type SearchHighlight = { allTitle?: string[]; abstract?: string[]; bundle_title?: string[]; bundle?: string[] }; - -export type HighlightingResponseType = { - [recordId: string]: SearchHighlight; -}; - -export const AUTOSUGGEST_HIGHLIGHT_OPEN_TAG = ''; - -export const AUTOSUGGEST_HIGHLIGHT_CLOSE_TAG = ''; - -export const AUTOSUGGEST_BUNDLE_OPEN_TAG = '['; - -export const AUTOSUGGEST_BUNDLE_CLOSE_TAG = ']'; - export const AUTOSUGGEST_TERM_DELIMITER = '|';