mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-17 19:57:56 +00:00
* 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:
parent
09b283818d
commit
331f043d3b
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 |
@ -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;
|
@ -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;
|
||||
}
|
@ -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 || '')}>
|
||||
|
@ -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'),
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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 () => {
|
||||
|
@ -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;
|
@ -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;
|
||||
}
|
@ -30,6 +30,7 @@
|
||||
border: @global-border;
|
||||
font-size: 14px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -90,8 +90,7 @@
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 0px;
|
||||
margin: 0px;
|
||||
color: @body-text-color;
|
||||
word-break: break-word;
|
||||
line-height: 20px;
|
||||
|
@ -25,4 +25,5 @@ export enum ExplorePageTabs {
|
||||
CONTAINERS = 'containers',
|
||||
GLOSSARY = 'glossary',
|
||||
TAG = 'tag',
|
||||
DASHBOARD_DATA_MODEL = 'dashboardDataModel',
|
||||
}
|
||||
|
@ -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',
|
||||
}
|
||||
|
@ -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',
|
||||
}
|
||||
|
@ -106,7 +106,6 @@ export type SearchIndexSearchSourceMapping = {
|
||||
[SearchIndex.TAG]: TagClassSearchSource;
|
||||
[SearchIndex.CONTAINER]: ContainerSearchSource;
|
||||
[SearchIndex.QUERY]: QuerySearchSource;
|
||||
[SearchIndex.DASHBOARD_DATA_MODEL]: dashboardDataModelSearchSource;
|
||||
};
|
||||
|
||||
export type SearchRequest<
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": "データベース",
|
||||
|
@ -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",
|
||||
|
@ -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": "数据库",
|
||||
|
@ -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);
|
||||
|
@ -195,7 +195,7 @@ const ServicePage: FunctionComponent = () => {
|
||||
servicePermission,
|
||||
dataModelPaging.total
|
||||
),
|
||||
[serviceName, paging, ingestions, servicePermission]
|
||||
[serviceName, paging, ingestions, servicePermission, dataModelPaging]
|
||||
);
|
||||
|
||||
const extraInfo: Array<ExtraInfo> = [
|
||||
|
@ -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[]
|
||||
|
@ -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,
|
||||
|
@ -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 [];
|
||||
}
|
||||
|
@ -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 />;
|
||||
|
||||
|
@ -268,6 +268,9 @@ export const getBreadCrumbList = (
|
||||
activeEntity,
|
||||
];
|
||||
}
|
||||
case EntityType.DASHBOARD_DATA_MODEL: {
|
||||
return [service(ServiceCategory.DASHBOARD_SERVICES), activeEntity];
|
||||
}
|
||||
|
||||
default:
|
||||
return [];
|
||||
|
@ -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,
|
||||
},
|
||||
];
|
||||
|
Loading…
x
Reference in New Issue
Block a user