mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-19 21:02:19 +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'),
|
title: t('label.name'),
|
||||||
dataIndex: 'displayName',
|
dataIndex: 'displayName',
|
||||||
key: 'displayName',
|
key: 'displayName',
|
||||||
|
width: 350,
|
||||||
render: (_, record: ServicePageData) => {
|
render: (_, record: ServicePageData) => {
|
||||||
return (
|
return (
|
||||||
<Link to={getDataModelDetailsPath(record.fullyQualifiedName || '')}>
|
<Link to={getDataModelDetailsPath(record.fullyQualifiedName || '')}>
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
updateDataModelColumnDescription,
|
updateDataModelColumnDescription,
|
||||||
updateDataModelColumnTags,
|
updateDataModelColumnTags,
|
||||||
} from 'utils/DataModelsUtils';
|
} from 'utils/DataModelsUtils';
|
||||||
|
import { getEntityName } from 'utils/EntityUtils';
|
||||||
import { fetchTagsAndGlossaryTerms } from 'utils/TagsUtils';
|
import { fetchTagsAndGlossaryTerms } from 'utils/TagsUtils';
|
||||||
import { ReactComponent as EditIcon } from '../../../assets/svg/ic-edit.svg';
|
import { ReactComponent as EditIcon } from '../../../assets/svg/ic-edit.svg';
|
||||||
import { ModelTabProps } from './ModelTab.interface';
|
import { ModelTabProps } from './ModelTab.interface';
|
||||||
@ -195,6 +196,9 @@ const ModelTab = ({
|
|||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
width: 250,
|
width: 250,
|
||||||
|
render: (_, record) => (
|
||||||
|
<Typography.Text>{getEntityName(record)}</Typography.Text>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.type'),
|
title: t('label.type'),
|
||||||
|
@ -16,12 +16,14 @@ import { Col, Drawer, Row, Typography } from 'antd';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import ContainerSummary from 'components/Explore/EntitySummaryPanel/ContainerSummary/ContainerSummary.component';
|
import ContainerSummary from 'components/Explore/EntitySummaryPanel/ContainerSummary/ContainerSummary.component';
|
||||||
import DashboardSummary from 'components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.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 MlModelSummary from 'components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.component';
|
||||||
import PipelineSummary from 'components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.component';
|
import PipelineSummary from 'components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.component';
|
||||||
import TableSummary from 'components/Explore/EntitySummaryPanel/TableSummary/TableSummary.component';
|
import TableSummary from 'components/Explore/EntitySummaryPanel/TableSummary/TableSummary.component';
|
||||||
import TopicSummary from 'components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.component';
|
import TopicSummary from 'components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.component';
|
||||||
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
|
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
|
||||||
import { Container } from 'generated/entity/data/container';
|
import { Container } from 'generated/entity/data/container';
|
||||||
|
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
||||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||||
import { EntityDetailUnion } from 'Models';
|
import { EntityDetailUnion } from 'Models';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
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:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ import ReactFlow, {
|
|||||||
useEdgesState,
|
useEdgesState,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
} from 'reactflow';
|
} from 'reactflow';
|
||||||
|
import { getDataModelDetails } from 'rest/dataModelsAPI';
|
||||||
import { getLineageByFQN } from 'rest/lineageAPI';
|
import { getLineageByFQN } from 'rest/lineageAPI';
|
||||||
import { searchData } from 'rest/miscAPI';
|
import { searchData } from 'rest/miscAPI';
|
||||||
import { getTableDetails } from 'rest/tableAPI';
|
import { getTableDetails } from 'rest/tableAPI';
|
||||||
@ -629,6 +630,16 @@ const EntityLineageComponent: FunctionComponent<EntityLineageProp> = ({
|
|||||||
[nodes, updatedLineageData]
|
[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
|
* take node and get the columns for that node
|
||||||
* @param expandNode
|
* @param expandNode
|
||||||
@ -636,11 +647,11 @@ const EntityLineageComponent: FunctionComponent<EntityLineageProp> = ({
|
|||||||
const getTableColumns = async (expandNode?: EntityReference) => {
|
const getTableColumns = async (expandNode?: EntityReference) => {
|
||||||
if (expandNode) {
|
if (expandNode) {
|
||||||
try {
|
try {
|
||||||
const res = await getTableDetails(expandNode.id, ['columns']);
|
const res = await getColumnsForNode(expandNode.type, expandNode.id);
|
||||||
const tableId = expandNode.id;
|
const nodeId = expandNode.id;
|
||||||
const { columns } = res;
|
const { columns } = res;
|
||||||
tableColumnsRef.current[tableId] = columns;
|
tableColumnsRef.current[nodeId] = columns;
|
||||||
updateColumnsToNode(columns, tableId);
|
updateColumnsToNode(columns, nodeId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast(
|
showErrorToast(
|
||||||
error as AxiosError,
|
error as AxiosError,
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import { EntityLineageNodeType } from 'enums/entity.enum';
|
import { EntityLineageNodeType, EntityType } from 'enums/entity.enum';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -32,7 +32,11 @@ const TableExpandButton = ({
|
|||||||
onNodeExpand,
|
onNodeExpand,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
}: LineageNodeLabelProps) => {
|
}: LineageNodeLabelProps) => {
|
||||||
if (node.type !== 'table') {
|
if (
|
||||||
|
![EntityType.TABLE, EntityType.DASHBOARD_DATA_MODEL].includes(
|
||||||
|
node.type as EntityType
|
||||||
|
)
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +85,15 @@ function DashboardSummary({
|
|||||||
[charts]
|
[charts]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const formattedDataModelData: BasicEntityInfo[] = useMemo(
|
||||||
|
() =>
|
||||||
|
getFormattedEntityData(
|
||||||
|
SummaryEntityType.COLUMN,
|
||||||
|
entityDetails.dataModels
|
||||||
|
),
|
||||||
|
[charts]
|
||||||
|
);
|
||||||
|
|
||||||
const isExplore = useMemo(
|
const isExplore = useMemo(
|
||||||
() => componentType === DRAWER_NAVIGATION_OPTIONS.explore,
|
() => componentType === DRAWER_NAVIGATION_OPTIONS.explore,
|
||||||
[componentType]
|
[componentType]
|
||||||
@ -173,6 +182,21 @@ function DashboardSummary({
|
|||||||
<SummaryList formattedEntityData={formattedChartsData} />
|
<SummaryList formattedEntityData={formattedChartsData} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</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>
|
</SummaryPanelSkeleton>
|
||||||
);
|
);
|
||||||
|
@ -45,13 +45,13 @@ describe('DashboardSummary component tests', () => {
|
|||||||
const dashboardUrlValue = screen.getByTestId('dashboard-url-value');
|
const dashboardUrlValue = screen.getByTestId('dashboard-url-value');
|
||||||
const dashboardLinkName = screen.getByTestId('dashboard-link-name');
|
const dashboardLinkName = screen.getByTestId('dashboard-link-name');
|
||||||
const chartsHeader = screen.getByTestId('charts-header');
|
const chartsHeader = screen.getByTestId('charts-header');
|
||||||
const summaryList = screen.getByTestId('SummaryList');
|
const summaryList = screen.getAllByTestId('SummaryList');
|
||||||
|
|
||||||
expect(dashboardLinkName).toBeInTheDocument();
|
expect(dashboardLinkName).toBeInTheDocument();
|
||||||
expect(dashboardUrlLabel).toBeInTheDocument();
|
expect(dashboardUrlLabel).toBeInTheDocument();
|
||||||
expect(dashboardUrlValue).toContainHTML(mockDashboardEntityDetails.name);
|
expect(dashboardUrlValue).toContainHTML(mockDashboardEntityDetails.name);
|
||||||
expect(chartsHeader).toBeInTheDocument();
|
expect(chartsHeader).toBeInTheDocument();
|
||||||
expect(summaryList).toBeInTheDocument();
|
expect(summaryList).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Component should render properly, when loaded in the Lineage page.', async () => {
|
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;
|
border: @global-border;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ import { DefaultOptionType } from 'antd/lib/select';
|
|||||||
import { SORT_ORDER } from 'enums/common.enum';
|
import { SORT_ORDER } from 'enums/common.enum';
|
||||||
import { Tag } from 'generated/entity/classification/tag';
|
import { Tag } from 'generated/entity/classification/tag';
|
||||||
import { Container } from 'generated/entity/data/container';
|
import { Container } from 'generated/entity/data/container';
|
||||||
|
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
||||||
import { Database } from 'generated/entity/data/database';
|
import { Database } from 'generated/entity/data/database';
|
||||||
import { DatabaseSchema } from 'generated/entity/data/databaseSchema';
|
import { DatabaseSchema } from 'generated/entity/data/databaseSchema';
|
||||||
import { Glossary } from 'generated/entity/data/glossary';
|
import { Glossary } from 'generated/entity/data/glossary';
|
||||||
@ -122,7 +123,8 @@ export type EntityUnion =
|
|||||||
| DatabaseSchema
|
| DatabaseSchema
|
||||||
| Database
|
| Database
|
||||||
| Glossary
|
| Glossary
|
||||||
| Tag;
|
| Tag
|
||||||
|
| DashboardDataModel;
|
||||||
|
|
||||||
export interface EntityDetailsObjectInterface {
|
export interface EntityDetailsObjectInterface {
|
||||||
details: EntityUnion;
|
details: EntityUnion;
|
||||||
|
@ -90,8 +90,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin-top: 10px;
|
margin: 0px;
|
||||||
margin-bottom: 0px;
|
|
||||||
color: @body-text-color;
|
color: @body-text-color;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
@ -25,4 +25,5 @@ export enum ExplorePageTabs {
|
|||||||
CONTAINERS = 'containers',
|
CONTAINERS = 'containers',
|
||||||
GLOSSARY = 'glossary',
|
GLOSSARY = 'glossary',
|
||||||
TAG = 'tag',
|
TAG = 'tag',
|
||||||
|
DASHBOARD_DATA_MODEL = 'dashboardDataModel',
|
||||||
}
|
}
|
||||||
|
@ -123,4 +123,5 @@ export enum EntityInfo {
|
|||||||
MAX_MESSAGE_SIZE = 'Max Message Size',
|
MAX_MESSAGE_SIZE = 'Max Message Size',
|
||||||
SIZE = 'size',
|
SIZE = 'size',
|
||||||
NUMBER_OF_OBJECTS = 'number-of-object-plural',
|
NUMBER_OF_OBJECTS = 'number-of-object-plural',
|
||||||
|
DATA_MODEL_TYPE = 'data-model-type',
|
||||||
}
|
}
|
||||||
|
@ -23,5 +23,4 @@ export enum SearchIndex {
|
|||||||
TAG = 'tag_search_index',
|
TAG = 'tag_search_index',
|
||||||
CONTAINER = 'container_search_index',
|
CONTAINER = 'container_search_index',
|
||||||
QUERY = 'query_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.TAG]: TagClassSearchSource;
|
||||||
[SearchIndex.CONTAINER]: ContainerSearchSource;
|
[SearchIndex.CONTAINER]: ContainerSearchSource;
|
||||||
[SearchIndex.QUERY]: QuerySearchSource;
|
[SearchIndex.QUERY]: QuerySearchSource;
|
||||||
[SearchIndex.DASHBOARD_DATA_MODEL]: dashboardDataModelSearchSource;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SearchRequest<
|
export type SearchRequest<
|
||||||
|
@ -182,6 +182,8 @@
|
|||||||
"data-insight-top-viewed-entity-summary": "Most Viewed Data Assets",
|
"data-insight-top-viewed-entity-summary": "Most Viewed Data Assets",
|
||||||
"data-insight-total-entity-summary": "Total Data Assets",
|
"data-insight-total-entity-summary": "Total Data Assets",
|
||||||
"data-model": "Data Model",
|
"data-model": "Data Model",
|
||||||
|
"data-model-plural": "Data Models",
|
||||||
|
"data-model-type": "Data Model Type",
|
||||||
"data-quality-test": "Data Quality Test",
|
"data-quality-test": "Data Quality Test",
|
||||||
"data-type": "Data Type",
|
"data-type": "Data Type",
|
||||||
"database": "Database",
|
"database": "Database",
|
||||||
|
@ -182,6 +182,8 @@
|
|||||||
"data-insight-top-viewed-entity-summary": "Activos de datos más vistos",
|
"data-insight-top-viewed-entity-summary": "Activos de datos más vistos",
|
||||||
"data-insight-total-entity-summary": "Total de activos de datos",
|
"data-insight-total-entity-summary": "Total de activos de datos",
|
||||||
"data-model": "Data Model",
|
"data-model": "Data Model",
|
||||||
|
"data-model-plural": "Data Models",
|
||||||
|
"data-model-type": "Data Model Type",
|
||||||
"data-quality-test": "Prueba de calidad de datos",
|
"data-quality-test": "Prueba de calidad de datos",
|
||||||
"data-type": "Tipo de datos",
|
"data-type": "Tipo de datos",
|
||||||
"database": "Base 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-top-viewed-entity-summary": "Resources de Données les plus Visitées",
|
||||||
"data-insight-total-entity-summary": "Total Resources de Données",
|
"data-insight-total-entity-summary": "Total Resources de Données",
|
||||||
"data-model": "Data Model",
|
"data-model": "Data Model",
|
||||||
|
"data-model-plural": "Data Models",
|
||||||
|
"data-model-type": "Data Model Type",
|
||||||
"data-quality-test": "Data Quality Test",
|
"data-quality-test": "Data Quality Test",
|
||||||
"data-type": "Type de donnée",
|
"data-type": "Type de donnée",
|
||||||
"database": "Base de Données",
|
"database": "Base de Données",
|
||||||
|
@ -182,6 +182,8 @@
|
|||||||
"data-insight-top-viewed-entity-summary": "最も閲覧されたデータアセット",
|
"data-insight-top-viewed-entity-summary": "最も閲覧されたデータアセット",
|
||||||
"data-insight-total-entity-summary": "全てのデータアセット",
|
"data-insight-total-entity-summary": "全てのデータアセット",
|
||||||
"data-model": "Data Model",
|
"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": "Recursos de dados mais visualizados",
|
"data-insight-top-viewed-entity-summary": "Recursos de dados mais visualizados",
|
||||||
"data-insight-total-entity-summary": "Total de recursos de dados",
|
"data-insight-total-entity-summary": "Total de recursos de dados",
|
||||||
"data-model": "Data Model",
|
"data-model": "Data Model",
|
||||||
|
"data-model-plural": "Data Models",
|
||||||
|
"data-model-type": "Data Model Type",
|
||||||
"data-quality-test": "Teste de qualidade de dados",
|
"data-quality-test": "Teste de qualidade de dados",
|
||||||
"data-type": "Tipo de dado",
|
"data-type": "Tipo de dado",
|
||||||
"database": "Banco de dados",
|
"database": "Banco de dados",
|
||||||
|
@ -182,6 +182,8 @@
|
|||||||
"data-insight-top-viewed-entity-summary": "查看次数最多的数据资产",
|
"data-insight-top-viewed-entity-summary": "查看次数最多的数据资产",
|
||||||
"data-insight-total-entity-summary": "所有数据资产",
|
"data-insight-total-entity-summary": "所有数据资产",
|
||||||
"data-model": "Data Model",
|
"data-model": "Data Model",
|
||||||
|
"data-model-plural": "Data Models",
|
||||||
|
"data-model-type": "Data Model Type",
|
||||||
"data-quality-test": "Data Quality Test",
|
"data-quality-test": "Data Quality Test",
|
||||||
"data-type": "Data Type",
|
"data-type": "Data Type",
|
||||||
"database": "数据库",
|
"database": "数据库",
|
||||||
|
@ -11,33 +11,19 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Card, Col, Row, Space, Tabs } from 'antd';
|
|
||||||
import AppState from 'AppState';
|
import AppState from 'AppState';
|
||||||
import { AxiosError } from 'axios';
|
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 ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||||
import PageContainerV1 from 'components/containers/PageContainerV1';
|
import DataModelDetails from 'components/DataModels/DataModelDetails.component';
|
||||||
import ModelTab from 'components/DataModels/ModelTab/ModelTab.component';
|
|
||||||
import EntityLineageComponent from 'components/EntityLineage/EntityLineage.component';
|
|
||||||
import Loader from 'components/Loader/Loader';
|
import Loader from 'components/Loader/Loader';
|
||||||
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
|
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
|
||||||
import {
|
import {
|
||||||
OperationPermission,
|
OperationPermission,
|
||||||
ResourceEntity,
|
ResourceEntity,
|
||||||
} from 'components/PermissionProvider/PermissionProvider.interface';
|
} 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 { NO_PERMISSION_TO_VIEW } from 'constants/HelperTextUtil';
|
||||||
import { CSMode } from 'enums/codemirror.enum';
|
import { EntityType } from 'enums/entity.enum';
|
||||||
import { EntityInfo, EntityType } from 'enums/entity.enum';
|
|
||||||
import { FeedFilter } from 'enums/mydata.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 { compare, Operation } from 'fast-json-patch';
|
||||||
import { CreateThread } from 'generated/api/feed/createThread';
|
import { CreateThread } from 'generated/api/feed/createThread';
|
||||||
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
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 { EntityFieldThreadCount } from 'interface/feed.interface';
|
||||||
import jsonData from 'jsons/en';
|
import jsonData from 'jsons/en';
|
||||||
import { isUndefined, omitBy } from 'lodash';
|
import { isUndefined, omitBy } from 'lodash';
|
||||||
import { EntityTags, ExtraInfo } from 'Models';
|
import { observer } from 'mobx-react';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import { EntityTags } from 'Models';
|
||||||
|
import {
|
||||||
|
default as React,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
@ -59,22 +52,14 @@ import {
|
|||||||
} from 'rest/dataModelsAPI';
|
} from 'rest/dataModelsAPI';
|
||||||
import { getAllFeeds, postFeedById, postThread } from 'rest/feedsAPI';
|
import { getAllFeeds, postFeedById, postThread } from 'rest/feedsAPI';
|
||||||
import {
|
import {
|
||||||
getCountBadge,
|
|
||||||
getCurrentUserId,
|
getCurrentUserId,
|
||||||
getEntityMissingError,
|
getEntityMissingError,
|
||||||
getEntityPlaceHolder,
|
|
||||||
getFeedCounts,
|
getFeedCounts,
|
||||||
getOwnerValue,
|
|
||||||
} from 'utils/CommonUtils';
|
} from 'utils/CommonUtils';
|
||||||
import { getDataModelsDetailPath } from 'utils/DataModelsUtils';
|
import { getDataModelsDetailPath } from 'utils/DataModelsUtils';
|
||||||
import { getEntityFeedLink, getEntityName } from 'utils/EntityUtils';
|
import { getEntityFeedLink } from 'utils/EntityUtils';
|
||||||
import {
|
import { deletePost, updateThreadData } from 'utils/FeedUtils';
|
||||||
deletePost,
|
|
||||||
getEntityFieldThreadCounts,
|
|
||||||
updateThreadData,
|
|
||||||
} from 'utils/FeedUtils';
|
|
||||||
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
|
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
|
||||||
import { serviceTypeLogo } from 'utils/ServiceUtils';
|
|
||||||
import { getTagsWithoutTier, getTierTags } from 'utils/TableUtils';
|
import { getTagsWithoutTier, getTierTags } from 'utils/TableUtils';
|
||||||
import { showErrorToast } from 'utils/ToastUtils';
|
import { showErrorToast } from 'utils/ToastUtils';
|
||||||
import { DATA_MODELS_DETAILS_TABS } from './DataModelsInterface';
|
import { DATA_MODELS_DETAILS_TABS } from './DataModelsInterface';
|
||||||
@ -86,14 +71,12 @@ const DataModelsPage = () => {
|
|||||||
const { getEntityPermissionByFqn } = usePermissionProvider();
|
const { getEntityPermissionByFqn } = usePermissionProvider();
|
||||||
const { dashboardDataModelFQN, tab } = useParams() as Record<string, string>;
|
const { dashboardDataModelFQN, tab } = useParams() as Record<string, string>;
|
||||||
|
|
||||||
const [isEditDescription, setIsEditDescription] = useState<boolean>(false);
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const [hasError, setHasError] = useState<boolean>(false);
|
const [hasError, setHasError] = useState<boolean>(false);
|
||||||
const [dataModelPermissions, setDataModelPermissions] =
|
const [dataModelPermissions, setDataModelPermissions] =
|
||||||
useState<OperationPermission>(DEFAULT_ENTITY_PERMISSION);
|
useState<OperationPermission>(DEFAULT_ENTITY_PERMISSION);
|
||||||
const [dataModelData, setDataModelData] = useState<DashboardDataModel>();
|
const [dataModelData, setDataModelData] = useState<DashboardDataModel>();
|
||||||
|
|
||||||
const [threadLink, setThreadLink] = useState<string>('');
|
|
||||||
const [entityThread, setEntityThread] = useState<Thread[]>([]);
|
const [entityThread, setEntityThread] = useState<Thread[]>([]);
|
||||||
const [isEntityThreadLoading, setIsEntityThreadLoading] =
|
const [isEntityThreadLoading, setIsEntityThreadLoading] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
@ -114,82 +97,22 @@ const DataModelsPage = () => {
|
|||||||
[AppState.userDetails, AppState.nonSecureUserDetails]
|
[AppState.userDetails, AppState.nonSecureUserDetails]
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const { hasViewPermission } = useMemo(() => {
|
||||||
hasViewPermission,
|
|
||||||
hasEditDescriptionPermission,
|
|
||||||
hasEditOwnerPermission,
|
|
||||||
hasEditTagsPermission,
|
|
||||||
hasEditTierPermission,
|
|
||||||
hasEditLineagePermission,
|
|
||||||
} = useMemo(() => {
|
|
||||||
return {
|
return {
|
||||||
hasViewPermission:
|
hasViewPermission:
|
||||||
dataModelPermissions.ViewAll || dataModelPermissions.ViewBasic,
|
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]);
|
}, [dataModelPermissions]);
|
||||||
|
|
||||||
const {
|
const { tier, isUserFollowing } = useMemo(() => {
|
||||||
tier,
|
|
||||||
deleted,
|
|
||||||
owner,
|
|
||||||
description,
|
|
||||||
version,
|
|
||||||
tags,
|
|
||||||
entityName,
|
|
||||||
entityId,
|
|
||||||
followers,
|
|
||||||
isUserFollowing,
|
|
||||||
} = useMemo(() => {
|
|
||||||
return {
|
return {
|
||||||
deleted: dataModelData?.deleted,
|
|
||||||
owner: dataModelData?.owner,
|
|
||||||
description: dataModelData?.description,
|
|
||||||
version: dataModelData?.version,
|
|
||||||
tier: getTierTags(dataModelData?.tags ?? []),
|
tier: getTierTags(dataModelData?.tags ?? []),
|
||||||
tags: getTagsWithoutTier(dataModelData?.tags ?? []),
|
|
||||||
entityId: dataModelData?.id,
|
|
||||||
entityName: getEntityName(dataModelData),
|
|
||||||
isUserFollowing: dataModelData?.followers?.some(
|
isUserFollowing: dataModelData?.followers?.some(
|
||||||
({ id }: { id: string }) => id === getCurrentUserId()
|
({ id }: { id: string }) => id === getCurrentUserId()
|
||||||
),
|
),
|
||||||
followers: dataModelData?.followers ?? [],
|
|
||||||
};
|
};
|
||||||
}, [dataModelData]);
|
}, [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(
|
const getFeedData = useCallback(
|
||||||
async (
|
async (
|
||||||
after?: string,
|
after?: string,
|
||||||
@ -245,10 +168,6 @@ const DataModelsPage = () => {
|
|||||||
deletePost(threadId, postId, isThread, setEntityThread);
|
deletePost(threadId, postId, isThread, setEntityThread);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onThreadLinkSelect = (link: string) => {
|
|
||||||
setThreadLink(link);
|
|
||||||
};
|
|
||||||
|
|
||||||
const postFeedHandler = (value: string, id: string) => {
|
const postFeedHandler = (value: string, id: string) => {
|
||||||
const currentUser = AppState.userDetails?.name ?? AppState.users[0]?.name;
|
const currentUser = AppState.userDetails?.name ?? AppState.users[0]?.name;
|
||||||
|
|
||||||
@ -348,10 +267,6 @@ const DataModelsPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onThreadPanelClose = () => {
|
|
||||||
setThreadLink('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTabChange = (tabValue: string) => {
|
const handleTabChange = (tabValue: string) => {
|
||||||
if (tabValue !== tab) {
|
if (tabValue !== tab) {
|
||||||
history.push({
|
history.push({
|
||||||
@ -379,6 +294,7 @@ const DataModelsPage = () => {
|
|||||||
description: newDescription,
|
description: newDescription,
|
||||||
version,
|
version,
|
||||||
}));
|
}));
|
||||||
|
getEntityFeedCount();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast(error as AxiosError);
|
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 () => {
|
const handleRemoveTier = async () => {
|
||||||
try {
|
try {
|
||||||
const { tags: newTags, version } = await handleUpdateDataModelData({
|
const { tags: newTags, version } = await handleUpdateDataModelData({
|
||||||
@ -568,162 +466,33 @@ const DataModelsPage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainerV1>
|
<DataModelDetails
|
||||||
<div className="entity-details-container">
|
activeTab={tab}
|
||||||
<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}
|
createThread={createThread}
|
||||||
|
dashboardDataModelFQN={dashboardDataModelFQN}
|
||||||
|
dataModelData={dataModelData}
|
||||||
|
dataModelPermissions={dataModelPermissions}
|
||||||
deletePostHandler={deletePostHandler}
|
deletePostHandler={deletePostHandler}
|
||||||
open={Boolean(threadLink)}
|
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}
|
postFeedHandler={postFeedHandler}
|
||||||
threadLink={threadLink}
|
|
||||||
updateThreadHandler={updateThreadHandler}
|
updateThreadHandler={updateThreadHandler}
|
||||||
onCancel={onThreadPanelClose}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</PageContainerV1>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DataModelsPage;
|
export default observer(DataModelsPage);
|
||||||
|
@ -195,7 +195,7 @@ const ServicePage: FunctionComponent = () => {
|
|||||||
servicePermission,
|
servicePermission,
|
||||||
dataModelPaging.total
|
dataModelPaging.total
|
||||||
),
|
),
|
||||||
[serviceName, paging, ingestions, servicePermission]
|
[serviceName, paging, ingestions, servicePermission, dataModelPaging]
|
||||||
);
|
);
|
||||||
|
|
||||||
const extraInfo: Array<ExtraInfo> = [
|
const extraInfo: Array<ExtraInfo> = [
|
||||||
|
@ -27,6 +27,17 @@ const configOptions = {
|
|||||||
headers: { 'Content-type': 'application/json' },
|
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 (
|
export const getDataModelsByName = async (
|
||||||
name: string,
|
name: string,
|
||||||
fields: string | string[]
|
fields: string | string[]
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Space, Typography } from 'antd';
|
import { Space, Typography } from 'antd';
|
||||||
|
import { NO_DATA_PLACEHOLDER } from 'constants/constants';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
@ -27,6 +28,14 @@ import SVGIcons from './SvgUtils';
|
|||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
export interface EntityNameProps {
|
||||||
|
name?: string;
|
||||||
|
displayName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTitleName = (data: EntityNameProps) =>
|
||||||
|
getEntityName(data) || NO_DATA_PLACEHOLDER;
|
||||||
|
|
||||||
export const getFormattedEntityData = (
|
export const getFormattedEntityData = (
|
||||||
entityType: SummaryEntityType,
|
entityType: SummaryEntityType,
|
||||||
entityInfo?: Array<Column | Field | Chart | Task | MlFeature>,
|
entityInfo?: Array<Column | Field | Chart | Task | MlFeature>,
|
||||||
@ -39,7 +48,7 @@ export const getFormattedEntityData = (
|
|||||||
case SummaryEntityType.COLUMN: {
|
case SummaryEntityType.COLUMN: {
|
||||||
return (entityInfo as Column[]).map((column) => ({
|
return (entityInfo as Column[]).map((column) => ({
|
||||||
name: column.name,
|
name: column.name,
|
||||||
title: <Text className="entity-title">{column.name}</Text>,
|
title: <Text className="entity-title">{getTitleName(column)}</Text>,
|
||||||
type: column.dataType,
|
type: column.dataType,
|
||||||
tags: column.tags,
|
tags: column.tags,
|
||||||
description: column.description,
|
description: column.description,
|
||||||
@ -58,7 +67,7 @@ export const getFormattedEntityData = (
|
|||||||
<Link target="_blank" to={{ pathname: chart.chartUrl }}>
|
<Link target="_blank" to={{ pathname: chart.chartUrl }}>
|
||||||
<Space className="m-b-xs">
|
<Space className="m-b-xs">
|
||||||
<Text className="entity-title text-primary font-medium">
|
<Text className="entity-title text-primary font-medium">
|
||||||
{getEntityName(chart)}
|
{getTitleName(chart)}
|
||||||
</Text>
|
</Text>
|
||||||
<SVGIcons alt="external-link" icon="external-link" width="12px" />
|
<SVGIcons alt="external-link" icon="external-link" width="12px" />
|
||||||
</Space>
|
</Space>
|
||||||
@ -76,7 +85,7 @@ export const getFormattedEntityData = (
|
|||||||
<Link target="_blank" to={{ pathname: task.taskUrl }}>
|
<Link target="_blank" to={{ pathname: task.taskUrl }}>
|
||||||
<Space className="m-b-xs">
|
<Space className="m-b-xs">
|
||||||
<Text className="entity-title text-primary font-medium">
|
<Text className="entity-title text-primary font-medium">
|
||||||
{task.name}
|
{getTitleName(task)}
|
||||||
</Text>
|
</Text>
|
||||||
<SVGIcons alt="external-link" icon="external-link" width="12px" />
|
<SVGIcons alt="external-link" icon="external-link" width="12px" />
|
||||||
</Space>
|
</Space>
|
||||||
@ -91,7 +100,7 @@ export const getFormattedEntityData = (
|
|||||||
return (entityInfo as MlFeature[]).map((feature) => ({
|
return (entityInfo as MlFeature[]).map((feature) => ({
|
||||||
algorithm: feature.featureAlgorithm,
|
algorithm: feature.featureAlgorithm,
|
||||||
name: feature.name || '--',
|
name: feature.name || '--',
|
||||||
title: <Text className="entity-title">{feature.name}</Text>,
|
title: <Text className="entity-title">{getTitleName(feature)}</Text>,
|
||||||
type: feature.dataType,
|
type: feature.dataType,
|
||||||
tags: feature.tags,
|
tags: feature.tags,
|
||||||
description: feature.description,
|
description: feature.description,
|
||||||
@ -100,7 +109,7 @@ export const getFormattedEntityData = (
|
|||||||
case SummaryEntityType.SCHEMAFIELD: {
|
case SummaryEntityType.SCHEMAFIELD: {
|
||||||
return (entityInfo as Field[]).map((field) => ({
|
return (entityInfo as Field[]).map((field) => ({
|
||||||
name: field.name,
|
name: field.name,
|
||||||
title: <Text className="entity-title">{field.name}</Text>,
|
title: <Text className="entity-title"> {getTitleName(field)}</Text>,
|
||||||
type: field.dataType,
|
type: field.dataType,
|
||||||
description: field.description,
|
description: field.description,
|
||||||
tags: field.tags,
|
tags: field.tags,
|
||||||
|
@ -21,6 +21,7 @@ import { EntityUnion } from 'components/Explore/explore.interface';
|
|||||||
import { ResourceEntity } from 'components/PermissionProvider/PermissionProvider.interface';
|
import { ResourceEntity } from 'components/PermissionProvider/PermissionProvider.interface';
|
||||||
import { ExplorePageTabs } from 'enums/Explore.enum';
|
import { ExplorePageTabs } from 'enums/Explore.enum';
|
||||||
import { Container } from 'generated/entity/data/container';
|
import { Container } from 'generated/entity/data/container';
|
||||||
|
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
||||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
import { get, isEmpty, isNil, isUndefined, lowerCase, startCase } from 'lodash';
|
import { get, isEmpty, isNil, isUndefined, lowerCase, startCase } from 'lodash';
|
||||||
@ -98,7 +99,8 @@ export const getEntityTags = (
|
|||||||
case EntityType.PIPELINE:
|
case EntityType.PIPELINE:
|
||||||
case EntityType.DASHBOARD:
|
case EntityType.DASHBOARD:
|
||||||
case EntityType.TOPIC:
|
case EntityType.TOPIC:
|
||||||
case EntityType.MLMODEL: {
|
case EntityType.MLMODEL:
|
||||||
|
case EntityType.DASHBOARD_DATA_MODEL: {
|
||||||
return entityDetail.tags || [];
|
return entityDetail.tags || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -463,6 +465,67 @@ export const getEntityOverview = (
|
|||||||
return overview;
|
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:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -330,7 +330,6 @@ export const getEntityIcon = (indexType: string) => {
|
|||||||
case EntityType.CONTAINER:
|
case EntityType.CONTAINER:
|
||||||
return <ContainerIcon />;
|
return <ContainerIcon />;
|
||||||
|
|
||||||
case SearchIndex.DASHBOARD_DATA_MODEL:
|
|
||||||
case EntityType.DASHBOARD_DATA_MODEL:
|
case EntityType.DASHBOARD_DATA_MODEL:
|
||||||
return <IconDataModel />;
|
return <IconDataModel />;
|
||||||
|
|
||||||
|
@ -268,6 +268,9 @@ export const getBreadCrumbList = (
|
|||||||
activeEntity,
|
activeEntity,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
case EntityType.DASHBOARD_DATA_MODEL: {
|
||||||
|
return [service(ServiceCategory.DASHBOARD_SERVICES), activeEntity];
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
|
@ -235,7 +235,7 @@ export const mockInvalidDataResponse = [
|
|||||||
description: undefined,
|
description: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
tags: undefined,
|
tags: undefined,
|
||||||
title: <Text className="entity-title" />,
|
title: <Text className="entity-title">---</Text>,
|
||||||
type: undefined,
|
type: undefined,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user