mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-12-24 22:18:41 +00:00
feat(ui): support assets selection from glossary page (#10803)
* feat: initial commit glossary redesign * chore: add localization * fix: update glossary ui * fix: missing localization * feat: update glossary ui * fix: jest tests * fix: jest tests * fix: update breadcrumbs * fix: update cypress tests * chore: remove logs * fix: update glossary right panel * fix: jest tests * fix: add reviewer functionality * feat(ui): support assets selection from glossary page --------- Co-authored-by: karanh37 <karanh37@gmail.com> Co-authored-by: karanh37 <33024356+karanh37@users.noreply.github.com>
This commit is contained in:
parent
a75bc74433
commit
53ec29bc82
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { EntityType } from 'enums/entity.enum';
|
||||
import { Container } from 'generated/entity/data/container';
|
||||
import { Dashboard } from 'generated/entity/data/dashboard';
|
||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||
import { Pipeline } from 'generated/entity/data/pipeline';
|
||||
import { Table } from 'generated/entity/data/table';
|
||||
import { Topic } from 'generated/entity/data/topic';
|
||||
|
||||
export interface AssetSelectionModalProps {
|
||||
glossaryFQN: string;
|
||||
open: boolean;
|
||||
onCancel: () => void;
|
||||
onSave?: () => void;
|
||||
}
|
||||
|
||||
export type AssetsUnion =
|
||||
| EntityType.TABLE
|
||||
| EntityType.PIPELINE
|
||||
| EntityType.DASHBOARD
|
||||
| EntityType.MLMODEL
|
||||
| EntityType.TOPIC
|
||||
| EntityType.CONTAINER;
|
||||
|
||||
export type AssetFilterKeys = AssetsUnion | 'all';
|
||||
|
||||
export type MapPatchAPIResponse = {
|
||||
[EntityType.TABLE]: Table;
|
||||
[EntityType.DASHBOARD]: Dashboard;
|
||||
[EntityType.MLMODEL]: Mlmodel;
|
||||
[EntityType.PIPELINE]: Pipeline;
|
||||
[EntityType.CONTAINER]: Container;
|
||||
[EntityType.TOPIC]: Topic;
|
||||
};
|
||||
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Button, List, Modal, Radio, Space } from 'antd';
|
||||
import Searchbar from 'components/common/searchbar/Searchbar';
|
||||
import TableDataCardV2 from 'components/common/table-data-card-v2/TableDataCardV2';
|
||||
import { EntityUnion } from 'components/Explore/explore.interface';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import { PAGE_SIZE_MEDIUM } from 'constants/constants';
|
||||
import { EntityType } from 'enums/entity.enum';
|
||||
import { SearchIndex } from 'enums/search.enum';
|
||||
import { compare, Operation } from 'fast-json-patch';
|
||||
import { cloneDeep, groupBy, map, startCase } from 'lodash';
|
||||
import { EntityDetailUnion } from 'Models';
|
||||
import VirtualList from 'rc-virtual-list';
|
||||
import {
|
||||
default as React,
|
||||
UIEventHandler,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { patchDashboardDetails } from 'rest/dashboardAPI';
|
||||
import { patchMlModelDetails } from 'rest/mlModelAPI';
|
||||
import { patchContainerDetails } from 'rest/objectStoreAPI';
|
||||
import { patchPipelineDetails } from 'rest/pipelineAPI';
|
||||
import { searchQuery } from 'rest/searchAPI';
|
||||
import { patchTableDetails } from 'rest/tableAPI';
|
||||
import { patchTopicDetails } from 'rest/topicsAPI';
|
||||
import { getCountBadge } from 'utils/CommonUtils';
|
||||
import { getQueryFilterToExcludeTerm } from 'utils/GlossaryUtils';
|
||||
import {
|
||||
AssetFilterKeys,
|
||||
AssetSelectionModalProps,
|
||||
AssetsUnion,
|
||||
MapPatchAPIResponse,
|
||||
} from './AssetSelectionModal.interface';
|
||||
|
||||
export const AssetSelectionModal = ({
|
||||
glossaryFQN,
|
||||
onCancel,
|
||||
open,
|
||||
}: AssetSelectionModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [search, setSearch] = useState('');
|
||||
const [items, setItems] = useState<EntityDetailUnion[]>([]);
|
||||
const [itemCount, setItemCount] = useState<Record<AssetFilterKeys, number>>();
|
||||
const [selectedItems, setSelectedItems] =
|
||||
useState<Map<string, EntityDetailUnion>>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [activeFilter, setActiveFilter] = useState<AssetFilterKeys>('all');
|
||||
const [pageNumber, setPageNumber] = useState(1);
|
||||
|
||||
const fetchEntities = async (searchText = '', page = 1) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await searchQuery({
|
||||
pageNumber: page,
|
||||
pageSize: PAGE_SIZE_MEDIUM,
|
||||
searchIndex: [
|
||||
SearchIndex.TABLE,
|
||||
SearchIndex.PIPELINE,
|
||||
SearchIndex.MLMODEL,
|
||||
SearchIndex.TOPIC,
|
||||
SearchIndex.DASHBOARD,
|
||||
SearchIndex.CONTAINER,
|
||||
],
|
||||
query: searchText,
|
||||
queryFilter: getQueryFilterToExcludeTerm(glossaryFQN),
|
||||
});
|
||||
|
||||
const groupedArray = groupBy(res.hits.hits, '_source.entityType');
|
||||
|
||||
setItemCount({
|
||||
all: res.hits.total.value,
|
||||
[EntityType.TABLE]: groupedArray[EntityType.TABLE]?.length ?? 0,
|
||||
[EntityType.PIPELINE]: groupedArray[EntityType.PIPELINE]?.length ?? 0,
|
||||
[EntityType.MLMODEL]: groupedArray[EntityType.MLMODEL]?.length ?? 0,
|
||||
[EntityType.TOPIC]: groupedArray[EntityType.TOPIC]?.length ?? 0,
|
||||
[EntityType.DASHBOARD]: groupedArray[EntityType.DASHBOARD]?.length ?? 0,
|
||||
[EntityType.CONTAINER]: groupedArray[EntityType.CONTAINER]?.length ?? 0,
|
||||
});
|
||||
setActiveFilter('all');
|
||||
setItems(res.hits.hits);
|
||||
setPageNumber(page);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCardClick = (details: EntityUnion) => {
|
||||
const id = details.id;
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
if (selectedItems?.has(id ?? '')) {
|
||||
setSelectedItems((prevItems) => {
|
||||
const selectedItemMap = new Map();
|
||||
|
||||
prevItems?.forEach(
|
||||
(item) => item.id !== id && selectedItemMap.set(item.id, item)
|
||||
);
|
||||
|
||||
return selectedItemMap;
|
||||
});
|
||||
} else {
|
||||
setSelectedItems((prevItems) => {
|
||||
const selectedItemMap = new Map();
|
||||
|
||||
prevItems?.forEach((item) => selectedItemMap.set(item.id, item));
|
||||
|
||||
selectedItemMap.set(
|
||||
id,
|
||||
items.find(({ _source }) => _source.id === id)._source
|
||||
);
|
||||
|
||||
return selectedItemMap;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getAPIfromSource = (
|
||||
source: AssetsUnion
|
||||
): ((
|
||||
id: string,
|
||||
jsonPatch: Operation[]
|
||||
) => Promise<MapPatchAPIResponse[typeof source]>) => {
|
||||
switch (source) {
|
||||
case EntityType.TABLE:
|
||||
return patchTableDetails;
|
||||
case EntityType.DASHBOARD:
|
||||
return patchDashboardDetails;
|
||||
case EntityType.MLMODEL:
|
||||
return patchMlModelDetails;
|
||||
case EntityType.PIPELINE:
|
||||
return patchPipelineDetails;
|
||||
case EntityType.TOPIC:
|
||||
return patchTopicDetails;
|
||||
case EntityType.CONTAINER:
|
||||
return patchContainerDetails;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
setIsLoading(true);
|
||||
const promises = [...(selectedItems?.values() ?? [])].map((item) => {
|
||||
const jsonPatch = compare(
|
||||
{ tags: item.tags },
|
||||
{
|
||||
tags: [
|
||||
...item.tags,
|
||||
{ tagFQN: glossaryFQN, source: 'Glossary', labelType: 'Manual' },
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
const api = getAPIfromSource(item.entityType);
|
||||
|
||||
return api(item.id, jsonPatch);
|
||||
});
|
||||
|
||||
try {
|
||||
await Promise.all(promises);
|
||||
onCancel();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchEntities();
|
||||
}, []);
|
||||
|
||||
const onScroll: UIEventHandler<HTMLElement> = (e) => {
|
||||
if (e.currentTarget.scrollHeight - e.currentTarget.scrollTop === 500) {
|
||||
fetchEntities(search, pageNumber + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
return activeFilter === 'all'
|
||||
? cloneDeep(items)
|
||||
: items.filter((i) => i._source.entityType === activeFilter);
|
||||
}, [items, activeFilter]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
destroyOnClose
|
||||
closable={false}
|
||||
closeIcon={null}
|
||||
footer={
|
||||
<>
|
||||
<Button onClick={onCancel}>{t('label.cancel')}</Button>
|
||||
<Button loading={isLoading} type="primary" onClick={handleSave}>
|
||||
{t('label.save')}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
open={open}
|
||||
style={{ top: 40 }}
|
||||
title={t('label.add-entity', { entity: t('label.asset-plural') })}
|
||||
width={750}>
|
||||
<Space className="w-full h-full" direction="vertical" size={16}>
|
||||
<Searchbar
|
||||
removeMargin
|
||||
placeholder={t('label.search-entity', {
|
||||
entity: t('label.asset-plural'),
|
||||
})}
|
||||
searchValue={search}
|
||||
onSearch={(s) => {
|
||||
setSearch(s);
|
||||
fetchEntities(s);
|
||||
}}
|
||||
/>
|
||||
<Radio.Group
|
||||
value={activeFilter}
|
||||
onChange={(e) => setActiveFilter(e.target.value)}>
|
||||
{map(
|
||||
itemCount,
|
||||
(value, key) =>
|
||||
value > 0 && (
|
||||
<Radio.Button key={key} value={key}>
|
||||
{startCase(key)} {getCountBadge(value)}
|
||||
</Radio.Button>
|
||||
)
|
||||
)}
|
||||
</Radio.Group>
|
||||
<List loading={{ spinning: isLoading, indicator: <Loader /> }}>
|
||||
<VirtualList
|
||||
data={filteredData}
|
||||
height={500}
|
||||
itemKey="id"
|
||||
onScroll={onScroll}>
|
||||
{({ _index: index, _source: item }) => (
|
||||
<TableDataCardV2
|
||||
showCheckboxes
|
||||
checked={selectedItems?.has(item.id)}
|
||||
className="m-b-xs"
|
||||
handleSummaryPanelDisplay={handleCardClick}
|
||||
id={`tabledatacard-${item.id}`}
|
||||
key={item.id}
|
||||
searchIndex={index}
|
||||
source={{ ...item, tags: [] }}
|
||||
/>
|
||||
)}
|
||||
</VirtualList>
|
||||
</List>
|
||||
</Space>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@ -49,7 +49,8 @@ export type ExploreSearchIndexKey =
|
||||
| 'PIPELINE'
|
||||
| 'DASHBOARD'
|
||||
| 'MLMODEL'
|
||||
| 'TOPIC';
|
||||
| 'TOPIC'
|
||||
| 'CONTAINER';
|
||||
|
||||
export type SearchHitCounts = Record<ExploreSearchIndex, number>;
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ import { ReactComponent as ExportIcon } from 'assets/svg/ic-export.svg';
|
||||
import { ReactComponent as ImportIcon } from 'assets/svg/ic-import.svg';
|
||||
import { ReactComponent as IconDropdown } from 'assets/svg/menu.svg';
|
||||
import { AxiosError } from 'axios';
|
||||
import { AssetSelectionModal } from 'components/Assets/AssetsSelectionModal/AssetSelectionModal';
|
||||
import EntityDeleteModal from 'components/Modals/EntityDeleteModal/EntityDeleteModal';
|
||||
import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface';
|
||||
import VersionButton from 'components/VersionButton/VersionButton.component';
|
||||
@ -66,6 +67,7 @@ const GlossaryHeaderButtons = ({
|
||||
const [showActions, setShowActions] = useState(false);
|
||||
const [isDelete, setIsDelete] = useState<boolean>(false);
|
||||
const [, setVersionList] = useState<EntityHistory>({} as EntityHistory);
|
||||
const [showAddAssets, setShowAddAssets] = useState(false);
|
||||
|
||||
const isExportAction = useMemo(
|
||||
() => action === GlossaryAction.EXPORT,
|
||||
@ -115,6 +117,10 @@ const GlossaryHeaderButtons = ({
|
||||
setIsDelete(false);
|
||||
};
|
||||
|
||||
const handleAddAssetsClick = () => {
|
||||
setShowAddAssets(true);
|
||||
};
|
||||
|
||||
const addButtonContent = [
|
||||
{
|
||||
label: t('label.glossary-term'),
|
||||
@ -124,6 +130,7 @@ const GlossaryHeaderButtons = ({
|
||||
{
|
||||
label: t('label.asset-plural'),
|
||||
key: '2',
|
||||
onClick: () => handleAddAssetsClick(),
|
||||
},
|
||||
];
|
||||
|
||||
@ -317,6 +324,13 @@ const GlossaryHeaderButtons = ({
|
||||
onOk={handleCancelGlossaryExport}
|
||||
/>
|
||||
)}
|
||||
{selectedData.fullyQualifiedName && !isGlossary && (
|
||||
<AssetSelectionModal
|
||||
glossaryFQN={selectedData.fullyQualifiedName}
|
||||
open={showAddAssets}
|
||||
onCancel={() => setShowAddAssets(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -48,6 +48,7 @@ const GlossaryTermReferencesModal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
destroyOnClose
|
||||
footer={[
|
||||
<Button key="cancel-btn" type="link" onClick={onClose}>
|
||||
{t('label.cancel')}
|
||||
@ -60,8 +61,8 @@ const GlossaryTermReferencesModal = ({
|
||||
{t('label.save')}
|
||||
</Button>,
|
||||
]}
|
||||
open={isVisible}
|
||||
title={t('label.reference-plural')}
|
||||
visible={isVisible}
|
||||
onCancel={onClose}>
|
||||
<Form className="reference-edit-form" form={form} onFinish={handleSubmit}>
|
||||
<Form.List name="references">
|
||||
|
||||
@ -12,11 +12,12 @@
|
||||
*/
|
||||
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons';
|
||||
import { Checkbox } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { EntityUnion } from 'components/Explore/explore.interface';
|
||||
import { isString, startCase, uniqueId } from 'lodash';
|
||||
import { ExtraInfo } from 'Models';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { forwardRef, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { getEntityId, getEntityName } from 'utils/EntityUtils';
|
||||
@ -56,146 +57,164 @@ export interface TableDataCardPropsV2 {
|
||||
details: EntityUnion,
|
||||
entityType: string
|
||||
) => void;
|
||||
checked?: boolean;
|
||||
showCheckboxes?: boolean;
|
||||
}
|
||||
|
||||
const TableDataCardV2: React.FC<TableDataCardPropsV2> = ({
|
||||
id,
|
||||
className,
|
||||
source,
|
||||
matches,
|
||||
searchIndex,
|
||||
handleSummaryPanelDisplay,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const { tab } = useParams<{ tab: string }>();
|
||||
const TableDataCardV2: React.FC<TableDataCardPropsV2> = forwardRef<
|
||||
HTMLDivElement,
|
||||
TableDataCardPropsV2
|
||||
>(
|
||||
(
|
||||
{
|
||||
id,
|
||||
className,
|
||||
source,
|
||||
matches,
|
||||
searchIndex,
|
||||
handleSummaryPanelDisplay,
|
||||
showCheckboxes,
|
||||
checked,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const { tab } = useParams<{ tab: string }>();
|
||||
|
||||
const otherDetails = useMemo(() => {
|
||||
const _otherDetails: ExtraInfo[] = [
|
||||
{
|
||||
key: 'Owner',
|
||||
value: getOwnerValue(source.owner as EntityReference),
|
||||
placeholderText: getEntityPlaceHolder(
|
||||
getEntityName(source.owner as EntityReference),
|
||||
source.owner?.deleted
|
||||
),
|
||||
id: getEntityId(source.owner as EntityReference),
|
||||
isEntityDetails: true,
|
||||
isLink: true,
|
||||
openInNewTab: false,
|
||||
profileName:
|
||||
source.owner?.type === OwnerType.USER
|
||||
? source.owner?.name
|
||||
: undefined,
|
||||
},
|
||||
];
|
||||
const otherDetails = useMemo(() => {
|
||||
const _otherDetails: ExtraInfo[] = [
|
||||
{
|
||||
key: 'Owner',
|
||||
value: getOwnerValue(source.owner as EntityReference),
|
||||
placeholderText: getEntityPlaceHolder(
|
||||
getEntityName(source.owner as EntityReference),
|
||||
source.owner?.deleted
|
||||
),
|
||||
id: getEntityId(source.owner as EntityReference),
|
||||
isEntityDetails: true,
|
||||
isLink: true,
|
||||
openInNewTab: false,
|
||||
profileName:
|
||||
source.owner?.type === OwnerType.USER
|
||||
? source.owner?.name
|
||||
: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
if (
|
||||
source.entityType !== EntityType.GLOSSARY_TERM &&
|
||||
source.entityType !== EntityType.TAG
|
||||
) {
|
||||
_otherDetails.push({
|
||||
key: 'Tier',
|
||||
value: source.tier
|
||||
? isString(source.tier)
|
||||
? source.tier
|
||||
: source.tier?.tagFQN.split(FQN_SEPARATOR_CHAR)[1]
|
||||
: '',
|
||||
});
|
||||
}
|
||||
if (
|
||||
source.entityType !== EntityType.GLOSSARY_TERM &&
|
||||
source.entityType !== EntityType.TAG
|
||||
) {
|
||||
_otherDetails.push({
|
||||
key: 'Tier',
|
||||
value: source.tier
|
||||
? isString(source.tier)
|
||||
? source.tier
|
||||
: source.tier?.tagFQN.split(FQN_SEPARATOR_CHAR)[1]
|
||||
: '',
|
||||
});
|
||||
}
|
||||
|
||||
if ('usageSummary' in source) {
|
||||
_otherDetails.push({
|
||||
value: getUsagePercentile(
|
||||
source.usageSummary?.weeklyStats?.percentileRank || 0,
|
||||
true
|
||||
),
|
||||
});
|
||||
}
|
||||
if ('usageSummary' in source) {
|
||||
_otherDetails.push({
|
||||
value: getUsagePercentile(
|
||||
source.usageSummary?.weeklyStats?.percentileRank || 0,
|
||||
true
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if ('tableType' in source) {
|
||||
_otherDetails.push({
|
||||
key: 'Type',
|
||||
value: source.tableType,
|
||||
showLabel: true,
|
||||
});
|
||||
}
|
||||
if ('tableType' in source) {
|
||||
_otherDetails.push({
|
||||
key: 'Type',
|
||||
value: source.tableType,
|
||||
showLabel: true,
|
||||
});
|
||||
}
|
||||
|
||||
return _otherDetails;
|
||||
}, [source]);
|
||||
return _otherDetails;
|
||||
}, [source]);
|
||||
|
||||
const handleLinkClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (location.pathname.includes(ROUTES.TOUR)) {
|
||||
AppState.currentTourPage = CurrentTourPageType.DATASET_PAGE;
|
||||
}
|
||||
};
|
||||
const handleLinkClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (location.pathname.includes(ROUTES.TOUR)) {
|
||||
AppState.currentTourPage = CurrentTourPageType.DATASET_PAGE;
|
||||
}
|
||||
};
|
||||
|
||||
const headerLabel = useMemo(() => {
|
||||
return getEntityHeaderLabel(source);
|
||||
}, [source]);
|
||||
const headerLabel = useMemo(() => {
|
||||
return getEntityHeaderLabel(source);
|
||||
}, [source]);
|
||||
|
||||
const serviceIcon = useMemo(() => {
|
||||
return getServiceIcon(source);
|
||||
}, [source]);
|
||||
const serviceIcon = useMemo(() => {
|
||||
return getServiceIcon(source);
|
||||
}, [source]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'data-asset-info-card-container',
|
||||
'table-data-card-container',
|
||||
className ? className : ''
|
||||
)}
|
||||
data-testid="table-data-card"
|
||||
id={id}
|
||||
onClick={() => {
|
||||
handleSummaryPanelDisplay &&
|
||||
handleSummaryPanelDisplay(source as EntityUnion, tab);
|
||||
}}>
|
||||
<div>
|
||||
{headerLabel}
|
||||
<div className="tw-flex tw-items-center">
|
||||
{serviceIcon}
|
||||
<TableDataCardTitle
|
||||
handleLinkClick={handleLinkClick}
|
||||
id={id}
|
||||
searchIndex={searchIndex}
|
||||
source={source}
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'data-asset-info-card-container',
|
||||
'table-data-card-container',
|
||||
className ? className : ''
|
||||
)}
|
||||
data-testid="table-data-card"
|
||||
id={id}
|
||||
ref={ref}
|
||||
onClick={() => {
|
||||
handleSummaryPanelDisplay &&
|
||||
handleSummaryPanelDisplay(source as EntityUnion, tab);
|
||||
}}>
|
||||
<div>
|
||||
{headerLabel}
|
||||
<div className="tw-flex tw-items-center">
|
||||
{serviceIcon}
|
||||
<TableDataCardTitle
|
||||
handleLinkClick={handleLinkClick}
|
||||
id={id}
|
||||
searchIndex={searchIndex}
|
||||
source={source}
|
||||
/>
|
||||
|
||||
{source.deleted && (
|
||||
<>
|
||||
<div
|
||||
className="tw-rounded tw-bg-error-lite tw-text-error tw-text-xs tw-font-medium tw-h-5 tw-px-1.5 tw-py-0.5 tw-ml-2"
|
||||
data-testid="deleted">
|
||||
<ExclamationCircleOutlined className="tw-mr-1" />
|
||||
{t('label.deleted')}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{showCheckboxes && (
|
||||
<Checkbox checked={checked} className="m-l-auto" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="tw-pt-3">
|
||||
<TableDataCardBody
|
||||
description={source.description || ''}
|
||||
extraInfo={otherDetails}
|
||||
tags={source.tags}
|
||||
/>
|
||||
|
||||
{source.deleted && (
|
||||
<>
|
||||
<div
|
||||
className="tw-rounded tw-bg-error-lite tw-text-error tw-text-xs tw-font-medium tw-h-5 tw-px-1.5 tw-py-0.5 tw-ml-2"
|
||||
data-testid="deleted">
|
||||
<ExclamationCircleOutlined className="tw-mr-1" />
|
||||
{t('label.deleted')}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{matches && matches.length > 0 ? (
|
||||
<div className="tw-pt-2" data-testid="matches-stats">
|
||||
<span className="tw-text-grey-muted">{`${t(
|
||||
'label.matches'
|
||||
)}:`}</span>
|
||||
{matches.map((data, i) => (
|
||||
<span className="tw-ml-2" key={uniqueId()}>
|
||||
{`${data.value} in ${startCase(data.key)}${
|
||||
i !== matches.length - 1 ? ',' : ''
|
||||
}`}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="tw-pt-3">
|
||||
<TableDataCardBody
|
||||
description={source.description || ''}
|
||||
extraInfo={otherDetails}
|
||||
tags={source.tags}
|
||||
/>
|
||||
</div>
|
||||
{matches && matches.length > 0 ? (
|
||||
<div className="tw-pt-2" data-testid="matches-stats">
|
||||
<span className="tw-text-grey-muted">{`${t('label.matches')}:`}</span>
|
||||
{matches.map((data, i) => (
|
||||
<span className="tw-ml-2" key={uniqueId()}>
|
||||
{`${data.value} in ${startCase(data.key)}${
|
||||
i !== matches.length - 1 ? ',' : ''
|
||||
}`}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default TableDataCardV2;
|
||||
|
||||
@ -160,3 +160,27 @@ export const getSearchedDataFromGlossaryTree = (
|
||||
return acc;
|
||||
}, [] as ModifiedGlossaryTerm[]);
|
||||
};
|
||||
|
||||
export const getQueryFilterToExcludeTerm = (fqn: string) => ({
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
term: {
|
||||
'tags.tagFQN': fqn,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user