UNPKG

@finos/legend-application-marketplace

Version:
252 lines 17.3 kB
import { createElement as _createElement } from "react"; import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; /** * Copyright (c) 2020-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { useState, useMemo, useCallback, useEffect } from 'react'; import { Autocomplete, Box, CircularProgress, FormControlLabel, IconButton, InputAdornment, Menu, MenuItem, Switch, TextField, Typography, } from '@mui/material'; import { clsx, SearchIcon, TuneIcon } from '@finos/legend-art'; import { observer } from 'mobx-react-lite'; import { LegendMarketplaceInfoTooltip } from '../InfoTooltip/LegendMarketplaceInfoTooltip.js'; import { LegendMarketplaceTelemetryHelper } from '../../__lib__/LegendMarketplaceTelemetryHelper.js'; import { useLegendMarketplaceBaseStore } from '../../application/providers/LegendMarketplaceFrameworkProvider.js'; import { createDefaultSuggestions, createAutosuggestSuggestions, createSearchQuerySuggestion, createLoadingSuggestion, SearchSuggestionType, SEARCH_SUGGESTION_CONSTANTS, } from '../../utils/SearchSuggestions.js'; import { debounce, assertErrorThrown, LogEvent, } from '@finos/legend-shared'; import { APPLICATION_EVENT } from '@finos/legend-application'; import { generatePathForDataProductSearchResult, convertAutosuggestResultToSearchResult, } from '../../utils/SearchUtils.js'; export const LegendMarketplaceSearchBar = observer((props) => { const { onSearch, stateSearchQuery, placeholder, onChange, className, showSettings, stateUseProducerSearch, stateUseFieldSearch, enableAutosuggest = true, } = props; const legendMarketplaceBaseStore = useLegendMarketplaceBaseStore(); const applicationStore = legendMarketplaceBaseStore.applicationStore; const [searchQuery, setSearchQuery] = useState(stateSearchQuery ?? ''); const [useProducerSearch, setUseProducerSearch] = useState(stateUseProducerSearch ?? false); const [useFieldSearch, setUseFieldSearch] = useState(stateUseFieldSearch ?? false); const [searchMenuAnchorEl, setSearchMenuAnchorEl] = useState(); const [suggestions, setSuggestions] = useState([]); const [loadingSuggestions, setLoadingSuggestions] = useState(false); const [isAutosuggestPopupOpen, setIsAutosuggestPopupOpen] = useState(false); const searchMenuOpen = Boolean(searchMenuAnchorEl); const defaultSuggestionsFromConfig = applicationStore.config.options.defaultSearchSuggestions; const fetchAutosuggestions = useCallback(async (query, signal) => { if (!enableAutosuggest) { return; } try { const response = await legendMarketplaceBaseStore.marketplaceServerClient.getAutosuggestions(query, legendMarketplaceBaseStore.envState.lakehouseEnvironment, SEARCH_SUGGESTION_CONSTANTS.AUTOSUGGEST_LIMIT, signal); const autosuggestResults = response.results; const userQuerySuggestion = createSearchQuerySuggestion(query); if (autosuggestResults.length > 0) { setSuggestions([ userQuerySuggestion, ...createAutosuggestSuggestions(autosuggestResults), ]); } else { setSuggestions([userQuerySuggestion]); } } catch (error) { assertErrorThrown(error); if (error.name === 'AbortError') { return; } applicationStore.logService.error(LogEvent.create(APPLICATION_EVENT.GENERIC_FAILURE), error); const fallbackQuerySuggestion = createSearchQuerySuggestion(query); setSuggestions([fallbackQuerySuggestion]); } finally { if (!signal?.aborted) { setLoadingSuggestions(false); } } }, [ enableAutosuggest, legendMarketplaceBaseStore.marketplaceServerClient, legendMarketplaceBaseStore.envState.lakehouseEnvironment, applicationStore.logService, ]); const debouncedFetchAutosuggestions = useMemo(() => debounce(fetchAutosuggestions, SEARCH_SUGGESTION_CONSTANTS.AUTOSUGGEST_DEBOUNCE_DELAY), [fetchAutosuggestions]); // Cleanup debounced function on unmount useEffect(() => { return () => { debouncedFetchAutosuggestions.cancel(); }; }, [debouncedFetchAutosuggestions]); // Ensure component's state is in sync with external state useEffect(() => { setSearchQuery(stateSearchQuery ?? ''); }, [stateSearchQuery]); useEffect(() => { setUseProducerSearch(stateUseProducerSearch ?? false); }, [stateUseProducerSearch]); useEffect(() => { setUseFieldSearch(stateUseFieldSearch ?? false); }, [stateUseFieldSearch]); useEffect(() => { const abortController = new AbortController(); if (isAutosuggestPopupOpen) { if (!searchQuery || searchQuery.trim().length === 0) { setSuggestions(createDefaultSuggestions(defaultSuggestionsFromConfig)); setLoadingSuggestions(false); } else { const userQuerySuggestion = createSearchQuerySuggestion(searchQuery); const loadingIndicator = createLoadingSuggestion(); setSuggestions([userQuerySuggestion, loadingIndicator]); setLoadingSuggestions(true); // eslint-disable-next-line no-void void debouncedFetchAutosuggestions(searchQuery, abortController.signal); } } return () => { abortController.abort(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchQuery, isAutosuggestPopupOpen, debouncedFetchAutosuggestions]); const handleInputChange = (_event, newInputValue) => { setSearchQuery(newInputValue); onChange?.(newInputValue); }; const handleSuggestionSelection = (_event, selectedSuggestion) => { if (!selectedSuggestion) { return; } if (typeof selectedSuggestion === 'string') { setSearchQuery(selectedSuggestion); return; } if (selectedSuggestion.type === SearchSuggestionType.LOADING) { return; } const selectedQuery = selectedSuggestion.query; setSearchQuery(selectedQuery); if (selectedSuggestion.type === SearchSuggestionType.SEARCH_QUERY || selectedSuggestion.type === SearchSuggestionType.DEFAULT) { onSearch?.(selectedQuery, useProducerSearch, useFieldSearch); LegendMarketplaceTelemetryHelper.logEvent_SearchAutosuggestSelection(applicationStore.telemetryService, selectedQuery, selectedSuggestion.type); } else { const autosuggestResult = selectedSuggestion.autosuggestResult; if (autosuggestResult) { const searchResult = convertAutosuggestResultToSearchResult(autosuggestResult); const dataProductViewerPath = generatePathForDataProductSearchResult(searchResult); if (dataProductViewerPath) { applicationStore.navigationService.navigator.visitAddress(applicationStore.navigationService.navigator.generateAddress(dataProductViewerPath)); } LegendMarketplaceTelemetryHelper.logEvent_SearchAutosuggestSelection(applicationStore.telemetryService, selectedQuery, selectedSuggestion.type); } } }; const handleSubmit = (event) => { event.preventDefault(); onSearch?.(searchQuery, useProducerSearch, useFieldSearch); }; const getOptionLabel = (option) => typeof option === 'string' ? option : option.query; const filterOptions = (options) => { return options; }; const getGroupLabel = (option) => { if (typeof option === 'string') { return ''; } if (option.type === SearchSuggestionType.SEARCH_QUERY || option.type === SearchSuggestionType.LOADING) { return ''; } return option.type === SearchSuggestionType.DEFAULT ? SEARCH_SUGGESTION_CONSTANTS.GROUP_HEADER_SUGGESTED_SEARCHES : SEARCH_SUGGESTION_CONSTANTS.GROUP_HEADER_DATA_PRODUCTS; }; return (_jsxs("form", { className: clsx('legend-marketplace__search-bar', className), onSubmit: handleSubmit, children: [_jsx(Autocomplete, { freeSolo: true, fullWidth: true, open: enableAutosuggest ? isAutosuggestPopupOpen : false, onOpen: () => { if (enableAutosuggest) { setIsAutosuggestPopupOpen(true); } }, onClose: () => { setIsAutosuggestPopupOpen(false); }, value: null, inputValue: searchQuery, onInputChange: handleInputChange, onChange: handleSuggestionSelection, options: suggestions, filterOptions: filterOptions, getOptionLabel: getOptionLabel, groupBy: getGroupLabel, slotProps: { popper: { className: 'legend-marketplace__search-bar__dropdown', modifiers: [ { name: 'offset', options: { offset: [0, 4], }, }, { name: 'sameWidth', enabled: true, phase: 'beforeWrite', requires: ['computeStyles'], fn: ({ state }) => { if (state.styles.popper) { state.styles.popper.width = `${state.rects.reference.width}px`; } }, effect: ({ state }) => { const referenceWidth = state.elements.reference.offsetWidth; state.elements.popper.style.width = `${referenceWidth}px`; }, }, ], placement: 'bottom-start', }, }, renderGroup: (params) => (_jsxs(Box, { children: [params.group === SEARCH_SUGGESTION_CONSTANTS.GROUP_HEADER_DATA_PRODUCTS && (_jsx("div", { className: "legend-marketplace__search-bar__section-divider" })), params.group && (_jsx(Typography, { className: "legend-marketplace__search-bar__autocomplete-group-header", children: params.group })), params.children] }, params.key)), renderOption: (params, suggestionOption) => { if (typeof suggestionOption === 'string') { return (_createElement(Box, { component: "li", ...params, key: suggestionOption }, _jsx(Typography, { className: "legend-marketplace__search-bar__autocomplete-option__text", children: suggestionOption }))); } if (suggestionOption.type === SearchSuggestionType.SEARCH_QUERY) { return (_createElement(Box, { component: "li", ...params, key: suggestionOption.query, className: "legend-marketplace__search-bar__autocomplete-option" }, _jsxs("div", { className: "legend-marketplace__search-bar__autocomplete-option__search-query", children: [_jsx(SearchIcon, { className: "legend-marketplace__search-bar__autocomplete-option__search-icon" }), _jsx(Typography, { className: "legend-marketplace__search-bar__autocomplete-option__text", children: suggestionOption.query })] }))); } if (suggestionOption.type === SearchSuggestionType.LOADING) { return (_createElement(Box, { component: "li", ...params, key: SEARCH_SUGGESTION_CONSTANTS.LOADING_KEY, className: "legend-marketplace__search-bar__autocomplete-option legend-marketplace__search-bar__autocomplete-option--loading", style: { cursor: 'default' } }, _jsxs("div", { className: "legend-marketplace__search-bar__autocomplete-option__loading", children: [_jsx(CircularProgress, { size: 16, sx: { color: 'var(--marketplace-text-secondary)' } }), _jsx(Typography, { className: "legend-marketplace__search-bar__autocomplete-option__text", sx: { color: 'var(--marketplace-text-secondary)' }, children: suggestionOption.query })] }))); } if (suggestionOption.type === SearchSuggestionType.DEFAULT) { return (_createElement(Box, { component: "li", ...params, key: suggestionOption.query, className: "legend-marketplace__search-bar__autocomplete-option" }, _jsx(Typography, { className: "legend-marketplace__search-bar__autocomplete-option__text", children: suggestionOption.query }))); } const autosuggestResult = suggestionOption.autosuggestResult; if (!autosuggestResult) { return null; } const dataProductName = autosuggestResult.dataProductName; const dataProductDescription = autosuggestResult.dataProductDescription; return (_createElement(Box, { component: "li", ...params, key: dataProductName, className: "legend-marketplace__search-bar__autocomplete-option" }, _jsxs("div", { className: "legend-marketplace__search-bar__autocomplete-option__data-product", children: [_jsx("div", { className: "legend-marketplace__search-bar__autocomplete-option__name", children: dataProductName }), dataProductDescription && (_jsx("div", { className: "legend-marketplace__search-bar__autocomplete-option__description", children: dataProductDescription }))] }))); }, renderInput: (params) => (_jsx(TextField, { ...params, className: "legend-marketplace__search-bar__text-field", placeholder: placeholder ?? SEARCH_SUGGESTION_CONSTANTS.DEFAULT_PLACEHOLDER, fullWidth: true, slotProps: { input: { ...params.InputProps, className: 'legend-marketplace__search-bar__input', endAdornment: (_jsxs(_Fragment, { children: [loadingSuggestions && (_jsx(CircularProgress, { color: "inherit", size: 28 })), params.InputProps.endAdornment, _jsxs(InputAdornment, { position: "end", children: [showSettings && (_jsx(IconButton, { onClick: (event) => setSearchMenuAnchorEl(event.currentTarget), title: "Search settings", className: "legend-marketplace__search-bar__settings-icon", children: _jsx(TuneIcon, {}) })), _jsx(IconButton, { type: "submit", title: "Search", className: "legend-marketplace__search-bar__search-icon", children: _jsx(SearchIcon, {}) })] })] })), }, } })) }), showSettings && (_jsxs(Menu, { anchorEl: searchMenuAnchorEl, open: searchMenuOpen, onClose: () => setSearchMenuAnchorEl(null), children: [_jsx(MenuItem, { children: _jsx(FormControlLabel, { control: _jsx(Switch, { checked: useProducerSearch, onChange: (event) => { setUseProducerSearch(event.target.checked); if (event.target.checked) { setUseFieldSearch(false); } LegendMarketplaceTelemetryHelper.logEvent_ToggleProducerSearch(applicationStore.telemetryService, event.target.checked); } }), label: _jsxs(_Fragment, { children: ["Producer Search", ' ', _jsx(LegendMarketplaceInfoTooltip, { title: "Use this search if you have just created a data product and would like to immediately see it" })] }) }) }), _jsx(MenuItem, { children: _jsx(FormControlLabel, { control: _jsx(Switch, { checked: useFieldSearch, onChange: (event) => { setUseFieldSearch(event.target.checked); if (event.target.checked) { setUseProducerSearch(false); } LegendMarketplaceTelemetryHelper.logEvent_ToggleFieldSearch(applicationStore.telemetryService, event.target.checked); } }), label: _jsxs(_Fragment, { children: ["Field Search", ' ', _jsx(LegendMarketplaceInfoTooltip, { title: "Use this search to discover data products and datasets that contain a specific field" })] }) }) })] }))] })); }); //# sourceMappingURL=LegendMarketplaceSearchBar.js.map