Feat 10854 : Added Version Page in DataModel entity (#11006)

* Added Version Page in DataModel entity

* minor changes
This commit is contained in:
Ashish Gupta 2023-04-11 22:02:29 +05:30 committed by GitHub
parent 9d11029ec8
commit d17db66312
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 593 additions and 33 deletions

View File

@ -0,0 +1,318 @@
/*
* Copyright 2022 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 { Table, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import classNames from 'classnames';
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
import PageContainer from 'components/containers/PageContainer';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import {
ChangeDescription,
Column,
DashboardDataModel,
} from 'generated/entity/data/dashboardDataModel';
import { isUndefined } from 'lodash';
import { ExtraInfo } from 'Models';
import React, { FC, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { getEntityName } from 'utils/EntityUtils';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { EntityField } from '../../constants/Feeds.constants';
import { OwnerType } from '../../enums/user.enum';
import { TagLabel } from '../../generated/type/tagLabel';
import {
getDescriptionDiff,
getDiffByFieldName,
getDiffValue,
getTagsDiff,
} from '../../utils/EntityVersionUtils';
import { TagLabelWithStatus } from '../../utils/EntityVersionUtils.interface';
import Description from '../common/description/Description';
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
import TabsPane from '../common/TabsPane/TabsPane';
import EntityVersionTimeLine from '../EntityVersionTimeLine/EntityVersionTimeLine';
import Loader from '../Loader/Loader';
import { DataModelVersionProp } from './DataModelVersion.interface';
const DataModelVersion: FC<DataModelVersionProp> = ({
version,
currentVersionData,
isVersionLoading,
owner,
tier,
slashedDataModelName,
versionList,
deleted = false,
backHandler,
versionHandler,
}: DataModelVersionProp) => {
const { t } = useTranslation();
const [changeDescription, setChangeDescription] = useState<ChangeDescription>(
currentVersionData.changeDescription as ChangeDescription
);
const tabs = [
{
name: t('label.detail-plural'),
isProtected: false,
position: 1,
},
];
const getDashboardDescription = () => {
const descriptionDiff = getDiffByFieldName(
EntityField.DESCRIPTION,
changeDescription
);
const oldDescription =
descriptionDiff?.added?.oldValue ??
descriptionDiff?.deleted?.oldValue ??
descriptionDiff?.updated?.oldValue;
const newDescription =
descriptionDiff?.added?.newValue ??
descriptionDiff?.deleted?.newValue ??
descriptionDiff?.updated?.newValue;
return getDescriptionDiff(
oldDescription,
newDescription,
currentVersionData.description
);
};
const getExtraInfo = () => {
const ownerDiff = getDiffByFieldName('owner', changeDescription);
const oldOwner = JSON.parse(
ownerDiff?.added?.oldValue ??
ownerDiff?.deleted?.oldValue ??
ownerDiff?.updated?.oldValue ??
'{}'
);
const newOwner = JSON.parse(
ownerDiff?.added?.newValue ??
ownerDiff?.deleted?.newValue ??
ownerDiff?.updated?.newValue ??
'{}'
);
const ownerPlaceHolder = owner?.name ?? owner?.displayName ?? '';
const tagsDiff = getDiffByFieldName('tags', changeDescription, true);
const newTier = [
...JSON.parse(
tagsDiff?.added?.newValue ??
tagsDiff?.deleted?.newValue ??
tagsDiff?.updated?.newValue ??
'[]'
),
].find((t) => (t?.tagFQN as string).startsWith('Tier'));
const oldTier = [
...JSON.parse(
tagsDiff?.added?.oldValue ??
tagsDiff?.deleted?.oldValue ??
tagsDiff?.updated?.oldValue ??
'[]'
),
].find((t) => (t?.tagFQN as string).startsWith('Tier'));
const extraInfo: Array<ExtraInfo> = [
{
key: 'Owner',
value:
!isUndefined(ownerDiff?.added) ||
!isUndefined(ownerDiff?.deleted) ||
!isUndefined(ownerDiff?.updated)
? getDiffValue(
oldOwner?.displayName || oldOwner?.name || '',
newOwner?.displayName || newOwner?.name || ''
)
: ownerPlaceHolder
? getDiffValue(ownerPlaceHolder, ownerPlaceHolder)
: '',
profileName:
newOwner?.type === OwnerType.USER ? newOwner?.name : undefined,
},
{
key: 'Tier',
value:
!isUndefined(newTier) || !isUndefined(oldTier)
? getDiffValue(
oldTier?.tagFQN?.split(FQN_SEPARATOR_CHAR)[1] || '',
newTier?.tagFQN?.split(FQN_SEPARATOR_CHAR)[1] || ''
)
: tier?.tagFQN
? tier?.tagFQN.split(FQN_SEPARATOR_CHAR)[1]
: '',
},
];
return extraInfo;
};
const getTags = () => {
const tagsDiff = getDiffByFieldName('tags', changeDescription, true);
const oldTags: Array<TagLabel> = JSON.parse(
tagsDiff?.added?.oldValue ??
tagsDiff?.deleted?.oldValue ??
tagsDiff?.updated?.oldValue ??
'[]'
);
const newTags: Array<TagLabel> = JSON.parse(
tagsDiff?.added?.newValue ??
tagsDiff?.deleted?.newValue ??
tagsDiff?.updated?.newValue ??
'[]'
);
const flag: { [x: string]: boolean } = {};
const uniqueTags: Array<TagLabelWithStatus> = [];
[
...(getTagsDiff(oldTags, newTags) ?? []),
...(currentVersionData.tags ?? []),
].forEach((elem) => {
if (!flag[elem.tagFQN as string]) {
flag[elem.tagFQN as string] = true;
uniqueTags.push(elem as TagLabelWithStatus);
}
});
return [
...uniqueTags.map((t) =>
t.tagFQN.startsWith('Tier')
? { ...t, tagFQN: t.tagFQN.split(FQN_SEPARATOR_CHAR)[1] }
: t
),
];
};
useEffect(() => {
setChangeDescription(
currentVersionData.changeDescription as ChangeDescription
);
}, [currentVersionData]);
const tableColumn: ColumnsType<Column> = useMemo(
() => [
{
title: t('label.name'),
dataIndex: 'name',
key: 'name',
width: 250,
render: (_, record) => (
<Typography.Text>{getEntityName(record)}</Typography.Text>
),
},
{
title: t('label.type'),
dataIndex: 'dataTypeDisplay',
key: 'dataTypeDisplay',
width: 100,
},
{
title: t('label.description'),
dataIndex: 'description',
key: 'description',
accessor: 'description',
render: (text) =>
text ? (
<RichTextEditorPreviewer markdown={text} />
) : (
<span className="tw-no-description">
{t('label.no-description')}
</span>
),
},
{
title: t('label.tag-plural'),
dataIndex: 'tags',
key: 'tags',
accessor: 'tags',
width: 350,
render: (tags: Column['tags']) => (
<TagsViewer sizeCap={-1} tags={tags || []} />
),
},
],
[]
);
return (
<PageContainer>
<div
className={classNames(
'tw-px-6 tw-w-full tw-h-full tw-flex tw-flex-col tw-relative'
)}
data-testid="data-model-version-container">
{isVersionLoading ? (
<Loader />
) : (
<div
className={classNames('version-data')}
data-testid="version-data">
<EntityPageInfo
isVersionSelected
deleted={deleted}
entityName={
currentVersionData.displayName ?? currentVersionData.name ?? ''
}
extraInfo={getExtraInfo()}
followersList={[]}
tags={getTags()}
tier={{} as TagLabel}
titleLinks={slashedDataModelName}
version={Number(version)}
versionHandler={backHandler}
/>
<div className="tw-mt-1 tw-flex tw-flex-col tw-flex-grow ">
<TabsPane activeTab={1} className="tw-flex-initial" tabs={tabs} />
<div className="tw-bg-white tw-flex-grow tw--mx-6 tw-px-7 tw-py-4">
<div className="tw-grid tw-grid-cols-4 tw-gap-4 tw-w-full">
<div className="tw-col-span-full">
<Description
isReadOnly
description={getDashboardDescription()}
/>
</div>
<div className="m-y-md tw-col-span-full">
<Table
bordered
columns={tableColumn}
data-testid="schema-table"
dataSource={
(currentVersionData as DashboardDataModel)?.columns
}
pagination={false}
rowKey="id"
size="small"
/>
</div>
</div>
</div>
</div>
</div>
)}
<EntityVersionTimeLine
show
currentVersion={version}
versionHandler={versionHandler}
versionList={versionList}
onBack={backHandler}
/>
</div>
</PageContainer>
);
};
export default DataModelVersion;

View File

@ -0,0 +1,32 @@
/*
* 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 { VersionData } from 'pages/EntityVersionPage/EntityVersionPage.component';
import { EntityHistory } from '../../generated/type/entityHistory';
import { TagLabel } from '../../generated/type/tagLabel';
import { TitleBreadcrumbProps } from '../common/title-breadcrumb/title-breadcrumb.interface';
export interface DataModelVersionProp {
version: string;
currentVersionData: VersionData;
isVersionLoading: boolean;
owner: DashboardDataModel['owner'];
tier: TagLabel;
slashedDataModelName: TitleBreadcrumbProps['titleLinks'];
topicFQN: string;
versionList: EntityHistory;
deleted?: boolean;
backHandler: () => void;
versionHandler: (v: string) => void;
}

View File

@ -21,7 +21,7 @@ import EntityLineageComponent from 'components/EntityLineage/EntityLineage.compo
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 { getServiceDetailsPath, getVersionPath } from 'constants/constants';
import { EntityField } from 'constants/Feeds.constants';
import { observerOptions } from 'constants/Mydata.constants';
import { CSMode } from 'enums/codemirror.enum';
@ -30,10 +30,12 @@ import { ServiceCategory } from 'enums/service.enum';
import { OwnerType } from 'enums/user.enum';
import { Paging } from 'generated/type/paging';
import { useInfiniteScroll } from 'hooks/useInfiniteScroll';
import { toString } from 'lodash';
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 { useHistory } from 'react-router-dom';
import {
getCountBadge,
getCurrentUserId,
@ -74,6 +76,7 @@ const DataModelDetails = ({
handleFeedFilterChange,
}: DataModelDetailsProps) => {
const { t } = useTranslation();
const history = useHistory();
const [elementRef, isInView] = useInfiniteScroll(observerOptions);
const [isEditDescription, setIsEditDescription] = useState<boolean>(false);
const [threadLink, setThreadLink] = useState<string>('');
@ -181,6 +184,16 @@ const DataModelDetails = ({
},
];
const versionHandler = () => {
history.push(
getVersionPath(
EntityType.DASHBOARD_DATA_MODEL,
dashboardDataModelFQN,
toString(version)
)
);
};
const onThreadLinkSelect = (link: string) => {
setThreadLink(link);
};
@ -241,6 +254,7 @@ const DataModelDetails = ({
updateOwner={hasEditOwnerPermission ? handleUpdateOwner : undefined}
updateTier={hasEditTierPermission ? handleUpdateTier : undefined}
version={version}
versionHandler={versionHandler}
onThreadLinkSelect={onThreadLinkSelect}
/>
<Tabs
@ -302,22 +316,20 @@ const DataModelDetails = ({
)}
</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>
<Card className="h-min-full">
<Row>
<Col data-testid="activityfeed" offset={3} span={18}>
<ActivityFeedList
isEntityFeed
withSidePanel
deletePostHandler={deletePostHandler}
entityName={entityName}
feedList={entityThread}
isFeedLoading={isEntityThreadLoading}
postFeedHandler={postFeedHandler}
updateThreadHandler={updateThreadHandler}
onFeedFiltersUpdate={handleFeedFilterChange}
/>
</Col>
</Row>
{loader}

View File

@ -61,7 +61,7 @@ const ModelTab = ({
}, [fetchTagsAndGlossaryTerms]);
const handleFieldTagsChange = useCallback(
async (selectedTags: EntityTags[] = [], selectedColumn: Column) => {
async (selectedTags: EntityTags[] = [], columnName: string) => {
const newSelectedTags: TagOption[] = selectedTags.map((tag) => ({
fqn: tag.tagFQN,
source: tag.source,
@ -69,11 +69,7 @@ const ModelTab = ({
const dataModelData = cloneDeep(data);
updateDataModelColumnTags(
dataModelData,
editContainerColumnTags?.name ?? selectedColumn.name,
newSelectedTags
);
updateDataModelColumnTags(dataModelData, columnName, newSelectedTags);
await onUpdate(dataModelData);
@ -172,7 +168,7 @@ const ModelTab = ({
type="label"
onCancel={() => setEditContainerColumnTags(undefined)}
onSelectionChange={(tags) =>
handleFieldTagsChange(tags, record)
handleFieldTagsChange(tags, record.name)
}
/>
</Space>
@ -238,7 +234,7 @@ const ModelTab = ({
bordered
className="p-t-xs"
columns={tableColumn}
data-testid="charts-table"
data-testid="data-model-column-table"
dataSource={data}
pagination={false}
rowKey="name"

View File

@ -21,6 +21,7 @@ import {
Position,
useUpdateNodeInternals,
} from 'reactflow';
import { getEntityName } from 'utils/EntityUtils';
import { EntityLineageNodeType } from '../../enums/entity.enum';
import { getNodeRemoveButton } from '../../utils/EntityLineageUtils';
import { getConstraintIcon } from '../../utils/TableUtils';
@ -175,7 +176,7 @@ const CustomNode = (props: NodeProps) => {
column.fullyQualifiedName
)}
{getConstraintIcon(column.constraint, 'tw-')}
<p className="m-0">{column.name}</p>
<p className="m-0">{getEntityName(column)}</p>
</div>
);
}

View File

@ -56,7 +56,10 @@ import {
getEntityMissingError,
getFeedCounts,
} from 'utils/CommonUtils';
import { getDataModelsDetailPath } from 'utils/DataModelsUtils';
import {
getDataModelsDetailPath,
getSortedDataModelColumnTags,
} from 'utils/DataModelsUtils';
import { getEntityFeedLink } from 'utils/EntityUtils';
import { deletePost, updateThreadData } from 'utils/FeedUtils';
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
@ -412,7 +415,7 @@ const DataModelsPage = () => {
}
};
const handleUpdateDataModel = async (
const handleColumnUpdateDataModel = async (
updatedDataModel: DashboardDataModel['columns']
) => {
try {
@ -423,9 +426,10 @@ const DataModelsPage = () => {
setDataModelData((prev) => ({
...(prev as DashboardDataModel),
columns: newColumns,
columns: getSortedDataModelColumnTags(newColumns),
version,
}));
getEntityFeedCount();
} catch (error) {
showErrorToast(error as AxiosError);
}
@ -435,7 +439,7 @@ const DataModelsPage = () => {
if (tab === DATA_MODELS_DETAILS_TABS.ACTIVITY) {
getFeedData();
}
}, [tab, dashboardDataModelFQN]);
}, [tab, feedCount, dashboardDataModelFQN]);
useEffect(() => {
if (hasViewPermission) {
@ -482,7 +486,7 @@ const DataModelsPage = () => {
handleFollowDataModel={handleFollowDataModel}
handleRemoveTier={handleRemoveTier}
handleTabChange={handleTabChange}
handleUpdateDataModel={handleUpdateDataModel}
handleUpdateDataModel={handleColumnUpdateDataModel}
handleUpdateDescription={handleUpdateDescription}
handleUpdateOwner={handleUpdateOwner}
handleUpdateTags={handleUpdateTags}

View File

@ -15,12 +15,14 @@ import { AxiosError } from 'axios';
import { TitleBreadcrumbProps } from 'components/common/title-breadcrumb/title-breadcrumb.interface';
import ContainerVersion from 'components/ContainerVersion/ContainerVersion.component';
import DashboardVersion from 'components/DashboardVersion/DashboardVersion.component';
import DataModelVersion from 'components/DataModelVersion/DataModelVersion.component';
import DatasetVersion from 'components/DatasetVersion/DatasetVersion.component';
import Loader from 'components/Loader/Loader';
import MlModelVersion from 'components/MlModelVersion/MlModelVersion.component';
import PipelineVersion from 'components/PipelineVersion/PipelineVersion.component';
import TopicVersion from 'components/TopicVersion/TopicVersion.component';
import { Container } from 'generated/entity/data/container';
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
import { Mlmodel } from 'generated/entity/data/mlmodel';
import React, { FunctionComponent, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -30,6 +32,11 @@ import {
getDashboardVersion,
getDashboardVersions,
} from 'rest/dashboardAPI';
import {
getDataModelDetailsByFQN,
getDataModelVersion,
getDataModelVersionsList,
} from 'rest/dataModelsAPI';
import {
getMlModelByFQN,
getMlModelVersion,
@ -62,6 +69,7 @@ import {
getDashboardDetailsPath,
getDatabaseDetailsPath,
getDatabaseSchemaDetailsPath,
getDataModelDetailsPath,
getMlModelDetailsPath,
getPipelineDetailsPath,
getServiceDetailsPath,
@ -81,7 +89,9 @@ import {
getPartialNameFromFQN,
getPartialNameFromTableFQN,
} from '../../utils/CommonUtils';
import { defaultFields as DataModelFields } from '../../utils/DataModelsUtils';
import { defaultFields as MlModelFields } from '../../utils/MlModelDetailsUtils';
import { serviceTypeLogo } from '../../utils/ServiceUtils';
import { getTierTags } from '../../utils/TableUtils';
import { showErrorToast } from '../../utils/ToastUtils';
@ -92,7 +102,8 @@ export type VersionData =
| Dashboard
| Pipeline
| Mlmodel
| Container;
| Container
| DashboardDataModel;
const EntityVersionPage: FunctionComponent = () => {
const { t } = useTranslation();
@ -109,6 +120,7 @@ const EntityVersionPage: FunctionComponent = () => {
string,
string
>;
const [isLoading, setIsloading] = useState<boolean>(false);
const [versionList, setVersionList] = useState<EntityHistory>(
{} as EntityHistory
@ -148,6 +160,10 @@ const EntityVersionPage: FunctionComponent = () => {
case EntityType.CONTAINER:
history.push(getContainerDetailPath(entityFQN));
break;
case EntityType.DASHBOARD_DATA_MODEL:
history.push(getDataModelDetailsPath(entityFQN));
break;
default:
@ -531,6 +547,57 @@ const EntityVersionPage: FunctionComponent = () => {
break;
}
case EntityType.DASHBOARD_DATA_MODEL: {
getDataModelDetailsByFQN(entityFQN, DataModelFields)
.then((res) => {
const { id, owner, tags = [], service, serviceType } = res;
const serviceName = service?.name ?? '';
setEntityState(tags, owner, res, [
{
name: serviceName,
url: serviceName
? getServiceDetailsPath(
serviceName,
ServiceCategory.DASHBOARD_SERVICES
)
: '',
imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined,
},
{
name: getEntityName(res),
url: '',
activeTitle: true,
},
]);
getDataModelVersionsList(id ?? '')
.then((vres) => {
setVersionList(vres);
setIsloading(false);
})
.catch((err: AxiosError) => {
showErrorToast(
err,
t('server.entity-fetch-version-error', {
entity: entityFQN,
version: '',
})
);
});
})
.catch((err: AxiosError) => {
showErrorToast(
err,
t('server.entity-fetch-version-error', {
entity: entityFQN,
version: '',
})
);
});
break;
}
default:
break;
@ -896,6 +963,58 @@ const EntityVersionPage: FunctionComponent = () => {
break;
}
case EntityType.DASHBOARD_DATA_MODEL: {
getDataModelDetailsByFQN(entityFQN, [])
.then((res) => {
const { id, service, serviceType } = res;
getDataModelVersion(id ?? '', version)
.then((vRes) => {
const { owner, tags = [] } = vRes;
const serviceName = service?.name ?? '';
setEntityState(tags, owner, vRes, [
{
name: serviceName,
url: serviceName
? getServiceDetailsPath(
serviceName,
ServiceCategory.DASHBOARD_SERVICES
)
: '',
imgSrc: serviceType
? serviceTypeLogo(serviceType)
: undefined,
},
{
name: getEntityName(res),
url: '',
activeTitle: true,
},
]);
setIsVersionLoading(false);
})
.catch((err: AxiosError) => {
showErrorToast(
err,
t('server.entity-fetch-version-error', {
entity: entityFQN,
version: version,
})
);
});
})
.catch((err: AxiosError) => {
showErrorToast(
err,
t('server.entity-fetch-version-error', {
entity: entityFQN,
version: version,
})
);
});
break;
}
default:
break;
}
@ -1009,6 +1128,24 @@ const EntityVersionPage: FunctionComponent = () => {
);
}
case EntityType.DASHBOARD_DATA_MODEL: {
return (
<DataModelVersion
backHandler={backHandler}
currentVersionData={currentVersionData}
deleted={currentVersionData.deleted}
isVersionLoading={isVersionLoading}
owner={owner}
slashedDataModelName={slashedEntityName}
tier={tier as TagLabel}
topicFQN={entityFQN}
version={version}
versionHandler={versionHandler}
versionList={versionList}
/>
);
}
default:
return null;
}

View File

@ -46,6 +46,10 @@ jest.mock('components/ContainerVersion/ContainerVersion.component', () => {
return jest.fn().mockReturnValue(<div>ContainerVersion component</div>);
});
jest.mock('components/DataModelVersion/DataModelVersion.component', () => {
return jest.fn().mockReturnValue(<div>DataModelVersion component</div>);
});
jest.mock('rest/dashboardAPI', () => ({
getDashboardByFqn: jest.fn().mockImplementation(() => Promise.resolve()),
getDashboardVersion: jest.fn().mockImplementation(() => Promise.resolve()),
@ -79,6 +83,16 @@ jest.mock('rest/objectStoreAPI', () => ({
getContainerVersions: jest.fn().mockImplementation(() => Promise.resolve()),
}));
jest.mock('rest/dataModelsAPI', () => ({
getDataModelDetailsByFQN: jest
.fn()
.mockImplementation(() => Promise.resolve()),
getDataModelVersion: jest.fn().mockImplementation(() => Promise.resolve()),
getDataModelVersionsList: jest
.fn()
.mockImplementation(() => Promise.resolve()),
}));
describe('Test EntityVersionPage component', () => {
it('Checks if the DatasetVersion component renderst if respective data pass', async () => {
await act(async () => {
@ -197,4 +211,25 @@ describe('Test EntityVersionPage component', () => {
expect(ContainerVersion).toBeInTheDocument();
});
});
it('Should render the DataModel Version Component', async () => {
mockParams = {
entityType: 'dashboardDataModel',
version: '0.1',
entityFQN: 'data_model.sales',
};
await act(async () => {
const { container } = render(<EntityVersionPage />, {
wrapper: MemoryRouter,
});
const ContainerVersion = await findByText(
container,
/DataModelVersion component/i
);
expect(ContainerVersion).toBeInTheDocument();
});
});
});

View File

@ -13,6 +13,7 @@
import { AxiosResponse } from 'axios';
import { Operation } from 'fast-json-patch';
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
import { EntityHistory } from 'generated/type/entityHistory';
import { EntityReference } from 'generated/type/entityReference';
import { getURLWithQueryFields } from 'utils/APIUtils';
import APIClient from './index';
@ -93,3 +94,19 @@ export const removeDataModelFollower = async (id: string, userId: string) => {
return response.data;
};
export const getDataModelVersionsList = async (id: string) => {
const url = `${URL}/${id}/versions`;
const response = await APIClient.get<EntityHistory>(url);
return response.data;
};
export const getDataModelVersion = async (id: string, version: string) => {
const url = `${URL}/${id}/versions/${version}`;
const response = await APIClient.get<DashboardDataModel>(url);
return response.data;
};

View File

@ -279,7 +279,8 @@
width: 100%;
display: flex;
flex-direction: column;
padding: 24px 16px 0px 16px;
padding: 16px;
padding-bottom: 0;
}
.description-text {

View File

@ -20,6 +20,7 @@ import { Column } from 'generated/entity/data/dashboardDataModel';
import { LabelType, State, TagLabel } from 'generated/type/tagLabel';
import { isEmpty } from 'lodash';
import { EntityTags, TagOption } from 'Models';
import { sortTagsCaseInsensitive } from './CommonUtils';
export const getDataModelsDetailPath = (dataModelFQN: string, tab?: string) => {
let path = tab
@ -116,3 +117,9 @@ export const updateDataModelColumnTags = (
export const defaultFields = `${TabSpecificField.TAGS}, ${TabSpecificField.OWNER},
${TabSpecificField.FOLLOWERS}`;
export const getSortedDataModelColumnTags = (column: Column[]): Column[] =>
column.map((item) => ({
...item,
tags: sortTagsCaseInsensitive(item.tags ?? []),
}));