diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataModelVersion/DataModelVersion.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataModelVersion/DataModelVersion.component.tsx new file mode 100644 index 00000000000..b0a1eb6aaad --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataModelVersion/DataModelVersion.component.tsx @@ -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 = ({ + version, + currentVersionData, + isVersionLoading, + owner, + tier, + slashedDataModelName, + versionList, + deleted = false, + backHandler, + versionHandler, +}: DataModelVersionProp) => { + const { t } = useTranslation(); + const [changeDescription, setChangeDescription] = useState( + 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 = [ + { + 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 = JSON.parse( + tagsDiff?.added?.oldValue ?? + tagsDiff?.deleted?.oldValue ?? + tagsDiff?.updated?.oldValue ?? + '[]' + ); + const newTags: Array = JSON.parse( + tagsDiff?.added?.newValue ?? + tagsDiff?.deleted?.newValue ?? + tagsDiff?.updated?.newValue ?? + '[]' + ); + const flag: { [x: string]: boolean } = {}; + const uniqueTags: Array = []; + + [ + ...(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 = useMemo( + () => [ + { + title: t('label.name'), + dataIndex: 'name', + key: 'name', + width: 250, + render: (_, record) => ( + {getEntityName(record)} + ), + }, + { + title: t('label.type'), + dataIndex: 'dataTypeDisplay', + key: 'dataTypeDisplay', + width: 100, + }, + { + title: t('label.description'), + dataIndex: 'description', + key: 'description', + accessor: 'description', + render: (text) => + text ? ( + + ) : ( + + {t('label.no-description')} + + ), + }, + { + title: t('label.tag-plural'), + dataIndex: 'tags', + key: 'tags', + accessor: 'tags', + width: 350, + render: (tags: Column['tags']) => ( + + ), + }, + ], + [] + ); + + return ( + +
+ {isVersionLoading ? ( + + ) : ( +
+ +
+ +
+
+
+ +
+
+ + + + + + + )} + + + + + ); +}; + +export default DataModelVersion; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataModelVersion/DataModelVersion.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/DataModelVersion/DataModelVersion.interface.ts new file mode 100644 index 00000000000..c5438f28b71 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataModelVersion/DataModelVersion.interface.ts @@ -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; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx index 94b753ed9d1..0b74f68537b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx @@ -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(false); const [threadLink, setThreadLink] = useState(''); @@ -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} /> }> - - - -
- -
+ + + + {loader} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/ModelTab/ModelTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/ModelTab/ModelTab.component.tsx index 32dbf19dc7d..f6bf8ac6280 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/ModelTab/ModelTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/ModelTab/ModelTab.component.tsx @@ -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) } /> @@ -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" diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/CustomNode.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/CustomNode.component.tsx index 3c541f89541..63c105e61d9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/CustomNode.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/CustomNode.component.tsx @@ -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-')} -

{column.name}

+

{getEntityName(column)}

); } diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.component.tsx index 10027896326..0c74abe1721 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.component.tsx @@ -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} diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/EntityVersionPage/EntityVersionPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/EntityVersionPage/EntityVersionPage.component.tsx index ed0be0ba64b..7649a097dec 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/EntityVersionPage/EntityVersionPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/EntityVersionPage/EntityVersionPage.component.tsx @@ -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(false); const [versionList, setVersionList] = useState( {} 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 ( + + ); + } + default: return null; } diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/EntityVersionPage/EntityVersionPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/EntityVersionPage/EntityVersionPage.test.tsx index 4a98416981f..f2a3a89a9c2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/EntityVersionPage/EntityVersionPage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/EntityVersionPage/EntityVersionPage.test.tsx @@ -46,6 +46,10 @@ jest.mock('components/ContainerVersion/ContainerVersion.component', () => { return jest.fn().mockReturnValue(
ContainerVersion component
); }); +jest.mock('components/DataModelVersion/DataModelVersion.component', () => { + return jest.fn().mockReturnValue(
DataModelVersion component
); +}); + 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(, { + wrapper: MemoryRouter, + }); + + const ContainerVersion = await findByText( + container, + /DataModelVersion component/i + ); + + expect(ContainerVersion).toBeInTheDocument(); + }); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/rest/dataModelsAPI.ts b/openmetadata-ui/src/main/resources/ui/src/rest/dataModelsAPI.ts index 36d5d1ed378..4cf5bab3429 100644 --- a/openmetadata-ui/src/main/resources/ui/src/rest/dataModelsAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/rest/dataModelsAPI.ts @@ -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(url); + + return response.data; +}; + +export const getDataModelVersion = async (id: string, version: string) => { + const url = `${URL}/${id}/versions/${version}`; + + const response = await APIClient.get(url); + + return response.data; +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/app.less b/openmetadata-ui/src/main/resources/ui/src/styles/app.less index 34553f28311..81587a16895 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/app.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/app.less @@ -279,7 +279,8 @@ width: 100%; display: flex; flex-direction: column; - padding: 24px 16px 0px 16px; + padding: 16px; + padding-bottom: 0; } .description-text { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataModelsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/DataModelsUtils.ts index e682c6a3014..6b0de5d21bb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DataModelsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataModelsUtils.ts @@ -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 ?? []), + }));