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,
...(appData?.appType === AppType.Internal && !appData?.deleted
...(!appData?.deleted
? [
{
label: (

View File

@ -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 = ({

View File

@ -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)}

View File

@ -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 {

View File

@ -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}

View File

@ -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 {

View File

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

View File

@ -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>
);
}

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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>
);
}

View File

@ -25,6 +25,8 @@ export enum AssetsOfEntity {
export interface AssetsTabsProps {
onAddAsset: () => void;
onRemoveAsset?: () => void;
entityFqn?: string;
permissions: OperationPermission;
assetCount: number;
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 ES_UPDATE_DELAY = 500;
export const globalSearchOptions = [
{ value: '', label: t('label.all') },
{ value: SearchIndex.TABLE, label: t('label.table') },

View File

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

View File

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

View File

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

View File

@ -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",

View File

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

View File

@ -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",

View File

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

View File

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

View File

@ -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);
}
};

View File

@ -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,
},
},
},
},
],