From 0f28114b474e260daabd8ed528933e0f89e96bee Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Mon, 27 Dec 2021 15:47:04 +0530 Subject: [PATCH] UI: Adding versioning for topic entity. (#1884) * UI: Adding versioning for topic entity. * Adding versions button * Adding topicversion component. * Adding diff for topic tags and tier. * Adding license for new file. * Removed Fragment . * Moving state dispatch to method. --- .../resources/ui/src/axiosAPIs/topicsAPI.ts | 16 + .../DatasetVersion.component.tsx | 16 +- .../DatasetVersion.interface.ts | 3 +- .../TopicDetails/TopicDetails.component.tsx | 4 + .../TopicDetails/TopicDetails.interface.ts | 2 + .../TopicVersion/TopicVersion.component.tsx | 318 +++++++++++++ .../TopicVersion/TopicVersion.interface.ts | 31 ++ .../common/entityPageInfo/EntityPageInfo.tsx | 27 +- .../resources/ui/src/constants/constants.ts | 15 +- .../DatasetDetailsPage.component.tsx | 18 +- .../EntityVersionPage.component.tsx | 443 ++++++++++++------ .../TopicDetailsPage.component.tsx | 17 +- .../ui/src/router/AuthenticatedAppRouter.tsx | 7 +- 13 files changed, 748 insertions(+), 169 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/TopicVersion/TopicVersion.component.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/TopicVersion/TopicVersion.interface.ts diff --git a/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/topicsAPI.ts b/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/topicsAPI.ts index 15c9b95474e..19e098d37a2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/topicsAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/topicsAPI.ts @@ -16,6 +16,22 @@ import { Topic } from 'Models'; import { getURLWithQueryFields } from '../utils/APIUtils'; import APIClient from './index'; +export const getTopicVersions: Function = ( + id: string +): Promise => { + const url = `/topics/${id}/versions`; + + return APIClient.get(url); +}; +export const getTopicVersion: Function = ( + id: string, + version: string +): Promise => { + const url = `/topics/${id}/versions/${version}`; + + return APIClient.get(url); +}; + export const getTopics: Function = ( serviceName: string, paging: string, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DatasetVersion/DatasetVersion.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DatasetVersion/DatasetVersion.component.tsx index 64ae14f8c5b..2b3d351fb2e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DatasetVersion/DatasetVersion.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DatasetVersion/DatasetVersion.component.tsx @@ -197,9 +197,9 @@ const DatasetVersion: React.FC = ({ }); }; - formatColumnData(colList); + formatColumnData(colList ?? []); - return colList; + return colList ?? []; } else if ( isEndsWithField( columnsDiff?.added?.name ?? @@ -244,9 +244,9 @@ const DatasetVersion: React.FC = ({ }); }; - formatColumnData(colList); + formatColumnData(colList ?? []); - return colList; + return colList ?? []; } else { const columnsDiff = getDiffByFieldName( 'columns', @@ -279,7 +279,7 @@ const DatasetVersion: React.FC = ({ } }); }; - formatColumnData(colList); + formatColumnData(colList ?? []); }); } if (columnsDiff.deleted) { @@ -302,10 +302,10 @@ const DatasetVersion: React.FC = ({ name: getDescriptionDiff(col.name, undefined, col.name), })); } else { - return colList; + return colList ?? []; } - return [...newColumns, ...colList]; + return [...newColumns, ...(colList ?? [])]; } }; @@ -341,7 +341,7 @@ const DatasetVersion: React.FC = ({
= ({ unfollowTopicHandler, descriptionUpdateHandler, tagUpdateHandler, + version, + versionHandler, }: TopicDetailsProps) => { const { isAuthDisabled } = useAuth(); const [isEdit, setIsEdit] = useState(false); @@ -286,6 +288,8 @@ const TopicDetails: React.FC = ({ tagsHandler={onTagUpdate} tier={tier ?? ''} titleLinks={slashedTopicName} + version={version} + versionHandler={versionHandler} />
; + version?: string; schemaText: string; schemaType: string; partitions: number; @@ -42,4 +43,5 @@ export interface TopicDetailsProps { settingsUpdateHandler: (updatedTopic: Topic) => Promise; descriptionUpdateHandler: (updatedTopic: Topic) => void; tagUpdateHandler: (updatedTopic: Topic) => void; + versionHandler: () => void; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TopicVersion/TopicVersion.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TopicVersion/TopicVersion.component.tsx new file mode 100644 index 00000000000..dc8304dd98a --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/TopicVersion/TopicVersion.component.tsx @@ -0,0 +1,318 @@ +/* + * Copyright 2021 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 classNames from 'classnames'; +import { isUndefined } from 'lodash'; +import { ExtraInfo } from 'Models'; +import React, { FC, useEffect, useState } from 'react'; +import { ChangeDescription } from '../../generated/entity/data/topic'; +import { TagLabel } from '../../generated/type/tagLabel'; +import { + getDescriptionDiff, + getDiffByFieldName, + getDiffValue, + getTagsDiff, +} from '../../utils/EntityVersionUtils'; +import { bytesToSize } from '../../utils/StringsUtils'; +import { getOwnerFromId } from '../../utils/TableUtils'; +import Description from '../common/description/Description'; +import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo'; +import TabsPane from '../common/TabsPane/TabsPane'; +import PageContainer from '../containers/PageContainer'; +import EntityVersionTimeLine from '../EntityVersionTimeLine/EntityVersionTimeLine'; +import Loader from '../Loader/Loader'; +import SchemaEditor from '../schema-editor/SchemaEditor'; +import { TopicVersionProp } from './TopicVersion.interface'; + +const TopicVersion: FC = ({ + version, + currentVersionData, + isVersionLoading, + owner, + tier, + slashedTopicName, + versionList, + backHandler, + versionHandler, +}: TopicVersionProp) => { + const [changeDescription, setChangeDescription] = useState( + currentVersionData.changeDescription as ChangeDescription + ); + const tabs = [ + { + name: 'Schema', + icon: { + alt: 'schema', + name: 'icon-schema', + title: 'Schema', + selectedName: 'icon-schemacolor', + }, + isProtected: false, + position: 1, + }, + ]; + + const getConfigDetails = () => { + return [ + { + key: 'Partitions', + value: `${currentVersionData.partitions ?? '--'} partitions`, + }, + { + key: 'Replication Factor', + value: `${ + currentVersionData.replicationFactor ?? '--' + } replication factor`, + }, + { + key: 'Retention Size', + value: `${bytesToSize( + currentVersionData.retentionSize ?? 0 + )} retention size`, + }, + { + key: 'Clean-up Policies', + value: `${currentVersionData?.cleanupPolicies?.join( + ', ' + )} clean-up policies`, + }, + { + key: 'Max Message Size', + value: `${bytesToSize( + currentVersionData.maximumMessageSize ?? 0 + )} maximum size`, + }, + ]; + }; + + const getTableDescription = () => { + const descriptionDiff = getDiffByFieldName( + '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 = getOwnerFromId( + JSON.parse( + ownerDiff?.added?.oldValue ?? + ownerDiff?.deleted?.oldValue ?? + ownerDiff?.updated?.oldValue ?? + '{}' + )?.id + ); + const newOwner = getOwnerFromId( + JSON.parse( + ownerDiff?.added?.newValue ?? + ownerDiff?.deleted?.newValue ?? + ownerDiff?.updated?.newValue ?? + '{}' + )?.id + ); + 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) + : '', + }, + { + key: 'Tier', + value: + !isUndefined(newTier) || !isUndefined(oldTier) + ? getDiffValue( + oldTier?.tagFQN?.split('.')[1] || '', + newTier?.tagFQN?.split('.')[1] || '' + ) + : tier?.tagFQN + ? tier?.tagFQN.split('.')[1] + : '', + }, + ...getConfigDetails(), + ]; + + 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: TagLabel & { added: boolean; removed: boolean }) => { + if (!flag[elem.tagFQN as string]) { + flag[elem.tagFQN as string] = true; + uniqueTags.push(elem); + } + }); + + return [ + ...uniqueTags.map((t) => + t.tagFQN.startsWith('Tier') + ? { ...t, tagFQN: t.tagFQN.split('.')[1] } + : t + ), + ]; + }; + + const getInfoBadge = (infos: Array>) => { + return ( +
+
+ {infos.map((info, index) => ( +
+ + {info.key} + + + {info.value} + +
+ ))} +
+
+
+ ); + }; + + useEffect(() => { + setChangeDescription( + currentVersionData.changeDescription as ChangeDescription + ); + }, [currentVersionData]); + + return ( + +
+ {isVersionLoading ? ( + + ) : ( +
+ +
+ +
+
+
+ +
+ +
+ {getInfoBadge([ + { + key: 'Schema', + value: currentVersionData.schemaType ?? '', + }, + ])} +
+ +
+
+
+
+
+
+ )} + + +
+
+ ); +}; + +export default TopicVersion; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TopicVersion/TopicVersion.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/TopicVersion/TopicVersion.interface.ts new file mode 100644 index 00000000000..7fc2d465d2d --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/TopicVersion/TopicVersion.interface.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2021 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 { Topic } from '../../generated/entity/data/topic'; +import { EntityHistory } from '../../generated/type/entityHistory'; +import { TagLabel } from '../../generated/type/tagLabel'; +import { VersionData } from '../../pages/EntityVersionPage/EntityVersionPage.component'; +import { TitleBreadcrumbProps } from '../common/title-breadcrumb/title-breadcrumb.interface'; + +export interface TopicVersionProp { + version: string; + currentVersionData: VersionData; + isVersionLoading: boolean; + owner: Topic['owner']; + tier: TagLabel; + slashedTopicName: TitleBreadcrumbProps['titleLinks']; + topicFQN: string; + versionList: EntityHistory; + backHandler: () => void; + versionHandler: (v: string) => void; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.tsx index 9f90e944cd6..646d6a38e01 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.tsx @@ -12,7 +12,7 @@ */ import classNames from 'classnames'; -import { isUndefined } from 'lodash'; +import { isEmpty, isUndefined } from 'lodash'; import { EntityTags, ExtraInfo, TableDetail } from 'Models'; import React, { useEffect, useState } from 'react'; import { FOLLOWERS_VIEW_CAP, LIST_SIZE } from '../../../constants/constants'; @@ -246,7 +246,7 @@ const EntityPageInfo = ({
-
+
{extraInfo.map((info, index) => ( {getInfoElements(info)} @@ -261,7 +261,7 @@ const EntityPageInfo = ({
{(!isEditable || !isTagEditable) && ( <> - {(tags.length > 0 || tier) && ( + {(tags.length > 0 || !isEmpty(tier)) && ( 0 && ( <> {tags.slice(0, LIST_SIZE).map((tag, index) => ( - + ))} {tags.slice(LIST_SIZE).length > 0 && ( @@ -288,7 +297,15 @@ const EntityPageInfo = ({ <> {tags.slice(LIST_SIZE).map((tag, index) => (

- +

))} diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts index ae6ee1f68cc..1ad4e52c0cd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts @@ -44,6 +44,8 @@ const PLACEHOLDER_ROUTE_SEARCHQUERY = ':searchQuery'; const PLACEHOLDER_ROUTE_TAB = ':tab'; const PLACEHOLDER_ROUTE_TEAM = ':team'; const PLAEHOLDER_ROUTE_VERSION = ':version'; +const PLACEHOLDER_ROUTE_ENTITY_TYPE = ':entityType'; +const PLACEHOLDER_ROUTE_ENTITY_FQN = ':entityFQN'; export const pagingObject = { after: '', before: '' }; @@ -135,7 +137,7 @@ export const ROUTES = { SIGNIN: '/signin', DATASET_DETAILS: `/dataset/${PLACEHOLDER_ROUTE_DATASET_FQN}`, DATASET_DETAILS_WITH_TAB: `/dataset/${PLACEHOLDER_ROUTE_DATASET_FQN}/${PLACEHOLDER_ROUTE_TAB}`, - DATASET_VERSION: `/dataset/${PLACEHOLDER_ROUTE_DATASET_FQN}/versions/${PLAEHOLDER_ROUTE_VERSION}`, + ENTITY_VERSION: `/${PLACEHOLDER_ROUTE_ENTITY_TYPE}/${PLACEHOLDER_ROUTE_ENTITY_FQN}/versions/${PLAEHOLDER_ROUTE_VERSION}`, TOPIC_DETAILS: `/topic/${PLACEHOLDER_ROUTE_TOPIC_FQN}`, TOPIC_DETAILS_WITH_TAB: `/topic/${PLACEHOLDER_ROUTE_TOPIC_FQN}/${PLACEHOLDER_ROUTE_TAB}`, DASHBOARD_DETAILS: `/dashboard/${PLACEHOLDER_ROUTE_DASHBOARD_FQN}`, @@ -162,10 +164,15 @@ export const getDatasetDetailsPath = ( return `${path}${columnName ? `.${columnName}` : ''}`; }; -export const getDatasetVersionPath = (datasetFQN: string, version: string) => { - let path = ROUTES.DATASET_VERSION; +export const getVersionPath = ( + entityType: string, + fqn: string, + version: string +) => { + let path = ROUTES.ENTITY_VERSION; path = path - .replace(PLACEHOLDER_ROUTE_DATASET_FQN, datasetFQN) + .replace(PLACEHOLDER_ROUTE_ENTITY_TYPE, entityType) + .replace(PLACEHOLDER_ROUTE_ENTITY_FQN, fqn) .replace(PLAEHOLDER_ROUTE_VERSION, version); return path; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DatasetDetailsPage/DatasetDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DatasetDetailsPage/DatasetDetailsPage.component.tsx index 2fcfb19b53d..0fa2e1cdd82 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DatasetDetailsPage/DatasetDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DatasetDetailsPage/DatasetDetailsPage.component.tsx @@ -31,7 +31,7 @@ import Loader from '../../components/Loader/Loader'; import { getDatabaseDetailsPath, getDatasetTabPath, - getDatasetVersionPath, + getVersionPath, getServiceDetailsPath, } from '../../constants/constants'; import { EntityType } from '../../enums/entity.enum'; @@ -100,7 +100,6 @@ const DatasetDetailsPage: FunctionComponent = () => { {} as TypeUsedToReturnUsageDetailsOfAnEntity ); const [currentVersion, setCurrentVersion] = useState(); - const [, setPreviousVersion] = useState(); const [isNodeLoading, setNodeLoading] = useState({ id: undefined, state: false, @@ -138,9 +137,8 @@ const DatasetDetailsPage: FunctionComponent = () => { const descriptionUpdateHandler = (updatedTable: Table) => { saveUpdatedTableData(updatedTable).then((res: AxiosResponse) => { - const { description, version, changeDescription } = res.data; + const { description, version } = res.data; setCurrentVersion(version); - setPreviousVersion(changeDescription.previousVersion); setTableDetails(res.data); setDescription(description); }); @@ -148,9 +146,8 @@ const DatasetDetailsPage: FunctionComponent = () => { const columnsUpdateHandler = (updatedTable: Table) => { saveUpdatedTableData(updatedTable).then((res: AxiosResponse) => { - const { columns, version, changeDescription } = res.data; + const { columns, version } = res.data; setCurrentVersion(version); - setPreviousVersion(changeDescription.previousVersion); setTableDetails(res.data); setColumns(columns); setTableTags(getTableTags(columns || [])); @@ -161,9 +158,8 @@ const DatasetDetailsPage: FunctionComponent = () => { return new Promise((resolve, reject) => { saveUpdatedTableData(updatedTable) .then((res) => { - const { version, changeDescription, owner, tags } = res.data; + const { version, owner, tags } = res.data; setCurrentVersion(version); - setPreviousVersion(changeDescription.previousVersion); setTableDetails(res.data); setOwner(getOwnerFromId(owner?.id)); setTier(getTierTags(tags)); @@ -191,7 +187,9 @@ const DatasetDetailsPage: FunctionComponent = () => { }; const versionHandler = () => { - history.push(getDatasetVersionPath(tableFQN, currentVersion as string)); + history.push( + getVersionPath(EntityType.TABLE, tableFQN, currentVersion as string) + ); }; const setLeafNode = (val: EntityLineage, pos: LineagePos) => { @@ -242,14 +240,12 @@ const DatasetDetailsPage: FunctionComponent = () => { sampleData, tableProfile, version, - changeDescription, service, serviceType, } = res.data; setTableDetails(res.data); setTableId(id); setCurrentVersion(version); - setPreviousVersion(changeDescription?.previousVersion); setTier(getTierTags(tags)); setOwner(getOwnerFromId(owner?.id)); setFollowers(followers); 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 36246f1347f..2ad67b53f5c 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 @@ -14,30 +14,45 @@ import { AxiosError, AxiosResponse } from 'axios'; import React, { FunctionComponent, useEffect, useState } from 'react'; import { useHistory, useParams } from 'react-router-dom'; -import { getDatabase } from '../../axiosAPIs/databaseAPI'; -import { getServiceById } from '../../axiosAPIs/serviceAPI'; import { getTableDetailsByFQN, getTableVersion, getTableVersions, } from '../../axiosAPIs/tableAPI'; +import { + getTopicByFqn, + getTopicVersion, + getTopicVersions, +} from '../../axiosAPIs/topicsAPI'; import { TitleBreadcrumbProps } from '../../components/common/title-breadcrumb/title-breadcrumb.interface'; import DatasetVersion from '../../components/DatasetVersion/DatasetVersion.component'; import Loader from '../../components/Loader/Loader'; +import TopicVersion from '../../components/TopicVersion/TopicVersion.component'; import { getDatabaseDetailsPath, getDatasetDetailsPath, - getDatasetVersionPath, getServiceDetailsPath, + getTopicDetailsPath, + getVersionPath, } from '../../constants/constants'; +import { EntityType } from '../../enums/entity.enum'; import { ServiceCategory } from '../../enums/service.enum'; +import { Dashboard } from '../../generated/entity/data/dashboard'; +import { Pipeline } from '../../generated/entity/data/pipeline'; import { Table } from '../../generated/entity/data/table'; +import { Topic } from '../../generated/entity/data/topic'; import { EntityHistory } from '../../generated/type/entityHistory'; import { TagLabel } from '../../generated/type/tagLabel'; import useToastContext from '../../hooks/useToastContext'; import { getPartialNameFromFQN } from '../../utils/CommonUtils'; import { serviceTypeLogo } from '../../utils/ServiceUtils'; import { getOwnerFromId, getTierTags } from '../../utils/TableUtils'; + +export type VersionData = Partial & + Partial & + Partial & + Partial; + const EntityVersionPage: FunctionComponent = () => { const history = useHistory(); const showToast = useToastContext(); @@ -45,183 +60,343 @@ const EntityVersionPage: FunctionComponent = () => { const [owner, setOwner] = useState< Table['owner'] & { displayName?: string } >(); - const [currentVersionData, setCurrentVersionData] = useState
( - {} as Table + const [currentVersionData, setCurrentVersionData] = useState( + {} as VersionData ); - const { version, datasetFQN } = useParams() as Record; + const { entityType, version, entityFQN } = useParams() as Record< + string, + string + >; const [isLoading, setIsloading] = useState(false); const [versionList, setVersionList] = useState( {} as EntityHistory ); const [isVersionLoading, setIsVersionLoading] = useState(false); - const [slashedTableName, setSlashedTableName] = useState< + const [slashedEntityName, setSlashedEntityName] = useState< TitleBreadcrumbProps['titleLinks'] >([]); const backHandler = () => { - history.push(getDatasetDetailsPath(datasetFQN)); + switch (entityType) { + case EntityType.TABLE: + history.push(getDatasetDetailsPath(entityFQN)); + + break; + + case EntityType.TOPIC: + history.push(getTopicDetailsPath(entityFQN)); + + break; + + default: + break; + } }; const versionHandler = (v = version) => { - history.push(getDatasetVersionPath(datasetFQN, v as string)); + history.push(getVersionPath(entityType, entityFQN, v as string)); + }; + + const setEntityState = ( + tags: TagLabel[], + owner: Table['owner'], + data: VersionData, + titleBreadCrumb: TitleBreadcrumbProps['titleLinks'] + ) => { + setTier(getTierTags(tags)); + setOwner(getOwnerFromId(owner?.id)); + setCurrentVersionData(data); + setSlashedEntityName(titleBreadCrumb); }; const fetchEntityVersions = () => { setIsloading(true); - getTableDetailsByFQN( - getPartialNameFromFQN(datasetFQN, ['service', 'database', 'table'], '.'), - ['owner', 'tags'] - ) - .then((res: AxiosResponse) => { - const { id, owner, tags, name, database } = res.data; - setTier(getTierTags(tags)); - setOwner(getOwnerFromId(owner?.id)); - setCurrentVersionData(res.data); - getDatabase(database.id).then((resDB: AxiosResponse) => { - getServiceById('databaseServices', resDB.data.service?.id).then( - (resService: AxiosResponse) => { - setSlashedTableName([ - { - name: resService.data.name, - url: resService.data.name - ? getServiceDetailsPath( - resService.data.name, - resService.data.serviceType, - ServiceCategory.DATABASE_SERVICES - ) - : '', - imgSrc: resService.data.serviceType - ? serviceTypeLogo(resService.data.serviceType) - : undefined, - }, - { - name: resDB.data.name, - url: getDatabaseDetailsPath(resDB.data.fullyQualifiedName), - }, - { - name: name, - url: '', - activeTitle: true, - }, - ]); - } - ); - }); - getTableVersions(id) - .then((vres: AxiosResponse) => { - setVersionList(vres.data); - setIsloading(false); + switch (entityType) { + case EntityType.TABLE: { + getTableDetailsByFQN( + getPartialNameFromFQN( + entityFQN, + ['service', 'database', 'table'], + '.' + ), + ['owner', 'tags'] + ) + .then((res: AxiosResponse) => { + const { id, owner, tags, name, database, service, serviceType } = + res.data; + setEntityState(tags, owner, res.data, [ + { + name: service.name, + url: service.name + ? getServiceDetailsPath( + service.name, + serviceType, + ServiceCategory.DATABASE_SERVICES + ) + : '', + imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined, + }, + { + name: database.name, + url: getDatabaseDetailsPath(database.fullyQualifiedName), + }, + { + name: name, + url: '', + activeTitle: true, + }, + ]); + + getTableVersions(id) + .then((vres: AxiosResponse) => { + setVersionList(vres.data); + setIsloading(false); + }) + .catch((err: AxiosError) => { + const msg = err.message; + showToast({ + variant: 'error', + body: msg ?? `Error while fetching ${entityFQN} versions`, + }); + }); }) .catch((err: AxiosError) => { const msg = err.message; showToast({ variant: 'error', - body: msg ?? `Error while fetching ${datasetFQN} versions`, + body: msg ?? `Error while fetching ${entityFQN} versions`, }); }); - }) - .catch((err: AxiosError) => { - const msg = err.message; - showToast({ - variant: 'error', - body: msg ?? `Error while fetching ${datasetFQN} versions`, - }); - }); + + break; + } + case EntityType.TOPIC: { + getTopicByFqn( + getPartialNameFromFQN(entityFQN, ['service', 'database'], '.'), + ['owner', 'tags'] + ) + .then((res: AxiosResponse) => { + const { id, owner, tags, name, service, serviceType } = res.data; + setEntityState(tags, owner, res.data, [ + { + name: service.name, + url: service.name + ? getServiceDetailsPath( + service.name, + serviceType, + ServiceCategory.MESSAGING_SERVICES + ) + : '', + imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined, + }, + { + name: name, + url: '', + activeTitle: true, + }, + ]); + + getTopicVersions(id) + .then((vres: AxiosResponse) => { + setVersionList(vres.data); + setIsloading(false); + }) + .catch((err: AxiosError) => { + const msg = err.message; + showToast({ + variant: 'error', + body: msg ?? `Error while fetching ${entityFQN} versions`, + }); + }); + }) + .catch((err: AxiosError) => { + const msg = err.message; + showToast({ + variant: 'error', + body: msg ?? `Error while fetching ${entityFQN} versions`, + }); + }); + + break; + } + + default: + break; + } }; const fetchCurrentVersion = () => { setIsVersionLoading(true); - getTableDetailsByFQN( - getPartialNameFromFQN(datasetFQN, ['service', 'database', 'table'], '.') - ) - .then((res: AxiosResponse) => { - const { id, database, name } = res.data; - getDatabase(database.id).then((resDB: AxiosResponse) => { - getServiceById('databaseServices', resDB.data.service?.id).then( - (resService: AxiosResponse) => { - setSlashedTableName([ - { - name: resService.data.name, - url: resService.data.name - ? getServiceDetailsPath( - resService.data.name, - resService.data.serviceType, - ServiceCategory.DATABASE_SERVICES - ) - : '', - imgSrc: resService.data.serviceType - ? serviceTypeLogo(resService.data.serviceType) - : undefined, - }, - { - name: resDB.data.name, - url: getDatabaseDetailsPath(resDB.data.fullyQualifiedName), - }, - { - name: name, - url: '', - activeTitle: true, - }, - ]); - } - ); - }); - - getTableVersion(id, version) - .then((vRes: AxiosResponse) => { - const { owner, tags } = vRes.data; - setTier(getTierTags(tags)); - setOwner(getOwnerFromId(owner?.id)); - setCurrentVersionData(vRes.data); - setIsVersionLoading(false); + switch (entityType) { + case EntityType.TABLE: { + getTableDetailsByFQN( + getPartialNameFromFQN( + entityFQN, + ['service', 'database', 'table'], + '.' + ) + ) + .then((res: AxiosResponse) => { + const { id, database, name, service, serviceType } = res.data; + getTableVersion(id, version) + .then((vRes: AxiosResponse) => { + const { owner, tags } = vRes.data; + setEntityState(tags, owner, vRes.data, [ + { + name: service.name, + url: service.name + ? getServiceDetailsPath( + service.name, + serviceType, + ServiceCategory.DATABASE_SERVICES + ) + : '', + imgSrc: serviceType + ? serviceTypeLogo(serviceType) + : undefined, + }, + { + name: database.name, + url: getDatabaseDetailsPath(database.fullyQualifiedName), + }, + { + name: name, + url: '', + activeTitle: true, + }, + ]); + setIsVersionLoading(false); + }) + .catch((err: AxiosError) => { + const msg = err.message; + showToast({ + variant: 'error', + body: + msg ?? + `Error while fetching ${entityFQN} version ${version}`, + }); + }); }) .catch((err: AxiosError) => { const msg = err.message; showToast({ variant: 'error', body: - msg ?? `Error while fetching ${datasetFQN} version ${version}`, + msg ?? `Error while fetching ${entityFQN} version ${version}`, }); }); - }) - .catch((err: AxiosError) => { - const msg = err.message; - showToast({ - variant: 'error', - body: msg ?? `Error while fetching ${datasetFQN} version ${version}`, - }); - }); + + break; + } + + case EntityType.TOPIC: { + getTopicByFqn( + getPartialNameFromFQN(entityFQN, ['service', 'database'], '.') + ) + .then((res: AxiosResponse) => { + const { id, name, service, serviceType } = res.data; + getTopicVersion(id, version) + .then((vRes: AxiosResponse) => { + const { owner, tags } = vRes.data; + setEntityState(tags, owner, vRes.data, [ + { + name: service.name, + url: service.name + ? getServiceDetailsPath( + service.name, + serviceType, + ServiceCategory.MESSAGING_SERVICES + ) + : '', + imgSrc: serviceType + ? serviceTypeLogo(serviceType) + : undefined, + }, + { + name: name, + url: '', + activeTitle: true, + }, + ]); + setIsVersionLoading(false); + }) + .catch((err: AxiosError) => { + const msg = err.message; + showToast({ + variant: 'error', + body: + msg ?? + `Error while fetching ${entityFQN} version ${version}`, + }); + }); + }) + .catch((err: AxiosError) => { + const msg = err.message; + showToast({ + variant: 'error', + body: + msg ?? `Error while fetching ${entityFQN} version ${version}`, + }); + }); + + break; + } + + default: + break; + } + }; + + const versionComponent = () => { + switch (entityType) { + case EntityType.TABLE: { + return ( + + ); + } + case EntityType.TOPIC: { + return ( + + ); + } + + default: + return null; + } }; useEffect(() => { fetchEntityVersions(); - }, [datasetFQN]); + }, [entityFQN]); useEffect(() => { fetchCurrentVersion(); }, [version]); - return ( - <> - {isLoading ? ( - - ) : ( - - )} - - ); + return <>{isLoading ? : versionComponent()}; }; export default EntityVersionPage; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TopicDetails/TopicDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TopicDetails/TopicDetailsPage.component.tsx index c926e86d8f7..cc21fd2cab7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TopicDetails/TopicDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TopicDetails/TopicDetailsPage.component.tsx @@ -30,6 +30,7 @@ import TopicDetails from '../../components/TopicDetails/TopicDetails.component'; import { getServiceDetailsPath, getTopicDetailsPath, + getVersionPath, } from '../../constants/constants'; import { EntityType } from '../../enums/entity.enum'; import { ServiceCategory } from '../../enums/service.enum'; @@ -78,6 +79,7 @@ const TopicDetailsPage: FunctionComponent = () => { const [slashedTopicName, setSlashedTopicName] = useState< TitleBreadcrumbProps['titleLinks'] >([]); + const [currentVersion, setCurrentVersion] = useState(); const activeTabHandler = (tabValue: number) => { const currentTabIndex = tabValue - 1; @@ -134,10 +136,12 @@ const TopicDetailsPage: FunctionComponent = () => { replicationFactor, retentionSize, serviceType, + version, } = res.data; setName(name); setTopicDetails(res.data); setTopicId(id); + setCurrentVersion(version); setDescription(description ?? ''); setSchemaType(schemaType); setFollowers(followers); @@ -206,7 +210,8 @@ const TopicDetailsPage: FunctionComponent = () => { const descriptionUpdateHandler = (updatedTopic: Topic) => { saveUpdatedTopicData(updatedTopic).then((res: AxiosResponse) => { - const { description } = res.data; + const { description, version } = res.data; + setCurrentVersion(version); setTopicDetails(res.data); setDescription(description); }); @@ -217,6 +222,7 @@ const TopicDetailsPage: FunctionComponent = () => { saveUpdatedTopicData(updatedTopic) .then((res) => { setTopicDetails(res.data); + setCurrentVersion(res.data.version); setOwner(getOwnerFromId(res.data.owner?.id)); setTier(getTierTags(res.data.tags)); resolve(); @@ -228,10 +234,17 @@ const TopicDetailsPage: FunctionComponent = () => { const onTagUpdate = (updatedTopic: Topic) => { saveUpdatedTopicData(updatedTopic).then((res: AxiosResponse) => { setTier(getTierTags(res.data.tags)); + setCurrentVersion(res.data.version); setTags(getTagsWithoutTier(res.data.tags)); }); }; + const versionHandler = () => { + history.push( + getVersionPath(EntityType.TOPIC, topicFQN, currentVersion as string) + ); + }; + useEffect(() => { fetchTopicDetail(topicFQN); }, [topicFQN]); @@ -270,6 +283,8 @@ const TopicDetailsPage: FunctionComponent = () => { topicTags={tags} unfollowTopicHandler={unfollowTopic} users={AppState.users} + version={currentVersion} + versionHandler={versionHandler} /> )} diff --git a/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx index aa43ec8faac..8cec6e112fe 100644 --- a/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx @@ -67,6 +67,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { /> @@ -91,11 +92,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { path={ROUTES.PIPELINE_DETAILS_WITH_TAB} /> - + {isAuthDisabled || isAdminUser ? (