diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppDetails/AppDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppDetails/AppDetails.component.tsx index 9aab53df867..5b13f11e287 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppDetails/AppDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppDetails/AppDetails.component.tsx @@ -365,7 +365,7 @@ const AppDetails = () => { ), }, ...tabConfiguration, - ...(appData?.appType === AppType.Internal && !appData?.deleted + ...(!appData?.deleted ? [ { label: ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppRunsHistory/AppRunsHistory.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppRunsHistory/AppRunsHistory.component.tsx index 00a1a122dae..cd7339e2a9d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppRunsHistory/AppRunsHistory.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppRunsHistory/AppRunsHistory.component.tsx @@ -212,21 +212,25 @@ const AppRunsHistory = forwardRef( if (isExternalApp) { const currentTime = Date.now(); - const oneDayAgo = getEpochMillisForPastDays(1); + // past 30 days + const startDay = getEpochMillisForPastDays(30); const { data } = await getApplicationRuns(fqn, { - startTs: oneDayAgo, + startTs: startDay, endTs: currentTime, }); setAppRunsHistoryData( - data.map((item) => ({ - ...item, - status: getStatusFromPipelineState( - (item as PipelineStatus).pipelineState ?? PipelineState.Failed - ), - id: (item as PipelineStatus).runId ?? '', - })) + data + .map((item) => ({ + ...item, + status: getStatusFromPipelineState( + (item as PipelineStatus).pipelineState ?? + PipelineState.Failed + ), + id: (item as PipelineStatus).runId ?? '', + })) + .slice(0, maxRecords) ); } else { const { data, paging } = await getApplicationRuns(fqn, { @@ -248,7 +252,7 @@ const AppRunsHistory = forwardRef( setIsLoading(false); } }, - [fqn, pageSize, maxRecords] + [fqn, pageSize, maxRecords, appData] ); const handleAppHistoryPageChange = ({ 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 447af159a5d..2d144865290 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 @@ -411,12 +411,14 @@ const DataProductsDetailsPage = ({ rightPanelWidth={400}> setAssetModelVisible(true)} onAssetClick={handleAssetClick} + onRemoveAsset={handleAssetSave} /> ), @@ -429,6 +431,7 @@ const DataProductsDetailsPage = ({ previewAsset, dataProduct, isVersionsView, + handleAssetSave, assetCount, activeTab, ]); @@ -571,7 +574,8 @@ const DataProductsDetailsPage = ({ entityFqn={dataProductFqn} open={assetModalVisible} queryFilter={getQueryFilterToIncludeDomain( - dataProduct.domain?.fullyQualifiedName ?? '' + dataProduct.domain?.fullyQualifiedName ?? '', + dataProduct.fullyQualifiedName ?? '' )} type={AssetsOfEntity.DATA_PRODUCT} onCancel={() => setAssetModelVisible(false)} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataProducts/DataProductsDetailsPage/data-products-details-page.less b/openmetadata-ui/src/main/resources/ui/src/components/DataProducts/DataProductsDetailsPage/data-products-details-page.less index cd099e288c1..6807fa2e733 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataProducts/DataProductsDetailsPage/data-products-details-page.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataProducts/DataProductsDetailsPage/data-products-details-page.less @@ -24,7 +24,8 @@ margin-left: 24px; } .assets-data-container { - padding: 12px 18px; + padding-left: 18px; + padding-right: 18px; } .page-layout-v1-vertical-scroll, .page-layout-leftpanel { 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 e5de84f8b52..d815a72fd85 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 @@ -71,6 +71,7 @@ import { Style } from '../../../generated/type/tagLabel'; import { addDataProducts } from '../../../rest/dataProductAPI'; import { searchData } from '../../../rest/miscAPI'; import { getIsErrorMatch } from '../../../utils/CommonUtils'; +import { getQueryFilterToExcludeDomainTerms } from '../../../utils/DomainUtils'; import { getEntityVersionByField } from '../../../utils/EntityVersionUtils'; import Fqn from '../../../utils/Fqn'; import { DEFAULT_ENTITY_PERMISSION } from '../../../utils/PermissionsUtils'; @@ -472,12 +473,14 @@ const DomainDetailsPage = ({ rightPanelWidth={400}> setAssetModelVisible(true)} onAssetClick={handleAssetClick} + onRemoveAsset={handleAssetSave} /> ), @@ -490,6 +493,7 @@ const DomainDetailsPage = ({ domainPermission, previewAsset, handleAssetClick, + handleAssetSave, assetCount, dataProductsCount, activeTab, @@ -627,6 +631,7 @@ const DomainDetailsPage = ({ setAssetModelVisible(false)} onSave={handleAssetSave} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/GlossaryTermSummary/GlossaryTermSummary.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/GlossaryTermSummary/GlossaryTermSummary.component.tsx index 5d5d4cf78a0..ede68aede21 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/GlossaryTermSummary/GlossaryTermSummary.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/GlossaryTermSummary/GlossaryTermSummary.component.tsx @@ -12,6 +12,7 @@ */ import { Col, Divider, Row, Space, Typography } from 'antd'; +import { isEmpty } from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { SummaryEntityType } from '../../../../enums/EntitySummary.enum'; @@ -48,7 +49,10 @@ function GlossaryTermSummary({ [selectedData] ); - const synonyms = useMemo(() => entityDetails.synonyms ?? [], [selectedData]); + const synonyms = useMemo( + () => entityDetails.synonyms?.filter((item) => !isEmpty(item)) ?? [], + [selectedData] + ); const fetchGlossaryTermDetails = useCallback(async () => { try { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.interface.ts index caa75387b29..79539a50c3b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.interface.ts @@ -31,4 +31,5 @@ export interface ExploreSearchCardProps { showTags?: boolean; openEntityInNewPage?: boolean; hideBreadcrumbs?: boolean; + actionPopoverContent?: React.ReactNode; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.tsx index 54f001ff305..73135f927a7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/ExploreSearchCard.tsx @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Button, Col, Row, Typography } from 'antd'; +import { Button, Col, Row, Space, Typography } from 'antd'; import classNames from 'classnames'; import { isString, startCase, uniqueId } from 'lodash'; import { ExtraInfo } from 'Models'; @@ -58,6 +58,7 @@ const ExploreSearchCard: React.FC = forwardRef< showTags = true, openEntityInNewPage, hideBreadcrumbs = false, + actionPopoverContent, }, ref ) => { @@ -245,6 +246,9 @@ const ExploreSearchCard: React.FC = forwardRef< ))} ) : null} + {actionPopoverContent && ( + {actionPopoverContent} + )} ); } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/explore-search-card.less b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/explore-search-card.less index b4a415c0995..eda66d16ed9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/explore-search-card.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/ExploreV1/ExploreSearchCard/explore-search-card.less @@ -15,6 +15,7 @@ .explore-search-card { background-color: @white; padding: 20px; + position: relative; &.highlight-card { border-left: 4px solid @info-color; box-shadow: none; @@ -22,4 +23,22 @@ .entity-summary-details { font-size: 12px; } + .explore-card-actions { + position: absolute; + right: 10px; + top: -12px; + transition: width 0.3s; + background: @white; + border: @global-border; + padding: 4px; + border-radius: 6px; + display: none; + } + + &:hover { + background-color: @grey-1; + .explore-card-actions { + display: flex; + } + } } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/GlossaryTermsV1.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/GlossaryTermsV1.component.tsx index 605622abd3e..9bd5337a7e3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/GlossaryTermsV1.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/GlossaryTermsV1.component.tsx @@ -13,7 +13,13 @@ import { Col, Row, Tabs } from 'antd'; import { t } from 'i18next'; -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { useHistory, useParams } from 'react-router-dom'; import { getGlossaryTermDetailsPath } from '../../../constants/constants'; import { EntityField } from '../../../constants/Feeds.constants'; @@ -97,6 +103,12 @@ const GlossaryTermsV1 = ({ ); }; + const handleAssetSave = useCallback(() => { + fetchGlossaryTermAssets(); + assetTabRef.current?.refreshAssets(); + tab !== 'assets' && activeTabHandler('assets'); + }, [assetTabRef, tab]); + const onExtensionUpdate = async (updatedTable: GlossaryTerm) => { await handleGlossaryTermUpdate({ ...glossaryTerm, @@ -162,11 +174,13 @@ const GlossaryTermsV1 = ({ children: ( setAssetModelVisible(true)} onAssetClick={onAssetClick} + onRemoveAsset={handleAssetSave} /> ), }, @@ -226,6 +240,7 @@ const GlossaryTermsV1 = ({ isSummaryPanelOpen, isVersionView, assetPermissions, + handleAssetSave, ]); const fetchGlossaryTermAssets = async () => { @@ -253,12 +268,6 @@ const GlossaryTermsV1 = ({ getEntityFeedCount(); }, [glossaryFqn]); - const handleAssetSave = () => { - fetchGlossaryTermAssets(); - assetTabRef.current?.refreshAssets(); - tab !== 'assets' && activeTabHandler('assets'); - }; - const name = useMemo( () => isVersionView 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 2fbce865558..1f5702965e1 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 @@ -1,3 +1,4 @@ +/* eslint-disable no-case-declarations */ /* * Copyright 2022 Collate. * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +29,7 @@ import { } from 'antd'; import { AxiosError } from 'axios'; import classNames from 'classnames'; +import { compare } from 'fast-json-patch'; import { t } from 'i18next'; import { isEmpty, isObject } from 'lodash'; import React, { @@ -40,18 +42,34 @@ import React, { } from 'react'; import { useParams } from 'react-router-dom'; import { ReactComponent as AddPlaceHolderIcon } from '../../../../assets/svg/add-placeholder.svg'; +import { ReactComponent as DeleteIcon } from '../../../../assets/svg/ic-delete.svg'; import { AssetsFilterOptions, ASSET_MENU_KEYS, ASSET_SUB_MENU_FILTER, } 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'; import { EntityType } from '../../../../enums/entity.enum'; import { SearchIndex } from '../../../../enums/search.enum'; +import { GlossaryTerm } from '../../../../generated/entity/data/glossaryTerm'; +import { DataProduct } from '../../../../generated/entity/domains/dataProduct'; +import { Domain } from '../../../../generated/entity/domains/domain'; import { usePaging } from '../../../../hooks/paging/usePaging'; +import { + getDataProductByName, + patchDataProduct, +} from '../../../../rest/dataProductAPI'; +import { getDomainByName } from '../../../../rest/domainAPI'; +import { getGlossaryTermByFQN } from '../../../../rest/glossaryAPI'; import { searchData } from '../../../../rest/miscAPI'; +import { + removeGlossaryTermAssets, + updateDomainAssets, +} from '../../../../utils/Assets/AssetsUtils'; import { getCountBadge, Transi18next } from '../../../../utils/CommonUtils'; +import { getEntityName } from '../../../../utils/EntityUtils'; import { getEntityTypeFromSearchIndex } from '../../../../utils/SearchUtils'; import { getEntityIcon } from '../../../../utils/TableUtils'; import { showErrorToast } from '../../../../utils/ToastUtils'; @@ -59,6 +77,7 @@ import ErrorPlaceHolder from '../../../common/ErrorWithPlaceholder/ErrorPlaceHol import NextPrevious from '../../../common/NextPrevious/NextPrevious'; import { PagingHandlerParams } from '../../../common/NextPrevious/NextPrevious.interface'; import ExploreSearchCard from '../../../ExploreV1/ExploreSearchCard/ExploreSearchCard'; +import ConfirmationModal from '../../../Modals/ConfirmationModal/ConfirmationModal'; import { SearchedDataProps, SourceType, @@ -78,11 +97,13 @@ const AssetsTabs = forwardRef( onAssetClick, isSummaryPanelOpen, onAddAsset, + onRemoveAsset, assetCount, queryFilter, isEntityDeleted = false, type = AssetsOfEntity.GLOSSARY, noDataPlaceholder, + entityFqn, }: AssetsTabsProps, ref ) => { @@ -104,10 +125,25 @@ const AssetsTabs = forwardRef( showPagination, } = usePaging(); + const isRemovable = useMemo( + () => + [ + AssetsOfEntity.DATA_PRODUCT, + AssetsOfEntity.DOMAIN, + AssetsOfEntity.GLOSSARY, + ].includes(type), + [type] + ); + 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< + Domain | DataProduct | GlossaryTerm + >(); const queryParam = useMemo(() => { switch (type) { @@ -199,6 +235,29 @@ const AssetsTabs = forwardRef( } }; + const fetchCurrentEntity = useCallback(async () => { + let data; + const fqn = encodeURIComponent(entityFqn ?? ''); + switch (type) { + case AssetsOfEntity.DOMAIN: + data = await getDomainByName(fqn, ''); + + break; + case AssetsOfEntity.DATA_PRODUCT: + data = await getDataProductByName(fqn, 'domain,assets'); + + break; + case AssetsOfEntity.GLOSSARY: + data = await getGlossaryTermByFQN(fqn); + + break; + default: + break; + } + + setActiveEntity(data); + }, [type, entityFqn]); + const tabs = useMemo(() => { return AssetsFilterOptions.map((option) => { return { @@ -288,6 +347,11 @@ const AssetsTabs = forwardRef( ] ); + const onExploreCardDelete = useCallback((source: SourceType) => { + setAssetToDelete(source); + setShowDeleteModal(true); + }, []); + const filteredAssetMenus = useMemo(() => { switch (type) { case AssetsOfEntity.DOMAIN: @@ -351,6 +415,12 @@ const AssetsTabs = forwardRef( }; }, []); + useEffect(() => { + if (entityFqn) { + fetchCurrentEntity(); + } + }, [entityFqn]); + const assetErrorPlaceHolder = useMemo(() => { if (!isEmpty(activeFilter)) { return ( @@ -426,17 +496,35 @@ const AssetsTabs = forwardRef( const assetListing = useMemo( () => data.length ? ( -
- {data.map(({ _source, _id = '' }, index) => ( +
+ {data.map(({ _source, _id = '' }) => ( + } + size="small" + type="text" + onClick={() => onExploreCardDelete(_source)} + /> + ) : null + } className={classNames( 'm-b-sm cursor-pointer', selectedCard?.id === _source.id ? 'highlight-card' : '' )} handleSummaryPanelDisplay={setSelectedCard} id={_id} - key={index} + key={'assets_' + _id} showTags={false} source={_source} /> @@ -458,7 +546,9 @@ const AssetsTabs = forwardRef(
{assetErrorPlaceHolder}
), [ + type, data, + permissions, paging, currentPage, selectedCard, @@ -539,6 +629,65 @@ const AssetsTabs = forwardRef( ); }, [assetsHeader, assetListing, selectedCard]); + const onAssetRemove = useCallback(async () => { + if (!activeEntity) { + return; + } + + try { + let updatedEntity; + switch (type) { + case AssetsOfEntity.DATA_PRODUCT: + const updatedAssets = ( + (activeEntity as DataProduct)?.assets ?? [] + ).filter((asset) => asset.id !== assetToDelete?.id); + updatedEntity = { + ...activeEntity, + assets: updatedAssets, + }; + const jsonPatch = compare(activeEntity, updatedEntity); + const res = await patchDataProduct( + (activeEntity as DataProduct).id, + jsonPatch + ); + setActiveEntity(res); + + break; + + case AssetsOfEntity.DOMAIN: + case AssetsOfEntity.GLOSSARY: + const selectedItemMap = new Map(); + selectedItemMap.set(assetToDelete?.id, assetToDelete); + + if (type === AssetsOfEntity.DOMAIN) { + await updateDomainAssets(undefined, type, selectedItemMap); + } else if (type === AssetsOfEntity.GLOSSARY) { + await removeGlossaryTermAssets( + entityFqn ?? '', + type, + selectedItemMap + ); + } + + break; + default: + // Handle other entity types here + break; + } + + await new Promise((resolve) => { + setTimeout(() => { + resolve(''); + }, ES_UPDATE_DELAY); + }); + } catch (err) { + showErrorToast(err as AxiosError); + } finally { + setShowDeleteModal(false); + onRemoveAsset?.(); + } + }, [type, activeEntity, assetToDelete, entityFqn]); + useEffect(() => { fetchAssets({ index: isEmpty(activeFilter) ? [SearchIndex.ALL] : activeFilter, @@ -589,6 +738,20 @@ const AssetsTabs = forwardRef( className={classNames('assets-tab-container p-md')} data-testid="table-container"> {layout} + setShowDeleteModal(false)} + onConfirm={onAssetRemove} + />
); } 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 60b4e43cec7..774a2027557 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 @@ -25,6 +25,8 @@ export enum AssetsOfEntity { export interface AssetsTabsProps { onAddAsset: () => void; + onRemoveAsset?: () => void; + entityFqn?: string; permissions: OperationPermission; assetCount: number; onAssetClick?: (asset?: EntityDetailsObjectInterface) => void; diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts index 03c8d4ee94b..38edff32064 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts @@ -119,6 +119,8 @@ export const pagingObject = { after: '', before: '', total: 0 }; export const ONLY_NUMBER_REGEX = /^[0-9\b]+$/; +export const ES_UPDATE_DELAY = 500; + export const globalSearchOptions = [ { value: '', label: t('label.all') }, { value: SearchIndex.TABLE, label: t('label.table') }, diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json index 55da8d9e24e..a5a6838029b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json @@ -818,6 +818,7 @@ "relevance": "Relevanz", "remove": "Entfernen", "remove-entity": "{{entity}} entfernen", + "remove-lowecase": "remove", "removed": "Entfernt", "removing-user": "Benutzer entfernen", "rename": "Umbenennen", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json index 3d0bd9b2f6e..b381e6f5eb3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -818,6 +818,7 @@ "relevance": "Relevance", "remove": "Remove", "remove-entity": "Remove {{entity}}", + "remove-lowecase": "remove", "removed": "Removed", "removing-user": "Removing User", "rename": "Rename", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json index f4ff4b5b1c3..7b87d34cbbf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json @@ -818,6 +818,7 @@ "relevance": "Relevancia", "remove": "Eliminar", "remove-entity": "Eliminar {{entity}}", + "remove-lowecase": "remove", "removed": "Eliminado", "removing-user": "Eliminando usuario", "rename": "Rename", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json index 72fa0da07f7..a0f097ecbec 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json @@ -818,6 +818,7 @@ "relevance": "Pertinence", "remove": "Retirer", "remove-entity": "Retirer un·e {{entity}}", + "remove-lowecase": "remove", "removed": "Retiré", "removing-user": "Retirer un Utilisateur", "rename": "Renommer", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json index bd7f3a3576d..d14f3513da0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json @@ -818,6 +818,7 @@ "relevance": "Relevance", "remove": "除外", "remove-entity": "{{entity}}を除外", + "remove-lowecase": "remove", "removed": "除外", "removing-user": "ユーザを除外する", "rename": "Rename", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json index bd827c25ba8..5ec4fc5ab62 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json @@ -818,6 +818,7 @@ "relevance": "Relevância", "remove": "Remover", "remove-entity": "Remover {{entity}}", + "remove-lowecase": "remove", "removed": "Removido", "removing-user": "Removendo usuário", "rename": "Rename", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json index 95ce063be4a..f4236dbdc48 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json @@ -818,6 +818,7 @@ "relevance": "Актуальность", "remove": "Удалить", "remove-entity": "Удалить {{entity}}", + "remove-lowecase": "remove", "removed": "Удаленный", "removing-user": "Удаление пользователя", "rename": "Переименовать", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json index f96afa3047f..d9e4865317b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json @@ -818,6 +818,7 @@ "relevance": "相关性", "remove": "删除", "remove-entity": "删除{{entity}}", + "remove-lowecase": "remove", "removed": "已删除", "removing-user": "正在删除用户", "rename": "重命名", diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/Assets/AssetsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/Assets/AssetsUtils.ts index 638bd45647c..563c94af4be 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/Assets/AssetsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/Assets/AssetsUtils.ts @@ -10,11 +10,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Operation } from 'fast-json-patch'; +import { AxiosError } from 'axios'; +import { compare, Operation } from 'fast-json-patch'; +import { EntityDetailUnion } from 'Models'; import { MapPatchAPIResponse } from '../../components/Assets/AssetsSelectionModal/AssetSelectionModal.interface'; import { AssetsOfEntity } from '../../components/Glossary/GlossaryTerms/tabs/AssetsTabs.interface'; import { EntityType } from '../../enums/entity.enum'; import { SearchIndex } from '../../enums/search.enum'; +import { Table } from '../../generated/entity/data/table'; +import { Domain } from '../../generated/entity/domains/domain'; import { getDashboardByFqn, patchDashboardDetails, @@ -57,6 +61,7 @@ import { getTableDetailsByFQN, patchTableDetails } from '../../rest/tableAPI'; import { getTeamByName, patchTeamDetail } from '../../rest/teamsAPI'; import { getTopicByFqn, patchTopicDetails } from '../../rest/topicsAPI'; import { getServiceCategoryFromEntityType } from '../../utils/ServiceUtils'; +import { showErrorToast } from '../ToastUtils'; export const getAPIfromSource = ( source: keyof MapPatchAPIResponse @@ -198,3 +203,114 @@ export const getAssetsFields = (source: AssetsOfEntity) => { return 'dataProducts'; } }; + +const getJsonPatchObject = (entity: Table, activeEntity: Domain) => { + let patchObj; + if (activeEntity) { + const { id, description, fullyQualifiedName, name, displayName } = + activeEntity; + patchObj = { + id, + description, + fullyQualifiedName, + name, + displayName, + type: 'domain', + }; + } + + const jsonPatch = compare(entity, { + ...entity, + domain: patchObj, + }); + + return jsonPatch; +}; + +export const updateDomainAssets = async ( + activeEntity: EntityDetailUnion | undefined, + type: AssetsOfEntity, + selectedItems: Map +) => { + try { + const entityDetails = [...(selectedItems?.values() ?? [])].map((item) => + getEntityAPIfromSource(item.entityType)( + item.fullyQualifiedName, + getAssetsFields(type) + ) + ); + const entityDetailsResponse = await Promise.allSettled(entityDetails); + const map = new Map(); + + entityDetailsResponse.forEach((response) => { + if (response.status === 'fulfilled') { + const entity = response.value; + entity && map.set(entity.fullyQualifiedName, entity); + } + }); + const patchAPIPromises = [...(selectedItems?.values() ?? [])] + .map((item) => { + if (map.has(item.fullyQualifiedName)) { + const entity = map.get(item.fullyQualifiedName); + const jsonPatch = getJsonPatchObject(entity, activeEntity as Domain); + const api = getAPIfromSource(item.entityType); + + return api(item.id, jsonPatch); + } + + return; + }) + .filter(Boolean); + + await Promise.all(patchAPIPromises); + } catch (err) { + showErrorToast(err as AxiosError); + } +}; + +export const removeGlossaryTermAssets = async ( + entityFqn: string, + type: AssetsOfEntity, + selectedItems: Map +) => { + const entityDetails = [...(selectedItems?.values() ?? [])].map((item) => + getEntityAPIfromSource(item.entityType)( + item.fullyQualifiedName, + getAssetsFields(type) + ) + ); + + try { + const entityDetailsResponse = await Promise.allSettled(entityDetails); + const map = new Map(); + entityDetailsResponse.forEach((response) => { + if (response.status === 'fulfilled') { + const entity = response.value; + entity && map.set(entity.fullyQualifiedName, (entity as Table).tags); + } + }); + const patchAPIPromises = [...(selectedItems?.values() ?? [])] + .map((item) => { + if (map.has(item.fullyQualifiedName)) { + const jsonPatch = compare( + { tags: map.get(item.fullyQualifiedName) }, + { + tags: (item.tags ?? []).filter( + (tag: EntityDetailUnion) => tag.tagFQN !== entityFqn + ), + } + ); + const api = getAPIfromSource(item.entityType); + + return api(item.id, jsonPatch); + } + + return; + }) + .filter(Boolean); + + await Promise.all(patchAPIPromises); + } catch (err) { + showErrorToast(err as AxiosError); + } +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.tsx index 297962c7833..bc7decc706c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DomainUtils.tsx @@ -113,7 +113,35 @@ export const getUserNames = ( return getOwner(hasPermission, getEntityName(entity.owner), entity.owner); }; -export const getQueryFilterToIncludeDomain = (fqn: string) => ({ +export const getQueryFilterToIncludeDomain = ( + domainFqn: string, + dataProductFqn: string +) => ({ + query: { + bool: { + must: [ + { + term: { + 'domain.fullyQualifiedName': domainFqn, + }, + }, + { + bool: { + must_not: [ + { + term: { + 'dataProducts.fullyQualifiedName': dataProductFqn, + }, + }, + ], + }, + }, + ], + }, + }, +}); + +export const getQueryFilterToExcludeDomainTerms = (fqn: string) => ({ query: { bool: { must: [ @@ -121,8 +149,12 @@ export const getQueryFilterToIncludeDomain = (fqn: string) => ({ bool: { must: [ { - term: { - 'domain.fullyQualifiedName': fqn, + bool: { + must_not: { + term: { + 'domain.fullyQualifiedName': fqn, + }, + }, }, }, ],