Skip to content

Commit b98f899

Browse files
shrabantipaul-collateShrabanti Paul
andauthored
Fixes 578 - Add NLQ Search to Marketplace Search Bar (#28112)
* add nlp search in marketplace * Disable NLP feature in MarketplaceSearchBar --------- Co-authored-by: Shrabanti Paul <shrabantipaul@Shrabantis-MacBook-Pro.local>
1 parent 4217e6d commit b98f899

5 files changed

Lines changed: 636 additions & 55 deletions

File tree

openmetadata-ui/src/main/resources/ui/src/components/DataMarketplace/MarketplaceSearchBar/MarketplaceSearchBar.component.tsx

Lines changed: 118 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,19 @@ import { debounce } from 'lodash';
2121
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2222
import { useTranslation } from 'react-i18next';
2323
import { useNavigate } from 'react-router-dom';
24+
import { ReactComponent as IconSuggestionsActive } from '../../../assets/svg/ic-suggestions-active.svg';
25+
import { ReactComponent as IconSuggestionsBlue } from '../../../assets/svg/ic-suggestions-blue.svg';
2426
import { INITIAL_PAGING_VALUE } from '../../../constants/constants';
2527
import { SearchIndex } from '../../../enums/search.enum';
2628
import { DataProduct } from '../../../generated/entity/domains/dataProduct';
2729
import { Domain } from '../../../generated/entity/domains/domain';
2830
import { useMarketplaceRecentSearches } from '../../../hooks/useMarketplaceRecentSearches';
2931
import { useMarketplaceStore } from '../../../hooks/useMarketplaceStore';
30-
import { searchQuery } from '../../../rest/searchAPI';
32+
import {
33+
getNLPEnabledStatus,
34+
nlqSearch,
35+
searchQuery,
36+
} from '../../../rest/searchAPI';
3137
import { getDataProductIconByUrl } from '../../../utils/DataProductUtils';
3238
import { getDomainIcon } from '../../../utils/DomainUtils';
3339
import { getDomainDetailsPath } from '../../../utils/RouterUtils';
@@ -40,6 +46,8 @@ const MarketplaceSearchBar = ({ isEditView }: { isEditView?: boolean }) => {
4046
const { t } = useTranslation();
4147
const navigate = useNavigate();
4248
const { dataProductBasePath } = useMarketplaceStore();
49+
const [isNLPEnabled, setIsNLPEnabled] = useState(false);
50+
const [isNLQActive, setIsNLQActive] = useState(false);
4351
const [searchValue, setSearchValue] = useState('');
4452
const [isOpen, setIsOpen] = useState(false);
4553
const [dataProducts, setDataProducts] = useState<DataProduct[]>([]);
@@ -48,41 +56,73 @@ const MarketplaceSearchBar = ({ isEditView }: { isEditView?: boolean }) => {
4856
const containerRef = useRef<HTMLDivElement>(null);
4957
const { addSearch } = useMarketplaceRecentSearches();
5058

51-
const fetchResults = useCallback(async (query: string) => {
52-
if (!query.trim()) {
53-
setDataProducts([]);
54-
setDomains([]);
59+
useEffect(() => {
60+
getNLPEnabledStatus()
61+
.then(setIsNLPEnabled)
62+
.catch(() => setIsNLPEnabled(false));
63+
}, []);
5564

56-
return;
57-
}
58-
setIsSearching(true);
59-
try {
60-
const [dpRes, domainRes] = await Promise.all([
61-
searchQuery({
62-
query,
63-
pageNumber: INITIAL_PAGING_VALUE,
64-
pageSize: PAGE_SIZE,
65-
searchIndex: SearchIndex.DATA_PRODUCT,
66-
}),
67-
searchQuery({
68-
query,
69-
pageNumber: INITIAL_PAGING_VALUE,
70-
pageSize: PAGE_SIZE,
71-
searchIndex: SearchIndex.DOMAIN,
72-
}),
73-
]);
65+
const fetchResults = useCallback(
66+
async (query: string) => {
67+
if (!query.trim()) {
68+
setDataProducts([]);
69+
setDomains([]);
7470

75-
setDataProducts(
76-
dpRes.hits.hits.map((hit) => hit._source) as DataProduct[]
77-
);
78-
setDomains(domainRes.hits.hits.map((hit) => hit._source) as Domain[]);
79-
} catch {
80-
setDataProducts([]);
81-
setDomains([]);
82-
} finally {
83-
setIsSearching(false);
84-
}
85-
}, []);
71+
return;
72+
}
73+
setIsSearching(true);
74+
try {
75+
if (isNLPEnabled && isNLQActive) {
76+
const res = await nlqSearch({
77+
query,
78+
pageNumber: INITIAL_PAGING_VALUE,
79+
pageSize: PAGE_SIZE * 2,
80+
searchIndex: SearchIndex.MARKETPLACE,
81+
});
82+
83+
const hits = res.hits.hits;
84+
setDataProducts(
85+
hits
86+
.filter((h) => h._source.entityType === SearchIndex.DATA_PRODUCT)
87+
.slice(0, PAGE_SIZE)
88+
.map((h) => h._source as unknown as DataProduct)
89+
);
90+
setDomains(
91+
hits
92+
.filter((h) => h._source.entityType === SearchIndex.DOMAIN)
93+
.slice(0, PAGE_SIZE)
94+
.map((h) => h._source as unknown as Domain)
95+
);
96+
} else {
97+
const [dpRes, domainRes] = await Promise.all([
98+
searchQuery({
99+
query,
100+
pageNumber: INITIAL_PAGING_VALUE,
101+
pageSize: PAGE_SIZE,
102+
searchIndex: SearchIndex.DATA_PRODUCT,
103+
}),
104+
searchQuery({
105+
query,
106+
pageNumber: INITIAL_PAGING_VALUE,
107+
pageSize: PAGE_SIZE,
108+
searchIndex: SearchIndex.DOMAIN,
109+
}),
110+
]);
111+
112+
setDataProducts(
113+
dpRes.hits.hits.map((hit) => hit._source) as DataProduct[]
114+
);
115+
setDomains(domainRes.hits.hits.map((hit) => hit._source) as Domain[]);
116+
}
117+
} catch {
118+
setDataProducts([]);
119+
setDomains([]);
120+
} finally {
121+
setIsSearching(false);
122+
}
123+
},
124+
[isNLPEnabled, isNLQActive]
125+
);
86126

87127
const debouncedFetch = useMemo(
88128
() => debounce(fetchResults, 400),
@@ -248,27 +288,50 @@ const MarketplaceSearchBar = ({ isEditView }: { isEditView?: boolean }) => {
248288
className="marketplace-search-bar"
249289
data-testid="marketplace-search-bar"
250290
ref={containerRef}>
251-
<Input
252-
autoComplete="off"
253-
data-testid="marketplace-search-input"
254-
fontSize="sm"
255-
icon={SearchLg}
256-
iconClassName="tw:!size-4"
257-
inputClassName="tw:!pl-9"
258-
isDisabled={isEditView}
259-
placeholder={t('label.search-for-type', {
260-
type:
261-
t('label.data-product-plural') + ', ' + t('label.domain-plural'),
262-
})}
263-
value={searchValue}
264-
wrapperClassName="marketplace-search-input tw:!rounded-xl tw:!items-center tw:!py-1"
265-
onChange={(value) => handleChange(value)}
266-
onKeyDown={(e) => {
267-
if (e.key === 'Enter') {
268-
handleSearch(searchValue);
269-
}
270-
}}
271-
/>
291+
<div className="tw:relative">
292+
<div className="tw:absolute tw:left-3 tw:top-1/2 tw:-translate-y-1/2 tw:z-10 tw:flex tw:items-center">
293+
{isNLPEnabled ? (
294+
<button
295+
className={`marketplace-nlq-button${
296+
isNLQActive ? ' active' : ''
297+
}`}
298+
data-testid="marketplace-nlq-toggle"
299+
title={
300+
isNLQActive
301+
? t('message.natural-language-search-active')
302+
: t('label.use-natural-language-search')
303+
}
304+
onClick={() => setIsNLQActive((prev) => !prev)}>
305+
{isNLQActive ? (
306+
<IconSuggestionsActive />
307+
) : (
308+
<IconSuggestionsBlue />
309+
)}
310+
</button>
311+
) : (
312+
<SearchLg className="tw:size-4 tw:text-text-tertiary" />
313+
)}
314+
</div>
315+
<Input
316+
autoComplete="off"
317+
data-testid="marketplace-search-input"
318+
fontSize="sm"
319+
inputClassName="tw:!pl-11"
320+
isDisabled={isEditView}
321+
placeholder={t('label.search-for-type', {
322+
type:
323+
t('label.data-product-plural') + ', ' + t('label.domain-plural'),
324+
})}
325+
value={searchValue}
326+
wrapperClassName="marketplace-search-input tw:!rounded-xl tw:!items-center tw:!py-1"
327+
onChange={(value) => handleChange(value)}
328+
onKeyDown={(e) => {
329+
if (e.key === 'Enter') {
330+
handleSearch(searchValue);
331+
}
332+
}}
333+
/>
334+
</div>
272335
<SelectPopover
273336
isNonModal
274337
className="!tw:max-h-[400px]"

0 commit comments

Comments
 (0)