fix: allow removing assets from assets page (#13936)

* fix: allow removing assets from assets tab

* fix: localization

* fix: glossary trim issue and permission fix

* fix: asset removal on domain and glossary term
This commit is contained in:
karanh37 2023-11-10 21:16:13 +05:30 committed by GitHub
parent 2c72245342
commit fcdeb1b9e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 403 additions and 29 deletions

View File

@ -365,7 +365,7 @@ const AppDetails = () => {
), ),
}, },
...tabConfiguration, ...tabConfiguration,
...(appData?.appType === AppType.Internal && !appData?.deleted ...(!appData?.deleted
? [ ? [
{ {
label: ( label: (

View File

@ -212,21 +212,25 @@ const AppRunsHistory = forwardRef(
if (isExternalApp) { if (isExternalApp) {
const currentTime = Date.now(); const currentTime = Date.now();
const oneDayAgo = getEpochMillisForPastDays(1); // past 30 days
const startDay = getEpochMillisForPastDays(30);
const { data } = await getApplicationRuns(fqn, { const { data } = await getApplicationRuns(fqn, {
startTs: oneDayAgo, startTs: startDay,
endTs: currentTime, endTs: currentTime,
}); });
setAppRunsHistoryData( setAppRunsHistoryData(
data.map((item) => ({ data
...item, .map((item) => ({
status: getStatusFromPipelineState( ...item,
(item as PipelineStatus).pipelineState ?? PipelineState.Failed status: getStatusFromPipelineState(
), (item as PipelineStatus).pipelineState ??
id: (item as PipelineStatus).runId ?? '', PipelineState.Failed
})) ),
id: (item as PipelineStatus).runId ?? '',
}))
.slice(0, maxRecords)
); );
} else { } else {
const { data, paging } = await getApplicationRuns(fqn, { const { data, paging } = await getApplicationRuns(fqn, {
@ -248,7 +252,7 @@ const AppRunsHistory = forwardRef(
setIsLoading(false); setIsLoading(false);
} }
}, },
[fqn, pageSize, maxRecords] [fqn, pageSize, maxRecords, appData]
); );
const handleAppHistoryPageChange = ({ const handleAppHistoryPageChange = ({

View File

@ -411,12 +411,14 @@ const DataProductsDetailsPage = ({
rightPanelWidth={400}> rightPanelWidth={400}>
<AssetsTabs <AssetsTabs
assetCount={assetCount} assetCount={assetCount}
entityFqn={dataProduct.fullyQualifiedName}
isSummaryPanelOpen={false} isSummaryPanelOpen={false}
permissions={dataProductPermission} permissions={dataProductPermission}
ref={assetTabRef} ref={assetTabRef}
type={AssetsOfEntity.DATA_PRODUCT} type={AssetsOfEntity.DATA_PRODUCT}
onAddAsset={() => setAssetModelVisible(true)} onAddAsset={() => setAssetModelVisible(true)}
onAssetClick={handleAssetClick} onAssetClick={handleAssetClick}
onRemoveAsset={handleAssetSave}
/> />
</PageLayoutV1> </PageLayoutV1>
), ),
@ -429,6 +431,7 @@ const DataProductsDetailsPage = ({
previewAsset, previewAsset,
dataProduct, dataProduct,
isVersionsView, isVersionsView,
handleAssetSave,
assetCount, assetCount,
activeTab, activeTab,
]); ]);
@ -571,7 +574,8 @@ const DataProductsDetailsPage = ({
entityFqn={dataProductFqn} entityFqn={dataProductFqn}
open={assetModalVisible} open={assetModalVisible}
queryFilter={getQueryFilterToIncludeDomain( queryFilter={getQueryFilterToIncludeDomain(
dataProduct.domain?.fullyQualifiedName ?? '' dataProduct.domain?.fullyQualifiedName ?? '',
dataProduct.fullyQualifiedName ?? ''
)} )}
type={AssetsOfEntity.DATA_PRODUCT} type={AssetsOfEntity.DATA_PRODUCT}
onCancel={() => setAssetModelVisible(false)} onCancel={() => setAssetModelVisible(false)}

View File

@ -24,7 +24,8 @@
margin-left: 24px; margin-left: 24px;
} }
.assets-data-container { .assets-data-container {
padding: 12px 18px; padding-left: 18px;
padding-right: 18px;
} }
.page-layout-v1-vertical-scroll, .page-layout-v1-vertical-scroll,
.page-layout-leftpanel { .page-layout-leftpanel {

View File

@ -71,6 +71,7 @@ import { Style } from '../../../generated/type/tagLabel';
import { addDataProducts } from '../../../rest/dataProductAPI'; import { addDataProducts } from '../../../rest/dataProductAPI';
import { searchData } from '../../../rest/miscAPI'; import { searchData } from '../../../rest/miscAPI';
import { getIsErrorMatch } from '../../../utils/CommonUtils'; import { getIsErrorMatch } from '../../../utils/CommonUtils';
import { getQueryFilterToExcludeDomainTerms } from '../../../utils/DomainUtils';
import { getEntityVersionByField } from '../../../utils/EntityVersionUtils'; import { getEntityVersionByField } from '../../../utils/EntityVersionUtils';
import Fqn from '../../../utils/Fqn'; import Fqn from '../../../utils/Fqn';
import { DEFAULT_ENTITY_PERMISSION } from '../../../utils/PermissionsUtils'; import { DEFAULT_ENTITY_PERMISSION } from '../../../utils/PermissionsUtils';
@ -472,12 +473,14 @@ const DomainDetailsPage = ({
rightPanelWidth={400}> rightPanelWidth={400}>
<AssetsTabs <AssetsTabs
assetCount={assetCount} assetCount={assetCount}
entityFqn={domainFqn}
isSummaryPanelOpen={false} isSummaryPanelOpen={false}
permissions={domainPermission} permissions={domainPermission}
ref={assetTabRef} ref={assetTabRef}
type={AssetsOfEntity.DOMAIN} type={AssetsOfEntity.DOMAIN}
onAddAsset={() => setAssetModelVisible(true)} onAddAsset={() => setAssetModelVisible(true)}
onAssetClick={handleAssetClick} onAssetClick={handleAssetClick}
onRemoveAsset={handleAssetSave}
/> />
</PageLayoutV1> </PageLayoutV1>
), ),
@ -490,6 +493,7 @@ const DomainDetailsPage = ({
domainPermission, domainPermission,
previewAsset, previewAsset,
handleAssetClick, handleAssetClick,
handleAssetSave,
assetCount, assetCount,
dataProductsCount, dataProductsCount,
activeTab, activeTab,
@ -627,6 +631,7 @@ const DomainDetailsPage = ({
<AssetSelectionModal <AssetSelectionModal
entityFqn={domainFqn} entityFqn={domainFqn}
open={assetModalVisible} open={assetModalVisible}
queryFilter={getQueryFilterToExcludeDomainTerms(domainFqn)}
type={AssetsOfEntity.DOMAIN} type={AssetsOfEntity.DOMAIN}
onCancel={() => setAssetModelVisible(false)} onCancel={() => setAssetModelVisible(false)}
onSave={handleAssetSave} onSave={handleAssetSave}

View File

@ -12,6 +12,7 @@
*/ */
import { Col, Divider, Row, Space, Typography } from 'antd'; import { Col, Divider, Row, Space, Typography } from 'antd';
import { isEmpty } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { SummaryEntityType } from '../../../../enums/EntitySummary.enum'; import { SummaryEntityType } from '../../../../enums/EntitySummary.enum';
@ -48,7 +49,10 @@ function GlossaryTermSummary({
[selectedData] [selectedData]
); );
const synonyms = useMemo(() => entityDetails.synonyms ?? [], [selectedData]); const synonyms = useMemo(
() => entityDetails.synonyms?.filter((item) => !isEmpty(item)) ?? [],
[selectedData]
);
const fetchGlossaryTermDetails = useCallback(async () => { const fetchGlossaryTermDetails = useCallback(async () => {
try { try {

View File

@ -31,4 +31,5 @@ export interface ExploreSearchCardProps {
showTags?: boolean; showTags?: boolean;
openEntityInNewPage?: boolean; openEntityInNewPage?: boolean;
hideBreadcrumbs?: boolean; hideBreadcrumbs?: boolean;
actionPopoverContent?: React.ReactNode;
} }

View File

@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { Button, Col, Row, Typography } from 'antd'; import { Button, Col, Row, Space, Typography } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { isString, startCase, uniqueId } from 'lodash'; import { isString, startCase, uniqueId } from 'lodash';
import { ExtraInfo } from 'Models'; import { ExtraInfo } from 'Models';
@ -58,6 +58,7 @@ const ExploreSearchCard: React.FC<ExploreSearchCardProps> = forwardRef<
showTags = true, showTags = true,
openEntityInNewPage, openEntityInNewPage,
hideBreadcrumbs = false, hideBreadcrumbs = false,
actionPopoverContent,
}, },
ref ref
) => { ) => {
@ -245,6 +246,9 @@ const ExploreSearchCard: React.FC<ExploreSearchCardProps> = forwardRef<
))} ))}
</div> </div>
) : null} ) : null}
{actionPopoverContent && (
<Space className="explore-card-actions">{actionPopoverContent}</Space>
)}
</div> </div>
); );
} }

View File

@ -15,6 +15,7 @@
.explore-search-card { .explore-search-card {
background-color: @white; background-color: @white;
padding: 20px; padding: 20px;
position: relative;
&.highlight-card { &.highlight-card {
border-left: 4px solid @info-color; border-left: 4px solid @info-color;
box-shadow: none; box-shadow: none;
@ -22,4 +23,22 @@
.entity-summary-details { .entity-summary-details {
font-size: 12px; 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;
}
}
} }

View File

@ -13,7 +13,13 @@
import { Col, Row, Tabs } from 'antd'; import { Col, Row, Tabs } from 'antd';
import { t } from 'i18next'; 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 { useHistory, useParams } from 'react-router-dom';
import { getGlossaryTermDetailsPath } from '../../../constants/constants'; import { getGlossaryTermDetailsPath } from '../../../constants/constants';
import { EntityField } from '../../../constants/Feeds.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) => { const onExtensionUpdate = async (updatedTable: GlossaryTerm) => {
await handleGlossaryTermUpdate({ await handleGlossaryTermUpdate({
...glossaryTerm, ...glossaryTerm,
@ -162,11 +174,13 @@ const GlossaryTermsV1 = ({
children: ( children: (
<AssetsTabs <AssetsTabs
assetCount={assetCount} assetCount={assetCount}
entityFqn={glossaryTerm.fullyQualifiedName ?? ''}
isSummaryPanelOpen={isSummaryPanelOpen} isSummaryPanelOpen={isSummaryPanelOpen}
permissions={assetPermissions} permissions={assetPermissions}
ref={assetTabRef} ref={assetTabRef}
onAddAsset={() => setAssetModelVisible(true)} onAddAsset={() => setAssetModelVisible(true)}
onAssetClick={onAssetClick} onAssetClick={onAssetClick}
onRemoveAsset={handleAssetSave}
/> />
), ),
}, },
@ -226,6 +240,7 @@ const GlossaryTermsV1 = ({
isSummaryPanelOpen, isSummaryPanelOpen,
isVersionView, isVersionView,
assetPermissions, assetPermissions,
handleAssetSave,
]); ]);
const fetchGlossaryTermAssets = async () => { const fetchGlossaryTermAssets = async () => {
@ -253,12 +268,6 @@ const GlossaryTermsV1 = ({
getEntityFeedCount(); getEntityFeedCount();
}, [glossaryFqn]); }, [glossaryFqn]);
const handleAssetSave = () => {
fetchGlossaryTermAssets();
assetTabRef.current?.refreshAssets();
tab !== 'assets' && activeTabHandler('assets');
};
const name = useMemo( const name = useMemo(
() => () =>
isVersionView isVersionView

View File

@ -1,3 +1,4 @@
/* eslint-disable no-case-declarations */
/* /*
* Copyright 2022 Collate. * Copyright 2022 Collate.
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -28,6 +29,7 @@ import {
} from 'antd'; } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import classNames from 'classnames'; import classNames from 'classnames';
import { compare } from 'fast-json-patch';
import { t } from 'i18next'; import { t } from 'i18next';
import { isEmpty, isObject } from 'lodash'; import { isEmpty, isObject } from 'lodash';
import React, { import React, {
@ -40,18 +42,34 @@ import React, {
} from 'react'; } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { ReactComponent as AddPlaceHolderIcon } from '../../../../assets/svg/add-placeholder.svg'; import { ReactComponent as AddPlaceHolderIcon } from '../../../../assets/svg/add-placeholder.svg';
import { ReactComponent as DeleteIcon } from '../../../../assets/svg/ic-delete.svg';
import { import {
AssetsFilterOptions, AssetsFilterOptions,
ASSET_MENU_KEYS, ASSET_MENU_KEYS,
ASSET_SUB_MENU_FILTER, ASSET_SUB_MENU_FILTER,
} from '../../../../constants/Assets.constants'; } from '../../../../constants/Assets.constants';
import { ES_UPDATE_DELAY } from '../../../../constants/constants';
import { GLOSSARIES_DOCS } from '../../../../constants/docs.constants'; import { GLOSSARIES_DOCS } from '../../../../constants/docs.constants';
import { ERROR_PLACEHOLDER_TYPE } from '../../../../enums/common.enum'; import { ERROR_PLACEHOLDER_TYPE } from '../../../../enums/common.enum';
import { EntityType } from '../../../../enums/entity.enum'; import { EntityType } from '../../../../enums/entity.enum';
import { SearchIndex } from '../../../../enums/search.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 { 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 { searchData } from '../../../../rest/miscAPI';
import {
removeGlossaryTermAssets,
updateDomainAssets,
} from '../../../../utils/Assets/AssetsUtils';
import { getCountBadge, Transi18next } from '../../../../utils/CommonUtils'; import { getCountBadge, Transi18next } from '../../../../utils/CommonUtils';
import { getEntityName } from '../../../../utils/EntityUtils';
import { getEntityTypeFromSearchIndex } from '../../../../utils/SearchUtils'; import { getEntityTypeFromSearchIndex } from '../../../../utils/SearchUtils';
import { getEntityIcon } from '../../../../utils/TableUtils'; import { getEntityIcon } from '../../../../utils/TableUtils';
import { showErrorToast } from '../../../../utils/ToastUtils'; import { showErrorToast } from '../../../../utils/ToastUtils';
@ -59,6 +77,7 @@ import ErrorPlaceHolder from '../../../common/ErrorWithPlaceholder/ErrorPlaceHol
import NextPrevious from '../../../common/NextPrevious/NextPrevious'; import NextPrevious from '../../../common/NextPrevious/NextPrevious';
import { PagingHandlerParams } from '../../../common/NextPrevious/NextPrevious.interface'; import { PagingHandlerParams } from '../../../common/NextPrevious/NextPrevious.interface';
import ExploreSearchCard from '../../../ExploreV1/ExploreSearchCard/ExploreSearchCard'; import ExploreSearchCard from '../../../ExploreV1/ExploreSearchCard/ExploreSearchCard';
import ConfirmationModal from '../../../Modals/ConfirmationModal/ConfirmationModal';
import { import {
SearchedDataProps, SearchedDataProps,
SourceType, SourceType,
@ -78,11 +97,13 @@ const AssetsTabs = forwardRef(
onAssetClick, onAssetClick,
isSummaryPanelOpen, isSummaryPanelOpen,
onAddAsset, onAddAsset,
onRemoveAsset,
assetCount, assetCount,
queryFilter, queryFilter,
isEntityDeleted = false, isEntityDeleted = false,
type = AssetsOfEntity.GLOSSARY, type = AssetsOfEntity.GLOSSARY,
noDataPlaceholder, noDataPlaceholder,
entityFqn,
}: AssetsTabsProps, }: AssetsTabsProps,
ref ref
) => { ) => {
@ -104,10 +125,25 @@ const AssetsTabs = forwardRef(
showPagination, showPagination,
} = usePaging(); } = usePaging();
const isRemovable = useMemo(
() =>
[
AssetsOfEntity.DATA_PRODUCT,
AssetsOfEntity.DOMAIN,
AssetsOfEntity.GLOSSARY,
].includes(type),
[type]
);
const [selectedCard, setSelectedCard] = useState<SourceType>(); const [selectedCard, setSelectedCard] = useState<SourceType>();
const [visible, setVisible] = useState<boolean>(false); const [visible, setVisible] = useState<boolean>(false);
const [openKeys, setOpenKeys] = useState<EntityType[]>([]); const [openKeys, setOpenKeys] = useState<EntityType[]>([]);
const [isCountLoading, setIsCountLoading] = useState<boolean>(true); const [isCountLoading, setIsCountLoading] = useState<boolean>(true);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [assetToDelete, setAssetToDelete] = useState<SourceType>();
const [activeEntity, setActiveEntity] = useState<
Domain | DataProduct | GlossaryTerm
>();
const queryParam = useMemo(() => { const queryParam = useMemo(() => {
switch (type) { 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(() => { const tabs = useMemo(() => {
return AssetsFilterOptions.map((option) => { return AssetsFilterOptions.map((option) => {
return { return {
@ -288,6 +347,11 @@ const AssetsTabs = forwardRef(
] ]
); );
const onExploreCardDelete = useCallback((source: SourceType) => {
setAssetToDelete(source);
setShowDeleteModal(true);
}, []);
const filteredAssetMenus = useMemo(() => { const filteredAssetMenus = useMemo(() => {
switch (type) { switch (type) {
case AssetsOfEntity.DOMAIN: case AssetsOfEntity.DOMAIN:
@ -351,6 +415,12 @@ const AssetsTabs = forwardRef(
}; };
}, []); }, []);
useEffect(() => {
if (entityFqn) {
fetchCurrentEntity();
}
}, [entityFqn]);
const assetErrorPlaceHolder = useMemo(() => { const assetErrorPlaceHolder = useMemo(() => {
if (!isEmpty(activeFilter)) { if (!isEmpty(activeFilter)) {
return ( return (
@ -426,17 +496,35 @@ const AssetsTabs = forwardRef(
const assetListing = useMemo( const assetListing = useMemo(
() => () =>
data.length ? ( data.length ? (
<div className="assets-data-container"> <div className="assets-data-container p-t-sm">
{data.map(({ _source, _id = '' }, index) => ( {data.map(({ _source, _id = '' }) => (
<ExploreSearchCard <ExploreSearchCard
showEntityIcon showEntityIcon
actionPopoverContent={
isRemovable && permissions.EditAll ? (
<Button
className="p-0 flex-center"
data-testid="delete-tag"
icon={
<DeleteIcon
data-testid="delete-icon"
name="Delete"
width={16}
/>
}
size="small"
type="text"
onClick={() => onExploreCardDelete(_source)}
/>
) : null
}
className={classNames( className={classNames(
'm-b-sm cursor-pointer', 'm-b-sm cursor-pointer',
selectedCard?.id === _source.id ? 'highlight-card' : '' selectedCard?.id === _source.id ? 'highlight-card' : ''
)} )}
handleSummaryPanelDisplay={setSelectedCard} handleSummaryPanelDisplay={setSelectedCard}
id={_id} id={_id}
key={index} key={'assets_' + _id}
showTags={false} showTags={false}
source={_source} source={_source}
/> />
@ -458,7 +546,9 @@ const AssetsTabs = forwardRef(
<div className="m-t-xlg">{assetErrorPlaceHolder}</div> <div className="m-t-xlg">{assetErrorPlaceHolder}</div>
), ),
[ [
type,
data, data,
permissions,
paging, paging,
currentPage, currentPage,
selectedCard, selectedCard,
@ -539,6 +629,65 @@ const AssetsTabs = forwardRef(
); );
}, [assetsHeader, assetListing, selectedCard]); }, [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(() => { useEffect(() => {
fetchAssets({ fetchAssets({
index: isEmpty(activeFilter) ? [SearchIndex.ALL] : activeFilter, index: isEmpty(activeFilter) ? [SearchIndex.ALL] : activeFilter,
@ -589,6 +738,20 @@ const AssetsTabs = forwardRef(
className={classNames('assets-tab-container p-md')} className={classNames('assets-tab-container p-md')}
data-testid="table-container"> data-testid="table-container">
{layout} {layout}
<ConfirmationModal
bodyText={t('message.are-you-sure-action-property', {
propertyName: getEntityName(assetToDelete),
action: t('label.remove-lowecase'),
})}
cancelText={t('label.cancel')}
confirmText={t('label.delete')}
header={t('label.remove-entity', {
entity: getEntityName(assetToDelete) + '?',
})}
visible={showDeleteModal}
onCancel={() => setShowDeleteModal(false)}
onConfirm={onAssetRemove}
/>
</div> </div>
); );
} }

View File

@ -25,6 +25,8 @@ export enum AssetsOfEntity {
export interface AssetsTabsProps { export interface AssetsTabsProps {
onAddAsset: () => void; onAddAsset: () => void;
onRemoveAsset?: () => void;
entityFqn?: string;
permissions: OperationPermission; permissions: OperationPermission;
assetCount: number; assetCount: number;
onAssetClick?: (asset?: EntityDetailsObjectInterface) => void; onAssetClick?: (asset?: EntityDetailsObjectInterface) => void;

View File

@ -119,6 +119,8 @@ export const pagingObject = { after: '', before: '', total: 0 };
export const ONLY_NUMBER_REGEX = /^[0-9\b]+$/; export const ONLY_NUMBER_REGEX = /^[0-9\b]+$/;
export const ES_UPDATE_DELAY = 500;
export const globalSearchOptions = [ export const globalSearchOptions = [
{ value: '', label: t('label.all') }, { value: '', label: t('label.all') },
{ value: SearchIndex.TABLE, label: t('label.table') }, { value: SearchIndex.TABLE, label: t('label.table') },

View File

@ -818,6 +818,7 @@
"relevance": "Relevanz", "relevance": "Relevanz",
"remove": "Entfernen", "remove": "Entfernen",
"remove-entity": "{{entity}} entfernen", "remove-entity": "{{entity}} entfernen",
"remove-lowecase": "remove",
"removed": "Entfernt", "removed": "Entfernt",
"removing-user": "Benutzer entfernen", "removing-user": "Benutzer entfernen",
"rename": "Umbenennen", "rename": "Umbenennen",

View File

@ -818,6 +818,7 @@
"relevance": "Relevance", "relevance": "Relevance",
"remove": "Remove", "remove": "Remove",
"remove-entity": "Remove {{entity}}", "remove-entity": "Remove {{entity}}",
"remove-lowecase": "remove",
"removed": "Removed", "removed": "Removed",
"removing-user": "Removing User", "removing-user": "Removing User",
"rename": "Rename", "rename": "Rename",

View File

@ -818,6 +818,7 @@
"relevance": "Relevancia", "relevance": "Relevancia",
"remove": "Eliminar", "remove": "Eliminar",
"remove-entity": "Eliminar {{entity}}", "remove-entity": "Eliminar {{entity}}",
"remove-lowecase": "remove",
"removed": "Eliminado", "removed": "Eliminado",
"removing-user": "Eliminando usuario", "removing-user": "Eliminando usuario",
"rename": "Rename", "rename": "Rename",

View File

@ -818,6 +818,7 @@
"relevance": "Pertinence", "relevance": "Pertinence",
"remove": "Retirer", "remove": "Retirer",
"remove-entity": "Retirer un·e {{entity}}", "remove-entity": "Retirer un·e {{entity}}",
"remove-lowecase": "remove",
"removed": "Retiré", "removed": "Retiré",
"removing-user": "Retirer un Utilisateur", "removing-user": "Retirer un Utilisateur",
"rename": "Renommer", "rename": "Renommer",

View File

@ -818,6 +818,7 @@
"relevance": "Relevance", "relevance": "Relevance",
"remove": "除外", "remove": "除外",
"remove-entity": "{{entity}}を除外", "remove-entity": "{{entity}}を除外",
"remove-lowecase": "remove",
"removed": "除外", "removed": "除外",
"removing-user": "ユーザを除外する", "removing-user": "ユーザを除外する",
"rename": "Rename", "rename": "Rename",

View File

@ -818,6 +818,7 @@
"relevance": "Relevância", "relevance": "Relevância",
"remove": "Remover", "remove": "Remover",
"remove-entity": "Remover {{entity}}", "remove-entity": "Remover {{entity}}",
"remove-lowecase": "remove",
"removed": "Removido", "removed": "Removido",
"removing-user": "Removendo usuário", "removing-user": "Removendo usuário",
"rename": "Rename", "rename": "Rename",

View File

@ -818,6 +818,7 @@
"relevance": "Актуальность", "relevance": "Актуальность",
"remove": "Удалить", "remove": "Удалить",
"remove-entity": "Удалить {{entity}}", "remove-entity": "Удалить {{entity}}",
"remove-lowecase": "remove",
"removed": "Удаленный", "removed": "Удаленный",
"removing-user": "Удаление пользователя", "removing-user": "Удаление пользователя",
"rename": "Переименовать", "rename": "Переименовать",

View File

@ -818,6 +818,7 @@
"relevance": "相关性", "relevance": "相关性",
"remove": "删除", "remove": "删除",
"remove-entity": "删除{{entity}}", "remove-entity": "删除{{entity}}",
"remove-lowecase": "remove",
"removed": "已删除", "removed": "已删除",
"removing-user": "正在删除用户", "removing-user": "正在删除用户",
"rename": "重命名", "rename": "重命名",

View File

@ -10,11 +10,15 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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 { MapPatchAPIResponse } from '../../components/Assets/AssetsSelectionModal/AssetSelectionModal.interface';
import { AssetsOfEntity } from '../../components/Glossary/GlossaryTerms/tabs/AssetsTabs.interface'; import { AssetsOfEntity } from '../../components/Glossary/GlossaryTerms/tabs/AssetsTabs.interface';
import { EntityType } from '../../enums/entity.enum'; import { EntityType } from '../../enums/entity.enum';
import { SearchIndex } from '../../enums/search.enum'; import { SearchIndex } from '../../enums/search.enum';
import { Table } from '../../generated/entity/data/table';
import { Domain } from '../../generated/entity/domains/domain';
import { import {
getDashboardByFqn, getDashboardByFqn,
patchDashboardDetails, patchDashboardDetails,
@ -57,6 +61,7 @@ import { getTableDetailsByFQN, patchTableDetails } from '../../rest/tableAPI';
import { getTeamByName, patchTeamDetail } from '../../rest/teamsAPI'; import { getTeamByName, patchTeamDetail } from '../../rest/teamsAPI';
import { getTopicByFqn, patchTopicDetails } from '../../rest/topicsAPI'; import { getTopicByFqn, patchTopicDetails } from '../../rest/topicsAPI';
import { getServiceCategoryFromEntityType } from '../../utils/ServiceUtils'; import { getServiceCategoryFromEntityType } from '../../utils/ServiceUtils';
import { showErrorToast } from '../ToastUtils';
export const getAPIfromSource = ( export const getAPIfromSource = (
source: keyof MapPatchAPIResponse source: keyof MapPatchAPIResponse
@ -198,3 +203,114 @@ export const getAssetsFields = (source: AssetsOfEntity) => {
return 'dataProducts'; 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<string, EntityDetailUnion>
) => {
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<string, EntityDetailUnion>
) => {
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);
}
};

View File

@ -113,7 +113,35 @@ export const getUserNames = (
return getOwner(hasPermission, getEntityName(entity.owner), entity.owner); 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: { query: {
bool: { bool: {
must: [ must: [
@ -121,8 +149,12 @@ export const getQueryFilterToIncludeDomain = (fqn: string) => ({
bool: { bool: {
must: [ must: [
{ {
term: { bool: {
'domain.fullyQualifiedName': fqn, must_not: {
term: {
'domain.fullyQualifiedName': fqn,
},
},
}, },
}, },
], ],