mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2026-01-08 21:48:22 +00:00
fix(ui): right panel for lineage mode (#9999)
* fix(ui): right panel for lineage mode * address comments * fix unit tests failing * update * address comments * improve ui * added skeleton * address comments * address feedbacks
This commit is contained in:
parent
a6048228f4
commit
724a10a82f
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none"><path fill="#6b7280" d="M6.856 12c.376 0 .73-.147.995-.413l3.739-3.744a1.409 1.409 0 0 0 0-1.987L6.55.807A2.731 2.731 0 0 0 4.604 0H1.406C.631 0 0 .63 0 1.406v3.188c0 .735.286 1.425.806 1.945l5.056 5.05c.266.265.619.411.994.411ZM4.604.937a1.8 1.8 0 0 1 1.282.532l5.04 5.05a.47.47 0 0 1 0 .662l-3.739 3.744a.466.466 0 0 1-.33.137h-.001a.466.466 0 0 1-.331-.136l-5.056-5.05a1.8 1.8 0 0 1-.531-1.282V1.406a.47.47 0 0 1 .468-.468h3.198Zm-1.205 3.82c.775 0 1.406-.63 1.406-1.405 0-.776-.63-1.407-1.406-1.407a1.408 1.408 0 0 0 0 2.813Zm0-1.874a.47.47 0 1 1-.001.938.47.47 0 0 1 0-.938Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none"><path fill="#6b7280" d="M6.856 12c.376 0 .73-.147.995-.413l3.739-3.744a1.409 1.409 0 0 0 0-1.987L6.55.807A2.731 2.731 0 0 0 4.604 0H1.406C.631 0 0 .63 0 1.406v3.188c0 .735.286 1.425.806 1.945l5.056 5.05c.266.265.619.411.994.411ZM4.604.937a1.8 1.8 0 0 1 1.282.532l5.04 5.05a.47.47 0 0 1 0 .662l-3.739 3.744a.466.466 0 0 1-.33.137h-.001a.466.466 0 0 1-.331-.136l-5.056-5.05a1.8 1.8 0 0 1-.531-1.282V1.406a.47.47 0 0 1 .468-.468h3.198Zm-1.205 3.82c.775 0 1.406-.63 1.406-1.405 0-.776-.63-1.407-1.406-1.407a1.408 1.408 0 0 0 0 2.813Zm0-1.874a.47.47 0 1 1-.001.938.47.47 0 0 1 0-.938Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 663 B After Width: | Height: | Size: 663 B |
@ -12,34 +12,40 @@
|
||||
*/
|
||||
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import { Divider, Drawer } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { Col, Drawer, Row, Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { t } from 'i18next';
|
||||
import DashboardSummary from 'components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.component';
|
||||
import MlModelSummary from 'components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.component';
|
||||
import PipelineSummary from 'components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.component';
|
||||
import TableSummary from 'components/Explore/EntitySummaryPanel/TableSummary/TableSummary.component';
|
||||
import TopicSummary from 'components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.component';
|
||||
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
|
||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getDashboardByFqn } from 'rest/dashboardAPI';
|
||||
import { getMlModelByFQN } from 'rest/mlModelAPI';
|
||||
import { getPipelineByFqn } from 'rest/pipelineAPI';
|
||||
import { getServiceById } from 'rest/serviceAPI';
|
||||
import { getTableDetailsByFQN } from 'rest/tableAPI';
|
||||
import { getTopicByFqn } from 'rest/topicsAPI';
|
||||
import { EntityType } from '../../enums/entity.enum';
|
||||
import { Dashboard } from '../../generated/entity/data/dashboard';
|
||||
import { Pipeline } from '../../generated/entity/data/pipeline';
|
||||
import { Table } from '../../generated/entity/data/table';
|
||||
import { Topic } from '../../generated/entity/data/topic';
|
||||
import { getHeaderLabel } from '../../utils/EntityLineageUtils';
|
||||
import { getEntityOverview, getEntityTags } from '../../utils/EntityUtils';
|
||||
import {
|
||||
DRAWER_NAVIGATION_OPTIONS,
|
||||
getEntityTags,
|
||||
} from '../../utils/EntityUtils';
|
||||
import { getEncodedFqn } from '../../utils/StringsUtils';
|
||||
import { getEntityIcon } from '../../utils/TableUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import { SelectedNode } from '../EntityLineage/EntityLineage.interface';
|
||||
import Loader from '../Loader/Loader';
|
||||
import TagsViewer from '../Tag/TagsViewer/tags-viewer';
|
||||
import { LineageDrawerProps } from './EntityInfoDrawer.interface';
|
||||
import './EntityInfoDrawer.style.less';
|
||||
|
||||
type EntityData = Table | Pipeline | Dashboard | Topic;
|
||||
type EntityData = Table | Pipeline | Dashboard | Topic | Mlmodel;
|
||||
|
||||
const EntityInfoDrawer = ({
|
||||
show,
|
||||
@ -47,10 +53,10 @@ const EntityInfoDrawer = ({
|
||||
selectedNode,
|
||||
isMainNode = false,
|
||||
}: LineageDrawerProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [entityDetail, setEntityDetail] = useState<EntityData>(
|
||||
{} as EntityData
|
||||
);
|
||||
const [serviceType, setServiceType] = useState<string>('');
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
@ -67,12 +73,12 @@ const EntityInfoDrawer = ({
|
||||
])
|
||||
.then((res) => {
|
||||
setEntityDetail(res);
|
||||
setServiceType(res.serviceType ?? '');
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
.catch(() => {
|
||||
showErrorToast(
|
||||
err,
|
||||
t('server.entity-fetch-error', { entity: selectedNode.name })
|
||||
t('server.error-selected-node-name-details', {
|
||||
selectedNodeName: selectedNode.name,
|
||||
})
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
@ -83,25 +89,42 @@ const EntityInfoDrawer = ({
|
||||
}
|
||||
case EntityType.PIPELINE: {
|
||||
setIsLoading(true);
|
||||
getPipelineByFqn(getEncodedFqn(selectedNode.fqn), ['tags', 'owner'])
|
||||
getPipelineByFqn(getEncodedFqn(selectedNode.fqn), [
|
||||
'tags',
|
||||
'owner',
|
||||
'followers',
|
||||
'tasks',
|
||||
'tier',
|
||||
])
|
||||
.then((res) => {
|
||||
getServiceById('pipelineServices', res.service?.id)
|
||||
.then((serviceRes) => {
|
||||
setServiceType(serviceRes.serviceType ?? '');
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
t('server.entity-fetch-error', { entity: selectedNode.name })
|
||||
);
|
||||
});
|
||||
setEntityDetail(res);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
.catch(() => {
|
||||
showErrorToast(
|
||||
err,
|
||||
t('server.entity-fetch-error', { entity: selectedNode.name })
|
||||
t('server.error-selected-node-name-details', {
|
||||
selectedNodeName: selectedNode.name,
|
||||
})
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityType.TOPIC: {
|
||||
setIsLoading(true);
|
||||
getTopicByFqn(selectedNode.fqn ?? '', ['tags', 'owner'])
|
||||
.then((res) => {
|
||||
setEntityDetail(res);
|
||||
})
|
||||
.catch(() => {
|
||||
showErrorToast(
|
||||
t('server.error-selected-node-name-details', {
|
||||
selectedNodeName: selectedNode.name,
|
||||
})
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
@ -112,25 +135,20 @@ const EntityInfoDrawer = ({
|
||||
}
|
||||
case EntityType.DASHBOARD: {
|
||||
setIsLoading(true);
|
||||
getDashboardByFqn(getEncodedFqn(selectedNode.fqn), ['tags', 'owner'])
|
||||
getDashboardByFqn(getEncodedFqn(selectedNode.fqn), [
|
||||
'tags',
|
||||
'owner',
|
||||
'charts',
|
||||
])
|
||||
.then((res) => {
|
||||
getServiceById('dashboardServices', res.service?.id)
|
||||
.then((serviceRes) => {
|
||||
setServiceType(serviceRes.serviceType ?? '');
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
t('server.entity-fetch-error', { entity: selectedNode.name })
|
||||
);
|
||||
});
|
||||
setEntityDetail(res);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
.catch(() => {
|
||||
showErrorToast(
|
||||
err,
|
||||
t('server.entity-fetch-error', { entity: selectedNode.name })
|
||||
t('server.error-selected-node-name-details', {
|
||||
selectedNodeName: selectedNode.name,
|
||||
})
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
@ -140,16 +158,97 @@ const EntityInfoDrawer = ({
|
||||
break;
|
||||
}
|
||||
|
||||
case EntityType.MLMODEL: {
|
||||
setIsLoading(true);
|
||||
getMlModelByFQN(getEncodedFqn(selectedNode.fqn), [
|
||||
'tags',
|
||||
'owner',
|
||||
'dashboard',
|
||||
])
|
||||
.then((res) => {
|
||||
setEntityDetail(res);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
showErrorToast(
|
||||
t('server.error-selected-node-name-details', {
|
||||
selectedNodeName: selectedNode.name,
|
||||
})
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const entityInfo = useMemo(
|
||||
() => getEntityOverview(selectedNode.type, entityDetail, serviceType),
|
||||
[selectedNode.type, entityDetail, serviceType]
|
||||
const tags = useMemo(
|
||||
() => getEntityTags(selectedNode.type, entityDetail),
|
||||
[entityDetail, selectedNode]
|
||||
);
|
||||
|
||||
const summaryComponent = useMemo(() => {
|
||||
switch (selectedNode.type) {
|
||||
case EntityType.TABLE:
|
||||
return (
|
||||
<TableSummary
|
||||
componentType={DRAWER_NAVIGATION_OPTIONS.lineage}
|
||||
entityDetails={entityDetail as Table}
|
||||
isLoading={isLoading}
|
||||
tags={tags}
|
||||
/>
|
||||
);
|
||||
|
||||
case EntityType.TOPIC:
|
||||
return (
|
||||
<TopicSummary
|
||||
componentType={DRAWER_NAVIGATION_OPTIONS.lineage}
|
||||
entityDetails={entityDetail as Topic}
|
||||
isLoading={isLoading}
|
||||
tags={tags}
|
||||
/>
|
||||
);
|
||||
|
||||
case EntityType.DASHBOARD:
|
||||
return (
|
||||
<DashboardSummary
|
||||
componentType={DRAWER_NAVIGATION_OPTIONS.lineage}
|
||||
entityDetails={entityDetail as Dashboard}
|
||||
isLoading={isLoading}
|
||||
tags={tags}
|
||||
/>
|
||||
);
|
||||
|
||||
case EntityType.PIPELINE:
|
||||
return (
|
||||
<PipelineSummary
|
||||
componentType={DRAWER_NAVIGATION_OPTIONS.lineage}
|
||||
entityDetails={entityDetail as Pipeline}
|
||||
isLoading={isLoading}
|
||||
tags={tags}
|
||||
/>
|
||||
);
|
||||
|
||||
case EntityType.MLMODEL:
|
||||
return (
|
||||
<MlModelSummary
|
||||
componentType={DRAWER_NAVIGATION_OPTIONS.lineage}
|
||||
entityDetails={entityDetail as Mlmodel}
|
||||
isLoading={isLoading}
|
||||
tags={tags}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}, [entityDetail, fetchEntityDetail, tags, selectedNode]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchEntityDetail(selectedNode);
|
||||
}, [selectedNode]);
|
||||
@ -157,88 +256,45 @@ const EntityInfoDrawer = ({
|
||||
return (
|
||||
<Drawer
|
||||
destroyOnClose
|
||||
bodyStyle={{ padding: 16 }}
|
||||
className="entity-panel-container"
|
||||
closable={false}
|
||||
extra={<CloseOutlined onClick={onCancel} />}
|
||||
extra={
|
||||
<CloseOutlined
|
||||
data-testid="entity-panel-close-icon"
|
||||
onClick={onCancel}
|
||||
/>
|
||||
}
|
||||
getContainer={false}
|
||||
headerStyle={{ padding: 16 }}
|
||||
mask={false}
|
||||
open={show}
|
||||
style={{ position: 'absolute' }}
|
||||
title={
|
||||
<p className="tw-flex">
|
||||
<span className="tw-mr-2">{getEntityIcon(selectedNode.type)}</span>
|
||||
{getHeaderLabel(
|
||||
selectedNode.displayName ?? selectedNode.name,
|
||||
selectedNode.fqn,
|
||||
selectedNode.type,
|
||||
isMainNode
|
||||
)}
|
||||
</p>
|
||||
}
|
||||
visible={show}>
|
||||
{isLoading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<>
|
||||
<section className="tw-mt-1">
|
||||
<div className="tw-flex tw-flex-col">
|
||||
{entityInfo.map((d) => {
|
||||
return (
|
||||
<div className="tw-py-1.5 tw-flex" key={d.name}>
|
||||
{d.name && <span>{d.name}:</span>}
|
||||
<span
|
||||
className={classNames(
|
||||
{ 'tw-ml-2': d.name },
|
||||
{
|
||||
'link-text': d.isLink,
|
||||
}
|
||||
)}>
|
||||
{d.isLink ? (
|
||||
<Link
|
||||
target={d.isExternal ? '_blank' : '_self'}
|
||||
to={{ pathname: d.url }}>
|
||||
{d.value}
|
||||
</Link>
|
||||
) : (
|
||||
d.value
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
{entityInfo.length > 0 && <Divider />}
|
||||
<section className="tw-mt-1">
|
||||
<span className="tw-text-grey-muted">{t('label.tag-plural')}</span>
|
||||
<div className="tw-flex tw-flex-wrap tw-pt-1.5">
|
||||
{getEntityTags(selectedNode.type, entityDetail).length > 0 ? (
|
||||
<TagsViewer
|
||||
sizeCap={-1}
|
||||
tags={getEntityTags(selectedNode.type, entityDetail)}
|
||||
/>
|
||||
) : (
|
||||
<p className="tw-text-xs tw-text-grey-muted">
|
||||
{t('label.no-tags-added')}
|
||||
</p>
|
||||
<Row gutter={[0, isMainNode ? 6 : 0]}>
|
||||
<Col span={24}>
|
||||
{'databaseSchema' in entityDetail && 'database' in entityDetail && (
|
||||
<span
|
||||
className="text-grey-muted text-xs"
|
||||
data-testid="database-schema">{`${entityDetail.database?.name}${FQN_SEPARATOR_CHAR}${entityDetail.databaseSchema?.name}`}</span>
|
||||
)}
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Typography
|
||||
className={classNames('flex items-center text-base', {
|
||||
'entity-info-header-link': !isMainNode,
|
||||
})}>
|
||||
<span className="m-r-xs">{getEntityIcon(selectedNode.type)}</span>
|
||||
{getHeaderLabel(
|
||||
selectedNode.displayName ?? selectedNode.name,
|
||||
selectedNode.fqn,
|
||||
selectedNode.type,
|
||||
isMainNode
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
<Divider />
|
||||
<section className="tw-mt-1">
|
||||
<span className="tw-text-grey-muted">{t('label.description')}</span>
|
||||
<div>
|
||||
{entityDetail.description?.trim() ? (
|
||||
<RichTextEditorPreviewer markdown={entityDetail.description} />
|
||||
) : (
|
||||
<p className="tw-text-xs tw-text-grey-muted">
|
||||
{t('label.no-description')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</Typography>
|
||||
</Col>
|
||||
</Row>
|
||||
}>
|
||||
{summaryComponent}
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -20,3 +20,39 @@
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.entity-panel-container {
|
||||
.ant-drawer-body {
|
||||
padding: unset;
|
||||
}
|
||||
|
||||
.entity-info-header-link {
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
.ant-btn-link > span {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-btn-link > span {
|
||||
color: @text-color-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
.summary-panel-statistics-count {
|
||||
color: @text-color-secondary;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: @success-color;
|
||||
}
|
||||
.failed {
|
||||
color: @failed-color;
|
||||
}
|
||||
.aborted {
|
||||
color: @aborted-color;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,28 +11,43 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Col, Divider, Row, Space, Typography } from 'antd';
|
||||
import { Col, Divider, Row, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component';
|
||||
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
|
||||
import { ExplorePageTabs } from 'enums/Explore.enum';
|
||||
import { TagLabel } from 'generated/type/tagLabel';
|
||||
import { ChartType } from 'pages/DashboardDetailsPage/DashboardDetailsPage.component';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
DRAWER_NAVIGATION_OPTIONS,
|
||||
getEntityOverview,
|
||||
} from 'utils/EntityUtils';
|
||||
import SVGIcons from 'utils/SvgUtils';
|
||||
import { SummaryEntityType } from '../../../../enums/EntitySummary.enum';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { Dashboard } from '../../../../generated/entity/data/dashboard';
|
||||
import { fetchCharts } from '../../../../utils/DashboardDetailsUtils';
|
||||
import { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils';
|
||||
import SVGIcons from '../../../../utils/SvgUtils';
|
||||
import { showErrorToast } from '../../../../utils/ToastUtils';
|
||||
import TableDataCardTitle from '../../../common/table-data-card-v2/TableDataCardTitle.component';
|
||||
import SummaryList from '../SummaryList/SummaryList.component';
|
||||
import { BasicEntityInfo } from '../SummaryList/SummaryList.interface';
|
||||
|
||||
interface DashboardSummaryProps {
|
||||
entityDetails: Dashboard;
|
||||
componentType?: string;
|
||||
tags?: TagLabel[];
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
function DashboardSummary({ entityDetails }: DashboardSummaryProps) {
|
||||
function DashboardSummary({
|
||||
entityDetails,
|
||||
componentType = DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
tags,
|
||||
isLoading,
|
||||
}: DashboardSummaryProps) {
|
||||
const { t } = useTranslation();
|
||||
const [charts, setCharts] = useState<ChartType[]>();
|
||||
|
||||
@ -56,6 +71,11 @@ function DashboardSummary({ entityDetails }: DashboardSummaryProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const entityInfo = useMemo(
|
||||
() => getEntityOverview(ExplorePageTabs.DASHBOARDS, entityDetails),
|
||||
[entityDetails]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchChartsDetails();
|
||||
}, [entityDetails]);
|
||||
@ -65,63 +85,96 @@ function DashboardSummary({ entityDetails }: DashboardSummaryProps) {
|
||||
[charts]
|
||||
);
|
||||
|
||||
const isExplore = useMemo(
|
||||
() => componentType === DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
[componentType]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row className="m-md" gutter={[0, 4]}>
|
||||
<Col span={24}>
|
||||
<TableDataCardTitle
|
||||
dataTestId="summary-panel-title"
|
||||
searchIndex={SearchIndex.DASHBOARD}
|
||||
source={entityDetails}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Row gutter={16}>
|
||||
<Col
|
||||
className="text-gray"
|
||||
data-testid="dashboard-url-label"
|
||||
span={10}>
|
||||
{`${t('label.dashboard')} ${t('label.url-uppercase')}`}
|
||||
</Col>
|
||||
<Col data-testid="dashboard-url-value" span={12}>
|
||||
{entityDetails.dashboardUrl ? (
|
||||
<Link
|
||||
target="_blank"
|
||||
to={{ pathname: entityDetails.dashboardUrl }}>
|
||||
<Space align="start">
|
||||
<Typography.Text
|
||||
className="link"
|
||||
data-testid="dashboard-link-name">
|
||||
{entityDetails.name}
|
||||
</Typography.Text>
|
||||
<SVGIcons
|
||||
alt="external-link"
|
||||
icon="external-link"
|
||||
width="12px"
|
||||
/>
|
||||
</Space>
|
||||
</Link>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider className="m-0" />
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="section-header"
|
||||
data-testid="charts-header">
|
||||
{t('label.chart-plural')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<SummaryList formattedEntityData={formattedChartsData} />
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
<SummaryPanelSkeleton loading={Boolean(isLoading)}>
|
||||
<>
|
||||
<Row className="m-md" gutter={[0, 4]}>
|
||||
<Col span={24}>
|
||||
<Row>
|
||||
{entityInfo.map((info) => {
|
||||
const isOwner = info.name === t('label.owner');
|
||||
|
||||
return info.visible?.includes(componentType) ? (
|
||||
<Col key={info.name} span={24}>
|
||||
<Row
|
||||
className={classNames('', {
|
||||
'p-b-md': isOwner,
|
||||
})}
|
||||
gutter={[16, 32]}>
|
||||
{!isOwner ? (
|
||||
<Col data-testid={`${info.name}-label`} span={8}>
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{info.name}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
) : null}
|
||||
<Col data-testid="dashboard-url-value" span={16}>
|
||||
{info.isLink ? (
|
||||
<Link
|
||||
component={Typography.Link}
|
||||
target={info.isExternal ? '_blank' : '_self'}
|
||||
to={{ pathname: info.url }}>
|
||||
<Typography.Link
|
||||
className="text-primary"
|
||||
data-testid="dashboard-link-name">
|
||||
{info.value}
|
||||
</Typography.Link>
|
||||
{info.isExternal ? (
|
||||
<SVGIcons
|
||||
alt="external-link"
|
||||
className="m-l-xs"
|
||||
icon="external-link"
|
||||
width="12px"
|
||||
/>
|
||||
) : null}
|
||||
</Link>
|
||||
) : (
|
||||
<Typography.Text
|
||||
className={classNames('text-grey-muted', {
|
||||
'text-grey-body': !isOwner,
|
||||
})}>
|
||||
{info.value}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
) : null;
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider className="m-y-xs" />
|
||||
|
||||
{!isExplore ? (
|
||||
<>
|
||||
<SummaryTagsDescription
|
||||
entityDetail={entityDetails}
|
||||
tags={tags ? tags : []}
|
||||
/>
|
||||
<Divider className="m-y-xs" />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="text-base text-grey-muted"
|
||||
data-testid="charts-header">
|
||||
{t('label.chart-plural')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<SummaryList formattedEntityData={formattedChartsData} />
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
</SummaryPanelSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -14,22 +14,13 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { DRAWER_NAVIGATION_OPTIONS } from 'utils/EntityUtils';
|
||||
import {
|
||||
mockDashboardEntityDetails,
|
||||
mockFetchChartsResponse,
|
||||
} from '../mocks/DashboardSummary.mock';
|
||||
import DashboardSummary from './DashboardSummary.component';
|
||||
|
||||
jest.mock(
|
||||
'../../../common/table-data-card-v2/TableDataCardTitle.component',
|
||||
() =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="TableDataCardTitle">TableDataCardTitle</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('../SummaryList/SummaryList.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
@ -48,19 +39,68 @@ describe('DashboardSummary component tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
const dashboardTitle = screen.getByTestId('TableDataCardTitle');
|
||||
const dashboardUrlLabel = screen.getByTestId('dashboard-url-label');
|
||||
const dashboardUrlValue = screen.getByTestId('dashboard-link-name');
|
||||
const dashboardUrlLabel = screen.getByText(
|
||||
'label.dashboard label.url-uppercase'
|
||||
);
|
||||
const dashboardUrlValue = screen.getByTestId('dashboard-url-value');
|
||||
const dashboardLinkName = screen.getByTestId('dashboard-link-name');
|
||||
const chartsHeader = screen.getByTestId('charts-header');
|
||||
const summaryList = screen.getByTestId('SummaryList');
|
||||
|
||||
expect(dashboardTitle).toBeInTheDocument();
|
||||
expect(dashboardLinkName).toBeInTheDocument();
|
||||
expect(dashboardUrlLabel).toBeInTheDocument();
|
||||
expect(dashboardUrlValue).toContainHTML(mockDashboardEntityDetails.name);
|
||||
expect(chartsHeader).toBeInTheDocument();
|
||||
expect(summaryList).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Component should render properly, when loaded in the Lineage page.', async () => {
|
||||
const labels = ['label.service-label', 'label.tier-label'];
|
||||
|
||||
await act(async () => {
|
||||
const { debug } = render(
|
||||
<DashboardSummary
|
||||
componentType={DRAWER_NAVIGATION_OPTIONS.lineage}
|
||||
entityDetails={mockDashboardEntityDetails}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
debug();
|
||||
});
|
||||
|
||||
const dashboardUrlLabel = screen.getByText(
|
||||
'label.dashboard label.url-uppercase'
|
||||
);
|
||||
const ownerLabel = screen.queryByTestId('label.owner-label');
|
||||
|
||||
const dashboardUrl = screen.getAllByTestId('dashboard-url-value');
|
||||
|
||||
const tags = screen.getByText('label.tag-plural');
|
||||
const description = screen.getByText('label.description');
|
||||
const noDataFound = screen.getByText('label.no-data-found');
|
||||
const dashboardLink = screen.getAllByTestId('dashboard-link-name');
|
||||
const dashboardValue = screen.getAllByTestId('dashboard-url-value');
|
||||
|
||||
labels.forEach((label) =>
|
||||
expect(screen.getByTestId(label)).toBeInTheDocument()
|
||||
);
|
||||
|
||||
expect(ownerLabel).not.toBeInTheDocument();
|
||||
|
||||
expect(dashboardUrl[0]).toBeInTheDocument();
|
||||
expect(dashboardLink[0]).toBeInTheDocument();
|
||||
expect(dashboardValue[0]).toBeInTheDocument();
|
||||
|
||||
expect(tags).toBeInTheDocument();
|
||||
expect(description).toBeInTheDocument();
|
||||
expect(noDataFound).toBeInTheDocument();
|
||||
|
||||
expect(dashboardUrlLabel).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('If the dashboard url is not present in dashboard details, "-" should be displayed as dashboard url value', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
|
||||
@ -12,8 +12,10 @@
|
||||
*/
|
||||
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import classNames from 'classnames';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Col, Drawer, Row } from 'antd';
|
||||
import TableDataCardTitle from 'components/common/table-data-card-v2/TableDataCardTitle.component';
|
||||
import { EntityType } from 'enums/entity.enum';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { ExplorePageTabs } from '../../../enums/Explore.enum';
|
||||
import { Dashboard } from '../../../generated/entity/data/dashboard';
|
||||
@ -34,16 +36,23 @@ export default function EntitySummaryPanel({
|
||||
handleClosePanel,
|
||||
}: EntitySummaryPanelProps) {
|
||||
const { tab } = useParams<{ tab: string }>();
|
||||
const [currentSearchIndex, setCurrentSearchIndex] = useState<EntityType>();
|
||||
|
||||
const summaryComponent = useMemo(() => {
|
||||
switch (entityDetails.entityType) {
|
||||
case ExplorePageTabs.TABLES:
|
||||
setCurrentSearchIndex(EntityType.TABLE);
|
||||
|
||||
return <TableSummary entityDetails={entityDetails.details as Table} />;
|
||||
|
||||
case ExplorePageTabs.TOPICS:
|
||||
setCurrentSearchIndex(EntityType.TOPIC);
|
||||
|
||||
return <TopicSummary entityDetails={entityDetails.details as Topic} />;
|
||||
|
||||
case ExplorePageTabs.DASHBOARDS:
|
||||
setCurrentSearchIndex(EntityType.DASHBOARD);
|
||||
|
||||
return (
|
||||
<DashboardSummary
|
||||
entityDetails={entityDetails.details as Dashboard}
|
||||
@ -51,11 +60,15 @@ export default function EntitySummaryPanel({
|
||||
);
|
||||
|
||||
case ExplorePageTabs.PIPELINES:
|
||||
setCurrentSearchIndex(EntityType.PIPELINE);
|
||||
|
||||
return (
|
||||
<PipelineSummary entityDetails={entityDetails.details as Pipeline} />
|
||||
);
|
||||
|
||||
case ExplorePageTabs.MLMODELS:
|
||||
setCurrentSearchIndex(EntityType.MLMODEL);
|
||||
|
||||
return (
|
||||
<MlModelSummary entityDetails={entityDetails.details as Mlmodel} />
|
||||
);
|
||||
@ -66,13 +79,34 @@ export default function EntitySummaryPanel({
|
||||
}, [tab, entityDetails]);
|
||||
|
||||
return (
|
||||
<div className={classNames('summary-panel-container')}>
|
||||
<Drawer
|
||||
destroyOnClose
|
||||
open
|
||||
className="summary-panel-container"
|
||||
closable={false}
|
||||
extra={
|
||||
<CloseOutlined
|
||||
data-testid="summary-panel-close-icon"
|
||||
onClick={handleClosePanel}
|
||||
/>
|
||||
}
|
||||
getContainer={false}
|
||||
headerStyle={{ padding: 16 }}
|
||||
mask={false}
|
||||
title={
|
||||
<Row gutter={[0, 6]}>
|
||||
<Col span={24}>
|
||||
<TableDataCardTitle
|
||||
isPanel
|
||||
dataTestId="summary-panel-title"
|
||||
searchIndex={currentSearchIndex as EntityType}
|
||||
source={entityDetails.details}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
}
|
||||
width="100%">
|
||||
{summaryComponent}
|
||||
<CloseOutlined
|
||||
className="close-icon"
|
||||
data-testid="summary-panel-close-icon"
|
||||
onClick={handleClosePanel}
|
||||
/>
|
||||
</div>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -33,31 +33,25 @@
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
|
||||
.ant-typography {
|
||||
color: #37352f;
|
||||
.ant-drawer-content-wrapper {
|
||||
box-shadow: -2px 2px 4px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
.ant-drawer-body {
|
||||
padding: unset;
|
||||
}
|
||||
|
||||
.summary-panel-statistics-count {
|
||||
color: #37352f;
|
||||
color: @text-color-secondary;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.text-gray {
|
||||
color: @label-color;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
font-size: 16px;
|
||||
color: @section-header-color;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: @successColor;
|
||||
}
|
||||
.failed {
|
||||
color: @failedColor;
|
||||
color: @failed-color;
|
||||
}
|
||||
.aborted {
|
||||
color: @abortedColor;
|
||||
|
||||
@ -64,6 +64,16 @@ jest.mock('./MlModelSummary/MlModelSummary.component', () =>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock(
|
||||
'components/common/table-data-card-v2/TableDataCardTitle.component',
|
||||
() =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="table-data-card-title">TableDataCardTitle</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useParams: jest.fn().mockImplementation(() => ({ tab: 'table' })),
|
||||
}));
|
||||
@ -80,9 +90,11 @@ describe('EntitySummaryPanel component tests', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const tableDataCardTitle = screen.getByText('TableDataCardTitle');
|
||||
const tableSummary = screen.getByTestId('TableSummary');
|
||||
const closeIcon = screen.getByTestId('summary-panel-close-icon');
|
||||
|
||||
expect(tableDataCardTitle).toBeInTheDocument();
|
||||
expect(tableSummary).toBeInTheDocument();
|
||||
expect(closeIcon).toBeInTheDocument();
|
||||
|
||||
|
||||
@ -12,50 +12,42 @@
|
||||
*/
|
||||
|
||||
import { Col, Divider, Row, Typography } from 'antd';
|
||||
import { startCase } from 'lodash';
|
||||
import React, { ReactNode, useMemo } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component';
|
||||
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
|
||||
import { ExplorePageTabs } from 'enums/Explore.enum';
|
||||
import { TagLabel } from 'generated/type/tagLabel';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getDashboardDetailsPath } from '../../../../constants/constants';
|
||||
import {
|
||||
DRAWER_NAVIGATION_OPTIONS,
|
||||
getEntityOverview,
|
||||
} from 'utils/EntityUtils';
|
||||
import SVGIcons from 'utils/SvgUtils';
|
||||
import { SummaryEntityType } from '../../../../enums/EntitySummary.enum';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { Mlmodel } from '../../../../generated/entity/data/mlmodel';
|
||||
import { getEntityName } from '../../../../utils/CommonUtils';
|
||||
import { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils';
|
||||
import TableDataCardTitle from '../../../common/table-data-card-v2/TableDataCardTitle.component';
|
||||
import SummaryList from '../SummaryList/SummaryList.component';
|
||||
import { BasicEntityInfo } from '../SummaryList/SummaryList.interface';
|
||||
|
||||
interface MlModelSummaryProps {
|
||||
entityDetails: Mlmodel;
|
||||
componentType?: string;
|
||||
tags?: TagLabel[];
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
interface BasicMlModelInfo {
|
||||
algorithm: string;
|
||||
target?: string;
|
||||
server?: ReactNode;
|
||||
dashboard?: ReactNode;
|
||||
}
|
||||
|
||||
function MlModelSummary({ entityDetails }: MlModelSummaryProps) {
|
||||
function MlModelSummary({
|
||||
entityDetails,
|
||||
componentType = DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
tags,
|
||||
isLoading,
|
||||
}: MlModelSummaryProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const basicMlModelInfo: BasicMlModelInfo = useMemo(
|
||||
() => ({
|
||||
algorithm: entityDetails.algorithm,
|
||||
target: entityDetails.target,
|
||||
server: entityDetails.server ? (
|
||||
<a href={entityDetails.server}>{entityDetails.server}</a>
|
||||
) : undefined,
|
||||
dashboard: entityDetails.dashboard ? (
|
||||
<Link
|
||||
to={getDashboardDetailsPath(
|
||||
entityDetails.dashboard?.fullyQualifiedName as string
|
||||
)}>
|
||||
{getEntityName(entityDetails.dashboard)}
|
||||
</Link>
|
||||
) : undefined,
|
||||
}),
|
||||
const entityInfo = useMemo(
|
||||
() => getEntityOverview(ExplorePageTabs.MLMODELS, entityDetails),
|
||||
[entityDetails]
|
||||
);
|
||||
|
||||
@ -68,55 +60,91 @@ function MlModelSummary({ entityDetails }: MlModelSummaryProps) {
|
||||
[entityDetails]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row className="m-md" gutter={[0, 4]}>
|
||||
<Col span={24}>
|
||||
<TableDataCardTitle
|
||||
dataTestId="summary-panel-title"
|
||||
searchIndex={SearchIndex.MLMODEL}
|
||||
source={entityDetails}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Row>
|
||||
{Object.keys(basicMlModelInfo).map((fieldName) => {
|
||||
const value =
|
||||
basicMlModelInfo[fieldName as keyof BasicMlModelInfo];
|
||||
const isExplore = useMemo(
|
||||
() => componentType === DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
[componentType]
|
||||
);
|
||||
|
||||
return (
|
||||
<Col key={fieldName} span={24}>
|
||||
<Row gutter={16}>
|
||||
<Col
|
||||
className="text-gray"
|
||||
data-testid={`${fieldName}-label`}
|
||||
span={10}>
|
||||
{startCase(fieldName)}
|
||||
</Col>
|
||||
<Col data-testid={`${fieldName}-value`} span={12}>
|
||||
{value ? value : '-'}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider className="m-0" />
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="section-header"
|
||||
data-testid="features-header">
|
||||
{t('label.feature-plural')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<SummaryList formattedEntityData={formattedFeaturesData} />
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
return (
|
||||
<SummaryPanelSkeleton loading={Boolean(isLoading)}>
|
||||
<>
|
||||
<Row className="m-md" gutter={[0, 4]}>
|
||||
<Col span={24}>
|
||||
<Row>
|
||||
{entityInfo.map((info) => {
|
||||
const isOwner = info.name === t('label.owner');
|
||||
|
||||
return info.visible?.includes(componentType) ? (
|
||||
<Col key={info.name} span={24}>
|
||||
<Row
|
||||
className={classNames('', {
|
||||
'p-b-md': isOwner,
|
||||
})}
|
||||
gutter={[16, 32]}>
|
||||
{!isOwner ? (
|
||||
<Col data-testid={`${info.name}-label`} span={8}>
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{info.name}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
) : null}
|
||||
<Col data-testid={`${info.name}-value`} span={16}>
|
||||
{info.isLink ? (
|
||||
<Link
|
||||
target={info.isExternal ? '_blank' : '_self'}
|
||||
to={{ pathname: info.url }}>
|
||||
{info.value}
|
||||
{info.isExternal ? (
|
||||
<SVGIcons
|
||||
alt="external-link"
|
||||
className="m-l-xs"
|
||||
icon="external-link"
|
||||
width="12px"
|
||||
/>
|
||||
) : null}
|
||||
</Link>
|
||||
) : (
|
||||
<Typography.Text
|
||||
className={classNames('text-grey-muted', {
|
||||
'text-grey-body': !isOwner,
|
||||
})}>
|
||||
{info.value}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
) : null;
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider className="m-y-xs" />
|
||||
|
||||
{!isExplore ? (
|
||||
<>
|
||||
<SummaryTagsDescription
|
||||
entityDetail={entityDetails}
|
||||
tags={tags ? tags : []}
|
||||
/>
|
||||
<Divider className="m-y-xs" />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="text-base text-grey-muted"
|
||||
data-testid="features-header">
|
||||
{t('label.feature-plural')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<SummaryList formattedEntityData={formattedFeaturesData} />
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
</SummaryPanelSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -20,16 +20,6 @@ import {
|
||||
} from '../mocks/MlModelSummary.mock';
|
||||
import MlModelSummary from './MlModelSummary.component';
|
||||
|
||||
jest.mock(
|
||||
'../../../common/table-data-card-v2/TableDataCardTitle.component',
|
||||
() =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="TableDataCardTitle">TableDataCardTitle</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('../SummaryList/SummaryList.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
@ -42,17 +32,15 @@ describe('MlModelSummary component tests', () => {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
const mlModelTitle = screen.getByTestId('TableDataCardTitle');
|
||||
const algorithmLabel = screen.getByTestId('algorithm-label');
|
||||
const targetLabel = screen.getByTestId('target-label');
|
||||
const serverLabel = screen.getByTestId('server-label');
|
||||
const dashboardLabel = screen.getByTestId('dashboard-label');
|
||||
const algorithmValue = screen.getByTestId('algorithm-value');
|
||||
const targetValue = screen.getByTestId('target-value');
|
||||
const serverValue = screen.getByTestId('server-value');
|
||||
const dashboardValue = screen.getByTestId('dashboard-value');
|
||||
const algorithmLabel = screen.getByTestId('label.algorithm-label');
|
||||
const targetLabel = screen.getByTestId('label.target-label');
|
||||
const serverLabel = screen.getByTestId('label.server-label');
|
||||
const dashboardLabel = screen.getByTestId('label.dashboard-label');
|
||||
const algorithmValue = screen.getByTestId('label.algorithm-value');
|
||||
const targetValue = screen.getByTestId('label.target-value');
|
||||
const serverValue = screen.getByTestId('label.server-value');
|
||||
const dashboardValue = screen.getByTestId('label.dashboard-value');
|
||||
|
||||
expect(mlModelTitle).toBeInTheDocument();
|
||||
expect(algorithmLabel).toBeInTheDocument();
|
||||
expect(targetLabel).toBeInTheDocument();
|
||||
expect(serverLabel).toBeInTheDocument();
|
||||
@ -68,14 +56,14 @@ describe('MlModelSummary component tests', () => {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
const algorithmLabel = screen.getByTestId('algorithm-label');
|
||||
const targetLabel = screen.queryByTestId('target-label');
|
||||
const serverLabel = screen.queryByTestId('server-label');
|
||||
const dashboardLabel = screen.queryByTestId('dashboard-label');
|
||||
const algorithmValue = screen.getByTestId('algorithm-value');
|
||||
const targetValue = screen.getByTestId('target-value');
|
||||
const serverValue = screen.getByTestId('server-value');
|
||||
const dashboardValue = screen.getByTestId('dashboard-value');
|
||||
const algorithmLabel = screen.getByTestId('label.algorithm-label');
|
||||
const targetLabel = screen.queryByTestId('label.target-label');
|
||||
const serverLabel = screen.queryByTestId('label.server-label');
|
||||
const dashboardLabel = screen.queryByTestId('label.dashboard-label');
|
||||
const algorithmValue = screen.getByTestId('label.algorithm-value');
|
||||
const targetValue = screen.getByTestId('label.target-value');
|
||||
const serverValue = screen.getByTestId('label.server-value');
|
||||
const dashboardValue = screen.getByTestId('label.dashboard-value');
|
||||
|
||||
expect(algorithmLabel).toBeInTheDocument();
|
||||
expect(targetLabel).toBeInTheDocument();
|
||||
|
||||
@ -12,23 +12,37 @@
|
||||
*/
|
||||
|
||||
import { Col, Divider, Row, Space, Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component';
|
||||
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
|
||||
import { ExplorePageTabs } from 'enums/Explore.enum';
|
||||
import { TagLabel } from 'generated/type/tagLabel';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
DRAWER_NAVIGATION_OPTIONS,
|
||||
getEntityOverview,
|
||||
} from 'utils/EntityUtils';
|
||||
import SVGIcons from 'utils/SvgUtils';
|
||||
import { SummaryEntityType } from '../../../../enums/EntitySummary.enum';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { Pipeline } from '../../../../generated/entity/data/pipeline';
|
||||
import { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils';
|
||||
import SVGIcons from '../../../../utils/SvgUtils';
|
||||
import TableDataCardTitle from '../../../common/table-data-card-v2/TableDataCardTitle.component';
|
||||
import SummaryList from '../SummaryList/SummaryList.component';
|
||||
import { BasicEntityInfo } from '../SummaryList/SummaryList.interface';
|
||||
|
||||
interface PipelineSummaryProps {
|
||||
entityDetails: Pipeline;
|
||||
componentType?: string;
|
||||
tags?: TagLabel[];
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
function PipelineSummary({ entityDetails }: PipelineSummaryProps) {
|
||||
function PipelineSummary({
|
||||
entityDetails,
|
||||
componentType = DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
tags,
|
||||
isLoading,
|
||||
}: PipelineSummaryProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const formattedTasksData: BasicEntityInfo[] = useMemo(
|
||||
@ -36,63 +50,105 @@ function PipelineSummary({ entityDetails }: PipelineSummaryProps) {
|
||||
[entityDetails]
|
||||
);
|
||||
|
||||
const entityInfo = useMemo(
|
||||
() => getEntityOverview(ExplorePageTabs.PIPELINES, entityDetails),
|
||||
[entityDetails]
|
||||
);
|
||||
|
||||
const isExplore = useMemo(
|
||||
() => componentType === DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
[componentType]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row className="m-md" gutter={[0, 4]}>
|
||||
<Col span={24}>
|
||||
<TableDataCardTitle
|
||||
dataTestId="summary-panel-title"
|
||||
searchIndex={SearchIndex.PIPELINE}
|
||||
source={entityDetails}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Row gutter={16}>
|
||||
<Col
|
||||
className="text-gray"
|
||||
data-testid="pipeline-url-label"
|
||||
span={10}>
|
||||
{`${t('label.pipeline')} ${t('label.url-uppercase')}`}
|
||||
</Col>
|
||||
<Col data-testid="pipeline-url-value" span={12}>
|
||||
{entityDetails.pipelineUrl ? (
|
||||
<Link
|
||||
target="_blank"
|
||||
to={{ pathname: entityDetails.pipelineUrl }}>
|
||||
<Space align="start">
|
||||
<Typography.Text
|
||||
className="link"
|
||||
data-testid="pipeline-link-name">
|
||||
{entityDetails.name}
|
||||
</Typography.Text>
|
||||
<SVGIcons
|
||||
alt="external-link"
|
||||
icon="external-link"
|
||||
width="12px"
|
||||
/>
|
||||
</Space>
|
||||
</Link>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider className="m-0" />
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="section-header"
|
||||
data-testid="tasks-header">
|
||||
{t('label.task-plural')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<SummaryList formattedEntityData={formattedTasksData} />
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
<SummaryPanelSkeleton loading={Boolean(isLoading)}>
|
||||
<>
|
||||
<Row className="m-md" gutter={[0, 4]}>
|
||||
<Col span={24}>
|
||||
<Row>
|
||||
{entityInfo.map((info) => {
|
||||
const isOwner = info.name === t('label.owner');
|
||||
|
||||
return info.visible?.includes(componentType) ? (
|
||||
<Col key={info.name} span={24}>
|
||||
<Row
|
||||
className={classNames('', {
|
||||
'p-b-md': isOwner,
|
||||
})}
|
||||
gutter={[16, 32]}>
|
||||
{!isOwner ? (
|
||||
<Col
|
||||
data-testid={
|
||||
info.dataTestId
|
||||
? info.dataTestId
|
||||
: `${info.name}-label`
|
||||
}
|
||||
span={8}>
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{info.name}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
) : null}
|
||||
<Col data-testid={`${info.name}-value`} span={16}>
|
||||
{info.isLink ? (
|
||||
<Space align="start">
|
||||
<Typography.Link
|
||||
data-testid="pipeline-link-name"
|
||||
href={info.url}
|
||||
target={info.isExternal ? '_blank' : '_self'}>
|
||||
{info.value}
|
||||
{info.isExternal ? (
|
||||
<SVGIcons
|
||||
alt="external-link"
|
||||
className="m-l-xs"
|
||||
icon="external-link"
|
||||
width="12px"
|
||||
/>
|
||||
) : null}
|
||||
</Typography.Link>
|
||||
</Space>
|
||||
) : (
|
||||
<Typography.Text
|
||||
className={classNames('text-grey-muted', {
|
||||
'text-grey-body': !isOwner,
|
||||
})}>
|
||||
{info.value}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
) : null;
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider className="m-y-xs" />
|
||||
|
||||
{!isExplore ? (
|
||||
<>
|
||||
<SummaryTagsDescription
|
||||
entityDetail={entityDetails}
|
||||
tags={tags ? tags : []}
|
||||
/>
|
||||
<Divider className="m-y-xs" />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="text-base text-grey-muted"
|
||||
data-testid="tasks-header">
|
||||
{t('label.task-plural')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<SummaryList formattedEntityData={formattedTasksData} />
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
</SummaryPanelSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -14,19 +14,10 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { DRAWER_NAVIGATION_OPTIONS } from 'utils/EntityUtils';
|
||||
import { mockPipelineEntityDetails } from '../mocks/PipelineSummary.mock';
|
||||
import PipelineSummary from './PipelineSummary.component';
|
||||
|
||||
jest.mock(
|
||||
'../../../common/table-data-card-v2/TableDataCardTitle.component',
|
||||
() =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="TableDataCardTitle">TableDataCardTitle</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('../SummaryList/SummaryList.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
@ -34,24 +25,72 @@ jest.mock('../SummaryList/SummaryList.component', () =>
|
||||
);
|
||||
|
||||
describe('PipelineSummary component tests', () => {
|
||||
it('Component should render properly', () => {
|
||||
it('Component should render properly, when loaded in the Explore page.', () => {
|
||||
render(<PipelineSummary entityDetails={mockPipelineEntityDetails} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
const pipelineTitle = screen.getByTestId('TableDataCardTitle');
|
||||
const pipelineUrlLabel = screen.getByTestId('pipeline-url-label');
|
||||
const pipelineUrlValue = screen.getByTestId('pipeline-link-name');
|
||||
const tasksHeader = screen.getByTestId('tasks-header');
|
||||
const summaryList = screen.getByTestId('SummaryList');
|
||||
|
||||
expect(pipelineTitle).toBeInTheDocument();
|
||||
expect(pipelineUrlLabel).toBeInTheDocument();
|
||||
expect(pipelineUrlValue).toContainHTML(mockPipelineEntityDetails.name);
|
||||
expect(tasksHeader).toBeInTheDocument();
|
||||
expect(summaryList).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Component should render properly, when loaded in the Lineage page.', async () => {
|
||||
const labels = [
|
||||
'pipeline-url-label',
|
||||
'label.pipeline label.url-uppercase-value',
|
||||
'label.service-label',
|
||||
'label.tier-label',
|
||||
];
|
||||
|
||||
const values = [
|
||||
'label.service-value',
|
||||
'label.owner-value',
|
||||
'label.tier-value',
|
||||
];
|
||||
render(
|
||||
<PipelineSummary
|
||||
componentType={DRAWER_NAVIGATION_OPTIONS.lineage}
|
||||
entityDetails={mockPipelineEntityDetails}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const schemaHeader = screen.getAllByTestId('schema-header');
|
||||
const tags = screen.getByText('label.tag-plural');
|
||||
const noTags = screen.getByText('label.no-tags-added');
|
||||
const pipelineName = screen.getAllByTestId('pipeline-link-name');
|
||||
|
||||
const viewerContainer = screen.getByTestId('viewer-container');
|
||||
const summaryList = screen.getByTestId('SummaryList');
|
||||
const ownerLabel = screen.queryByTestId('label.owner-label');
|
||||
|
||||
labels.forEach((label) =>
|
||||
expect(screen.getByTestId(label)).toBeInTheDocument()
|
||||
);
|
||||
values.forEach((value) =>
|
||||
expect(screen.getByTestId(value)).toBeInTheDocument()
|
||||
);
|
||||
|
||||
expect(ownerLabel).not.toBeInTheDocument();
|
||||
|
||||
expect(schemaHeader[0]).toBeInTheDocument();
|
||||
expect(tags).toBeInTheDocument();
|
||||
expect(pipelineName[0]).toBeInTheDocument();
|
||||
expect(noTags).toBeInTheDocument();
|
||||
|
||||
expect(summaryList).toBeInTheDocument();
|
||||
expect(viewerContainer).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('If the pipeline url is not present in pipeline details, "-" should be displayed as pipeline url value', () => {
|
||||
render(
|
||||
<PipelineSummary
|
||||
@ -62,7 +101,9 @@ describe('PipelineSummary component tests', () => {
|
||||
}
|
||||
);
|
||||
|
||||
const pipelineUrlValue = screen.getByTestId('pipeline-url-value');
|
||||
const pipelineUrlValue = screen.getByTestId(
|
||||
'label.pipeline label.url-uppercase-value'
|
||||
);
|
||||
|
||||
expect(pipelineUrlValue).toContainHTML('-');
|
||||
});
|
||||
|
||||
@ -32,7 +32,9 @@ export default function SummaryList({
|
||||
<Row>
|
||||
{isEmpty(formattedEntityData) ? (
|
||||
<div className="m-y-md">
|
||||
<Text className="text-gray">{t('message.no-data-available')}</Text>
|
||||
<Text className="text-grey-body">
|
||||
{t('message.no-data-available')}
|
||||
</Text>
|
||||
</div>
|
||||
) : (
|
||||
formattedEntityData.map((entity) =>
|
||||
|
||||
@ -49,8 +49,12 @@ function SummaryListItem({
|
||||
<Col>
|
||||
{entityDetails.type && (
|
||||
<Space size={4}>
|
||||
<Text className="text-gray">{`${t('label.type')}:`}</Text>
|
||||
<Text className="font-medium" data-testid="entity-type">
|
||||
<Text className="text-grey-muted">{`${t(
|
||||
'label.type'
|
||||
)}:`}</Text>
|
||||
<Text
|
||||
className="font-medium text-grey-body"
|
||||
data-testid="entity-type">
|
||||
{entityDetails.type}
|
||||
</Text>
|
||||
</Space>
|
||||
@ -64,10 +68,12 @@ function SummaryListItem({
|
||||
</Col>
|
||||
<Col>
|
||||
<Space size={4}>
|
||||
<Text className="text-gray">{`${t(
|
||||
<Text className="text-grey-muted">{`${t(
|
||||
'label.algorithm'
|
||||
)}:`}</Text>
|
||||
<Text className="font-medium" data-testid="algorithm">
|
||||
<Text
|
||||
className="font-medium text-grey-body"
|
||||
data-testid="algorithm">
|
||||
{entityDetails.algorithm}
|
||||
</Text>
|
||||
</Space>
|
||||
@ -99,8 +105,8 @@ function SummaryListItem({
|
||||
)}
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Paragraph>
|
||||
<Col className="m-t-md" span={24}>
|
||||
<Paragraph className="text-grey-body">
|
||||
{entityDetails.description ? (
|
||||
<RichTextEditorPreviewer
|
||||
markdown={entityDetails.description || ''}
|
||||
@ -112,7 +118,7 @@ function SummaryListItem({
|
||||
</Paragraph>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider />
|
||||
<Divider className="m-y-xs" />
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
@ -14,38 +14,56 @@
|
||||
import { Col, Divider, Row, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component';
|
||||
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
|
||||
import { ExplorePageTabs } from 'enums/Explore.enum';
|
||||
import { isEmpty, isUndefined } from 'lodash';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
default as React,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
getLatestTableProfileByFqn,
|
||||
getTableQueryByTableId,
|
||||
} from 'rest/tableAPI';
|
||||
import { getListTestCase } from 'rest/testAPI';
|
||||
import {
|
||||
DRAWER_NAVIGATION_OPTIONS,
|
||||
getEntityOverview,
|
||||
} from 'utils/EntityUtils';
|
||||
import SVGIcons from 'utils/SvgUtils';
|
||||
import { API_RES_MAX_SIZE } from '../../../../constants/constants';
|
||||
import { INITIAL_TEST_RESULT_SUMMARY } from '../../../../constants/profiler.constant';
|
||||
import { SummaryEntityType } from '../../../../enums/EntitySummary.enum';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { Table, TableType } from '../../../../generated/entity/data/table';
|
||||
import { Table } from '../../../../generated/entity/data/table';
|
||||
import { Include } from '../../../../generated/type/include';
|
||||
import {
|
||||
formatNumberWithComma,
|
||||
formTwoDigitNmber,
|
||||
formTwoDigitNmber as formTwoDigitNumber,
|
||||
} from '../../../../utils/CommonUtils';
|
||||
import { updateTestResults } from '../../../../utils/DataQualityAndProfilerUtils';
|
||||
import { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils';
|
||||
import { generateEntityLink } from '../../../../utils/TableUtils';
|
||||
import { showErrorToast } from '../../../../utils/ToastUtils';
|
||||
import TableDataCardTitle from '../../../common/table-data-card-v2/TableDataCardTitle.component';
|
||||
import {
|
||||
OverallTableSummeryType,
|
||||
TableTestsType,
|
||||
} from '../../../TableProfiler/TableProfiler.interface';
|
||||
import SummaryList from '../SummaryList/SummaryList.component';
|
||||
import { BasicEntityInfo } from '../SummaryList/SummaryList.interface';
|
||||
import { BasicTableInfo, TableSummaryProps } from './TableSummary.interface';
|
||||
import { TableSummaryProps } from './TableSummary.interface';
|
||||
|
||||
function TableSummary({ entityDetails }: TableSummaryProps) {
|
||||
function TableSummary({
|
||||
entityDetails,
|
||||
componentType = DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
tags,
|
||||
isLoading,
|
||||
}: TableSummaryProps) {
|
||||
const { t } = useTranslation();
|
||||
const [tableDetails, setTableDetails] = useState<Table>(entityDetails);
|
||||
const [tableTests, setTableTests] = useState<TableTestsType>({
|
||||
@ -53,6 +71,11 @@ function TableSummary({ entityDetails }: TableSummaryProps) {
|
||||
results: INITIAL_TEST_RESULT_SUMMARY,
|
||||
});
|
||||
|
||||
const isExplore = useMemo(
|
||||
() => componentType === DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
[componentType]
|
||||
);
|
||||
|
||||
const isTableDeleted = useMemo(() => entityDetails.deleted, [entityDetails]);
|
||||
|
||||
const fetchAllTests = async () => {
|
||||
@ -84,13 +107,13 @@ function TableSummary({ entityDetails }: TableSummaryProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchProfilerData = async () => {
|
||||
const fetchProfilerData = useCallback(async () => {
|
||||
try {
|
||||
const profileResponse = await getLatestTableProfileByFqn(
|
||||
entityDetails?.fullyQualifiedName || ''
|
||||
);
|
||||
|
||||
const { profile } = profileResponse;
|
||||
const { profile, tableConstraints } = profileResponse;
|
||||
|
||||
const queriesResponse = await getTableQueryByTableId(
|
||||
entityDetails.id || ''
|
||||
@ -100,7 +123,7 @@ function TableSummary({ entityDetails }: TableSummaryProps) {
|
||||
|
||||
setTableDetails((prev) => {
|
||||
if (prev) {
|
||||
return { ...prev, profile, tableQueries };
|
||||
return { ...prev, profile, tableQueries, tableConstraints };
|
||||
} else {
|
||||
return {} as Table;
|
||||
}
|
||||
@ -113,7 +136,7 @@ function TableSummary({ entityDetails }: TableSummaryProps) {
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [entityDetails]);
|
||||
|
||||
const overallSummary: OverallTableSummeryType[] | undefined = useMemo(() => {
|
||||
if (isUndefined(tableDetails.profile)) {
|
||||
@ -142,31 +165,27 @@ function TableSummary({ entityDetails }: TableSummaryProps) {
|
||||
},
|
||||
{
|
||||
title: `${t('label.test-plural')} ${t('label.passed')}`,
|
||||
value: formTwoDigitNmber(tableTests.results.success),
|
||||
value: formTwoDigitNumber(tableTests.results.success),
|
||||
className: 'success',
|
||||
},
|
||||
{
|
||||
title: `${t('label.test-plural')} ${t('label.aborted')}`,
|
||||
value: formTwoDigitNmber(tableTests.results.aborted),
|
||||
value: formTwoDigitNumber(tableTests.results.aborted),
|
||||
className: 'aborted',
|
||||
},
|
||||
{
|
||||
title: `${t('label.test-plural')} ${t('label.failed')}`,
|
||||
value: formTwoDigitNmber(tableTests.results.failed),
|
||||
value: formTwoDigitNumber(tableTests.results.failed),
|
||||
className: 'failed',
|
||||
},
|
||||
];
|
||||
}, [tableDetails, tableTests]);
|
||||
|
||||
const { tableType, columns, tableQueries } = tableDetails;
|
||||
const { columns } = tableDetails;
|
||||
|
||||
const basicTableInfo: BasicTableInfo = useMemo(
|
||||
() => ({
|
||||
Type: tableType || TableType.Regular,
|
||||
Queries: tableQueries?.length ? `${tableQueries?.length}` : '-',
|
||||
Columns: columns?.length ? `${columns?.length}` : '-',
|
||||
}),
|
||||
[tableType, columns, tableQueries]
|
||||
const entityInfo = useMemo(
|
||||
() => getEntityOverview(ExplorePageTabs.TABLES, tableDetails),
|
||||
[tableDetails]
|
||||
);
|
||||
|
||||
const formattedColumnsData: BasicEntityInfo[] = useMemo(
|
||||
@ -182,102 +201,148 @@ function TableSummary({ entityDetails }: TableSummaryProps) {
|
||||
useEffect(() => {
|
||||
if (!isEmpty(entityDetails)) {
|
||||
setTableDetails(entityDetails);
|
||||
fetchAllTests();
|
||||
!isTableDeleted && fetchProfilerData();
|
||||
if (
|
||||
!isTableDeleted &&
|
||||
entityDetails.service?.type === 'databaseService'
|
||||
) {
|
||||
fetchProfilerData();
|
||||
fetchAllTests();
|
||||
}
|
||||
}
|
||||
}, [entityDetails]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row className={classNames('m-md')} gutter={[0, 4]}>
|
||||
<Col span={24}>
|
||||
<TableDataCardTitle
|
||||
dataTestId="summary-panel-title"
|
||||
searchIndex={SearchIndex.TABLE}
|
||||
source={tableDetails}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Row>
|
||||
{Object.keys(basicTableInfo).map((fieldName) => (
|
||||
<Col key={fieldName} span={24}>
|
||||
<Row gutter={16}>
|
||||
<Col
|
||||
className="text-gray"
|
||||
data-testid={`${fieldName}-label`}
|
||||
span={10}>
|
||||
{fieldName}
|
||||
</Col>
|
||||
<Col data-testid={`${fieldName}-value`} span={12}>
|
||||
{basicTableInfo[fieldName as keyof BasicTableInfo]}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider className="m-0" />
|
||||
<SummaryPanelSkeleton loading={isLoading || isEmpty(tableDetails)}>
|
||||
<>
|
||||
<Row className="m-md" gutter={[0, 4]}>
|
||||
<Col span={24}>
|
||||
<Row>
|
||||
{entityInfo.map((info) => {
|
||||
const isOwner = info.name === t('label.owner');
|
||||
|
||||
<Row className={classNames('m-md')} gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="section-header"
|
||||
data-testid="profiler-header">
|
||||
{t('label.profiler-amp-data-quality')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
{isUndefined(overallSummary) ? (
|
||||
<Typography.Text data-testid="no-profiler-enabled-message">
|
||||
{t('message.no-profiler-enabled-summary-message')}
|
||||
</Typography.Text>
|
||||
) : (
|
||||
<Row gutter={[16, 16]}>
|
||||
{overallSummary.map((field) => (
|
||||
<Col key={field.title} span={10}>
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="text-gray"
|
||||
data-testid={`${field.title}-label`}>
|
||||
{field.title}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className={classNames(
|
||||
'summary-panel-statistics-count',
|
||||
field.className
|
||||
return info.visible?.includes(componentType) ? (
|
||||
<Col key={info.name} span={24}>
|
||||
<Row
|
||||
className={classNames('', {
|
||||
'p-b-md': isOwner,
|
||||
})}
|
||||
gutter={[16, 32]}>
|
||||
{!isOwner ? (
|
||||
<Col data-testid={`${info.name}-label`} span={8}>
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{info.name}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
) : null}
|
||||
<Col data-testid={`${info.name}-value`} span={16}>
|
||||
{info.isLink ? (
|
||||
<Link
|
||||
component={Typography.Link}
|
||||
target={info.isExternal ? '_blank' : '_self'}
|
||||
to={{ pathname: info.url }}>
|
||||
{info.value}
|
||||
{info.isExternal ? (
|
||||
<SVGIcons
|
||||
alt="external-link"
|
||||
className="m-l-xs"
|
||||
icon="external-link"
|
||||
width="12px"
|
||||
/>
|
||||
) : null}
|
||||
</Link>
|
||||
) : (
|
||||
<Typography.Text
|
||||
className={classNames('text-grey-muted', {
|
||||
'text-grey-body': !isOwner,
|
||||
})}>
|
||||
{info.value}
|
||||
</Typography.Text>
|
||||
)}
|
||||
data-testid={`${field.title}-value`}>
|
||||
{field.value}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
))}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
) : null;
|
||||
})}
|
||||
</Row>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider className="m-0" />
|
||||
<Row className={classNames('m-md')} gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="section-header"
|
||||
data-testid="schema-header">
|
||||
{t('label.schema')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<SummaryList
|
||||
entityType={SummaryEntityType.COLUMN}
|
||||
formattedEntityData={formattedColumnsData}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Divider className="m-y-xs" />
|
||||
|
||||
{!isExplore ? (
|
||||
<>
|
||||
<SummaryTagsDescription
|
||||
entityDetail={entityDetails}
|
||||
tags={tags ? tags : []}
|
||||
/>
|
||||
<Divider className="m-y-xs" />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="text-base text-grey-muted"
|
||||
data-testid="profiler-header">
|
||||
{t('label.profiler-amp-data-quality')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
{isUndefined(overallSummary) ? (
|
||||
<Typography.Text
|
||||
className="text-grey-body"
|
||||
data-testid="no-profiler-enabled-message">
|
||||
{t('message.no-profiler-enabled-summary-message')}
|
||||
</Typography.Text>
|
||||
) : (
|
||||
<Row gutter={[0, 16]}>
|
||||
{overallSummary.map((field) => (
|
||||
<Col key={field.title} span={10}>
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="text-grey-muted"
|
||||
data-testid={`${field.title}-label`}>
|
||||
{field.title}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className={classNames(
|
||||
'summary-panel-statistics-count',
|
||||
field.className
|
||||
)}
|
||||
data-testid={`${field.title}-value`}>
|
||||
{field.value}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Divider className="m-y-xs" />
|
||||
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="text-base text-grey-muted"
|
||||
data-testid="schema-header">
|
||||
{t('label.schema')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<SummaryList
|
||||
entityType={SummaryEntityType.COLUMN}
|
||||
formattedEntityData={formattedColumnsData}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
</SummaryPanelSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -11,10 +11,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Table, TableType } from '../../../../generated/entity/data/table';
|
||||
import {
|
||||
Table,
|
||||
TableType,
|
||||
TagLabel,
|
||||
} from '../../../../generated/entity/data/table';
|
||||
|
||||
export interface TableSummaryProps {
|
||||
entityDetails: Table;
|
||||
componentType?: string;
|
||||
tags?: TagLabel[];
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export interface BasicTableInfo {
|
||||
|
||||
@ -13,7 +13,9 @@
|
||||
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { getLatestTableProfileByFqn } from 'rest/tableAPI';
|
||||
import { DRAWER_NAVIGATION_OPTIONS } from 'utils/EntityUtils';
|
||||
import { mockTableEntityDetails } from '../mocks/TableSummary.mock';
|
||||
import TableSummary from './TableSummary.component';
|
||||
|
||||
@ -30,16 +32,6 @@ jest.mock('rest/tableAPI', () => ({
|
||||
.mockImplementation(() => mockTableEntityDetails),
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'../../../common/table-data-card-v2/TableDataCardTitle.component',
|
||||
() =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="TableDataCardTitle">TableDataCardTitle</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('../SummaryList/SummaryList.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
@ -47,33 +39,93 @@ jest.mock('../SummaryList/SummaryList.component', () =>
|
||||
);
|
||||
|
||||
describe('TableSummary component tests', () => {
|
||||
it('Component should render properly', async () => {
|
||||
it('Component should render properly, when loaded in the Explore page.', async () => {
|
||||
await act(async () => {
|
||||
render(<TableSummary entityDetails={mockTableEntityDetails} />);
|
||||
});
|
||||
|
||||
const tableTitle = screen.getByTestId('TableDataCardTitle');
|
||||
const profilerHeader = screen.getByTestId('profiler-header');
|
||||
const schemaHeader = screen.getByTestId('schema-header');
|
||||
const typeLabel = screen.getByTestId('Type-label');
|
||||
const queriesLabel = screen.getByTestId('Queries-label');
|
||||
const columnsLabel = screen.getByTestId('Columns-label');
|
||||
const typeValue = screen.getByTestId('Type-value');
|
||||
const queriesValue = screen.getByTestId('Queries-value');
|
||||
const columnsValue = screen.getByTestId('Columns-value');
|
||||
const typeLabel = screen.getByTestId('label.type-label');
|
||||
const queriesLabel = screen.getByTestId('label.query-plural-label');
|
||||
const columnsLabel = screen.getByTestId('label.column-plural-label');
|
||||
const typeValue = screen.getByTestId('label.type-value');
|
||||
const queriesValue = screen.getByTestId('label.query-plural-value');
|
||||
const columnsValue = screen.getByTestId('label.column-plural-value');
|
||||
const noProfilerPlaceholder = screen.getByTestId(
|
||||
'no-profiler-enabled-message'
|
||||
);
|
||||
const summaryList = screen.getByTestId('SummaryList');
|
||||
|
||||
expect(tableTitle).toBeInTheDocument();
|
||||
expect(profilerHeader).toBeInTheDocument();
|
||||
expect(schemaHeader).toBeInTheDocument();
|
||||
expect(typeLabel).toBeInTheDocument();
|
||||
expect(queriesLabel).toBeInTheDocument();
|
||||
expect(columnsLabel).toBeInTheDocument();
|
||||
expect(typeValue).toContainHTML('Regular');
|
||||
expect(queriesValue).toContainHTML('2');
|
||||
expect(queriesValue.textContent).toBe('2 past week');
|
||||
expect(columnsValue).toContainHTML('2');
|
||||
expect(noProfilerPlaceholder).toContainHTML(
|
||||
'message.no-profiler-enabled-summary-message'
|
||||
);
|
||||
expect(summaryList).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Component should render properly, when loaded in the Lineage page.', async () => {
|
||||
const labels = [
|
||||
'label.service-label',
|
||||
'label.type-label',
|
||||
'label.database-label',
|
||||
'label.schema-label',
|
||||
'label.query-plural-label',
|
||||
'label.column-plural-label',
|
||||
];
|
||||
|
||||
const values = [
|
||||
'label.type-value',
|
||||
'label.service-value',
|
||||
'label.database-value',
|
||||
'label.schema-value',
|
||||
];
|
||||
render(
|
||||
<TableSummary
|
||||
componentType={DRAWER_NAVIGATION_OPTIONS.lineage}
|
||||
entityDetails={mockTableEntityDetails}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const profilerHeader = screen.getByTestId('profiler-header');
|
||||
const schemaHeader = screen.getAllByTestId('schema-header');
|
||||
const queriesLabel = screen.getByTestId('label.query-plural-label');
|
||||
const columnsLabel = screen.getByTestId('label.column-plural-label');
|
||||
const typeValue = screen.getByTestId('label.type-value');
|
||||
const queriesValue = screen.getByTestId('label.query-plural-value');
|
||||
const columnsValue = screen.getByTestId('label.column-plural-value');
|
||||
const noProfilerPlaceholder = screen.getByTestId(
|
||||
'no-profiler-enabled-message'
|
||||
);
|
||||
const ownerLabel = screen.queryByTestId('label.owner-label');
|
||||
|
||||
const summaryList = screen.getByTestId('SummaryList');
|
||||
|
||||
expect(ownerLabel).not.toBeInTheDocument();
|
||||
|
||||
labels.forEach((label) =>
|
||||
expect(screen.getByTestId(label)).toBeInTheDocument()
|
||||
);
|
||||
values.forEach((value) =>
|
||||
expect(screen.getByTestId(value)).toBeInTheDocument()
|
||||
);
|
||||
|
||||
expect(profilerHeader).toBeInTheDocument();
|
||||
expect(schemaHeader[0]).toBeInTheDocument();
|
||||
expect(queriesLabel).toBeInTheDocument();
|
||||
expect(columnsLabel).toBeInTheDocument();
|
||||
expect(typeValue).toContainHTML('Regular');
|
||||
expect(queriesValue.textContent).toBe('2 past week');
|
||||
expect(columnsValue).toContainHTML('2');
|
||||
expect(noProfilerPlaceholder).toContainHTML(
|
||||
'message.no-profiler-enabled-summary-message'
|
||||
|
||||
@ -12,55 +12,80 @@
|
||||
*/
|
||||
|
||||
import { Col, Divider, Row, Typography } from 'antd';
|
||||
import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component';
|
||||
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
|
||||
import { getTeamAndUserDetailsPath } from 'constants/constants';
|
||||
import { isArray, isEmpty } from 'lodash';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getTopicByFqn } from 'rest/topicsAPI';
|
||||
import {
|
||||
DRAWER_NAVIGATION_OPTIONS,
|
||||
getOwnerNameWithProfilePic,
|
||||
} from 'utils/EntityUtils';
|
||||
import { showErrorToast } from 'utils/ToastUtils';
|
||||
import { SummaryEntityType } from '../../../../enums/EntitySummary.enum';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { Topic } from '../../../../generated/entity/data/topic';
|
||||
import { TagLabel, Topic } from '../../../../generated/entity/data/topic';
|
||||
import { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils';
|
||||
import { bytesToSize } from '../../../../utils/StringsUtils';
|
||||
import { showErrorToast } from '../../../../utils/ToastUtils';
|
||||
import { getConfigObject } from '../../../../utils/TopicDetailsUtils';
|
||||
import TableDataCardTitle from '../../../common/table-data-card-v2/TableDataCardTitle.component';
|
||||
import { TopicConfigObjectInterface } from '../../../TopicDetails/TopicDetails.interface';
|
||||
import SummaryList from '../SummaryList/SummaryList.component';
|
||||
import { BasicEntityInfo } from '../SummaryList/SummaryList.interface';
|
||||
|
||||
interface TopicSummaryProps {
|
||||
entityDetails: Topic;
|
||||
componentType?: string;
|
||||
tags?: TagLabel[];
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
function TopicSummary({ entityDetails }: TopicSummaryProps) {
|
||||
function TopicSummary({
|
||||
entityDetails,
|
||||
componentType = DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
tags,
|
||||
isLoading,
|
||||
}: TopicSummaryProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [topicDetails, setTopicDetails] = useState<Topic>(entityDetails);
|
||||
|
||||
const isExplore = useMemo(
|
||||
() => componentType === DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
[componentType]
|
||||
);
|
||||
const topicConfig = useMemo(() => {
|
||||
const configs = getConfigObject(topicDetails);
|
||||
const combined = { ...topicDetails, ...entityDetails };
|
||||
const configs = getConfigObject(combined);
|
||||
|
||||
return {
|
||||
...configs,
|
||||
'Retention Size': bytesToSize(configs['Retention Size'] ?? 0),
|
||||
'Max Message Size': bytesToSize(configs['Max Message Size'] ?? 0),
|
||||
};
|
||||
}, [topicDetails]);
|
||||
}, [entityDetails, topicDetails]);
|
||||
|
||||
const formattedSchemaFieldsData: BasicEntityInfo[] = useMemo(
|
||||
() =>
|
||||
getFormattedEntityData(
|
||||
SummaryEntityType.SCHEMAFIELD,
|
||||
topicDetails.messageSchema?.schemaFields
|
||||
),
|
||||
[topicDetails]
|
||||
);
|
||||
const ownerDetails = useMemo(() => {
|
||||
const owner = entityDetails.owner;
|
||||
|
||||
const fetchExtraTopicInfo = async () => {
|
||||
return {
|
||||
value:
|
||||
getOwnerNameWithProfilePic(owner) ||
|
||||
t('label.no-entity', {
|
||||
entity: t('label.owner'),
|
||||
}),
|
||||
url: getTeamAndUserDetailsPath(owner?.name || ''),
|
||||
isLink: owner?.name ? true : false,
|
||||
};
|
||||
}, [entityDetails, topicDetails]);
|
||||
|
||||
const fetchExtraTopicInfo = useCallback(async () => {
|
||||
try {
|
||||
const res = await getTopicByFqn(
|
||||
entityDetails.fullyQualifiedName ?? '',
|
||||
''
|
||||
);
|
||||
const res = await getTopicByFqn(entityDetails.fullyQualifiedName ?? '', [
|
||||
'tags',
|
||||
'owner',
|
||||
]);
|
||||
|
||||
const { partitions, messageSchema } = res;
|
||||
|
||||
@ -73,73 +98,104 @@ function TopicSummary({ entityDetails }: TopicSummaryProps) {
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchExtraTopicInfo();
|
||||
}, [entityDetails]);
|
||||
|
||||
const formattedSchemaFieldsData: BasicEntityInfo[] = useMemo(
|
||||
() =>
|
||||
getFormattedEntityData(
|
||||
SummaryEntityType.SCHEMAFIELD,
|
||||
topicDetails.messageSchema?.schemaFields
|
||||
),
|
||||
[topicDetails]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (entityDetails.service?.type === 'messagingService') {
|
||||
fetchExtraTopicInfo();
|
||||
}
|
||||
}, [entityDetails, componentType]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row className="m-md" gutter={[0, 4]}>
|
||||
<Col span={24}>
|
||||
<TableDataCardTitle
|
||||
dataTestId="summary-panel-title"
|
||||
searchIndex={SearchIndex.TOPIC}
|
||||
source={entityDetails}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Row>
|
||||
{Object.keys(topicConfig).map((fieldName) => {
|
||||
const value =
|
||||
topicConfig[fieldName as keyof TopicConfigObjectInterface];
|
||||
<SummaryPanelSkeleton loading={Boolean(isLoading)}>
|
||||
<>
|
||||
<Row className="m-md" gutter={[0, 4]}>
|
||||
{!isExplore ? (
|
||||
<Col className="p-b-md" span={24}>
|
||||
{ownerDetails.isLink ? (
|
||||
<Link
|
||||
component={Typography.Link}
|
||||
to={{ pathname: ownerDetails.url }}>
|
||||
{ownerDetails.value}
|
||||
</Link>
|
||||
) : (
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{ownerDetails.value}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</Col>
|
||||
) : null}
|
||||
<Col span={24}>
|
||||
<Row>
|
||||
{Object.keys(topicConfig).map((fieldName) => {
|
||||
const value =
|
||||
topicConfig[fieldName as keyof TopicConfigObjectInterface];
|
||||
|
||||
const fieldValue = isArray(value) ? value.join(', ') : value;
|
||||
const fieldValue = isArray(value) ? value.join(', ') : value;
|
||||
|
||||
return (
|
||||
<Col key={fieldName} span={24}>
|
||||
<Row gutter={16}>
|
||||
<Col
|
||||
className="text-gray"
|
||||
data-testid={`${fieldName}-label`}
|
||||
span={10}>
|
||||
{fieldName}
|
||||
</Col>
|
||||
<Col data-testid={`${fieldName}-value`} span={12}>
|
||||
{fieldValue ? fieldValue : '-'}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider className="m-0" />
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="section-header"
|
||||
data-testid="schema-header">
|
||||
{t('label.schema')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
{isEmpty(topicDetails.messageSchema?.schemaFields) ? (
|
||||
<div className="m-y-md">
|
||||
<Typography.Text
|
||||
className="text-gray"
|
||||
data-testid="no-data-message">
|
||||
{t('message.no-data-available')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
) : (
|
||||
<SummaryList formattedEntityData={formattedSchemaFieldsData} />
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
return (
|
||||
<Col key={fieldName} span={24}>
|
||||
<Row gutter={[16, 32]}>
|
||||
<Col data-testid={`${fieldName}-label`} span={10}>
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{fieldName}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col data-testid={`${fieldName}-value`} span={14}>
|
||||
{fieldValue ? fieldValue : '-'}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider className="m-y-xs" />
|
||||
|
||||
{!isExplore ? (
|
||||
<>
|
||||
<SummaryTagsDescription
|
||||
entityDetail={entityDetails}
|
||||
tags={tags ? tags : []}
|
||||
/>
|
||||
<Divider className="m-y-xs" />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="text-base text-grey-muted"
|
||||
data-testid="schema-header">
|
||||
{t('label.schema')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
{isEmpty(topicDetails?.messageSchema?.schemaFields) ? (
|
||||
<div className="m-y-md">
|
||||
<Typography.Text data-testid="no-data-message">
|
||||
<Typography.Text className="text-grey-body">
|
||||
{t('message.no-data-available')}
|
||||
</Typography.Text>
|
||||
</Typography.Text>
|
||||
</div>
|
||||
) : (
|
||||
<SummaryList formattedEntityData={formattedSchemaFieldsData} />
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
</SummaryPanelSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -20,16 +20,6 @@ import {
|
||||
} from '../mocks/TopicSummary.mock';
|
||||
import TopicSummary from './TopicSummary.component';
|
||||
|
||||
jest.mock(
|
||||
'../../../common/table-data-card-v2/TableDataCardTitle.component',
|
||||
() =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="TableDataCardTitle">TableDataCardTitle</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('../SummaryList/SummaryList.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
@ -46,7 +36,6 @@ describe('TopicSummary component tests', () => {
|
||||
render(<TopicSummary entityDetails={mockTopicEntityDetails} />);
|
||||
});
|
||||
|
||||
const topicTitle = screen.getByTestId('TableDataCardTitle');
|
||||
const partitionsLabel = screen.getByTestId('Partitions-label');
|
||||
const replicationFactorLabel = screen.getByTestId(
|
||||
'Replication Factor-label'
|
||||
@ -64,13 +53,12 @@ describe('TopicSummary component tests', () => {
|
||||
const schemaHeader = screen.getByTestId('schema-header');
|
||||
const summaryList = screen.getByTestId('SummaryList');
|
||||
|
||||
expect(topicTitle).toBeInTheDocument();
|
||||
expect(partitionsLabel).toBeInTheDocument();
|
||||
expect(replicationFactorLabel).toBeInTheDocument();
|
||||
expect(retentionSizeLabel).toBeInTheDocument();
|
||||
expect(cleanUpPoliciesLabel).toBeInTheDocument();
|
||||
expect(maxMessageSizeLabel).toBeInTheDocument();
|
||||
expect(partitionsValue).toContainHTML('128');
|
||||
expect(partitionsValue).toContainHTML('-');
|
||||
expect(replicationFactorValue).toContainHTML('4');
|
||||
expect(retentionSizeValue).toContainHTML('1018.83 MB');
|
||||
expect(cleanUpPoliciesValue).toContainHTML('delete');
|
||||
|
||||
@ -97,5 +97,48 @@ export const mockTableEntityDetails: Table = {
|
||||
queryDate: mockDate,
|
||||
},
|
||||
],
|
||||
service: {
|
||||
id: '0875717c-5855-427c-8dd6-92d4cbfe7c51',
|
||||
type: 'databaseService',
|
||||
name: 'sample_data',
|
||||
fullyQualifiedName: 'sample_data',
|
||||
deleted: false,
|
||||
href: 'http://localhost:8585/api/v1/services/databaseServices/0875717c-5855-427c-8dd6-92d4cbfe7c51',
|
||||
},
|
||||
usageSummary: {
|
||||
dailyStats: {
|
||||
count: 0,
|
||||
percentileRank: 0,
|
||||
},
|
||||
weeklyStats: {
|
||||
count: 2,
|
||||
percentileRank: 0,
|
||||
},
|
||||
monthlyStats: {
|
||||
count: 0,
|
||||
percentileRank: 0,
|
||||
},
|
||||
date: '2023-02-01' as unknown as Date,
|
||||
},
|
||||
databaseSchema: {
|
||||
id: '406d4782-b480-42a4-ab8b-e6fed20f3eef',
|
||||
type: 'databaseSchema',
|
||||
name: 'shopify',
|
||||
fullyQualifiedName: 'sample_data.ecommerce_db.shopify',
|
||||
description:
|
||||
'This **mock** database contains schema related to shopify sales and orders with related dimension tables.',
|
||||
deleted: false,
|
||||
href: 'http://localhost:8585/api/v1/databaseSchemas/406d4782-b480-42a4-ab8b-e6fed20f3eef',
|
||||
},
|
||||
database: {
|
||||
id: '78a58be0-26a9-4ac8-b515-067db85bbb41',
|
||||
type: 'database',
|
||||
name: 'ecommerce_db',
|
||||
fullyQualifiedName: 'sample_data.ecommerce_db',
|
||||
description:
|
||||
'This **mock** database contains schemas related to shopify sales and orders with related dimension tables.',
|
||||
deleted: false,
|
||||
href: 'http://localhost:8585/api/v1/databases/78a58be0-26a9-4ac8-b515-067db85bbb41',
|
||||
},
|
||||
followers: [],
|
||||
};
|
||||
|
||||
@ -47,6 +47,14 @@ jest.mock('components/searched-data/SearchedData', () => {
|
||||
));
|
||||
});
|
||||
|
||||
jest.mock('./EntitySummaryPanel/EntitySummaryPanel.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="EntitySummaryPanel">EntitySummaryPanel</div>
|
||||
))
|
||||
);
|
||||
|
||||
const mockFunction = jest.fn();
|
||||
|
||||
jest.mock('../containers/PageLayoutV1', () =>
|
||||
|
||||
@ -22,12 +22,14 @@ const LabelCountSkeleton = ({
|
||||
labelProps,
|
||||
selectProps,
|
||||
countProps,
|
||||
firstColSize = 20,
|
||||
secondColSize = 4,
|
||||
...props
|
||||
}: LabelCountSkeletonProps) => {
|
||||
return (
|
||||
<Row justify="space-between" key={key}>
|
||||
{isSelect || isLabel ? (
|
||||
<Col span={20}>
|
||||
<Col span={firstColSize}>
|
||||
<div className="w-48 flex">
|
||||
{isSelect ? (
|
||||
<div>
|
||||
@ -58,7 +60,7 @@ const LabelCountSkeleton = ({
|
||||
</div>
|
||||
</Col>
|
||||
) : null}
|
||||
<Col span={4}>
|
||||
<Col span={secondColSize}>
|
||||
{isCount ? (
|
||||
<Skeleton
|
||||
active
|
||||
|
||||
@ -40,6 +40,8 @@ export interface LabelCountSkeletonProps extends SkeletonProps, Partial<Key> {
|
||||
labelProps?: SkeletonProps;
|
||||
selectProps?: SkeletonProps;
|
||||
countProps?: SkeletonProps;
|
||||
firstColSize?: number;
|
||||
secondColSize?: number;
|
||||
}
|
||||
|
||||
export type EntityListSkeletonProps = SkeletonInterface &
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 { Col, Row } from 'antd';
|
||||
import ButtonSkeleton from 'components/Skeleton/CommonSkeletons/ControlElements/ControlElements.component';
|
||||
import LabelCountSkeleton from 'components/Skeleton/CommonSkeletons/LabelCountSkeleton/LabelCountSkeleton.component';
|
||||
import { SkeletonInterface } from 'components/Skeleton/Skeleton.interfaces';
|
||||
import { getSkeletonMockData } from 'components/Skeleton/SkeletonUtils/Skeleton.utils';
|
||||
import { uniqueId } from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
const SummaryPanelSkeleton = ({ loading, children }: SkeletonInterface) => {
|
||||
return loading ? (
|
||||
<div className="m-b-md p-md">
|
||||
<Row gutter={32} justify="space-between">
|
||||
<Col className="m-t-md" span={24}>
|
||||
{getSkeletonMockData(5).map(() => (
|
||||
<LabelCountSkeleton
|
||||
isCount
|
||||
isLabel
|
||||
firstColSize={8}
|
||||
key={uniqueId()}
|
||||
secondColSize={16}
|
||||
title={{
|
||||
width: 100,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Col>
|
||||
|
||||
<Col className="m-l-xss" span={24}>
|
||||
{getSkeletonMockData(10).map(() => (
|
||||
<ButtonSkeleton key={uniqueId()} size="large" />
|
||||
))}
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
) : (
|
||||
children
|
||||
);
|
||||
};
|
||||
|
||||
export default SummaryPanelSkeleton;
|
||||
@ -98,6 +98,7 @@ export interface TopicDetailsProps {
|
||||
}
|
||||
|
||||
export interface TopicConfigObjectInterface {
|
||||
Owner?: Record<string, string | JSX.Element | undefined>;
|
||||
Partitions: number;
|
||||
'Replication Factor'?: number;
|
||||
'Retention Size'?: number;
|
||||
|
||||
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 { Col, Divider, Row, Typography } from 'antd';
|
||||
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
|
||||
import { TagLabel } from 'generated/type/tagLabel';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReactComponent as TagIcon } from '../../../assets/svg/tag-grey.svg';
|
||||
import { EntityData } from '../PopOverCard/EntityPopOverCard';
|
||||
import RichTextEditorPreviewer from '../rich-text-editor/RichTextEditorPreviewer';
|
||||
|
||||
const SummaryTagsDescription = ({
|
||||
tags,
|
||||
entityDetail,
|
||||
}: {
|
||||
tags: TagLabel[];
|
||||
entityDetail: EntityData;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text className="text-base text-grey-muted">
|
||||
{t('label.tag-plural')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<div className="flex flex-wrap items-center">
|
||||
{tags.length > 0 ? (
|
||||
<>
|
||||
<TagIcon className="m-r-xs" data-testid="tag-grey-icon" />
|
||||
<TagsViewer sizeCap={-1} tags={tags} />
|
||||
</>
|
||||
) : (
|
||||
<Typography.Text className="text-grey-body">
|
||||
{t('label.no-tags-added')}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Divider className="m-y-xs" />
|
||||
|
||||
<Row className="m-md" gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Text
|
||||
className="text-base text-grey-muted"
|
||||
data-testid="schema-header">
|
||||
{t('label.description')}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<div>
|
||||
{entityDetail.description?.trim() ? (
|
||||
<RichTextEditorPreviewer markdown={entityDetail.description} />
|
||||
) : (
|
||||
<Typography className="text-grey-body">
|
||||
{t('label.no-data-found')}
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SummaryTagsDescription;
|
||||
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import { Button, Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { toString } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
@ -32,6 +33,7 @@ interface TableDataCardTitleProps {
|
||||
id?: string;
|
||||
searchIndex: SearchIndex | EntityType;
|
||||
source: SourceType;
|
||||
isPanel?: boolean;
|
||||
handleLinkClick?: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
@ -41,6 +43,7 @@ const TableDataCardTitle = ({
|
||||
searchIndex,
|
||||
source,
|
||||
handleLinkClick,
|
||||
isPanel = false,
|
||||
}: TableDataCardTitleProps) => {
|
||||
const isTourRoute = location.pathname.includes(ROUTES.TOUR);
|
||||
|
||||
@ -76,7 +79,12 @@ const TableDataCardTitle = ({
|
||||
level={5}
|
||||
title={displayName}>
|
||||
<Link
|
||||
className="table-data-card-title-container w-fit-content w-max-90"
|
||||
className={classNames(
|
||||
'table-data-card-title-container w-fit-content w-max-90',
|
||||
{
|
||||
'button-hover': isPanel,
|
||||
}
|
||||
)}
|
||||
to={getEntityLink(searchIndex, source.fullyQualifiedName ?? '')}>
|
||||
{title}
|
||||
</Link>
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@import url('../../../styles/variables.less');
|
||||
@link-btn-color: #37352f;
|
||||
|
||||
.table-data-card-title-container {
|
||||
@ -24,3 +24,11 @@
|
||||
color: @link-btn-color;
|
||||
}
|
||||
}
|
||||
|
||||
.button-hover {
|
||||
.ant-btn-link > span {
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1161,6 +1161,7 @@
|
||||
"entity-fetch-error": "Error while fetching {{entity}}",
|
||||
"entity-fetch-version-error": "Error while fetching {{entity}} versions {{version}}",
|
||||
"entity-updating-error": "Error while updating {{entity}}",
|
||||
"error-selected-node-name-details": "Error while getting {{selectedNodeName}} details",
|
||||
"error-while-renewing-id-token-with-message": "Error while renewing id token from Auth0 SSO: {{message}}",
|
||||
"feed-post-error": "Error while posting the message!",
|
||||
"fetch-entity-permissions-error": "Unable to get permission for {{entity}}.",
|
||||
|
||||
@ -41,7 +41,7 @@ export const getMlModelVersion = async (id: string, version: string) => {
|
||||
|
||||
export const getMlModelByFQN = async (
|
||||
fqn: string,
|
||||
arrQueryFields: string,
|
||||
arrQueryFields: string | string[],
|
||||
include = Include.All
|
||||
) => {
|
||||
const url = getURLWithQueryFields(
|
||||
|
||||
@ -67,7 +67,7 @@ export const getTopicDetails = (
|
||||
|
||||
export const getTopicByFqn = async (
|
||||
fqn: string,
|
||||
arrQueryFields: string | TabSpecificField[]
|
||||
arrQueryFields: string[] | string | TabSpecificField[]
|
||||
) => {
|
||||
const url = getURLWithQueryFields(
|
||||
`/topics/name/${fqn}`,
|
||||
|
||||
@ -49,30 +49,6 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
// font size
|
||||
.text-xs {
|
||||
font-size: 12px;
|
||||
}
|
||||
.text-lg {
|
||||
font-size: 18px;
|
||||
}
|
||||
.text-sm {
|
||||
font-size: 14px;
|
||||
}
|
||||
.text-base {
|
||||
font-size: 1rem /* 16px */;
|
||||
line-height: 1.5rem /* 24px */;
|
||||
}
|
||||
.text-xl {
|
||||
font-size: 1.25rem /* 20px */;
|
||||
line-height: 1.75rem /* 28px */;
|
||||
}
|
||||
|
||||
.text-2xl {
|
||||
font-size: 1.5rem /* 24px */;
|
||||
line-height: 2rem /* 32px */;
|
||||
}
|
||||
|
||||
// text color
|
||||
.text-primary {
|
||||
color: @primary;
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
@success-color: #008376;
|
||||
@warning-color: #ffc34e;
|
||||
@error-color: #ff4c3b;
|
||||
@failed-color: #cb2431;
|
||||
@aborted-color: #efae2f;
|
||||
@info-color: #1890ff;
|
||||
@text-color: #000000;
|
||||
@text-color-secondary: #37352f;
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import { CheckOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { Button, Typography } from 'antd';
|
||||
import {
|
||||
CustomEdgeData,
|
||||
CustomElement,
|
||||
@ -91,13 +92,20 @@ export const getHeaderLabel = (
|
||||
{name || prepareLabel(type, fqn, false)}
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
className="tw-break-words description-text tw-self-center link-text tw-font-medium"
|
||||
data-testid="lineage-entity">
|
||||
<Link to={getEntityLink(type, fqn)}>
|
||||
{name || prepareLabel(type, fqn, false)}
|
||||
<Typography.Title
|
||||
ellipsis
|
||||
className="m-b-0 text-base"
|
||||
level={5}
|
||||
title={name || prepareLabel(type, fqn, false)}>
|
||||
<Link className="" to={getEntityLink(type, fqn)}>
|
||||
<Button
|
||||
className="text-base font-semibold p-0"
|
||||
data-testid="link-button"
|
||||
type="link">
|
||||
{name || prepareLabel(type, fqn, false)}
|
||||
</Button>
|
||||
</Link>
|
||||
</span>
|
||||
</Typography.Title>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@ -57,7 +57,9 @@ export const getFormattedEntityData = (
|
||||
title: (
|
||||
<Link target="_blank" to={{ pathname: chart.chartUrl }}>
|
||||
<Space className="m-b-xs">
|
||||
<Text className="entity-title link">{getEntityName(chart)}</Text>
|
||||
<Text className="entity-title text-primary font-medium">
|
||||
{getEntityName(chart)}
|
||||
</Text>
|
||||
<SVGIcons alt="external-link" icon="external-link" width="12px" />
|
||||
</Space>
|
||||
</Link>
|
||||
@ -73,7 +75,9 @@ export const getFormattedEntityData = (
|
||||
title: (
|
||||
<Link target="_blank" to={{ pathname: task.taskUrl }}>
|
||||
<Space className="m-b-xs">
|
||||
<Text className="entity-title link">{task.name}</Text>
|
||||
<Text className="entity-title text-primary font-medium">
|
||||
{task.name}
|
||||
</Text>
|
||||
<SVGIcons alt="external-link" icon="external-link" width="12px" />
|
||||
</Space>
|
||||
</Link>
|
||||
|
||||
@ -13,11 +13,14 @@
|
||||
|
||||
import { Popover } from 'antd';
|
||||
import { EntityData } from 'components/common/PopOverCard/EntityPopOverCard';
|
||||
import ProfilePicture from 'components/common/ProfilePicture/ProfilePicture';
|
||||
import {
|
||||
LeafNodes,
|
||||
LineagePos,
|
||||
} from 'components/EntityLineage/EntityLineage.interface';
|
||||
import { ResourceEntity } from 'components/PermissionProvider/PermissionProvider.interface';
|
||||
import { ExplorePageTabs } from 'enums/Explore.enum';
|
||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||
import i18next from 'i18next';
|
||||
import { isEmpty, isNil, isUndefined, lowerCase, startCase } from 'lodash';
|
||||
import { Bucket } from 'Models';
|
||||
@ -25,11 +28,11 @@ import React, { Fragment } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FQN_SEPARATOR_CHAR } from '../constants/char.constants';
|
||||
import {
|
||||
getDashboardDetailsPath,
|
||||
getDatabaseDetailsPath,
|
||||
getDatabaseSchemaDetailsPath,
|
||||
getServiceDetailsPath,
|
||||
getTableDetailsPath,
|
||||
getTeamAndUserDetailsPath,
|
||||
} from '../constants/constants';
|
||||
import { AssetsType, EntityType, FqnPart } from '../enums/entity.enum';
|
||||
import { SearchIndex } from '../enums/search.enum';
|
||||
@ -42,6 +45,7 @@ import {
|
||||
ColumnJoins,
|
||||
JoinedWith,
|
||||
Table,
|
||||
TableType,
|
||||
} from '../generated/entity/data/table';
|
||||
import { Topic } from '../generated/entity/data/topic';
|
||||
import { Edge, EntityLineage } from '../generated/type/entityLineage';
|
||||
@ -49,6 +53,7 @@ import { EntityReference } from '../generated/type/entityUsage';
|
||||
import { TagLabel } from '../generated/type/tagLabel';
|
||||
import {
|
||||
getEntityName,
|
||||
getOwnerValue,
|
||||
getPartialNameFromTableFQN,
|
||||
getTableFQNFromColumnFQN,
|
||||
} from './CommonUtils';
|
||||
@ -59,10 +64,15 @@ import {
|
||||
} from './TableUtils';
|
||||
import { getTableTags } from './TagsUtils';
|
||||
|
||||
export enum DRAWER_NAVIGATION_OPTIONS {
|
||||
explore = 'Explore',
|
||||
lineage = 'Lineage',
|
||||
}
|
||||
|
||||
export const getEntityTags = (
|
||||
type: string,
|
||||
entityDetail: Table | Pipeline | Dashboard | Topic
|
||||
): Array<TagLabel | undefined> => {
|
||||
entityDetail: Table | Pipeline | Dashboard | Topic | Mlmodel
|
||||
): Array<TagLabel> => {
|
||||
switch (type) {
|
||||
case EntityType.TABLE: {
|
||||
const tableTags: Array<TagLabel> = [
|
||||
@ -72,10 +82,10 @@ export const getEntityTags = (
|
||||
|
||||
return tableTags;
|
||||
}
|
||||
case EntityType.PIPELINE: {
|
||||
return entityDetail.tags || [];
|
||||
}
|
||||
case EntityType.DASHBOARD: {
|
||||
case EntityType.PIPELINE:
|
||||
case EntityType.DASHBOARD:
|
||||
case EntityType.TOPIC:
|
||||
case EntityType.MLMODEL: {
|
||||
return entityDetail.tags || [];
|
||||
}
|
||||
|
||||
@ -84,19 +94,38 @@ export const getEntityTags = (
|
||||
}
|
||||
};
|
||||
|
||||
export const getOwnerNameWithProfilePic = (
|
||||
owner: EntityReference | undefined
|
||||
) =>
|
||||
owner ? (
|
||||
<div className="flex items-center gap-2">
|
||||
{' '}
|
||||
<ProfilePicture
|
||||
displayName={owner.displayName}
|
||||
id={owner.id as string}
|
||||
name={owner.name || ''}
|
||||
width="20"
|
||||
/>
|
||||
<span>{getEntityName(owner)}</span>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
export const getEntityOverview = (
|
||||
type: string,
|
||||
entityDetail: EntityData,
|
||||
serviceType: string
|
||||
entityDetail: EntityData
|
||||
): Array<{
|
||||
name: string;
|
||||
value: string | number | React.ReactNode;
|
||||
isLink: boolean;
|
||||
isExternal?: boolean;
|
||||
url?: string;
|
||||
visible?: Array<string>;
|
||||
dataTestId?: string;
|
||||
}> => {
|
||||
const NO_DATA = '-';
|
||||
|
||||
switch (type) {
|
||||
case EntityType.TABLE: {
|
||||
case ExplorePageTabs.TABLES: {
|
||||
const {
|
||||
fullyQualifiedName,
|
||||
owner,
|
||||
@ -104,31 +133,56 @@ export const getEntityOverview = (
|
||||
usageSummary,
|
||||
profile,
|
||||
columns,
|
||||
tableType,
|
||||
} = entityDetail as Table;
|
||||
const [service, database, schema] = getPartialNameFromTableFQN(
|
||||
fullyQualifiedName ?? '',
|
||||
[FqnPart.Service, FqnPart.Database, FqnPart.Schema],
|
||||
FQN_SEPARATOR_CHAR
|
||||
).split(FQN_SEPARATOR_CHAR);
|
||||
|
||||
const tier = getTierFromTableTags(tags || []);
|
||||
|
||||
const usage = !isNil(usageSummary?.weeklyStats?.percentileRank)
|
||||
? getUsagePercentile(usageSummary?.weeklyStats?.percentileRank || 0)
|
||||
: '--';
|
||||
const queries = usageSummary?.weeklyStats?.count.toLocaleString() || '--';
|
||||
: '-';
|
||||
|
||||
const queries = usageSummary?.weeklyStats?.count.toLocaleString() || '0';
|
||||
|
||||
const overview = [
|
||||
{
|
||||
name: i18next.t('label.owner'),
|
||||
value:
|
||||
getOwnerNameWithProfilePic(owner) ||
|
||||
i18next.t('label.no-entity', {
|
||||
entity: i18next.t('label.owner'),
|
||||
}),
|
||||
url: getOwnerValue(owner as EntityReference),
|
||||
isLink: owner?.name ? true : false,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.type'),
|
||||
value: tableType || TableType.Regular,
|
||||
isLink: false,
|
||||
visible: [
|
||||
DRAWER_NAVIGATION_OPTIONS.lineage,
|
||||
DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.service'),
|
||||
value: service,
|
||||
value: service || NO_DATA,
|
||||
url: getServiceDetailsPath(
|
||||
service,
|
||||
ServiceCategory.DATABASE_SERVICES
|
||||
),
|
||||
isLink: true,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.database'),
|
||||
value: database,
|
||||
value: database || NO_DATA,
|
||||
url: getDatabaseDetailsPath(
|
||||
getPartialNameFromTableFQN(
|
||||
fullyQualifiedName ?? '',
|
||||
@ -137,10 +191,11 @@ export const getEntityOverview = (
|
||||
)
|
||||
),
|
||||
isLink: true,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.schema'),
|
||||
value: schema,
|
||||
value: schema || NO_DATA,
|
||||
url: getDatabaseSchemaDetailsPath(
|
||||
getPartialNameFromTableFQN(
|
||||
fullyQualifiedName ?? '',
|
||||
@ -149,126 +204,217 @@ export const getEntityOverview = (
|
||||
)
|
||||
),
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.owner'),
|
||||
value: getEntityName(owner) || '--',
|
||||
url: getTeamAndUserDetailsPath(owner?.name || ''),
|
||||
isLink: owner ? owner.type === 'team' : false,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.tier'),
|
||||
value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : '--',
|
||||
value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : NO_DATA,
|
||||
isLink: false,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.usage'),
|
||||
value: usage,
|
||||
value: usage || NO_DATA,
|
||||
isLink: false,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.query-plural'),
|
||||
value: `${queries} past week`,
|
||||
isLink: false,
|
||||
visible: [
|
||||
DRAWER_NAVIGATION_OPTIONS.lineage,
|
||||
DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.column-plural'),
|
||||
value: columns ? columns.length : '--',
|
||||
value: columns ? columns.length : NO_DATA,
|
||||
isLink: false,
|
||||
visible: [
|
||||
DRAWER_NAVIGATION_OPTIONS.lineage,
|
||||
DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.row-plural'),
|
||||
value: profile && profile?.rowCount ? profile.rowCount : '--',
|
||||
value: profile && profile?.rowCount ? profile.rowCount : NO_DATA,
|
||||
isLink: false,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
];
|
||||
|
||||
return overview;
|
||||
}
|
||||
|
||||
case EntityType.PIPELINE: {
|
||||
const { owner, tags, pipelineUrl, service, fullyQualifiedName } =
|
||||
case ExplorePageTabs.PIPELINES: {
|
||||
const { owner, tags, pipelineUrl, service, displayName } =
|
||||
entityDetail as Pipeline;
|
||||
const tier = getTierFromTableTags(tags || []);
|
||||
|
||||
const overview = [
|
||||
{
|
||||
name: i18next.t('label.owner'),
|
||||
value:
|
||||
getOwnerNameWithProfilePic(owner) ||
|
||||
i18next.t('label.no-entity', {
|
||||
entity: i18next.t('label.owner'),
|
||||
}),
|
||||
url: getOwnerValue(owner as EntityReference),
|
||||
isLink: owner?.name ? true : false,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
{
|
||||
name: `${i18next.t('label.pipeline')} ${i18next.t(
|
||||
'label.url-uppercase'
|
||||
)}`,
|
||||
dataTestId: 'pipeline-url-label',
|
||||
value: displayName || NO_DATA,
|
||||
url: pipelineUrl,
|
||||
isLink: true,
|
||||
isExternal: true,
|
||||
visible: [
|
||||
DRAWER_NAVIGATION_OPTIONS.lineage,
|
||||
DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.service'),
|
||||
value: service?.name as string,
|
||||
value: (service?.name as string) || NO_DATA,
|
||||
url: getServiceDetailsPath(
|
||||
service?.name as string,
|
||||
ServiceCategory.PIPELINE_SERVICES
|
||||
),
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.owner'),
|
||||
value: getEntityName(owner) || '--',
|
||||
url: getTeamAndUserDetailsPath(owner?.name || ''),
|
||||
isLink: owner ? owner.type === 'team' : false,
|
||||
isExternal: false,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.tier'),
|
||||
value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : '--',
|
||||
value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : NO_DATA,
|
||||
isLink: false,
|
||||
},
|
||||
{
|
||||
name: `${serviceType} ${i18next.t('label.url-lowercase')}`,
|
||||
value: fullyQualifiedName?.split(FQN_SEPARATOR_CHAR)[1] as string,
|
||||
url: pipelineUrl as string,
|
||||
isLink: true,
|
||||
isExternal: true,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
];
|
||||
|
||||
return overview;
|
||||
}
|
||||
case EntityType.DASHBOARD: {
|
||||
const {
|
||||
owner,
|
||||
tags,
|
||||
dashboardUrl,
|
||||
service,
|
||||
fullyQualifiedName,
|
||||
displayName,
|
||||
} = entityDetail as Dashboard;
|
||||
case ExplorePageTabs.DASHBOARDS: {
|
||||
const { owner, tags, dashboardUrl, service, displayName } =
|
||||
entityDetail as Dashboard;
|
||||
const tier = getTierFromTableTags(tags || []);
|
||||
|
||||
const overview = [
|
||||
{
|
||||
name: i18next.t('label.owner'),
|
||||
value:
|
||||
getOwnerNameWithProfilePic(owner) ||
|
||||
i18next.t('label.no-entity', {
|
||||
entity: i18next.t('label.owner'),
|
||||
}),
|
||||
url: getOwnerValue(owner as EntityReference),
|
||||
isLink: owner?.name ? true : false,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
{
|
||||
name: `${i18next.t('label.dashboard')} ${i18next.t(
|
||||
'label.url-uppercase'
|
||||
)}`,
|
||||
value: displayName || NO_DATA,
|
||||
url: dashboardUrl,
|
||||
isLink: true,
|
||||
isExternal: true,
|
||||
visible: [
|
||||
DRAWER_NAVIGATION_OPTIONS.lineage,
|
||||
DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.service'),
|
||||
value: service?.name as string,
|
||||
value: (service?.fullyQualifiedName as string) || NO_DATA,
|
||||
url: getServiceDetailsPath(
|
||||
service?.name as string,
|
||||
ServiceCategory.DASHBOARD_SERVICES
|
||||
),
|
||||
isExternal: false,
|
||||
isLink: true,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.owner'),
|
||||
value: getEntityName(owner) || '--',
|
||||
url: getTeamAndUserDetailsPath(owner?.name || ''),
|
||||
isLink: owner ? owner.type === 'team' : false,
|
||||
},
|
||||
|
||||
{
|
||||
name: i18next.t('label.tier'),
|
||||
value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : '--',
|
||||
value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : NO_DATA,
|
||||
isLink: false,
|
||||
},
|
||||
{
|
||||
name: `${serviceType} ${i18next.t('label.url-lowercase')}`,
|
||||
value:
|
||||
displayName ||
|
||||
(fullyQualifiedName?.split(FQN_SEPARATOR_CHAR)[1] as string),
|
||||
url: dashboardUrl as string,
|
||||
isLink: true,
|
||||
isExternal: true,
|
||||
isExternal: false,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
];
|
||||
|
||||
return overview;
|
||||
}
|
||||
|
||||
case ExplorePageTabs.MLMODELS: {
|
||||
const { algorithm, target, server, dashboard, owner } =
|
||||
entityDetail as Mlmodel;
|
||||
|
||||
const overview = [
|
||||
{
|
||||
name: i18next.t('label.owner'),
|
||||
value:
|
||||
getOwnerNameWithProfilePic(owner) ||
|
||||
i18next.t('label.no-entity', {
|
||||
entity: i18next.t('label.owner'),
|
||||
}),
|
||||
url: getOwnerValue(owner as EntityReference),
|
||||
isLink: owner?.name ? true : false,
|
||||
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.algorithm'),
|
||||
value: algorithm || NO_DATA,
|
||||
url: '',
|
||||
isLink: false,
|
||||
visible: [
|
||||
DRAWER_NAVIGATION_OPTIONS.lineage,
|
||||
DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.target'),
|
||||
value: target || NO_DATA,
|
||||
url: '',
|
||||
isLink: false,
|
||||
visible: [
|
||||
DRAWER_NAVIGATION_OPTIONS.lineage,
|
||||
DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.server'),
|
||||
value: server || NO_DATA,
|
||||
url: server,
|
||||
isLink: true,
|
||||
isExternal: true,
|
||||
visible: [
|
||||
DRAWER_NAVIGATION_OPTIONS.lineage,
|
||||
DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.dashboard'),
|
||||
value: getEntityName(dashboard) || NO_DATA,
|
||||
url: getDashboardDetailsPath(dashboard?.fullyQualifiedName as string),
|
||||
isLink: true,
|
||||
isExternal: false,
|
||||
visible: [
|
||||
DRAWER_NAVIGATION_OPTIONS.lineage,
|
||||
DRAWER_NAVIGATION_OPTIONS.explore,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return overview;
|
||||
}
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@ export const getUsagePercentile = (pctRank: number, isLiteral = false) => {
|
||||
const ordinalPercentile = ordinalize(percentile);
|
||||
const usagePercentile = `${
|
||||
isLiteral ? t('label.usage') : ''
|
||||
} - ${ordinalPercentile} ${t('label.pctile-lowercase')}`;
|
||||
} ${ordinalPercentile} ${t('label.pctile-lowercase')}`;
|
||||
|
||||
return usagePercentile;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user