mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-19 21:02:19 +00:00
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:
parent
2c72245342
commit
fcdeb1b9e8
@ -365,7 +365,7 @@ const AppDetails = () => {
|
||||
),
|
||||
},
|
||||
...tabConfiguration,
|
||||
...(appData?.appType === AppType.Internal && !appData?.deleted
|
||||
...(!appData?.deleted
|
||||
? [
|
||||
{
|
||||
label: (
|
||||
|
@ -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 = ({
|
||||
|
@ -411,12 +411,14 @@ const DataProductsDetailsPage = ({
|
||||
rightPanelWidth={400}>
|
||||
<AssetsTabs
|
||||
assetCount={assetCount}
|
||||
entityFqn={dataProduct.fullyQualifiedName}
|
||||
isSummaryPanelOpen={false}
|
||||
permissions={dataProductPermission}
|
||||
ref={assetTabRef}
|
||||
type={AssetsOfEntity.DATA_PRODUCT}
|
||||
onAddAsset={() => setAssetModelVisible(true)}
|
||||
onAssetClick={handleAssetClick}
|
||||
onRemoveAsset={handleAssetSave}
|
||||
/>
|
||||
</PageLayoutV1>
|
||||
),
|
||||
@ -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)}
|
||||
|
@ -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 {
|
||||
|
@ -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}>
|
||||
<AssetsTabs
|
||||
assetCount={assetCount}
|
||||
entityFqn={domainFqn}
|
||||
isSummaryPanelOpen={false}
|
||||
permissions={domainPermission}
|
||||
ref={assetTabRef}
|
||||
type={AssetsOfEntity.DOMAIN}
|
||||
onAddAsset={() => setAssetModelVisible(true)}
|
||||
onAssetClick={handleAssetClick}
|
||||
onRemoveAsset={handleAssetSave}
|
||||
/>
|
||||
</PageLayoutV1>
|
||||
),
|
||||
@ -490,6 +493,7 @@ const DomainDetailsPage = ({
|
||||
domainPermission,
|
||||
previewAsset,
|
||||
handleAssetClick,
|
||||
handleAssetSave,
|
||||
assetCount,
|
||||
dataProductsCount,
|
||||
activeTab,
|
||||
@ -627,6 +631,7 @@ const DomainDetailsPage = ({
|
||||
<AssetSelectionModal
|
||||
entityFqn={domainFqn}
|
||||
open={assetModalVisible}
|
||||
queryFilter={getQueryFilterToExcludeDomainTerms(domainFqn)}
|
||||
type={AssetsOfEntity.DOMAIN}
|
||||
onCancel={() => setAssetModelVisible(false)}
|
||||
onSave={handleAssetSave}
|
||||
|
@ -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 {
|
||||
|
@ -31,4 +31,5 @@ export interface ExploreSearchCardProps {
|
||||
showTags?: boolean;
|
||||
openEntityInNewPage?: boolean;
|
||||
hideBreadcrumbs?: boolean;
|
||||
actionPopoverContent?: React.ReactNode;
|
||||
}
|
||||
|
@ -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<ExploreSearchCardProps> = forwardRef<
|
||||
showTags = true,
|
||||
openEntityInNewPage,
|
||||
hideBreadcrumbs = false,
|
||||
actionPopoverContent,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
@ -245,6 +246,9 @@ const ExploreSearchCard: React.FC<ExploreSearchCardProps> = forwardRef<
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{actionPopoverContent && (
|
||||
<Space className="explore-card-actions">{actionPopoverContent}</Space>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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: (
|
||||
<AssetsTabs
|
||||
assetCount={assetCount}
|
||||
entityFqn={glossaryTerm.fullyQualifiedName ?? ''}
|
||||
isSummaryPanelOpen={isSummaryPanelOpen}
|
||||
permissions={assetPermissions}
|
||||
ref={assetTabRef}
|
||||
onAddAsset={() => 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
|
||||
|
@ -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<SourceType>();
|
||||
const [visible, setVisible] = useState<boolean>(false);
|
||||
const [openKeys, setOpenKeys] = useState<EntityType[]>([]);
|
||||
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(() => {
|
||||
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 ? (
|
||||
<div className="assets-data-container">
|
||||
{data.map(({ _source, _id = '' }, index) => (
|
||||
<div className="assets-data-container p-t-sm">
|
||||
{data.map(({ _source, _id = '' }) => (
|
||||
<ExploreSearchCard
|
||||
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(
|
||||
'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(
|
||||
<div className="m-t-xlg">{assetErrorPlaceHolder}</div>
|
||||
),
|
||||
[
|
||||
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}
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ export enum AssetsOfEntity {
|
||||
|
||||
export interface AssetsTabsProps {
|
||||
onAddAsset: () => void;
|
||||
onRemoveAsset?: () => void;
|
||||
entityFqn?: string;
|
||||
permissions: OperationPermission;
|
||||
assetCount: number;
|
||||
onAssetClick?: (asset?: EntityDetailsObjectInterface) => void;
|
||||
|
@ -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') },
|
||||
|
@ -818,6 +818,7 @@
|
||||
"relevance": "Relevanz",
|
||||
"remove": "Entfernen",
|
||||
"remove-entity": "{{entity}} entfernen",
|
||||
"remove-lowecase": "remove",
|
||||
"removed": "Entfernt",
|
||||
"removing-user": "Benutzer entfernen",
|
||||
"rename": "Umbenennen",
|
||||
|
@ -818,6 +818,7 @@
|
||||
"relevance": "Relevance",
|
||||
"remove": "Remove",
|
||||
"remove-entity": "Remove {{entity}}",
|
||||
"remove-lowecase": "remove",
|
||||
"removed": "Removed",
|
||||
"removing-user": "Removing User",
|
||||
"rename": "Rename",
|
||||
|
@ -818,6 +818,7 @@
|
||||
"relevance": "Relevancia",
|
||||
"remove": "Eliminar",
|
||||
"remove-entity": "Eliminar {{entity}}",
|
||||
"remove-lowecase": "remove",
|
||||
"removed": "Eliminado",
|
||||
"removing-user": "Eliminando usuario",
|
||||
"rename": "Rename",
|
||||
|
@ -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",
|
||||
|
@ -818,6 +818,7 @@
|
||||
"relevance": "Relevance",
|
||||
"remove": "除外",
|
||||
"remove-entity": "{{entity}}を除外",
|
||||
"remove-lowecase": "remove",
|
||||
"removed": "除外",
|
||||
"removing-user": "ユーザを除外する",
|
||||
"rename": "Rename",
|
||||
|
@ -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",
|
||||
|
@ -818,6 +818,7 @@
|
||||
"relevance": "Актуальность",
|
||||
"remove": "Удалить",
|
||||
"remove-entity": "Удалить {{entity}}",
|
||||
"remove-lowecase": "remove",
|
||||
"removed": "Удаленный",
|
||||
"removing-user": "Удаление пользователя",
|
||||
"rename": "Переименовать",
|
||||
|
@ -818,6 +818,7 @@
|
||||
"relevance": "相关性",
|
||||
"remove": "删除",
|
||||
"remove-entity": "删除{{entity}}",
|
||||
"remove-lowecase": "remove",
|
||||
"removed": "已删除",
|
||||
"removing-user": "正在删除用户",
|
||||
"rename": "重命名",
|
||||
|
@ -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<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);
|
||||
}
|
||||
};
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
Loading…
x
Reference in New Issue
Block a user