diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.js index 6d11ddd988b..57599ee349f 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.js @@ -288,7 +288,7 @@ const updateTerms = (newTerm) => { .contains(newTerm) .should('be.visible') .click(); - cy.get('[data-testid="save-related-term-btn"]').should('be.visible').click(); + cy.get('[data-testid="saveAssociatedTag"]').should('be.visible').click(); verifyResponseStatusCode('@saveGlossaryTermData', 200); cy.get('[data-testid="related-term-container"]') diff --git a/openmetadata-ui/src/main/resources/ui/src/components/InfiniteSelectScroll/InfiniteSelectScroll.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/AsyncSelectList/AsyncSelectList.interface.ts similarity index 95% rename from openmetadata-ui/src/main/resources/ui/src/components/InfiniteSelectScroll/InfiniteSelectScroll.interface.ts rename to openmetadata-ui/src/main/resources/ui/src/components/AsyncSelectList/AsyncSelectList.interface.ts index 95159768b16..ddc513a306f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/InfiniteSelectScroll/InfiniteSelectScroll.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/AsyncSelectList/AsyncSelectList.interface.ts @@ -18,7 +18,7 @@ export type SelectOption = { value: string; }; -export interface InfiniteSelectScrollProps { +export interface AsyncSelectListProps { mode?: 'multiple'; placeholder?: string; debounceTimeout?: number; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/InfiniteSelectScroll/InfiniteSelectScroll.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AsyncSelectList/AsyncSelectList.tsx similarity index 95% rename from openmetadata-ui/src/main/resources/ui/src/components/InfiniteSelectScroll/InfiniteSelectScroll.tsx rename to openmetadata-ui/src/main/resources/ui/src/components/AsyncSelectList/AsyncSelectList.tsx index c5f22c0bf62..23c7d1a8704 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/InfiniteSelectScroll/InfiniteSelectScroll.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AsyncSelectList/AsyncSelectList.tsx @@ -21,11 +21,11 @@ import { tagRender } from 'utils/TagsUtils'; import { showErrorToast } from 'utils/ToastUtils'; import Fqn from '../../utils/Fqn'; import { - InfiniteSelectScrollProps, + AsyncSelectListProps, SelectOption, -} from './InfiniteSelectScroll.interface'; +} from './AsyncSelectList.interface'; -const InfiniteSelectScroll: FC = ({ +const AsyncSelectList: FC = ({ mode, onChange, fetchOptions, @@ -44,10 +44,11 @@ const InfiniteSelectScroll: FC = ({ setOptions([]); setIsLoading(true); try { - const res = await fetchOptions(value, currentPage); + const res = await fetchOptions(value, 1); setOptions(res.data); setPaging(res.paging); setSearchValue(value); + setCurrentPage(1); } catch (error) { showErrorToast(error as AxiosError); } finally { @@ -156,4 +157,4 @@ const InfiniteSelectScroll: FC = ({ ); }; -export default InfiniteSelectScroll; +export default AsyncSelectList; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/tabs/GlossaryOverviewTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/tabs/GlossaryOverviewTab.component.tsx index aa8759931ea..ad417efc04a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/tabs/GlossaryOverviewTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/tabs/GlossaryOverviewTab.component.tsx @@ -32,7 +32,7 @@ import RelatedTerms from './RelatedTerms'; type Props = { selectedData: Glossary | GlossaryTerm; permissions: OperationPermission; - onUpdate: (data: GlossaryTerm | Glossary) => void; + onUpdate: (data: GlossaryTerm | Glossary) => Promise; isGlossary: boolean; isVersionView?: boolean; }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/tabs/RelatedTerms.tsx b/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/tabs/RelatedTerms.tsx index 5b0fca09807..d90e6f8d822 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/tabs/RelatedTerms.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/tabs/RelatedTerms.tsx @@ -11,19 +11,21 @@ * limitations under the License. */ -import { CheckOutlined, CloseOutlined } from '@ant-design/icons'; -import { Button, Select, Space, Spin, Tooltip, Typography } from 'antd'; +import { Button, Tooltip, Typography } from 'antd'; import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg'; import { ReactComponent as IconFlatDoc } from 'assets/svg/ic-flat-doc.svg'; +import TagSelectForm from 'components/Tag/TagsSelectForm/TagsSelectForm.component'; import TagButton from 'components/TagButton/TagButton.component'; import { EntityField } from 'constants/Feeds.constants'; import { NO_PERMISSION_FOR_ACTION } from 'constants/HelperTextUtil'; import { ChangeDescription } from 'generated/entity/type'; +import { Paging } from 'generated/type/paging'; import { t } from 'i18next'; -import { cloneDeep, debounce, includes, isEmpty } from 'lodash'; +import { cloneDeep, includes, isEmpty, uniqWith } from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { searchData } from 'rest/miscAPI'; +import { formatSearchGlossaryTermResponse } from 'utils/APIUtils'; import { getEntityName } from 'utils/EntityUtils'; import { getChangedEntityNewValue, @@ -41,7 +43,6 @@ import { import { SearchIndex } from '../../../enums/search.enum'; import { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm'; import { EntityReference } from '../../../generated/type/entityReference'; -import { formatSearchGlossaryTermResponse } from '../../../utils/APIUtils'; import { getEntityReferenceFromGlossary } from '../../../utils/GlossaryUtils'; import { OperationPermission } from '../../PermissionProvider/PermissionProvider.interface'; @@ -49,7 +50,7 @@ interface RelatedTermsProps { isVersionView?: boolean; permissions: OperationPermission; glossaryTerm: GlossaryTerm; - onGlossaryTermUpdate: (data: GlossaryTerm) => void; + onGlossaryTermUpdate: (data: GlossaryTerm) => Promise; } const RelatedTerms = ({ @@ -60,26 +61,21 @@ const RelatedTerms = ({ }: RelatedTermsProps) => { const history = useHistory(); const [isIconVisible, setIsIconVisible] = useState(true); - const [isLoading, setIsLoading] = useState(false); const [options, setOptions] = useState([]); const [selectedOption, setSelectedOption] = useState([]); - const getSearchedTerms = (searchedData: EntityReference[]) => { - const currOptions = selectedOption.map( - (item) => item.fullyQualifiedName || item.name - ); - const data = searchedData.filter((item: EntityReference) => { - return !currOptions.includes(item.fullyQualifiedName); - }); - - return [...selectedOption, ...data]; - }; - const handleRelatedTermClick = (fqn: string) => { history.push(getGlossaryPath(fqn)); }; - const handleRelatedTermsSave = (newOptions: EntityReference[]) => { + const handleRelatedTermsSave = async ( + selectedData: string[] + ): Promise => { + const newOptions = uniqWith( + options, + (arrVal, othVal) => arrVal.id === othVal.id + ).filter((item) => includes(selectedData, item.fullyQualifiedName)); + let updatedGlossaryTerm = cloneDeep(glossaryTerm); const oldTerms = newOptions.filter((d) => includes(glossaryTerm.relatedTerms, d) @@ -97,33 +93,50 @@ const RelatedTerms = ({ relatedTerms: [...oldTerms, ...newTerms], }; - onGlossaryTermUpdate(updatedGlossaryTerm); + await onGlossaryTermUpdate(updatedGlossaryTerm); setIsIconVisible(true); }; - const suggestionSearch = (searchText = '') => { - setIsLoading(true); - searchData(searchText, 1, PAGE_SIZE, '', '', '', SearchIndex.GLOSSARY) - .then((res) => { - const termResult = formatSearchGlossaryTermResponse( - res.data.hits.hits - ).filter((item) => { - return item.fullyQualifiedName !== glossaryTerm.fullyQualifiedName; - }); + const fetchGlossaryTerms = async ( + searchText = '', + page: number + ): Promise<{ + data: { + label: string; + value: string; + }[]; + paging: Paging; + }> => { + const res = await searchData( + searchText, + page, + PAGE_SIZE, + '', + '', + '', + SearchIndex.GLOSSARY + ); - const results = termResult.map(getEntityReferenceFromGlossary); + const termResult = formatSearchGlossaryTermResponse( + res.data.hits.hits + ).filter( + (item) => item.fullyQualifiedName !== glossaryTerm.fullyQualifiedName + ); - const data = searchText ? getSearchedTerms(results) : results; - setOptions(data); - }) - .catch(() => { - setOptions(selectedOption); - }) - .finally(() => setIsLoading(false)); + const results = termResult.map(getEntityReferenceFromGlossary); + setOptions((prev) => [...prev, ...results]); + + return { + data: results.map((item) => ({ + label: item.fullyQualifiedName ?? '', + value: item.fullyQualifiedName ?? '', + })), + paging: { + total: res.data.hits.total.value, + }, + }; }; - const debounceOnSearch = useCallback(debounce(suggestionSearch, 250), []); - const formatOptions = (data: EntityReference[]) => { return data.map((value) => ({ ...value, @@ -134,14 +147,13 @@ const RelatedTerms = ({ }; const handleCancel = () => { - setSelectedOption(formatOptions(glossaryTerm.relatedTerms || [])); setIsIconVisible(true); }; useEffect(() => { - if (glossaryTerm.relatedTerms?.length) { - setOptions(glossaryTerm.relatedTerms); - setSelectedOption(formatOptions(glossaryTerm.relatedTerms)); + if (glossaryTerm) { + setOptions(glossaryTerm.relatedTerms ?? []); + setSelectedOption(formatOptions(glossaryTerm.relatedTerms ?? [])); } }, [glossaryTerm]); @@ -279,41 +291,17 @@ const RelatedTerms = ({ {isIconVisible ? ( relatedTermsContainer ) : ( - <> - -