mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-07-21 00:12:02 +00:00
fix(ui): revamp asset filtering style (#13542)
* revamp asset filtering style * minor changes * make parent menu disabled if whole list doesn't have data * fix code smell * changes made as per comments * fix popup issue * supported all asset when landing and when removing all filter * supported entity icon in asset cards
This commit is contained in:
parent
7c25b5fddd
commit
31ffe5d168
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 7.9 KiB |
@ -420,6 +420,7 @@ const DataProductsDetailsPage = ({
|
||||
}
|
||||
rightPanelWidth={400}>
|
||||
<AssetsTabs
|
||||
assetCount={assetCount}
|
||||
isSummaryPanelOpen={false}
|
||||
permissions={dataProductPermission}
|
||||
ref={assetTabRef}
|
||||
|
@ -481,6 +481,7 @@ const DomainDetailsPage = ({
|
||||
}
|
||||
rightPanelWidth={400}>
|
||||
<AssetsTabs
|
||||
assetCount={assetCount}
|
||||
isSummaryPanelOpen={false}
|
||||
permissions={domainPermission}
|
||||
ref={assetTabRef}
|
||||
|
@ -25,6 +25,7 @@ export interface ExploreSearchCardProps {
|
||||
details: SearchedDataProps['data'][number]['_source'],
|
||||
entityType: string
|
||||
) => void;
|
||||
showEntityIcon?: boolean;
|
||||
checked?: boolean;
|
||||
showCheckboxes?: boolean;
|
||||
showTags?: boolean;
|
||||
|
@ -32,7 +32,11 @@ import {
|
||||
} from '../../../utils/EntityUtils';
|
||||
import { getDomainPath } from '../../../utils/RouterUtils';
|
||||
import { stringToHTML } from '../../../utils/StringsUtils';
|
||||
import { getServiceIcon, getUsagePercentile } from '../../../utils/TableUtils';
|
||||
import {
|
||||
getEntityIcon,
|
||||
getServiceIcon,
|
||||
getUsagePercentile,
|
||||
} from '../../../utils/TableUtils';
|
||||
import TitleBreadcrumb from '../../common/title-breadcrumb/title-breadcrumb.component';
|
||||
import TableDataCardBody from '../../TableDataCardBody/TableDataCardBody';
|
||||
import { useTourProvider } from '../../TourProvider/TourProvider';
|
||||
@ -49,6 +53,7 @@ const ExploreSearchCard: React.FC<ExploreSearchCardProps> = forwardRef<
|
||||
className,
|
||||
source,
|
||||
matches,
|
||||
showEntityIcon,
|
||||
handleSummaryPanelDisplay,
|
||||
showTags = true,
|
||||
openEntityInNewPage,
|
||||
@ -151,24 +156,32 @@ const ExploreSearchCard: React.FC<ExploreSearchCardProps> = forwardRef<
|
||||
</Typography.Text>
|
||||
</Button>
|
||||
) : (
|
||||
<Link
|
||||
className="no-underline"
|
||||
data-testid="entity-link"
|
||||
target={openEntityInNewPage ? '_blank' : '_self'}
|
||||
to={
|
||||
source.fullyQualifiedName && source.entityType
|
||||
? getEntityLinkFromType(
|
||||
source.fullyQualifiedName,
|
||||
source.entityType as EntityType
|
||||
)
|
||||
: ''
|
||||
}>
|
||||
<Typography.Text
|
||||
className="text-lg font-medium text-link-color"
|
||||
data-testid="entity-header-display-name">
|
||||
{stringToHTML(getEntityName(source))}
|
||||
</Typography.Text>
|
||||
</Link>
|
||||
<div className="w-full d-flex items-start">
|
||||
{showEntityIcon && (
|
||||
<span className="w-6 h-6 m-r-xs d-flex text-xl">
|
||||
{getEntityIcon(source.entityType ?? '')}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<Link
|
||||
className="no-underline w-full"
|
||||
data-testid="entity-link"
|
||||
target={openEntityInNewPage ? '_blank' : '_self'}
|
||||
to={
|
||||
source.fullyQualifiedName && source.entityType
|
||||
? getEntityLinkFromType(
|
||||
source.fullyQualifiedName,
|
||||
source.entityType as EntityType
|
||||
)
|
||||
: ''
|
||||
}>
|
||||
<Typography.Text
|
||||
className="text-lg font-medium text-link-color"
|
||||
data-testid="entity-header-display-name">
|
||||
{stringToHTML(getEntityName(source))}
|
||||
</Typography.Text>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -167,6 +167,7 @@ const GlossaryTermsV1 = ({
|
||||
key: 'assets',
|
||||
children: (
|
||||
<AssetsTabs
|
||||
assetCount={assetCount}
|
||||
isSummaryPanelOpen={isSummaryPanelOpen}
|
||||
permissions={permissions}
|
||||
ref={assetTabRef}
|
||||
|
@ -11,11 +11,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, Col, Menu, Row, Skeleton, Space } from 'antd';
|
||||
import type { ButtonType } from 'antd/lib/button';
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Checkbox,
|
||||
Col,
|
||||
Menu,
|
||||
MenuProps,
|
||||
Popover,
|
||||
Row,
|
||||
Skeleton,
|
||||
Space,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { t } from 'i18next';
|
||||
import { find, startCase } from 'lodash';
|
||||
import { find, isEmpty } from 'lodash';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
@ -28,6 +39,8 @@ import { useParams } from 'react-router-dom';
|
||||
import {
|
||||
AssetsFilterOptions,
|
||||
ASSETS_INDEXES,
|
||||
ASSET_MENU_KEYS,
|
||||
ASSET_SUB_MENU_FILTER,
|
||||
} from '../../../../constants/Assets.constants';
|
||||
import { PAGE_SIZE } from '../../../../constants/constants';
|
||||
import { GLOSSARIES_DOCS } from '../../../../constants/docs.constants';
|
||||
@ -46,6 +59,9 @@ import {
|
||||
SearchedDataProps,
|
||||
SourceType,
|
||||
} from '../../../searched-data/SearchedData.interface';
|
||||
|
||||
import { FilterOutlined } from '@ant-design/icons';
|
||||
import { getEntityIcon } from '../../../../utils/TableUtils';
|
||||
import './assets-tabs.less';
|
||||
import {
|
||||
AssetsOfEntity,
|
||||
@ -65,29 +81,107 @@ const AssetsTabs = forwardRef(
|
||||
onAssetClick,
|
||||
isSummaryPanelOpen,
|
||||
onAddAsset,
|
||||
assetCount,
|
||||
type = AssetsOfEntity.GLOSSARY,
|
||||
viewType = AssetsViewType.PILLS,
|
||||
}: AssetsTabsProps,
|
||||
ref
|
||||
) => {
|
||||
const [itemCount, setItemCount] = useState<Record<EntityType, number>>({
|
||||
table: 0,
|
||||
pipeline: 0,
|
||||
mlmodel: 0,
|
||||
container: 0,
|
||||
topic: 0,
|
||||
dashboard: 0,
|
||||
glossaryTerm: 0,
|
||||
} as Record<EntityType, number>);
|
||||
const [activeFilter, setActiveFilter] = useState<SearchIndex>(
|
||||
SearchIndex.TABLE
|
||||
const popupRef = React.useRef<HTMLElement>(null);
|
||||
const [itemCount, setItemCount] = useState<Record<EntityType, number>>(
|
||||
{} as Record<EntityType, number>
|
||||
);
|
||||
const [activeFilter, setActiveFilter] = useState<SearchIndex[]>([]);
|
||||
const { fqn } = useParams<{ fqn: string }>();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [data, setData] = useState<SearchedDataProps['data']>([]);
|
||||
const [total, setTotal] = useState<number>(0);
|
||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||
const [selectedCard, setSelectedCard] = useState<SourceType>();
|
||||
const [visible, setVisible] = useState<boolean>(false);
|
||||
const [openKeys, setOpenKeys] = useState<EntityType[]>([]);
|
||||
|
||||
const queryParam = useMemo(() => {
|
||||
if (type === AssetsOfEntity.DOMAIN) {
|
||||
return `(domain.fullyQualifiedName:"${fqn}")`;
|
||||
} else if (type === AssetsOfEntity.DATA_PRODUCT) {
|
||||
return `(dataProducts.fullyQualifiedName:"${fqn}")`;
|
||||
} else if (type === AssetsOfEntity.TEAM) {
|
||||
return `(owner.fullyQualifiedName:"${fqn}")`;
|
||||
} else {
|
||||
return `(tags.tagFQN:"${fqn}")`;
|
||||
}
|
||||
}, [type, fqn]);
|
||||
|
||||
const fetchAssets = useCallback(
|
||||
async ({
|
||||
index = activeFilter,
|
||||
page = 1,
|
||||
}: {
|
||||
index?: SearchIndex[];
|
||||
page?: number;
|
||||
}) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await searchData(
|
||||
'',
|
||||
page,
|
||||
PAGE_SIZE,
|
||||
queryParam,
|
||||
'',
|
||||
'',
|
||||
index
|
||||
);
|
||||
|
||||
// Extract useful details from the Response
|
||||
const totalCount = res?.data?.hits?.total.value ?? 0;
|
||||
const hits = res?.data?.hits?.hits;
|
||||
|
||||
// Find EntityType for selected searchIndex
|
||||
const entityType = AssetsFilterOptions.find((f) =>
|
||||
activeFilter.includes(f.value)
|
||||
)?.label;
|
||||
|
||||
// Update states
|
||||
setTotal(totalCount);
|
||||
entityType &&
|
||||
setItemCount((prevCount) => ({
|
||||
...prevCount,
|
||||
[entityType]: totalCount,
|
||||
}));
|
||||
setData(hits as SearchedDataProps['data']);
|
||||
|
||||
// Select first card to show summary right panel
|
||||
hits[0] && setSelectedCard(hits[0]._source as SourceType);
|
||||
} catch (_) {
|
||||
// Nothing here
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[activeFilter, currentPage]
|
||||
);
|
||||
const onOpenChange: MenuProps['onOpenChange'] = (keys) => {
|
||||
const latestOpenKey = keys.find(
|
||||
(key) => openKeys.indexOf(key as EntityType) === -1
|
||||
);
|
||||
if (ASSET_MENU_KEYS.indexOf(latestOpenKey as EntityType) === -1) {
|
||||
setOpenKeys(keys as EntityType[]);
|
||||
} else {
|
||||
setOpenKeys(latestOpenKey ? [latestOpenKey as EntityType] : []);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAssetButtonVisibleChange = (newVisible: boolean) =>
|
||||
setVisible(newVisible);
|
||||
|
||||
const handleActiveFilter = (key: SearchIndex) => {
|
||||
if (activeFilter.includes(key)) {
|
||||
setActiveFilter((prev) => prev.filter((item) => item !== key));
|
||||
} else {
|
||||
setActiveFilter((prev) => [...prev, key]);
|
||||
}
|
||||
};
|
||||
|
||||
const tabs = useMemo(() => {
|
||||
return AssetsFilterOptions.map((option) => {
|
||||
@ -102,7 +196,7 @@ const AssetsTabs = forwardRef(
|
||||
{getCountBadge(
|
||||
itemCount[option.key],
|
||||
'',
|
||||
activeFilter === option.value
|
||||
activeFilter.includes(option.value)
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
@ -111,19 +205,79 @@ const AssetsTabs = forwardRef(
|
||||
value: option.value,
|
||||
};
|
||||
});
|
||||
}, [itemCount]);
|
||||
}, [activeFilter, itemCount]);
|
||||
|
||||
const queryParam = useMemo(() => {
|
||||
if (type === AssetsOfEntity.DOMAIN) {
|
||||
return `(domain.fullyQualifiedName:"${fqn}")`;
|
||||
} else if (type === AssetsOfEntity.DATA_PRODUCT) {
|
||||
return `(dataProducts.fullyQualifiedName:"${fqn}")`;
|
||||
} else if (type === AssetsOfEntity.TEAM) {
|
||||
return `(owner.fullyQualifiedName:"${fqn}")`;
|
||||
} else {
|
||||
return `(tags.tagFQN:"${fqn}")`;
|
||||
}
|
||||
}, [type, fqn]);
|
||||
const getAssetMenuCount = useCallback(
|
||||
(key: EntityType) =>
|
||||
ASSET_SUB_MENU_FILTER.find((item) => item.key === key)
|
||||
?.children.map((item) => itemCount[item.value] ?? 0)
|
||||
?.reduce((acc, cv) => acc + cv, 0),
|
||||
[itemCount]
|
||||
);
|
||||
|
||||
const getOptions = useCallback(
|
||||
(
|
||||
option: {
|
||||
label: string;
|
||||
key: EntityType | SearchIndex;
|
||||
value?: EntityType;
|
||||
},
|
||||
isChildren?: boolean
|
||||
) => {
|
||||
const assetCount = isChildren
|
||||
? itemCount[option.value as EntityType]
|
||||
: getAssetMenuCount(option.key as EntityType);
|
||||
|
||||
return {
|
||||
label: isChildren ? (
|
||||
<div className="w-full d-flex justify-between">
|
||||
<div className="w-full d-flex items-center justify-between p-r-xss">
|
||||
<span className="d-flex items-center">
|
||||
<span className="m-r-xs w-4 d-flex">
|
||||
{getEntityIcon(option.key)}
|
||||
</span>
|
||||
|
||||
<Typography.Text
|
||||
className="asset-sub-menu-title text-color-inherit"
|
||||
ellipsis={{ tooltip: true }}>
|
||||
{option.label}
|
||||
</Typography.Text>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
{getCountBadge(assetCount, 'asset-badge-container')}
|
||||
</span>
|
||||
</div>
|
||||
<Checkbox
|
||||
checked={activeFilter.includes(option.key as SearchIndex)}
|
||||
className="asset-sub-menu-checkbox"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="d-flex justify-between">
|
||||
<span>{option.label}</span>
|
||||
<span>{getCountBadge(assetCount, 'asset-badge-container')}</span>
|
||||
</div>
|
||||
),
|
||||
key: option.key,
|
||||
value: option.key,
|
||||
};
|
||||
},
|
||||
[
|
||||
getEntityIcon,
|
||||
setCurrentPage,
|
||||
handleActiveFilter,
|
||||
setSelectedCard,
|
||||
activeFilter,
|
||||
]
|
||||
);
|
||||
|
||||
const subMenuItems = useMemo(() => {
|
||||
return ASSET_SUB_MENU_FILTER.map((option) => ({
|
||||
...getOptions(option),
|
||||
children: option.children.map((item) => getOptions(item, true)),
|
||||
}));
|
||||
}, [itemCount, getOptions]);
|
||||
|
||||
const searchIndexes = useMemo(() => {
|
||||
const indexesToFetch = [...ASSETS_INDEXES];
|
||||
@ -160,6 +314,9 @@ const AssetsTabs = forwardRef(
|
||||
pipelineServiceResponse,
|
||||
storageServiceResponse,
|
||||
searchServiceResponse,
|
||||
domainResponse,
|
||||
dataProductResponse,
|
||||
tagResponse,
|
||||
glossaryResponse,
|
||||
]) => {
|
||||
const counts = {
|
||||
@ -191,7 +348,10 @@ const AssetsTabs = forwardRef(
|
||||
storageServiceResponse.data.hits.total.value,
|
||||
[EntityType.SEARCH_SERVICE]:
|
||||
searchServiceResponse.data.hits.total.value,
|
||||
|
||||
[EntityType.DOMAIN]: domainResponse.data.hits.total.value,
|
||||
[EntityType.DATA_PRODUCT]:
|
||||
dataProductResponse.data.hits.total.value,
|
||||
[EntityType.TAG]: tagResponse.data.hits.total.value,
|
||||
[EntityType.GLOSSARY_TERM]:
|
||||
type !== AssetsOfEntity.GLOSSARY
|
||||
? glossaryResponse.data.hits.total.value
|
||||
@ -200,18 +360,22 @@ const AssetsTabs = forwardRef(
|
||||
|
||||
setItemCount(counts as Record<EntityType, number>);
|
||||
|
||||
find(counts, (count, key) => {
|
||||
if (count > 0) {
|
||||
const option = AssetsFilterOptions.find((el) => el.key === key);
|
||||
if (option) {
|
||||
setActiveFilter(option.value);
|
||||
if (viewType !== AssetsViewType.PILLS) {
|
||||
find(counts, (count, key) => {
|
||||
if (count > 0) {
|
||||
const option = AssetsFilterOptions.find(
|
||||
(el) => el.key === key
|
||||
);
|
||||
if (option) {
|
||||
handleActiveFilter(option.value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
)
|
||||
.catch((err) => {
|
||||
@ -228,55 +392,6 @@ const AssetsTabs = forwardRef(
|
||||
};
|
||||
}, []);
|
||||
|
||||
const fetchAssets = useCallback(
|
||||
async ({
|
||||
index = activeFilter,
|
||||
page = 1,
|
||||
}: {
|
||||
index?: SearchIndex;
|
||||
page?: number;
|
||||
}) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await searchData(
|
||||
'',
|
||||
page,
|
||||
PAGE_SIZE,
|
||||
queryParam,
|
||||
'',
|
||||
'',
|
||||
index
|
||||
);
|
||||
|
||||
// Extract useful details from the Response
|
||||
const totalCount = res?.data?.hits?.total.value ?? 0;
|
||||
const hits = res?.data?.hits?.hits;
|
||||
|
||||
// Find EntityType for selected searchIndex
|
||||
const entityType = AssetsFilterOptions.find(
|
||||
(f) => f.value === activeFilter
|
||||
)?.label;
|
||||
|
||||
// Update states
|
||||
setTotal(totalCount);
|
||||
entityType &&
|
||||
setItemCount((prevCount) => ({
|
||||
...prevCount,
|
||||
[entityType]: totalCount,
|
||||
}));
|
||||
setData(hits as SearchedDataProps['data']);
|
||||
|
||||
// Select first card to show summary right panel
|
||||
hits[0] && setSelectedCard(hits[0]._source as SourceType);
|
||||
} catch (_) {
|
||||
// Nothing here
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[activeFilter, currentPage]
|
||||
);
|
||||
|
||||
const assetListing = useMemo(() => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
@ -295,6 +410,7 @@ const AssetsTabs = forwardRef(
|
||||
<div className="assets-data-container">
|
||||
{data.map(({ _source, _id = '' }, index) => (
|
||||
<ExploreSearchCard
|
||||
showEntityIcon
|
||||
className={classNames(
|
||||
'm-b-sm cursor-pointer',
|
||||
selectedCard?.id === _source.id ? 'highlight-card' : ''
|
||||
@ -320,64 +436,97 @@ const AssetsTabs = forwardRef(
|
||||
</div>
|
||||
) : (
|
||||
<div className="m-t-xlg">
|
||||
<ErrorPlaceHolder
|
||||
doc={GLOSSARIES_DOCS}
|
||||
heading={t('label.asset')}
|
||||
permission={permissions.Create}
|
||||
type={ERROR_PLACEHOLDER_TYPE.CREATE}
|
||||
onClick={onAddAsset}
|
||||
/>
|
||||
{!isEmpty(activeFilter) ? (
|
||||
<ErrorPlaceHolder
|
||||
heading={t('label.asset')}
|
||||
type={ERROR_PLACEHOLDER_TYPE.FILTER}
|
||||
/>
|
||||
) : (
|
||||
<ErrorPlaceHolder
|
||||
doc={GLOSSARIES_DOCS}
|
||||
heading={t('label.asset')}
|
||||
permission={permissions.Create}
|
||||
type={ERROR_PLACEHOLDER_TYPE.CREATE}
|
||||
onClick={onAddAsset}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}, [data, isLoading, total, currentPage, selectedCard, setSelectedCard]);
|
||||
|
||||
const assetsHeader = useMemo(() => {
|
||||
if (viewType === AssetsViewType.PILLS) {
|
||||
return AssetsFilterOptions.map((option) => {
|
||||
const buttonStyle =
|
||||
activeFilter === option.value
|
||||
? {
|
||||
ghost: true,
|
||||
type: 'primary' as ButtonType,
|
||||
style: { background: 'white' },
|
||||
}
|
||||
: {};
|
||||
|
||||
return itemCount[option.key] > 0 ? (
|
||||
<Button
|
||||
{...buttonStyle}
|
||||
className="m-r-sm m-b-sm"
|
||||
key={option.value}
|
||||
onClick={() => {
|
||||
setCurrentPage(1);
|
||||
setActiveFilter(option.value);
|
||||
}}>
|
||||
{startCase(option.label)}{' '}
|
||||
<span className="p-l-xs">
|
||||
{getCountBadge(
|
||||
itemCount[option.key],
|
||||
'',
|
||||
activeFilter === option.value
|
||||
)}
|
||||
</span>
|
||||
</Button>
|
||||
) : null;
|
||||
});
|
||||
return (
|
||||
<div className="w-full d-flex justify-end">
|
||||
<Popover
|
||||
align={{ targetOffset: [0, 10] }}
|
||||
content={
|
||||
<Menu
|
||||
multiple
|
||||
items={subMenuItems}
|
||||
mode="inline"
|
||||
openKeys={openKeys}
|
||||
rootClassName="asset-multi-menu-selector"
|
||||
selectedKeys={activeFilter}
|
||||
style={{ width: 256, height: 340 }}
|
||||
onClick={(value) => {
|
||||
setCurrentPage(1);
|
||||
handleActiveFilter(value.key as SearchIndex);
|
||||
setSelectedCard(undefined);
|
||||
}}
|
||||
onOpenChange={onOpenChange}
|
||||
/>
|
||||
}
|
||||
getPopupContainer={(triggerNode: HTMLElement) =>
|
||||
popupRef.current ?? triggerNode
|
||||
}
|
||||
key="asset-options-popover"
|
||||
open={visible}
|
||||
overlayClassName="ant-popover-asset"
|
||||
placement="bottomRight"
|
||||
showArrow={false}
|
||||
trigger="click"
|
||||
onOpenChange={handleAssetButtonVisibleChange}>
|
||||
{Boolean(assetCount) && (
|
||||
<Badge count={activeFilter.length}>
|
||||
<Button
|
||||
ghost
|
||||
icon={<FilterOutlined />}
|
||||
ref={popupRef}
|
||||
style={{ background: 'white' }}
|
||||
type="primary">
|
||||
{t('label.filter-plural')}
|
||||
</Button>
|
||||
</Badge>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Menu
|
||||
className="p-t-sm"
|
||||
items={tabs}
|
||||
selectedKeys={[activeFilter]}
|
||||
selectedKeys={activeFilter}
|
||||
onClick={(value) => {
|
||||
setCurrentPage(1);
|
||||
setActiveFilter(value.key as SearchIndex);
|
||||
setActiveFilter([value.key as SearchIndex]);
|
||||
setSelectedCard(undefined);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}, [viewType, activeFilter, currentPage, tabs, itemCount]);
|
||||
}, [
|
||||
viewType,
|
||||
activeFilter,
|
||||
openKeys,
|
||||
visible,
|
||||
currentPage,
|
||||
tabs,
|
||||
itemCount,
|
||||
onOpenChange,
|
||||
handleAssetButtonVisibleChange,
|
||||
]);
|
||||
|
||||
const layout = useMemo(() => {
|
||||
if (viewType === AssetsViewType.PILLS) {
|
||||
@ -397,7 +546,10 @@ const AssetsTabs = forwardRef(
|
||||
}, [viewType, assetsHeader, assetListing, selectedCard]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAssets({ index: activeFilter, page: currentPage });
|
||||
fetchAssets({
|
||||
index: isEmpty(activeFilter) ? [SearchIndex.ALL] : activeFilter,
|
||||
page: currentPage,
|
||||
});
|
||||
}, [activeFilter, currentPage]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
|
@ -29,6 +29,7 @@ export enum AssetsViewType {
|
||||
export interface AssetsTabsProps {
|
||||
onAddAsset: () => void;
|
||||
permissions: OperationPermission;
|
||||
assetCount: number;
|
||||
onAssetClick?: (asset?: EntityDetailsObjectInterface) => void;
|
||||
isSummaryPanelOpen: boolean;
|
||||
type?: AssetsOfEntity;
|
||||
|
@ -12,5 +12,62 @@
|
||||
*/
|
||||
@import url('../../../../styles/variables.less');
|
||||
|
||||
.assets-tab-container {
|
||||
.ant-popover-asset {
|
||||
.ant-popover-inner-content {
|
||||
padding: 0;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
.asset-multi-menu-selector.ant-menu:not(.ant-menu-horizontal)
|
||||
.ant-menu-item-selected {
|
||||
background-color: transparent;
|
||||
color: initial;
|
||||
}
|
||||
|
||||
.asset-multi-menu-selector {
|
||||
.ant-menu-submenu-open {
|
||||
background: @user-profile-background;
|
||||
}
|
||||
.ant-menu-inline {
|
||||
.asset-sub-menu-title {
|
||||
width: 150px;
|
||||
}
|
||||
.asset-sub-menu-checkbox {
|
||||
padding-right: 2px;
|
||||
}
|
||||
.ant-menu-item::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-menu-submenu-inline {
|
||||
.ant-menu-submenu-title {
|
||||
padding-left: 14px !important;
|
||||
}
|
||||
.ant-menu-sub {
|
||||
background: @user-profile-background;
|
||||
|
||||
.ant-menu-item-only-child {
|
||||
padding-left: 22px !important;
|
||||
padding-right: 14px;
|
||||
color: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-menu-submenu-selected {
|
||||
.ant-menu-submenu-title {
|
||||
.asset-badge-container {
|
||||
background: @primary-color;
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.asset-badge-container {
|
||||
background: rgba(41, 41, 41, 0.1);
|
||||
border: none;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
}
|
||||
|
@ -722,13 +722,14 @@ const TeamDetailsV1 = ({
|
||||
() => (
|
||||
<AssetsTabs
|
||||
isSummaryPanelOpen
|
||||
assetCount={assetsCount}
|
||||
permissions={entityPermissions}
|
||||
type={AssetsOfEntity.TEAM}
|
||||
onAddAsset={() => history.push(ROUTES.EXPLORE)}
|
||||
onAssetClick={setPreviewAsset}
|
||||
/>
|
||||
),
|
||||
[entityPermissions, setPreviewAsset]
|
||||
[entityPermissions, assetsCount, setPreviewAsset]
|
||||
);
|
||||
|
||||
const rolesTabRender = useMemo(
|
||||
|
@ -133,6 +133,195 @@ export const AssetsFilterOptions: Array<{
|
||||
},
|
||||
];
|
||||
|
||||
export const ASSET_SUB_MENU_FILTER: Array<{
|
||||
label: string;
|
||||
key: EntityType;
|
||||
children: {
|
||||
label: string;
|
||||
value: EntityType;
|
||||
key: SearchIndex;
|
||||
}[];
|
||||
}> = [
|
||||
{
|
||||
key: EntityType.DOMAIN,
|
||||
label: i18n.t('label.domain-plural'),
|
||||
children: [
|
||||
{
|
||||
key: SearchIndex.DOMAIN,
|
||||
label: i18n.t('label.domain-plural'),
|
||||
value: EntityType.DOMAIN,
|
||||
},
|
||||
{
|
||||
key: SearchIndex.DATA_PRODUCT,
|
||||
label: i18n.t('label.data-product-plural'),
|
||||
value: EntityType.DATA_PRODUCT,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: i18n.t('label.database-plural'),
|
||||
key: EntityType.DATABASE,
|
||||
children: [
|
||||
{
|
||||
label: i18n.t('label.entity-service', {
|
||||
entity: i18n.t('label.database'),
|
||||
}),
|
||||
value: EntityType.SEARCH_SERVICE,
|
||||
key: SearchIndex.DATABASE_SERVICE,
|
||||
},
|
||||
{
|
||||
key: SearchIndex.DATABASE,
|
||||
label: i18n.t('label.database'),
|
||||
value: EntityType.DATABASE,
|
||||
},
|
||||
{
|
||||
key: SearchIndex.DATABASE_SCHEMA,
|
||||
label: i18n.t('label.database-schema'),
|
||||
value: EntityType.DATABASE_SCHEMA,
|
||||
},
|
||||
{
|
||||
key: SearchIndex.TABLE,
|
||||
label: i18n.t('label.table'),
|
||||
value: EntityType.TABLE,
|
||||
},
|
||||
{
|
||||
key: SearchIndex.STORED_PROCEDURE,
|
||||
label: i18n.t('label.stored-procedure'),
|
||||
value: EntityType.STORED_PROCEDURE,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: EntityType.TOPIC,
|
||||
label: i18n.t('label.messaging-plural'),
|
||||
children: [
|
||||
{
|
||||
key: SearchIndex.MESSAGING_SERVICE,
|
||||
label: i18n.t('label.entity-service', {
|
||||
entity: i18n.t('label.messaging'),
|
||||
}),
|
||||
value: EntityType.MESSAGING_SERVICE,
|
||||
},
|
||||
{
|
||||
key: SearchIndex.TOPIC,
|
||||
label: i18n.t('label.topic'),
|
||||
value: EntityType.TOPIC,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: EntityType.DASHBOARD,
|
||||
label: i18n.t('label.dashboard-plural'),
|
||||
children: [
|
||||
{
|
||||
key: SearchIndex.DASHBOARD_SERVICE,
|
||||
label: i18n.t('label.entity-service', {
|
||||
entity: i18n.t('label.dashboard'),
|
||||
}),
|
||||
value: EntityType.DASHBOARD_SERVICE,
|
||||
},
|
||||
{
|
||||
key: SearchIndex.DASHBOARD,
|
||||
label: i18n.t('label.dashboard'),
|
||||
value: EntityType.DASHBOARD,
|
||||
},
|
||||
{
|
||||
key: SearchIndex.DASHBOARD_DATA_MODEL,
|
||||
label: i18n.t('label.dashboard-data-model-plural'),
|
||||
value: EntityType.DASHBOARD_DATA_MODEL,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: EntityType.MLMODEL,
|
||||
label: i18n.t('label.machine-learning'),
|
||||
children: [
|
||||
{
|
||||
key: SearchIndex.ML_MODEL_SERVICE,
|
||||
label: i18n.t('label.entity-service', {
|
||||
entity: i18n.t('label.ml-model'),
|
||||
}),
|
||||
value: EntityType.MLMODEL_SERVICE,
|
||||
},
|
||||
{
|
||||
key: SearchIndex.MLMODEL,
|
||||
label: i18n.t('label.ml-model'),
|
||||
value: EntityType.MLMODEL,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: EntityType.PIPELINE,
|
||||
label: i18n.t('label.pipeline-plural'),
|
||||
children: [
|
||||
{
|
||||
key: SearchIndex.PIPELINE_SERVICE,
|
||||
label: i18n.t('label.entity-service', {
|
||||
entity: i18n.t('label.pipeline'),
|
||||
}),
|
||||
value: EntityType.PIPELINE_SERVICE,
|
||||
},
|
||||
{
|
||||
key: SearchIndex.PIPELINE,
|
||||
label: i18n.t('label.pipeline'),
|
||||
value: EntityType.PIPELINE,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: EntityType.CONTAINER,
|
||||
label: i18n.t('label.storage'),
|
||||
children: [
|
||||
{
|
||||
key: SearchIndex.STORAGE_SERVICE,
|
||||
label: i18n.t('label.entity-service', {
|
||||
entity: i18n.t('label.storage'),
|
||||
}),
|
||||
value: EntityType.STORAGE_SERVICE,
|
||||
},
|
||||
{
|
||||
key: SearchIndex.CONTAINER,
|
||||
label: i18n.t('label.container'),
|
||||
value: EntityType.CONTAINER,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: EntityType.SEARCH_INDEX,
|
||||
label: i18n.t('label.search'),
|
||||
children: [
|
||||
{
|
||||
key: SearchIndex.SEARCH_SERVICE,
|
||||
label: i18n.t('label.entity-service', {
|
||||
entity: i18n.t('label.search'),
|
||||
}),
|
||||
value: EntityType.SEARCH_SERVICE,
|
||||
},
|
||||
{
|
||||
key: SearchIndex.SEARCH_INDEX,
|
||||
label: i18n.t('label.search-index'),
|
||||
value: EntityType.SEARCH_INDEX,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: EntityType.GOVERN,
|
||||
label: i18n.t('label.governance'),
|
||||
children: [
|
||||
{
|
||||
key: SearchIndex.GLOSSARY,
|
||||
label: i18n.t('label.glossary-plural'),
|
||||
value: EntityType.GLOSSARY,
|
||||
},
|
||||
{
|
||||
key: SearchIndex.TAG,
|
||||
label: i18n.t('label.tag-plural'),
|
||||
value: EntityType.TAG,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const ASSETS_INDEXES = [
|
||||
SearchIndex.TABLE,
|
||||
SearchIndex.TOPIC,
|
||||
@ -152,4 +341,19 @@ export const ASSETS_INDEXES = [
|
||||
SearchIndex.PIPELINE_SERVICE,
|
||||
SearchIndex.STORAGE_SERVICE,
|
||||
SearchIndex.SEARCH_SERVICE,
|
||||
SearchIndex.DOMAIN,
|
||||
SearchIndex.DATA_PRODUCT,
|
||||
SearchIndex.TAG,
|
||||
];
|
||||
|
||||
export const ASSET_MENU_KEYS = [
|
||||
EntityType.DOMAIN,
|
||||
EntityType.DATABASE,
|
||||
EntityType.TOPIC,
|
||||
EntityType.PIPELINE,
|
||||
EntityType.DASHBOARD,
|
||||
EntityType.MLMODEL,
|
||||
EntityType.CONTAINER,
|
||||
EntityType.SEARCH_INDEX,
|
||||
EntityType.GOVERN,
|
||||
];
|
||||
|
@ -58,6 +58,7 @@ export enum EntityType {
|
||||
DOC_STORE = 'docStore',
|
||||
PAGE = 'Page',
|
||||
knowledgePanels = 'KnowLedgePanels',
|
||||
GOVERN = 'govern',
|
||||
}
|
||||
|
||||
export enum AssetsType {
|
||||
|
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
export enum SearchIndex {
|
||||
ALL = 'all',
|
||||
TABLE = 'table_search_index',
|
||||
TOPIC = 'topic_search_index',
|
||||
DASHBOARD = 'dashboard_search_index',
|
||||
|
@ -172,6 +172,7 @@ export type ExploreSearchSource =
|
||||
| SearchIndexSearchSource;
|
||||
|
||||
export type SearchIndexSearchSourceMapping = {
|
||||
[SearchIndex.ALL]: TableSearchSource;
|
||||
[SearchIndex.TABLE]: TableSearchSource;
|
||||
[SearchIndex.MLMODEL]: MlmodelSearchSource;
|
||||
[SearchIndex.PIPELINE]: PipelineSearchSource;
|
||||
|
@ -571,6 +571,7 @@
|
||||
"login": "Anmelden",
|
||||
"logo-url": "URL des Logos",
|
||||
"logout": "Abmelden",
|
||||
"machine-learning": "Machine Learning",
|
||||
"major": "Haupt-",
|
||||
"manage-entity": "{{entity}} verwalten",
|
||||
"manage-rule": "Regeln verwalten",
|
||||
|
@ -571,6 +571,7 @@
|
||||
"login": "Login",
|
||||
"logo-url": "Logo URL",
|
||||
"logout": "Logout",
|
||||
"machine-learning": "Machine Learning",
|
||||
"major": "Major",
|
||||
"manage-entity": "Manage {{entity}}",
|
||||
"manage-rule": "Manage Rule",
|
||||
|
@ -571,6 +571,7 @@
|
||||
"login": "Iniciar sesión",
|
||||
"logo-url": "Logo URL",
|
||||
"logout": "Cerrar sesión",
|
||||
"machine-learning": "Machine Learning",
|
||||
"major": "Principal",
|
||||
"manage-entity": "Administrar {{entity}}",
|
||||
"manage-rule": "Administrar regla",
|
||||
|
@ -571,6 +571,7 @@
|
||||
"login": "Se Connecter",
|
||||
"logo-url": "URL du Logo",
|
||||
"logout": "Se Déconnecter",
|
||||
"machine-learning": "Machine Learning",
|
||||
"major": "Majeur",
|
||||
"manage-entity": "Gérer {{entity}}",
|
||||
"manage-rule": "Gérer les Règles",
|
||||
|
@ -571,6 +571,7 @@
|
||||
"login": "ログイン",
|
||||
"logo-url": "Logo URL",
|
||||
"logout": "ログアウト",
|
||||
"machine-learning": "Machine Learning",
|
||||
"major": "メジャー",
|
||||
"manage-entity": "{{entity}}の管理",
|
||||
"manage-rule": "ルールの管理",
|
||||
|
@ -571,6 +571,7 @@
|
||||
"login": "Entrar",
|
||||
"logo-url": "Logo URL",
|
||||
"logout": "Sair",
|
||||
"machine-learning": "Machine Learning",
|
||||
"major": "Principal",
|
||||
"manage-entity": "Gerenciar {{numberOfDays}}",
|
||||
"manage-rule": "Gerenciar regra",
|
||||
|
@ -571,6 +571,7 @@
|
||||
"login": "Логин",
|
||||
"logo-url": "URL-адрес логотипа",
|
||||
"logout": "Выйти",
|
||||
"machine-learning": "Machine Learning",
|
||||
"major": "Главный",
|
||||
"manage-entity": "Управление {{entity}}",
|
||||
"manage-rule": "Правила управления",
|
||||
|
@ -571,6 +571,7 @@
|
||||
"login": "登录",
|
||||
"logo-url": "Logo URL",
|
||||
"logout": "退出",
|
||||
"machine-learning": "Machine Learning",
|
||||
"major": "主要",
|
||||
"manage-entity": "管理{{entity}}",
|
||||
"manage-rule": "管理规则",
|
||||
|
@ -88,6 +88,10 @@ pre {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.text-inherit {
|
||||
font-size: inherit;
|
||||
}
|
||||
.text-color-inherit {
|
||||
color: inherit;
|
||||
}
|
||||
|
@ -12,7 +12,6 @@
|
||||
*/
|
||||
|
||||
import Icon, { FilterOutlined, SearchOutlined } from '@ant-design/icons';
|
||||
import { EntityTags } from 'Models';
|
||||
import { Tooltip } from 'antd';
|
||||
import { ExpandableConfig } from 'antd/lib/table/interface';
|
||||
import classNames from 'classnames';
|
||||
@ -27,6 +26,7 @@ import {
|
||||
uniqueId,
|
||||
upperCase,
|
||||
} from 'lodash';
|
||||
import { EntityTags } from 'Models';
|
||||
import React from 'react';
|
||||
import { ReactComponent as IconTerm } from '../assets/svg/book.svg';
|
||||
import { ReactComponent as ClassificationIcon } from '../assets/svg/classification.svg';
|
||||
@ -53,17 +53,14 @@ import { ReactComponent as IconNotNull } from '../assets/svg/icon-not-null.svg';
|
||||
import { ReactComponent as IconUniqueLineThrough } from '../assets/svg/icon-unique-line-through.svg';
|
||||
import { ReactComponent as IconUnique } from '../assets/svg/icon-unique.svg';
|
||||
import { SourceType } from '../components/searched-data/SearchedData.interface';
|
||||
import { GlobalSettingsMenuCategory } from '../constants/GlobalSettings.constants';
|
||||
import { FQN_SEPARATOR_CHAR } from '../constants/char.constants';
|
||||
import {
|
||||
DE_ACTIVE_COLOR,
|
||||
PRIMERY_COLOR,
|
||||
TEXT_BODY_COLOR,
|
||||
getContainerDetailPath,
|
||||
getDashboardDetailsPath,
|
||||
getDataModelDetailsPath,
|
||||
getDatabaseDetailsPath,
|
||||
getDatabaseSchemaDetailsPath,
|
||||
getDataModelDetailsPath,
|
||||
getEditWebhookPath,
|
||||
getMlModelPath,
|
||||
getPipelineDetailsPath,
|
||||
@ -73,7 +70,10 @@ import {
|
||||
getTableTabPath,
|
||||
getTagsDetailsPath,
|
||||
getTopicDetailsPath,
|
||||
PRIMERY_COLOR,
|
||||
TEXT_BODY_COLOR,
|
||||
} from '../constants/constants';
|
||||
import { GlobalSettingsMenuCategory } from '../constants/GlobalSettings.constants';
|
||||
import { EntityTabs, EntityType, FqnPart } from '../enums/entity.enum';
|
||||
import { SearchIndex } from '../enums/search.enum';
|
||||
import { ConstraintTypes, PrimaryTableDataTypes } from '../enums/table.enum';
|
||||
@ -321,22 +321,27 @@ export const getEntityIcon = (indexType: string) => {
|
||||
switch (indexType) {
|
||||
case SearchIndex.TOPIC:
|
||||
case EntityType.TOPIC:
|
||||
case SearchIndex.MESSAGING_SERVICE:
|
||||
return <TopicIcon />;
|
||||
|
||||
case SearchIndex.DASHBOARD:
|
||||
case EntityType.DASHBOARD:
|
||||
case SearchIndex.DASHBOARD_SERVICE:
|
||||
return <DashboardIcon />;
|
||||
|
||||
case SearchIndex.MLMODEL:
|
||||
case EntityType.MLMODEL:
|
||||
case SearchIndex.ML_MODEL_SERVICE:
|
||||
return <MlModelIcon />;
|
||||
|
||||
case SearchIndex.PIPELINE:
|
||||
case EntityType.PIPELINE:
|
||||
case SearchIndex.PIPELINE_SERVICE:
|
||||
return <PipelineIcon />;
|
||||
|
||||
case SearchIndex.CONTAINER:
|
||||
case EntityType.CONTAINER:
|
||||
case SearchIndex.STORAGE_SERVICE:
|
||||
return <ContainerIcon />;
|
||||
|
||||
case SearchIndex.DASHBOARD_DATA_MODEL:
|
||||
@ -347,8 +352,10 @@ export const getEntityIcon = (indexType: string) => {
|
||||
case EntityType.STORED_PROCEDURE:
|
||||
return <IconStoredProcedure />;
|
||||
|
||||
case SearchIndex.TAG:
|
||||
case EntityType.TAG:
|
||||
return <ClassificationIcon />;
|
||||
case SearchIndex.GLOSSARY:
|
||||
case EntityType.GLOSSARY:
|
||||
return <GlossaryIcon />;
|
||||
case EntityType.GLOSSARY_TERM:
|
||||
@ -356,7 +363,20 @@ export const getEntityIcon = (indexType: string) => {
|
||||
|
||||
case EntityType.SEARCH_INDEX:
|
||||
case SearchIndex.SEARCH_INDEX:
|
||||
return <SearchOutlined className="text-sm" />;
|
||||
case EntityType.SEARCH_SERVICE:
|
||||
case SearchIndex.SEARCH_SERVICE:
|
||||
return (
|
||||
<SearchOutlined
|
||||
className="text-sm text-inherit"
|
||||
style={{ color: DE_ACTIVE_COLOR }}
|
||||
/>
|
||||
);
|
||||
|
||||
case EntityType.DOMAIN:
|
||||
case EntityType.DATA_PRODUCT:
|
||||
case SearchIndex.DATA_PRODUCT:
|
||||
case SearchIndex.DOMAIN:
|
||||
return <DomainIcon />;
|
||||
|
||||
case SearchIndex.TABLE:
|
||||
case EntityType.TABLE:
|
||||
|
Loading…
x
Reference in New Issue
Block a user