Fixes #10584 : Improvements in DataModel Entity (#10983)

* Improvements in DataModel Entity

* Added lineage view in DataModel Entity

* changes as per commets

* fix unit test issue

* fix code smell

* changes as per commets
This commit is contained in:
Ashish Gupta 2023-04-11 14:04:10 +05:30 committed by GitHub
parent 09b283818d
commit 331f043d3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 861 additions and 298 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -0,0 +1,389 @@
/*
* 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 { Card, Col, Row, Space, Tabs } from 'antd';
import ActivityFeedList from 'components/ActivityFeed/ActivityFeedList/ActivityFeedList';
import ActivityThreadPanel from 'components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
import Description from 'components/common/description/Description';
import EntityPageInfo from 'components/common/entityPageInfo/EntityPageInfo';
import PageContainerV1 from 'components/containers/PageContainerV1';
import EntityLineageComponent from 'components/EntityLineage/EntityLineage.component';
import Loader from 'components/Loader/Loader';
import SchemaEditor from 'components/schema-editor/SchemaEditor';
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
import { getServiceDetailsPath } from 'constants/constants';
import { EntityField } from 'constants/Feeds.constants';
import { observerOptions } from 'constants/Mydata.constants';
import { CSMode } from 'enums/codemirror.enum';
import { EntityInfo, EntityType } from 'enums/entity.enum';
import { ServiceCategory } from 'enums/service.enum';
import { OwnerType } from 'enums/user.enum';
import { Paging } from 'generated/type/paging';
import { useInfiniteScroll } from 'hooks/useInfiniteScroll';
import { ExtraInfo } from 'Models';
import { DATA_MODELS_DETAILS_TABS } from 'pages/DataModelPage/DataModelsInterface';
import React, { RefObject, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
getCountBadge,
getCurrentUserId,
getEntityPlaceHolder,
getOwnerValue,
} from 'utils/CommonUtils';
import { getEntityName } from 'utils/EntityUtils';
import { getEntityFieldThreadCounts } from 'utils/FeedUtils';
import { serviceTypeLogo } from 'utils/ServiceUtils';
import { getTagsWithoutTier, getTierTags } from 'utils/TableUtils';
import { DataModelDetailsProps } from './DataModelDetails.interface';
import ModelTab from './ModelTab/ModelTab.component';
const DataModelDetails = ({
isEntityThreadLoading,
paging,
entityFieldTaskCount,
entityFieldThreadCount,
entityThread,
feedCount,
dataModelData,
dashboardDataModelFQN,
postFeedHandler,
dataModelPermissions,
createThread,
deletePostHandler,
updateThreadHandler,
handleFollowDataModel,
handleRemoveTier,
fetchFeedHandler,
handleUpdateTags,
handleUpdateOwner,
handleUpdateTier,
activeTab,
handleTabChange,
handleUpdateDescription,
handleUpdateDataModel,
handleFeedFilterChange,
}: DataModelDetailsProps) => {
const { t } = useTranslation();
const [elementRef, isInView] = useInfiniteScroll(observerOptions);
const [isEditDescription, setIsEditDescription] = useState<boolean>(false);
const [threadLink, setThreadLink] = useState<string>('');
const loader = useMemo(
() => (isEntityThreadLoading ? <Loader /> : null),
[isEntityThreadLoading]
);
const {
hasEditDescriptionPermission,
hasEditOwnerPermission,
hasEditTagsPermission,
hasEditTierPermission,
hasEditLineagePermission,
} = useMemo(() => {
return {
hasEditDescriptionPermission:
dataModelPermissions.EditAll || dataModelPermissions.EditDescription,
hasEditOwnerPermission:
dataModelPermissions.EditAll || dataModelPermissions.EditOwner,
hasEditTagsPermission:
dataModelPermissions.EditAll || dataModelPermissions.EditTags,
hasEditTierPermission:
dataModelPermissions.EditAll || dataModelPermissions.EditTier,
hasEditLineagePermission:
dataModelPermissions.EditAll || dataModelPermissions.EditLineage,
};
}, [dataModelPermissions]);
const {
tier,
deleted,
owner,
description,
version,
tags,
entityName,
entityId,
followers,
dataModelType,
isUserFollowing,
} = useMemo(() => {
return {
deleted: dataModelData?.deleted,
owner: dataModelData?.owner,
description: dataModelData?.description,
version: dataModelData?.version,
tier: getTierTags(dataModelData?.tags ?? []),
tags: getTagsWithoutTier(dataModelData?.tags ?? []),
entityId: dataModelData?.id,
entityName: getEntityName(dataModelData),
isUserFollowing: dataModelData?.followers?.some(
({ id }: { id: string }) => id === getCurrentUserId()
),
followers: dataModelData?.followers ?? [],
dataModelType: dataModelData?.dataModelType,
};
}, [dataModelData]);
const breadcrumbTitles = useMemo(() => {
const serviceType = dataModelData?.serviceType;
const service = dataModelData?.service;
const serviceName = service?.name;
return [
{
name: serviceName || '',
url: serviceName
? getServiceDetailsPath(
serviceName,
ServiceCategory.DASHBOARD_SERVICES
)
: '',
imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined,
},
{
name: entityName,
url: '',
activeTitle: true,
},
];
}, [dataModelData, dashboardDataModelFQN, entityName]);
const extraInfo: Array<ExtraInfo> = [
{
key: EntityInfo.OWNER,
value: owner && getOwnerValue(owner),
placeholderText: getEntityPlaceHolder(
getEntityName(owner),
owner?.deleted
),
isLink: true,
openInNewTab: false,
profileName: owner?.type === OwnerType.USER ? owner?.name : undefined,
},
{
key: EntityInfo.TIER,
value: tier?.tagFQN ? tier.tagFQN.split(FQN_SEPARATOR_CHAR)[1] : '',
},
{
key: EntityInfo.DATA_MODEL_TYPE,
value: dataModelType,
showLabel: true,
},
];
const onThreadLinkSelect = (link: string) => {
setThreadLink(link);
};
const onThreadPanelClose = () => {
setThreadLink('');
};
const fetchMoreThread = (
isElementInView: boolean,
pagingObj: Paging,
isLoading: boolean
) => {
if (
isElementInView &&
pagingObj?.after &&
!isLoading &&
activeTab === DATA_MODELS_DETAILS_TABS.ACTIVITY
) {
fetchFeedHandler(pagingObj.after);
}
};
useEffect(() => {
fetchMoreThread(isInView as boolean, paging, isEntityThreadLoading);
}, [paging, isEntityThreadLoading, isInView]);
return (
<PageContainerV1>
<div className="entity-details-container">
<EntityPageInfo
canDelete={dataModelPermissions.Delete}
currentOwner={owner}
deleted={deleted}
entityFieldTasks={getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldTaskCount
)}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldThreadCount
)}
entityFqn={dashboardDataModelFQN}
entityId={entityId}
entityName={entityName || ''}
entityType={EntityType.DASHBOARD_DATA_MODEL}
extraInfo={extraInfo}
followHandler={handleFollowDataModel}
followers={followers.length}
followersList={followers}
isFollowing={isUserFollowing}
isTagEditable={hasEditTagsPermission}
removeTier={hasEditTierPermission ? handleRemoveTier : undefined}
tags={tags}
tagsHandler={handleUpdateTags}
tier={tier}
titleLinks={breadcrumbTitles}
updateOwner={hasEditOwnerPermission ? handleUpdateOwner : undefined}
updateTier={hasEditTierPermission ? handleUpdateTier : undefined}
version={version}
onThreadLinkSelect={onThreadLinkSelect}
/>
<Tabs
activeKey={activeTab}
className="h-full"
onChange={handleTabChange}>
<Tabs.TabPane
key={DATA_MODELS_DETAILS_TABS.MODEL}
tab={
<span data-testid={DATA_MODELS_DETAILS_TABS.MODEL}>
{t('label.model')}
</span>
}>
<Card className="h-full">
<Space className="w-full" direction="vertical" size={8}>
<Description
description={description}
entityFieldTasks={getEntityFieldThreadCounts(
EntityField.DESCRIPTION,
entityFieldTaskCount
)}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.DESCRIPTION,
entityFieldThreadCount
)}
entityFqn={dashboardDataModelFQN}
entityName={entityName}
entityType={EntityType.DASHBOARD_DATA_MODEL}
hasEditAccess={hasEditDescriptionPermission}
isEdit={isEditDescription}
isReadOnly={deleted}
owner={owner}
onCancel={() => setIsEditDescription(false)}
onDescriptionEdit={() => setIsEditDescription(true)}
onDescriptionUpdate={handleUpdateDescription}
onThreadLinkSelect={onThreadLinkSelect}
/>
<ModelTab
data={dataModelData?.columns || []}
hasEditDescriptionPermission={hasEditDescriptionPermission}
hasEditTagsPermission={hasEditTagsPermission}
isReadOnly={Boolean(deleted)}
onUpdate={handleUpdateDataModel}
/>
</Space>
</Card>
</Tabs.TabPane>
<Tabs.TabPane
key={DATA_MODELS_DETAILS_TABS.ACTIVITY}
tab={
<span data-testid={DATA_MODELS_DETAILS_TABS.ACTIVITY}>
{t('label.activity-feed-and-task-plural')}{' '}
{getCountBadge(
feedCount,
'',
DATA_MODELS_DETAILS_TABS.ACTIVITY === activeTab
)}
</span>
}>
<Card>
<Row justify="center">
<Col span={18}>
<div id="activityfeed">
<ActivityFeedList
isEntityFeed
withSidePanel
deletePostHandler={deletePostHandler}
entityName={entityName}
feedList={entityThread}
isFeedLoading={isEntityThreadLoading}
postFeedHandler={postFeedHandler}
updateThreadHandler={updateThreadHandler}
onFeedFiltersUpdate={handleFeedFilterChange}
/>
</div>
</Col>
</Row>
{loader}
</Card>
</Tabs.TabPane>
{dataModelData?.sql && (
<Tabs.TabPane
key={DATA_MODELS_DETAILS_TABS.SQL}
tab={
<span data-testid={DATA_MODELS_DETAILS_TABS.SQL}>
{t('label.sql-uppercase')}
</span>
}>
<Card className="h-full">
<SchemaEditor
editorClass="custom-code-mirror-theme full-screen-editor-height"
mode={{ name: CSMode.SQL }}
options={{
styleActiveLine: false,
readOnly: 'nocursor',
}}
value={dataModelData.sql}
/>
</Card>
</Tabs.TabPane>
)}
<Tabs.TabPane
key={DATA_MODELS_DETAILS_TABS.LINEAGE}
tab={
<span data-testid={DATA_MODELS_DETAILS_TABS.LINEAGE}>
{t('label.lineage')}
</span>
}>
<Card
className="h-full card-body-full"
data-testid="lineage-details">
<EntityLineageComponent
deleted={deleted}
entityType={EntityType.DASHBOARD_DATA_MODEL}
hasEditAccess={hasEditLineagePermission}
/>
</Card>
</Tabs.TabPane>
</Tabs>
{threadLink ? (
<ActivityThreadPanel
createThread={createThread}
deletePostHandler={deletePostHandler}
open={Boolean(threadLink)}
postFeedHandler={postFeedHandler}
threadLink={threadLink}
updateThreadHandler={updateThreadHandler}
onCancel={onThreadPanelClose}
/>
) : null}
<div
data-testid="observer-element"
id="observer-element"
ref={elementRef as RefObject<HTMLDivElement>}
/>
</div>
</PageContainerV1>
);
};
export default DataModelDetails;

View File

@ -0,0 +1,64 @@
/*
* 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 { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface';
import { FeedFilter } from 'enums/mydata.enum';
import { CreateThread, ThreadType } from 'generated/api/feed/createThread';
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
import { Column } from 'generated/entity/data/table';
import { Thread } from 'generated/entity/feed/thread';
import { Paging } from 'generated/type/paging';
import {
EntityFieldThreadCount,
ThreadUpdatedFunc,
} from 'interface/feed.interface';
import { EntityTags } from 'Models';
export interface DataModelDetailsProps {
isEntityThreadLoading: boolean;
paging: Paging;
entityFieldThreadCount: EntityFieldThreadCount[];
entityFieldTaskCount: EntityFieldThreadCount[];
entityThread: Thread[];
feedCount: number;
dataModelData?: DashboardDataModel;
dashboardDataModelFQN: string;
postFeedHandler: (value: string, id: string) => void;
dataModelPermissions: OperationPermission;
createThread: (data: CreateThread) => void;
deletePostHandler: (
threadId: string,
postId: string,
isThread: boolean
) => void;
updateThreadHandler: ThreadUpdatedFunc;
handleFollowDataModel: () => void;
handleRemoveTier: () => void;
fetchFeedHandler: (
after?: string,
feedType?: FeedFilter,
threadType?: ThreadType
) => void;
handleUpdateTags: (selectedTags?: Array<EntityTags>) => void;
handleUpdateOwner: (updatedOwner?: DashboardDataModel['owner']) => void;
handleUpdateTier: (updatedTier?: string) => void;
activeTab: string;
handleTabChange: (tabValue: string) => void;
handleUpdateDescription: (value: string) => Promise<void>;
handleUpdateDataModel: (updatedDataModel: Column[]) => Promise<void>;
handleFeedFilterChange: (
feedType: FeedFilter,
threadType?: ThreadType
) => void;
}

View File

@ -36,6 +36,7 @@ const DataModelTable = ({ data, isLoading }: DataModelTableProps) => {
title: t('label.name'),
dataIndex: 'displayName',
key: 'displayName',
width: 350,
render: (_, record: ServicePageData) => {
return (
<Link to={getDataModelDetailsPath(record.fullyQualifiedName || '')}>

View File

@ -26,6 +26,7 @@ import {
updateDataModelColumnDescription,
updateDataModelColumnTags,
} from 'utils/DataModelsUtils';
import { getEntityName } from 'utils/EntityUtils';
import { fetchTagsAndGlossaryTerms } from 'utils/TagsUtils';
import { ReactComponent as EditIcon } from '../../../assets/svg/ic-edit.svg';
import { ModelTabProps } from './ModelTab.interface';
@ -195,6 +196,9 @@ const ModelTab = ({
dataIndex: 'name',
key: 'name',
width: 250,
render: (_, record) => (
<Typography.Text>{getEntityName(record)}</Typography.Text>
),
},
{
title: t('label.type'),

View File

@ -16,12 +16,14 @@ import { Col, Drawer, Row, Typography } from 'antd';
import classNames from 'classnames';
import ContainerSummary from 'components/Explore/EntitySummaryPanel/ContainerSummary/ContainerSummary.component';
import DashboardSummary from 'components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.component';
import DataModelSummary from 'components/Explore/EntitySummaryPanel/DataModelSummary/DataModelSummary.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 { Container } from 'generated/entity/data/container';
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
import { Mlmodel } from 'generated/entity/data/mlmodel';
import { EntityDetailUnion } from 'Models';
import React, { useEffect, useMemo, useState } from 'react';
@ -209,6 +211,16 @@ const EntityInfoDrawer = ({
/>
);
case EntityType.DASHBOARD_DATA_MODEL:
return (
<DataModelSummary
componentType={DRAWER_NAVIGATION_OPTIONS.lineage}
entityDetails={entityDetail as DashboardDataModel}
isLoading={isLoading}
tags={tags}
/>
);
default:
return null;
}

View File

@ -51,6 +51,7 @@ import ReactFlow, {
useEdgesState,
useNodesState,
} from 'reactflow';
import { getDataModelDetails } from 'rest/dataModelsAPI';
import { getLineageByFQN } from 'rest/lineageAPI';
import { searchData } from 'rest/miscAPI';
import { getTableDetails } from 'rest/tableAPI';
@ -629,6 +630,16 @@ const EntityLineageComponent: FunctionComponent<EntityLineageProp> = ({
[nodes, updatedLineageData]
);
const getColumnsForNode = async (type: string, id: string) => {
const fields = ['columns'];
if (type === EntityType.DASHBOARD_DATA_MODEL) {
return await getDataModelDetails(id, fields);
}
return await getTableDetails(id, fields);
};
/**
* take node and get the columns for that node
* @param expandNode
@ -636,11 +647,11 @@ const EntityLineageComponent: FunctionComponent<EntityLineageProp> = ({
const getTableColumns = async (expandNode?: EntityReference) => {
if (expandNode) {
try {
const res = await getTableDetails(expandNode.id, ['columns']);
const tableId = expandNode.id;
const res = await getColumnsForNode(expandNode.type, expandNode.id);
const nodeId = expandNode.id;
const { columns } = res;
tableColumnsRef.current[tableId] = columns;
updateColumnsToNode(columns, tableId);
tableColumnsRef.current[nodeId] = columns;
updateColumnsToNode(columns, nodeId);
} catch (error) {
showErrorToast(
error as AxiosError,

View File

@ -12,7 +12,7 @@
*/
import { Button } from 'antd';
import { EntityLineageNodeType } from 'enums/entity.enum';
import { EntityLineageNodeType, EntityType } from 'enums/entity.enum';
import { get } from 'lodash';
import React from 'react';
import { useTranslation } from 'react-i18next';
@ -32,7 +32,11 @@ const TableExpandButton = ({
onNodeExpand,
isExpanded,
}: LineageNodeLabelProps) => {
if (node.type !== 'table') {
if (
![EntityType.TABLE, EntityType.DASHBOARD_DATA_MODEL].includes(
node.type as EntityType
)
) {
return null;
}

View File

@ -85,6 +85,15 @@ function DashboardSummary({
[charts]
);
const formattedDataModelData: BasicEntityInfo[] = useMemo(
() =>
getFormattedEntityData(
SummaryEntityType.COLUMN,
entityDetails.dataModels
),
[charts]
);
const isExplore = useMemo(
() => componentType === DRAWER_NAVIGATION_OPTIONS.explore,
[componentType]
@ -173,6 +182,21 @@ function DashboardSummary({
<SummaryList formattedEntityData={formattedChartsData} />
</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="data-model-header">
{t('label.data-model-plural')}
</Typography.Text>
</Col>
<Col span={24}>
<SummaryList formattedEntityData={formattedDataModelData} />
</Col>
</Row>
</>
</SummaryPanelSkeleton>
);

View File

@ -45,13 +45,13 @@ describe('DashboardSummary component tests', () => {
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');
const summaryList = screen.getAllByTestId('SummaryList');
expect(dashboardLinkName).toBeInTheDocument();
expect(dashboardUrlLabel).toBeInTheDocument();
expect(dashboardUrlValue).toContainHTML(mockDashboardEntityDetails.name);
expect(chartsHeader).toBeInTheDocument();
expect(summaryList).toBeInTheDocument();
expect(summaryList).toHaveLength(2);
});
it('Component should render properly, when loaded in the Lineage page.', async () => {

View File

@ -0,0 +1,158 @@
/*
* 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 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 { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
import { isEmpty } from 'lodash';
import { default as 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 { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils';
import SummaryList from '../SummaryList/SummaryList.component';
import { BasicEntityInfo } from '../SummaryList/SummaryList.interface';
import { DataModelSummaryProps } from './DataModelSummary.interface';
const DataModelSummary = ({
entityDetails,
componentType = DRAWER_NAVIGATION_OPTIONS.explore,
tags,
isLoading,
}: DataModelSummaryProps) => {
const { t } = useTranslation();
const { columns } = entityDetails;
const [dataModelDetails, setDataModelDetails] =
useState<DashboardDataModel>(entityDetails);
const isExplore = useMemo(
() => componentType === DRAWER_NAVIGATION_OPTIONS.explore,
[componentType]
);
const entityInfo = useMemo(
() =>
getEntityOverview(ExplorePageTabs.DASHBOARD_DATA_MODEL, dataModelDetails),
[dataModelDetails]
);
const formattedColumnsData: BasicEntityInfo[] = useMemo(
() => getFormattedEntityData(SummaryEntityType.COLUMN, columns),
[columns, dataModelDetails]
);
useEffect(() => {
if (!isEmpty(entityDetails)) {
setDataModelDetails(entityDetails);
}
}, [entityDetails]);
return (
<SummaryPanelSkeleton loading={isLoading || isEmpty(dataModelDetails)}>
<>
<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
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>
)}
</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}
<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="column-header">
{t('label.column-plural')}
</Typography.Text>
</Col>
<Col span={24}>
<SummaryList
entityType={SummaryEntityType.COLUMN}
formattedEntityData={formattedColumnsData}
/>
</Col>
</Row>
</>
</SummaryPanelSkeleton>
);
};
export default DataModelSummary;

View File

@ -0,0 +1,28 @@
/*
* 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 { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
import { TableType, TagLabel } from '../../../../generated/entity/data/table';
export interface DataModelSummaryProps {
entityDetails: DashboardDataModel;
componentType?: string;
tags?: TagLabel[];
isLoading?: boolean;
}
export interface BasicTableInfo {
Type: TableType | string;
Queries: string;
Columns: string;
}

View File

@ -30,6 +30,7 @@
border: @global-border;
font-size: 14px;
overflow-y: scroll;
overflow-x: hidden;
-ms-overflow-style: none;
scrollbar-width: none;

View File

@ -15,6 +15,7 @@ import { DefaultOptionType } from 'antd/lib/select';
import { SORT_ORDER } from 'enums/common.enum';
import { Tag } from 'generated/entity/classification/tag';
import { Container } from 'generated/entity/data/container';
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
import { Database } from 'generated/entity/data/database';
import { DatabaseSchema } from 'generated/entity/data/databaseSchema';
import { Glossary } from 'generated/entity/data/glossary';
@ -122,7 +123,8 @@ export type EntityUnion =
| DatabaseSchema
| Database
| Glossary
| Tag;
| Tag
| DashboardDataModel;
export interface EntityDetailsObjectInterface {
details: EntityUnion;

View File

@ -90,8 +90,7 @@
}
p {
margin-top: 10px;
margin-bottom: 0px;
margin: 0px;
color: @body-text-color;
word-break: break-word;
line-height: 20px;

View File

@ -25,4 +25,5 @@ export enum ExplorePageTabs {
CONTAINERS = 'containers',
GLOSSARY = 'glossary',
TAG = 'tag',
DASHBOARD_DATA_MODEL = 'dashboardDataModel',
}

View File

@ -123,4 +123,5 @@ export enum EntityInfo {
MAX_MESSAGE_SIZE = 'Max Message Size',
SIZE = 'size',
NUMBER_OF_OBJECTS = 'number-of-object-plural',
DATA_MODEL_TYPE = 'data-model-type',
}

View File

@ -23,5 +23,4 @@ export enum SearchIndex {
TAG = 'tag_search_index',
CONTAINER = 'container_search_index',
QUERY = 'query_search_index',
DASHBOARD_DATA_MODEL = 'dashboard_data_model_search_index',
}

View File

@ -106,7 +106,6 @@ export type SearchIndexSearchSourceMapping = {
[SearchIndex.TAG]: TagClassSearchSource;
[SearchIndex.CONTAINER]: ContainerSearchSource;
[SearchIndex.QUERY]: QuerySearchSource;
[SearchIndex.DASHBOARD_DATA_MODEL]: dashboardDataModelSearchSource;
};
export type SearchRequest<

View File

@ -182,6 +182,8 @@
"data-insight-top-viewed-entity-summary": "Most Viewed Data Assets",
"data-insight-total-entity-summary": "Total Data Assets",
"data-model": "Data Model",
"data-model-plural": "Data Models",
"data-model-type": "Data Model Type",
"data-quality-test": "Data Quality Test",
"data-type": "Data Type",
"database": "Database",

View File

@ -182,6 +182,8 @@
"data-insight-top-viewed-entity-summary": "Activos de datos más vistos",
"data-insight-total-entity-summary": "Total de activos de datos",
"data-model": "Data Model",
"data-model-plural": "Data Models",
"data-model-type": "Data Model Type",
"data-quality-test": "Prueba de calidad de datos",
"data-type": "Tipo de datos",
"database": "Base de datos",

View File

@ -182,6 +182,8 @@
"data-insight-top-viewed-entity-summary": "Resources de Données les plus Visitées",
"data-insight-total-entity-summary": "Total Resources de Données",
"data-model": "Data Model",
"data-model-plural": "Data Models",
"data-model-type": "Data Model Type",
"data-quality-test": "Data Quality Test",
"data-type": "Type de donnée",
"database": "Base de Données",

View File

@ -182,6 +182,8 @@
"data-insight-top-viewed-entity-summary": "最も閲覧されたデータアセット",
"data-insight-total-entity-summary": "全てのデータアセット",
"data-model": "Data Model",
"data-model-plural": "Data Models",
"data-model-type": "Data Model Type",
"data-quality-test": "データ品質テスト",
"data-type": "データ型",
"database": "データベース",

View File

@ -182,6 +182,8 @@
"data-insight-top-viewed-entity-summary": "Recursos de dados mais visualizados",
"data-insight-total-entity-summary": "Total de recursos de dados",
"data-model": "Data Model",
"data-model-plural": "Data Models",
"data-model-type": "Data Model Type",
"data-quality-test": "Teste de qualidade de dados",
"data-type": "Tipo de dado",
"database": "Banco de dados",

View File

@ -182,6 +182,8 @@
"data-insight-top-viewed-entity-summary": "查看次数最多的数据资产",
"data-insight-total-entity-summary": "所有数据资产",
"data-model": "Data Model",
"data-model-plural": "Data Models",
"data-model-type": "Data Model Type",
"data-quality-test": "Data Quality Test",
"data-type": "Data Type",
"database": "数据库",

View File

@ -11,33 +11,19 @@
* limitations under the License.
*/
import { Card, Col, Row, Space, Tabs } from 'antd';
import AppState from 'AppState';
import { AxiosError } from 'axios';
import ActivityFeedList from 'components/ActivityFeed/ActivityFeedList/ActivityFeedList';
import ActivityThreadPanel from 'components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
import Description from 'components/common/description/Description';
import EntityPageInfo from 'components/common/entityPageInfo/EntityPageInfo';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import PageContainerV1 from 'components/containers/PageContainerV1';
import ModelTab from 'components/DataModels/ModelTab/ModelTab.component';
import EntityLineageComponent from 'components/EntityLineage/EntityLineage.component';
import DataModelDetails from 'components/DataModels/DataModelDetails.component';
import Loader from 'components/Loader/Loader';
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
import {
OperationPermission,
ResourceEntity,
} from 'components/PermissionProvider/PermissionProvider.interface';
import SchemaEditor from 'components/schema-editor/SchemaEditor';
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
import { getServiceDetailsPath } from 'constants/constants';
import { EntityField } from 'constants/Feeds.constants';
import { NO_PERMISSION_TO_VIEW } from 'constants/HelperTextUtil';
import { CSMode } from 'enums/codemirror.enum';
import { EntityInfo, EntityType } from 'enums/entity.enum';
import { EntityType } from 'enums/entity.enum';
import { FeedFilter } from 'enums/mydata.enum';
import { ServiceCategory } from 'enums/service.enum';
import { OwnerType } from 'enums/user.enum';
import { compare, Operation } from 'fast-json-patch';
import { CreateThread } from 'generated/api/feed/createThread';
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
@ -47,8 +33,15 @@ import { LabelType, State, TagSource } from 'generated/type/tagLabel';
import { EntityFieldThreadCount } from 'interface/feed.interface';
import jsonData from 'jsons/en';
import { isUndefined, omitBy } from 'lodash';
import { EntityTags, ExtraInfo } from 'Models';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { observer } from 'mobx-react';
import { EntityTags } from 'Models';
import {
default as React,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom';
import {
@ -59,22 +52,14 @@ import {
} from 'rest/dataModelsAPI';
import { getAllFeeds, postFeedById, postThread } from 'rest/feedsAPI';
import {
getCountBadge,
getCurrentUserId,
getEntityMissingError,
getEntityPlaceHolder,
getFeedCounts,
getOwnerValue,
} from 'utils/CommonUtils';
import { getDataModelsDetailPath } from 'utils/DataModelsUtils';
import { getEntityFeedLink, getEntityName } from 'utils/EntityUtils';
import {
deletePost,
getEntityFieldThreadCounts,
updateThreadData,
} from 'utils/FeedUtils';
import { getEntityFeedLink } from 'utils/EntityUtils';
import { deletePost, updateThreadData } from 'utils/FeedUtils';
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
import { serviceTypeLogo } from 'utils/ServiceUtils';
import { getTagsWithoutTier, getTierTags } from 'utils/TableUtils';
import { showErrorToast } from 'utils/ToastUtils';
import { DATA_MODELS_DETAILS_TABS } from './DataModelsInterface';
@ -86,14 +71,12 @@ const DataModelsPage = () => {
const { getEntityPermissionByFqn } = usePermissionProvider();
const { dashboardDataModelFQN, tab } = useParams() as Record<string, string>;
const [isEditDescription, setIsEditDescription] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [hasError, setHasError] = useState<boolean>(false);
const [dataModelPermissions, setDataModelPermissions] =
useState<OperationPermission>(DEFAULT_ENTITY_PERMISSION);
const [dataModelData, setDataModelData] = useState<DashboardDataModel>();
const [threadLink, setThreadLink] = useState<string>('');
const [entityThread, setEntityThread] = useState<Thread[]>([]);
const [isEntityThreadLoading, setIsEntityThreadLoading] =
useState<boolean>(false);
@ -114,82 +97,22 @@ const DataModelsPage = () => {
[AppState.userDetails, AppState.nonSecureUserDetails]
);
const {
hasViewPermission,
hasEditDescriptionPermission,
hasEditOwnerPermission,
hasEditTagsPermission,
hasEditTierPermission,
hasEditLineagePermission,
} = useMemo(() => {
const { hasViewPermission } = useMemo(() => {
return {
hasViewPermission:
dataModelPermissions.ViewAll || dataModelPermissions.ViewBasic,
hasEditDescriptionPermission:
dataModelPermissions.EditAll || dataModelPermissions.EditDescription,
hasEditOwnerPermission:
dataModelPermissions.EditAll || dataModelPermissions.EditOwner,
hasEditTagsPermission:
dataModelPermissions.EditAll || dataModelPermissions.EditTags,
hasEditTierPermission:
dataModelPermissions.EditAll || dataModelPermissions.EditTier,
hasEditLineagePermission:
dataModelPermissions.EditAll || dataModelPermissions.EditLineage,
};
}, [dataModelPermissions]);
const {
tier,
deleted,
owner,
description,
version,
tags,
entityName,
entityId,
followers,
isUserFollowing,
} = useMemo(() => {
const { tier, isUserFollowing } = useMemo(() => {
return {
deleted: dataModelData?.deleted,
owner: dataModelData?.owner,
description: dataModelData?.description,
version: dataModelData?.version,
tier: getTierTags(dataModelData?.tags ?? []),
tags: getTagsWithoutTier(dataModelData?.tags ?? []),
entityId: dataModelData?.id,
entityName: getEntityName(dataModelData),
isUserFollowing: dataModelData?.followers?.some(
({ id }: { id: string }) => id === getCurrentUserId()
),
followers: dataModelData?.followers ?? [],
};
}, [dataModelData]);
const breadcrumbTitles = useMemo(() => {
const serviceType = dataModelData?.serviceType;
const service = dataModelData?.service;
const serviceName = service?.name;
return [
{
name: serviceName || '',
url: serviceName
? getServiceDetailsPath(
serviceName,
ServiceCategory.DASHBOARD_SERVICES
)
: '',
imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined,
},
{
name: entityName,
url: '',
activeTitle: true,
},
];
}, [dataModelData, dashboardDataModelFQN, entityName]);
const getFeedData = useCallback(
async (
after?: string,
@ -245,10 +168,6 @@ const DataModelsPage = () => {
deletePost(threadId, postId, isThread, setEntityThread);
};
const onThreadLinkSelect = (link: string) => {
setThreadLink(link);
};
const postFeedHandler = (value: string, id: string) => {
const currentUser = AppState.userDetails?.name ?? AppState.users[0]?.name;
@ -348,10 +267,6 @@ const DataModelsPage = () => {
}
};
const onThreadPanelClose = () => {
setThreadLink('');
};
const handleTabChange = (tabValue: string) => {
if (tabValue !== tab) {
history.push({
@ -379,6 +294,7 @@ const DataModelsPage = () => {
description: newDescription,
version,
}));
getEntityFeedCount();
} catch (error) {
showErrorToast(error as AxiosError);
}
@ -412,24 +328,6 @@ const DataModelsPage = () => {
}
};
const extraInfo: Array<ExtraInfo> = [
{
key: EntityInfo.OWNER,
value: owner && getOwnerValue(owner),
placeholderText: getEntityPlaceHolder(
getEntityName(owner),
owner?.deleted
),
isLink: true,
openInNewTab: false,
profileName: owner?.type === OwnerType.USER ? owner?.name : undefined,
},
{
key: EntityInfo.TIER,
value: tier?.tagFQN ? tier.tagFQN.split(FQN_SEPARATOR_CHAR)[1] : '',
},
];
const handleRemoveTier = async () => {
try {
const { tags: newTags, version } = await handleUpdateDataModelData({
@ -568,162 +466,33 @@ const DataModelsPage = () => {
}
return (
<PageContainerV1>
<div className="entity-details-container">
<EntityPageInfo
canDelete={dataModelPermissions.Delete}
currentOwner={owner}
deleted={deleted}
entityFieldTasks={getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldTaskCount
)}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldThreadCount
)}
entityFqn={dashboardDataModelFQN}
entityId={entityId}
entityName={entityName || ''}
entityType={EntityType.DASHBOARD_DATA_MODEL}
extraInfo={extraInfo}
followHandler={handleFollowDataModel}
followers={followers.length}
followersList={followers}
isFollowing={isUserFollowing}
isTagEditable={hasEditTagsPermission}
removeTier={hasEditTierPermission ? handleRemoveTier : undefined}
tags={tags}
tagsHandler={handleUpdateTags}
tier={tier}
titleLinks={breadcrumbTitles}
updateOwner={hasEditOwnerPermission ? handleUpdateOwner : undefined}
updateTier={hasEditTierPermission ? handleUpdateTier : undefined}
version={version}
onThreadLinkSelect={onThreadLinkSelect}
/>
<Tabs activeKey={tab} className="h-full" onChange={handleTabChange}>
<Tabs.TabPane
key={DATA_MODELS_DETAILS_TABS.MODEL}
tab={
<span data-testid={DATA_MODELS_DETAILS_TABS.MODEL}>
{t('label.model')}
</span>
}>
<Card className="h-full">
<Space className="w-full" direction="vertical" size={8}>
<Description
description={description}
entityFqn={dashboardDataModelFQN}
entityName={entityName}
entityType={EntityType.DASHBOARD_DATA_MODEL}
hasEditAccess={hasEditDescriptionPermission}
isEdit={isEditDescription}
isReadOnly={deleted}
owner={owner}
onCancel={() => setIsEditDescription(false)}
onDescriptionEdit={() => setIsEditDescription(true)}
onDescriptionUpdate={handleUpdateDescription}
/>
<ModelTab
data={dataModelData?.columns || []}
hasEditDescriptionPermission={hasEditDescriptionPermission}
hasEditTagsPermission={hasEditTagsPermission}
isReadOnly={Boolean(deleted)}
onUpdate={handleUpdateDataModel}
/>
</Space>
</Card>
</Tabs.TabPane>
<Tabs.TabPane
key={DATA_MODELS_DETAILS_TABS.ACTIVITY}
tab={
<span data-testid={DATA_MODELS_DETAILS_TABS.ACTIVITY}>
{t('label.activity-feed-and-task-plural')}{' '}
{getCountBadge(
feedCount,
'',
DATA_MODELS_DETAILS_TABS.ACTIVITY === tab
)}
</span>
}>
<Card className="m-y-md">
<Row justify="center">
<Col span={18}>
<div id="activityfeed">
<ActivityFeedList
isEntityFeed
withSidePanel
deletePostHandler={deletePostHandler}
entityName={entityName}
feedList={entityThread}
isFeedLoading={isEntityThreadLoading}
postFeedHandler={postFeedHandler}
updateThreadHandler={updateThreadHandler}
onFeedFiltersUpdate={handleFeedFilterChange}
/>
</div>
</Col>
</Row>
</Card>
</Tabs.TabPane>
{dataModelData?.sql && (
<Tabs.TabPane
key={DATA_MODELS_DETAILS_TABS.SQL}
tab={
<span data-testid={DATA_MODELS_DETAILS_TABS.SQL}>
{t('label.sql-uppercase')}
</span>
}>
<Card className="h-full">
<SchemaEditor
editorClass="custom-code-mirror-theme full-screen-editor-height"
mode={{ name: CSMode.SQL }}
options={{
styleActiveLine: false,
readOnly: 'nocursor',
}}
value={dataModelData.sql}
/>
</Card>
</Tabs.TabPane>
)}
<Tabs.TabPane
key={DATA_MODELS_DETAILS_TABS.LINEAGE}
tab={
<span data-testid={DATA_MODELS_DETAILS_TABS.LINEAGE}>
{t('label.lineage')}
</span>
}>
<Card
className="h-full card-body-full"
data-testid="lineage-details">
<EntityLineageComponent
deleted={deleted}
entityType={EntityType.DASHBOARD_DATA_MODEL}
hasEditAccess={hasEditLineagePermission}
/>
</Card>
</Tabs.TabPane>
</Tabs>
{threadLink ? (
<ActivityThreadPanel
createThread={createThread}
deletePostHandler={deletePostHandler}
open={Boolean(threadLink)}
postFeedHandler={postFeedHandler}
threadLink={threadLink}
updateThreadHandler={updateThreadHandler}
onCancel={onThreadPanelClose}
/>
) : null}
</div>
</PageContainerV1>
<DataModelDetails
activeTab={tab}
createThread={createThread}
dashboardDataModelFQN={dashboardDataModelFQN}
dataModelData={dataModelData}
dataModelPermissions={dataModelPermissions}
deletePostHandler={deletePostHandler}
entityFieldTaskCount={entityFieldTaskCount}
entityFieldThreadCount={entityFieldThreadCount}
entityThread={entityThread}
feedCount={feedCount}
fetchFeedHandler={getFeedData}
handleFeedFilterChange={handleFeedFilterChange}
handleFollowDataModel={handleFollowDataModel}
handleRemoveTier={handleRemoveTier}
handleTabChange={handleTabChange}
handleUpdateDataModel={handleUpdateDataModel}
handleUpdateDescription={handleUpdateDescription}
handleUpdateOwner={handleUpdateOwner}
handleUpdateTags={handleUpdateTags}
handleUpdateTier={handleUpdateTier}
isEntityThreadLoading={isEntityThreadLoading}
paging={paging}
postFeedHandler={postFeedHandler}
updateThreadHandler={updateThreadHandler}
/>
);
};
export default DataModelsPage;
export default observer(DataModelsPage);

View File

@ -195,7 +195,7 @@ const ServicePage: FunctionComponent = () => {
servicePermission,
dataModelPaging.total
),
[serviceName, paging, ingestions, servicePermission]
[serviceName, paging, ingestions, servicePermission, dataModelPaging]
);
const extraInfo: Array<ExtraInfo> = [

View File

@ -27,6 +27,17 @@ const configOptions = {
headers: { 'Content-type': 'application/json' },
};
export const getDataModelDetails = async (
id: string,
arrQueryFields: string | string[]
) => {
const url = getURLWithQueryFields(`${URL}/${id}`, arrQueryFields);
const response = await APIClient.get<DashboardDataModel>(url);
return response.data;
};
export const getDataModelsByName = async (
name: string,
fields: string | string[]

View File

@ -12,6 +12,7 @@
*/
import { Space, Typography } from 'antd';
import { NO_DATA_PLACEHOLDER } from 'constants/constants';
import { isEmpty } from 'lodash';
import React from 'react';
import { Link } from 'react-router-dom';
@ -27,6 +28,14 @@ import SVGIcons from './SvgUtils';
const { Text } = Typography;
export interface EntityNameProps {
name?: string;
displayName?: string;
}
const getTitleName = (data: EntityNameProps) =>
getEntityName(data) || NO_DATA_PLACEHOLDER;
export const getFormattedEntityData = (
entityType: SummaryEntityType,
entityInfo?: Array<Column | Field | Chart | Task | MlFeature>,
@ -39,7 +48,7 @@ export const getFormattedEntityData = (
case SummaryEntityType.COLUMN: {
return (entityInfo as Column[]).map((column) => ({
name: column.name,
title: <Text className="entity-title">{column.name}</Text>,
title: <Text className="entity-title">{getTitleName(column)}</Text>,
type: column.dataType,
tags: column.tags,
description: column.description,
@ -58,7 +67,7 @@ export const getFormattedEntityData = (
<Link target="_blank" to={{ pathname: chart.chartUrl }}>
<Space className="m-b-xs">
<Text className="entity-title text-primary font-medium">
{getEntityName(chart)}
{getTitleName(chart)}
</Text>
<SVGIcons alt="external-link" icon="external-link" width="12px" />
</Space>
@ -76,7 +85,7 @@ export const getFormattedEntityData = (
<Link target="_blank" to={{ pathname: task.taskUrl }}>
<Space className="m-b-xs">
<Text className="entity-title text-primary font-medium">
{task.name}
{getTitleName(task)}
</Text>
<SVGIcons alt="external-link" icon="external-link" width="12px" />
</Space>
@ -91,7 +100,7 @@ export const getFormattedEntityData = (
return (entityInfo as MlFeature[]).map((feature) => ({
algorithm: feature.featureAlgorithm,
name: feature.name || '--',
title: <Text className="entity-title">{feature.name}</Text>,
title: <Text className="entity-title">{getTitleName(feature)}</Text>,
type: feature.dataType,
tags: feature.tags,
description: feature.description,
@ -100,7 +109,7 @@ export const getFormattedEntityData = (
case SummaryEntityType.SCHEMAFIELD: {
return (entityInfo as Field[]).map((field) => ({
name: field.name,
title: <Text className="entity-title">{field.name}</Text>,
title: <Text className="entity-title"> {getTitleName(field)}</Text>,
type: field.dataType,
description: field.description,
tags: field.tags,

View File

@ -21,6 +21,7 @@ import { EntityUnion } from 'components/Explore/explore.interface';
import { ResourceEntity } from 'components/PermissionProvider/PermissionProvider.interface';
import { ExplorePageTabs } from 'enums/Explore.enum';
import { Container } from 'generated/entity/data/container';
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
import { Mlmodel } from 'generated/entity/data/mlmodel';
import i18next from 'i18next';
import { get, isEmpty, isNil, isUndefined, lowerCase, startCase } from 'lodash';
@ -98,7 +99,8 @@ export const getEntityTags = (
case EntityType.PIPELINE:
case EntityType.DASHBOARD:
case EntityType.TOPIC:
case EntityType.MLMODEL: {
case EntityType.MLMODEL:
case EntityType.DASHBOARD_DATA_MODEL: {
return entityDetail.tags || [];
}
@ -463,6 +465,67 @@ export const getEntityOverview = (
return overview;
}
case ExplorePageTabs.DASHBOARD_DATA_MODEL: {
const { owner, tags, href, service, displayName, dataModelType } =
entityDetail as DashboardDataModel;
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.data-model')} ${i18next.t(
'label.url-uppercase'
)}`,
value: displayName || NO_DATA,
url: href,
isLink: true,
isExternal: true,
visible: [
DRAWER_NAVIGATION_OPTIONS.lineage,
DRAWER_NAVIGATION_OPTIONS.explore,
],
},
{
name: i18next.t('label.service'),
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.tier'),
value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : NO_DATA,
isLink: false,
isExternal: false,
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
},
{
name: i18next.t('label.data-model-type'),
value: dataModelType,
isLink: false,
isExternal: false,
visible: [DRAWER_NAVIGATION_OPTIONS.lineage],
},
];
return overview;
}
default:
return [];
}

View File

@ -330,7 +330,6 @@ export const getEntityIcon = (indexType: string) => {
case EntityType.CONTAINER:
return <ContainerIcon />;
case SearchIndex.DASHBOARD_DATA_MODEL:
case EntityType.DASHBOARD_DATA_MODEL:
return <IconDataModel />;

View File

@ -268,6 +268,9 @@ export const getBreadCrumbList = (
activeEntity,
];
}
case EntityType.DASHBOARD_DATA_MODEL: {
return [service(ServiceCategory.DASHBOARD_SERVICES), activeEntity];
}
default:
return [];

View File

@ -235,7 +235,7 @@ export const mockInvalidDataResponse = [
description: undefined,
name: undefined,
tags: undefined,
title: <Text className="entity-title" />,
title: <Text className="entity-title">---</Text>,
type: undefined,
},
];