diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataQualityAndProfiler.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataQualityAndProfiler.spec.ts index 6ceaa212c3e1..59769ac3d813 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataQualityAndProfiler.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataQualityAndProfiler.spec.ts @@ -941,10 +941,13 @@ test('TestCase filters', PLAYWRIGHT_INGESTION_TAG_OBJ, async ({ page }) => { await expect(page.locator('[value="tier"]')).not.toBeVisible(); // Apply domain globally - await page.locator('[data-testid="domain-dropdown"]').click(); + await page.getByTestId('domain-dropdown').click(); + await page - .locator(`li[data-menu-id*='${domain.responseData?.['name']}']`) + .getByTestId(`tag-${domain.responseData.fullyQualifiedName}`) .click(); + await page.getByTestId('saveAssociatedTag').click(); + await sidebarClick(page, SidebarItem.DATA_QUALITY); const getTestCaseList = page.waitForResponse( '/api/v1/dataQuality/testCases/search/list?*' diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Domains.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Domains.spec.ts index 6d644b2f5ab5..219790217f74 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Domains.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Domains.spec.ts @@ -240,11 +240,13 @@ test.describe('Domains', () => { await domain.create(apiContext); await page.reload(); await page.getByTestId('domain-dropdown').click(); + await page - .locator( - `[data-menu-id*="${domain.data.name}"] > .ant-dropdown-menu-title-content` - ) + .getByTestId(`tag-${domain.responseData.fullyQualifiedName}`) .click(); + await page.getByTestId('saveAssociatedTag').click(); + + await page.waitForLoadState('networkidle'); await redirectToHomePage(page); @@ -566,14 +568,11 @@ test.describe('Domains Rbac', () => { .click(); await expect( - userPage - .getByRole('menuitem', { name: domain1.data.displayName }) - .locator('span') + userPage.getByTestId(`tag-${domain1.responseData.fullyQualifiedName}`) ).toBeVisible(); + await expect( - userPage - .getByRole('menuitem', { name: domain3.data.displayName }) - .locator('span') + userPage.getByTestId(`tag-${domain3.responseData.fullyQualifiedName}`) ).toBeVisible(); // Visit explore page and verify if domain is passed in the query diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/MyData.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/MyData.spec.ts index a71cf6ad1f52..5d2de7cfc77d 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/MyData.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/MyData.spec.ts @@ -89,7 +89,7 @@ test.describe.serial('My Data page', () => { // Verify entities await verifyEntities( page, - '/api/v1/search/query?q=*&index=all&from=0&size=25', + '/api/v1/search/query?q=*&index=all&from=0&size=25*', TableEntities ); }); @@ -105,7 +105,7 @@ test.describe.serial('My Data page', () => { // Verify entities await verifyEntities( page, - '/api/v1/search/query?q=*followers:*&index=all&from=0&size=25', + '/api/v1/search/query?q=*followers:*&index=all&from=0&size=25*', TableEntities ); }); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts index 199add87205a..297bce29856e 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts @@ -347,11 +347,8 @@ export const addAssetsToDomain = async ( await page.getByTestId('save-btn').click(); await assetsAddRes; - const countRes = page.waitForResponse( - '/api/v1/search/query?q=*&index=all&from=0&size=15' - ); await page.reload(); - await countRes; + await page.waitForLoadState('networkidle'); await checkAssetsCount(page, assets.length); }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts index 471f607a0ea6..8cb2c836978b 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts @@ -303,7 +303,7 @@ export const verifyAssetsInTeamsPage = async ( .locator(`a:has-text("${team.data.displayName}")`) .click(); - const res = page.waitForResponse('/api/v1/search/query?*size=15'); + const res = page.waitForResponse('/api/v1/search/query?*size=15*'); await page.getByTestId('assets').click(); await res; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/AssetsSelectionModal/AssetSelectionModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/AssetsSelectionModal/AssetSelectionModal.tsx index 003edda57f2f..0f6ec886d9bd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/AssetsSelectionModal/AssetSelectionModal.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/AssetsSelectionModal/AssetSelectionModal.tsx @@ -131,14 +131,6 @@ export const AssetSelectionModal = ({ >([]); const [quickFilterQuery, setQuickFilterQuery] = useState(); - const [updatedQueryFilter, setUpdatedQueryFilter] = - useState( - getCombinedQueryFilterObject(queryFilter as QueryFilterInterface, { - query: { - bool: {}, - }, - }) - ); const [selectedFilter, setSelectedFilter] = useState([]); const [filters, setFilters] = useState([]); @@ -237,13 +229,17 @@ export const AssetSelectionModal = ({ useEffect(() => { if (open) { + const combinedQueryFilter = getCombinedQueryFilterObject( + queryFilter as unknown as QueryFilterInterface, + quickFilterQuery as QueryFilterInterface + ); fetchEntities({ index: activeFilter, searchText: search, - updatedQueryFilter, + updatedQueryFilter: combinedQueryFilter, }); } - }, [open, activeFilter, search, type, updatedQueryFilter]); + }, [open, activeFilter, search, type, quickFilterQuery, queryFilter]); useEffect(() => { if (open) { @@ -357,18 +353,6 @@ export const AssetSelectionModal = ({ handleSave(); }, [type, handleSave]); - const mergeFilters = useCallback(() => { - const res = getCombinedQueryFilterObject( - queryFilter as QueryFilterInterface, - quickFilterQuery as QueryFilterInterface - ); - setUpdatedQueryFilter(res); - }, [queryFilter, quickFilterQuery]); - - useEffect(() => { - mergeFilters(); - }, [quickFilterQuery, queryFilter]); - useEffect(() => { const updatedQuickFilters = filters .filter((filter) => selectedFilter.includes(filter.key)) @@ -402,22 +386,28 @@ export const AssetSelectionModal = ({ scrollHeight < 501 && items.length < totalCount ) { + const combinedQueryFilter = getCombinedQueryFilterObject( + queryFilter as unknown as QueryFilterInterface, + quickFilterQuery as QueryFilterInterface + ); + !isLoading && fetchEntities({ searchText: search, page: pageNumber + 1, index: activeFilter, - updatedQueryFilter, + updatedQueryFilter: combinedQueryFilter, }); } }, [ pageNumber, - updatedQueryFilter, activeFilter, search, totalCount, items, + quickFilterQuery, + queryFilter, isLoading, fetchEntities, ] diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataProducts/DataProductsDetailsPage/DataProductsDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataProducts/DataProductsDetailsPage/DataProductsDetailsPage.component.tsx index bf3513bd515e..6647b6711826 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataProducts/DataProductsDetailsPage/DataProductsDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataProducts/DataProductsDetailsPage/DataProductsDetailsPage.component.tsx @@ -391,7 +391,7 @@ const DataProductsDetailsPage = ({ const handelExtensionUpdate = useCallback( async (updatedDataProduct: DataProduct) => { await onUpdate({ - ...(dataProduct as DataProduct), + ...dataProduct, extension: updatedDataProduct.extension, }); }, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetailsPage/DomainDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetailsPage/DomainDetailsPage.component.tsx index 379f6afaed9d..b46180b573c8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetailsPage/DomainDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Domain/DomainDetailsPage/DomainDetailsPage.component.tsx @@ -74,15 +74,15 @@ import { Domain } from '../../../generated/entity/domains/domain'; import { ChangeDescription } from '../../../generated/entity/type'; import { Style } from '../../../generated/type/tagLabel'; import { useFqn } from '../../../hooks/useFqn'; -import { QueryFilterInterface } from '../../../pages/ExplorePage/ExplorePage.interface'; import { addDataProducts } from '../../../rest/dataProductAPI'; import { addDomains } from '../../../rest/domainAPI'; import { searchData } from '../../../rest/miscAPI'; +import { searchQuery } from '../../../rest/searchAPI'; import { formatDomainsResponse } from '../../../utils/APIUtils'; import { getIsErrorMatch } from '../../../utils/CommonUtils'; import { + getQueryFilterForDomain, getQueryFilterToExcludeDomainTerms, - getQueryFilterToIncludeDomain, } from '../../../utils/DomainUtils'; import { getEntityName } from '../../../utils/EntityUtils'; import { getEntityVersionByField } from '../../../utils/EntityVersionUtils'; @@ -348,17 +348,17 @@ const DomainDetailsPage = ({ const fetchDomainAssets = async () => { if (domainFqn && !isVersionsView) { try { - const res = await searchData( - '', - 1, - 0, - `(domain.fullyQualifiedName:"${encodedFqn}") AND !(entityType:"dataProduct")`, - '', - '', - SearchIndex.ALL - ); - - setAssetCount(res.data.hits.total.value ?? 0); + const res = await searchQuery({ + query: '', + pageNumber: 0, + pageSize: 0, + queryFilter, + searchIndex: SearchIndex.ALL, + filters: '', + }); + + const totalCount = res?.hits?.total.value ?? 0; + setAssetCount(totalCount); } catch (error) { setAssetCount(0); } @@ -506,6 +506,10 @@ const DomainDetailsPage = ({ : []), ]; + const queryFilter = useMemo(() => { + return getQueryFilterForDomain(domainFqn); + }, [domainFqn]); + const tabs = useMemo(() => { return [ { @@ -586,6 +590,7 @@ const DomainDetailsPage = ({ entityFqn={domainFqn} isSummaryPanelOpen={false} permissions={domainPermission} + queryFilter={queryFilter} ref={assetTabRef} type={AssetsOfEntity.DOMAIN} onAddAsset={() => setAssetModalVisible(true)} @@ -628,6 +633,7 @@ const DomainDetailsPage = ({ activeTab, subDomains, isSubDomainsLoading, + queryFilter, ]); useEffect(() => { @@ -824,14 +830,7 @@ const DomainDetailsPage = ({ setAssetModalVisible(false)} onSave={handleAssetSave} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/tabs/AssetsTabs.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/tabs/AssetsTabs.component.tsx index 579be93c4fd4..4581cf02b3ea 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/tabs/AssetsTabs.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/tabs/AssetsTabs.component.tsx @@ -43,10 +43,7 @@ import { ReactComponent as AddPlaceHolderIcon } from '../../../../assets/svg/add import { ReactComponent as DeleteIcon } from '../../../../assets/svg/ic-delete.svg'; import { ReactComponent as FilterIcon } from '../../../../assets/svg/ic-feeds-filter.svg'; import { ReactComponent as IconDropdown } from '../../../../assets/svg/menu.svg'; -import { - AssetsFilterOptions, - ASSET_MENU_KEYS, -} from '../../../../constants/Assets.constants'; +import { ASSET_MENU_KEYS } from '../../../../constants/Assets.constants'; import { ES_UPDATE_DELAY } from '../../../../constants/constants'; import { GLOSSARIES_DOCS } from '../../../../constants/docs.constants'; import { ERROR_PLACEHOLDER_TYPE } from '../../../../enums/common.enum'; @@ -60,6 +57,7 @@ import { usePaging } from '../../../../hooks/paging/usePaging'; import { useApplicationStore } from '../../../../hooks/useApplicationStore'; import { useFqn } from '../../../../hooks/useFqn'; import { Aggregations } from '../../../../interface/search.interface'; +import { QueryFilterInterface } from '../../../../pages/ExplorePage/ExplorePage.interface'; import { getDataProductByName, removeAssetsFromDataProduct, @@ -81,6 +79,7 @@ import { getEntityName, getEntityReferenceFromEntity, } from '../../../../utils/EntityUtils'; +import { getCombinedQueryFilterObject } from '../../../../utils/ExplorePage/ExplorePageUtils'; import { getAggregations, getQuickFilterQuery, @@ -130,17 +129,14 @@ const AssetsTabs = forwardRef( ref ) => { const { theme } = useApplicationStore(); - const [itemCount, setItemCount] = useState>( - {} as Record - ); const [assetRemoving, setAssetRemoving] = useState(false); - const [activeFilter, _] = useState([]); const { fqn } = useFqn(); const [isLoading, setIsLoading] = useState(true); const [data, setData] = useState([]); const [quickFilterQuery, setQuickFilterQuery] = - useState>(); + useState(); + const { currentPage, pageSize, @@ -165,7 +161,6 @@ const AssetsTabs = forwardRef( const [selectedCard, setSelectedCard] = useState(); const [visible, setVisible] = useState(false); const [openKeys, setOpenKeys] = useState([]); - const [isCountLoading, setIsCountLoading] = useState(true); const [showDeleteModal, setShowDeleteModal] = useState(false); const [assetToDelete, setAssetToDelete] = useState(); const [activeEntity, setActiveEntity] = useState< @@ -201,8 +196,7 @@ const AssetsTabs = forwardRef( const encodedFqn = getEncodedFqn(escapeESReservedCharacters(entityFqn)); switch (type) { case AssetsOfEntity.DOMAIN: - return `(domain.fullyQualifiedName:"${encodedFqn}") AND !(entityType:"dataProduct")`; - + return ''; case AssetsOfEntity.DATA_PRODUCT: return `(dataProducts.fullyQualifiedName:"${encodedFqn}")`; @@ -224,9 +218,11 @@ const AssetsTabs = forwardRef( async ({ index = activeFilter, page = currentPage, + queryFilter, }: { index?: SearchIndex[]; page?: number; + queryFilter?: Record; }) => { try { setIsLoading(true); @@ -235,23 +231,10 @@ const AssetsTabs = forwardRef( pageSize: pageSize, searchIndex: index, query: `*${searchValue}*`, - filters: queryParam, - queryFilter: quickFilterQuery, + filters: queryParam as string, + queryFilter: queryFilter, }); const hits = res.hits.hits as SearchedDataProps['data']; - const totalCount = res?.hits?.total.value ?? 0; - - // Find EntityType for selected searchIndex - const entityType = AssetsFilterOptions.find((f) => - activeFilter.includes(f.value) - )?.label; - - entityType && - setItemCount((prevCount) => ({ - ...prevCount, - [entityType]: totalCount, - })); - handlePagingChange({ total: res.hits.total.value ?? 0 }); setData(hits); setAggregations(getAggregations(res?.aggregations)); @@ -262,14 +245,7 @@ const AssetsTabs = forwardRef( setIsLoading(false); } }, - [ - activeFilter, - currentPage, - pageSize, - searchValue, - queryParam, - quickFilterQuery, - ] + [activeFilter, currentPage, pageSize, searchValue, queryParam] ); const hideNotification = () => { @@ -362,34 +338,6 @@ const AssetsTabs = forwardRef( }); }; - const fetchCountsByEntity = async () => { - try { - setIsCountLoading(true); - - const res = await searchQuery({ - query: `*${searchValue}*`, - pageNumber: 0, - pageSize: 0, - queryFilter: quickFilterQuery, - searchIndex: SearchIndex.ALL, - filters: queryParam, - }); - - const buckets = res.aggregations[`index_count`].buckets; - const counts: Record = {}; - buckets.forEach((item) => { - if (item) { - counts[item.key ?? ''] = item.doc_count; - } - }); - setItemCount(counts as Record); - } catch (err) { - showErrorToast(err as AxiosError); - } finally { - setIsCountLoading(false); - } - }; - const onAssetRemove = useCallback( async (assetsData: SourceType[]) => { if (!activeEntity) { @@ -465,8 +413,6 @@ const AssetsTabs = forwardRef( }, [selectedItems]); useEffect(() => { - fetchCountsByEntity(); - return () => { onAssetClick?.(undefined); hideNotification(); @@ -715,7 +661,6 @@ const AssetsTabs = forwardRef( openKeys, visible, currentPage, - itemCount, onOpenChange, handleAssetButtonVisibleChange, onSelectAll, @@ -748,11 +693,24 @@ const AssetsTabs = forwardRef( ]); useEffect(() => { + const newFilter = getCombinedQueryFilterObject( + queryFilter as unknown as QueryFilterInterface, + quickFilterQuery as QueryFilterInterface + ); + fetchAssets({ index: isEmpty(activeFilter) ? [SearchIndex.ALL] : activeFilter, page: currentPage, + queryFilter: newFilter, }); - }, [activeFilter, currentPage, pageSize, searchValue, quickFilterQuery]); + }, [ + activeFilter, + currentPage, + pageSize, + searchValue, + queryFilter, + quickFilterQuery, + ]); useEffect(() => { const dropdownItems = getAssetsPageQuickFilters(type); @@ -801,7 +759,6 @@ const AssetsTabs = forwardRef( index: isEmpty(activeFilter) ? [SearchIndex.ALL] : activeFilter, page: 1, }); - fetchCountsByEntity(); }, closeSummaryPanel() { setSelectedCard(undefined); @@ -877,7 +834,7 @@ const AssetsTabs = forwardRef( )} - {isLoading || isCountLoading ? ( + {isLoading ? ( @@ -908,7 +865,7 @@ const AssetsTabs = forwardRef( } /> - {!(isLoading || isCountLoading) && ( + {!isLoading && (
0, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/tabs/AssetsTabs.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/tabs/AssetsTabs.interface.ts index 41c39355603a..9f458a943a2f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/tabs/AssetsTabs.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/tabs/AssetsTabs.interface.ts @@ -36,7 +36,7 @@ export interface AssetsTabsProps { isSummaryPanelOpen: boolean; isEntityDeleted?: boolean; type?: AssetsOfEntity; - queryFilter?: string; + queryFilter?: string | Record; noDataPlaceholder?: string | AssetNoDataPlaceholderProps; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx index 70c0e31606cc..80bd336ee683 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx @@ -57,6 +57,7 @@ import { GlobalSettingsMenuCategory } from '../../constants/GlobalSettings.const import { HELP_ITEMS_ENUM } from '../../constants/Navbar.constants'; import { useWebSocketConnector } from '../../context/WebSocketProvider/WebSocketProvider'; import { EntityTabs, EntityType } from '../../enums/entity.enum'; +import { EntityReference } from '../../generated/entity/type'; import { BackgroundJob, JobType } from '../../generated/jobs/backgroundJob'; import { useApplicationStore } from '../../hooks/useApplicationStore'; import useCustomLocation from '../../hooks/useCustomLocation/useCustomLocation'; @@ -94,6 +95,7 @@ import { ActivityFeedTabs } from '../ActivityFeed/ActivityFeedTab/ActivityFeedTa import SearchOptions from '../AppBar/SearchOptions'; import Suggestions from '../AppBar/Suggestions'; import CmdKIcon from '../common/CmdKIcon/CmdKIcon.component'; +import DomainSelectableList from '../common/DomainSelectableList/DomainSelectableList.component'; import { useEntityExportModalProvider } from '../Entity/EntityExportModalProvider/EntityExportModalProvider.component'; import { CSVExportWebsocketResponse } from '../Entity/EntityExportModalProvider/EntityExportModalProvider.interface'; import WhatsNewModal from '../Modals/WhatsNewModal/WhatsNewModal'; @@ -124,12 +126,8 @@ const NavBar = ({ useState(false); const location = useCustomLocation(); const history = useHistory(); - const { - domainOptions, - activeDomain, - activeDomainEntityRef, - updateActiveDomain, - } = useDomainStore(); + const { activeDomain, activeDomainEntityRef, updateActiveDomain } = + useDomainStore(); const { t } = useTranslation(); const { Option } = Select; const searchRef = useRef(null); @@ -142,6 +140,8 @@ const NavBar = ({ const [activeTab, setActiveTab] = useState('Task'); const [isFeatureModalOpen, setIsFeatureModalOpen] = useState(false); const [version, setVersion] = useState(); + const [isDomainDropdownOpen, setIsDomainDropdownOpen] = useState(false); + const domainContainerRef = useRef(null); const fetchOMVersion = async () => { try { @@ -420,6 +420,30 @@ const NavBar = ({ fetchOMVersion(); }, []); + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + // Check if click is inside the domain-select-popover + const isClickInPopover = (event.target as Element)?.closest( + '.domain-select-popover' + ); + + if ( + domainContainerRef.current && + !domainContainerRef.current.contains(event.target as Node) && + !isClickInPopover && + isDomainDropdownOpen + ) { + setIsDomainDropdownOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isDomainDropdownOpen]); + useEffect(() => { const targetNode = document.body; targetNode.addEventListener('keydown', handleKeyPress); @@ -427,10 +451,14 @@ const NavBar = ({ return () => targetNode.removeEventListener('keydown', handleKeyPress); }, [handleKeyPress]); - const handleDomainChange = useCallback(({ key }) => { - updateActiveDomain(key); - refreshPage(); - }, []); + const handleDomainChange = useCallback( + async (domain: EntityReference | EntityReference[]) => { + updateActiveDomain(domain as EntityReference); + setIsDomainDropdownOpen(false); + refreshPage(); + }, + [] + ); const handleLanguageChange = useCallback(({ key }) => { i18next.changeLanguage(key); @@ -552,37 +580,38 @@ const NavBar = ({
- - - - - - - - {activeDomainEntityRef - ? getEntityName(activeDomainEntityRef) - : activeDomain} - - - - - - - +
+ setIsDomainDropdownOpen(false)} + onUpdate={handleDomainChange}> + setIsDomainDropdownOpen(!isDomainDropdownOpen)}> + + + + + + {activeDomainEntityRef + ? getEntityName(activeDomainEntityRef) + : activeDomain} + + + + + + + +
- - - diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableList/DomainSelectableList.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableList/DomainSelectableList.component.tsx index b6c64ad0262a..02ec15d89a30 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableList/DomainSelectableList.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableList/DomainSelectableList.component.tsx @@ -58,6 +58,7 @@ const DomainSelectableList = ({ popoverProps, selectedDomain, multiple = false, + onCancel, }: DomainSelectableListProps) => { const { t } = useTranslation(); const [popupVisible, setPopupVisible] = useState(false); @@ -72,6 +73,14 @@ const DomainSelectableList = ({ return []; }, [selectedDomain]); + const initialDomains = useMemo(() => { + if (selectedDomain) { + return Array.isArray(selectedDomain) ? selectedDomain : [selectedDomain]; + } + + return []; + }, [selectedDomain]); + const handleUpdate = useCallback( async (domains: EntityReference[]) => { if (multiple) { @@ -85,6 +94,11 @@ const DomainSelectableList = ({ [onUpdate, multiple] ); + const handleCancel = useCallback(() => { + setPopupVisible(false); + onCancel?.(); + }, [onCancel]); + return ( // Used Button to stop click propagation event anywhere in the component to parent // TeamDetailV1 collapsible panel @@ -95,10 +109,11 @@ const DomainSelectableList = ({ destroyTooltipOnHide content={ setPopupVisible(false)} + onCancel={handleCancel} onSubmit={handleUpdate} /> } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableList/DomainSelectableList.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableList/DomainSelectableList.interface.ts index 9962a54c8a3f..0fcd98a5055b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableList/DomainSelectableList.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableList/DomainSelectableList.interface.ts @@ -21,4 +21,5 @@ export interface DomainSelectableListProps { popoverProps?: PopoverProps; selectedDomain?: EntityReference | EntityReference[]; multiple?: boolean; + onCancel?: () => void; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableTree/DomainSelectableTree.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableTree/DomainSelectableTree.interface.ts index b3c140eda20f..076576357ee8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableTree/DomainSelectableTree.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableTree/DomainSelectableTree.interface.ts @@ -19,6 +19,7 @@ export interface DomainSelectableTreeProps { visible: boolean; onCancel: () => void; isMultiple?: boolean; + initialDomains?: EntityReference[]; } export type TreeListItem = Omit; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableTree/DomainSelectableTree.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableTree/DomainSelectableTree.tsx index f6619142bb53..950242adf193 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableTree/DomainSelectableTree.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/DomainSelectableTree/DomainSelectableTree.tsx @@ -29,7 +29,10 @@ import { EntityType } from '../../../enums/entity.enum'; import { Domain } from '../../../generated/entity/domains/domain'; import { EntityReference } from '../../../generated/tests/testCase'; import { listDomainHierarchy, searchDomains } from '../../../rest/domainAPI'; -import { convertDomainsToTreeOptions } from '../../../utils/DomainUtils'; +import { + convertDomainsToTreeOptions, + isDomainExist, +} from '../../../utils/DomainUtils'; import { getEntityReferenceFromEntity } from '../../../utils/EntityUtils'; import { findItemByFqn } from '../../../utils/GlossaryUtils'; import { @@ -50,6 +53,7 @@ const DomainSelectablTree: FC = ({ visible, onCancel, isMultiple = false, + initialDomains, }) => { const { t } = useTranslation(); const [treeData, setTreeData] = useState([]); @@ -59,14 +63,30 @@ const DomainSelectablTree: FC = ({ const [selectedDomains, setSelectedDomains] = useState([]); const [searchTerm, setSearchTerm] = useState(''); - const handleSave = async () => { - setIsSubmitLoading(true); - if (isMultiple) { + const handleMultiDomainSave = async () => { + const selectedFqns = selectedDomains + .map((domain) => domain.fullyQualifiedName) + .sort((a, b) => (a ?? '').localeCompare(b ?? '')); + const initialFqns = (value as string[]).sort((a, b) => a.localeCompare(b)); + + if (JSON.stringify(selectedFqns) !== JSON.stringify(initialFqns)) { + setIsSubmitLoading(true); const domains1 = selectedDomains.map((item) => getEntityReferenceFromEntity(item, EntityType.DOMAIN) ); await onSubmit(domains1); + setIsSubmitLoading(false); } else { + onCancel(); + } + }; + + const handleSingleDomainSave = async () => { + const selectedFqn = selectedDomains[0]?.fullyQualifiedName; + const initialFqn = value?.[0]; + + if (selectedFqn !== initialFqn) { + setIsSubmitLoading(true); let retn: EntityReference[] = []; if (selectedDomains.length > 0) { const domain = getEntityReferenceFromEntity( @@ -76,23 +96,35 @@ const DomainSelectablTree: FC = ({ retn = [domain]; } await onSubmit(retn); + setIsSubmitLoading(false); + } else { + onCancel(); } - - setIsSubmitLoading(false); }; const fetchAPI = useCallback(async () => { try { setIsLoading(true); const data = await listDomainHierarchy({ limit: 100 }); - setTreeData(convertDomainsToTreeOptions(data.data, 0, isMultiple)); - setDomains(data.data); + + const combinedData = [...data.data]; + initialDomains?.forEach((selectedDomain) => { + const exists = combinedData.some((domain: Domain) => + isDomainExist(domain, selectedDomain.fullyQualifiedName ?? '') + ); + if (!exists) { + combinedData.push(selectedDomain as unknown as Domain); + } + }); + + setTreeData(convertDomainsToTreeOptions(combinedData, 0, isMultiple)); + setDomains(combinedData); } catch (error) { showErrorToast(error as AxiosError); } finally { setIsLoading(false); } - }, []); + }, [initialDomains]); const onSelect = (selectedKeys: React.Key[]) => { if (!isMultiple) { @@ -120,13 +152,11 @@ const DomainSelectablTree: FC = ({ setSelectedDomains(selectedData); } else { - setSelectedDomains([ - findItemByFqn( - domains, - checked.checked as unknown as string, - false - ) as Domain, - ]); + const selected = checked.checked.map( + (item) => findItemByFqn(domains, item as string, false) as Domain + ); + + setSelectedDomains(selected); } }; @@ -217,7 +247,7 @@ const DomainSelectablTree: FC = ({ loading={isSubmitLoading} size="small" type="default" - onClick={() => handleSave()}> + onClick={isMultiple ? handleMultiDomainSave : handleSingleDomainSave}> {t('label.update')}