From 32b8bea8c22175c1a198fb045e2e6df26b503701 Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Tue, 31 Aug 2021 11:26:05 +0530 Subject: [PATCH] refactoring mydata details page (#326) * refactoring mydata details page * addressing review comment --- .../components/common/TabsPane/TabsPane.tsx | 64 +++ .../common/description/Description.tsx | 73 ++++ .../common/entityPageInfo/EntityPageInfo.tsx | 130 ++++++ .../common/table-data-card/TableDataCard.tsx | 7 +- .../components/searched-data/SearchedData.tsx | 6 +- .../resources/ui/src/constants/constants.ts | 9 + .../resources/ui/src/enums/search.enum.ts | 5 + .../ui/src/pages/explore/explore.interface.ts | 5 - .../resources/ui/src/pages/explore/index.tsx | 1 + .../ui/src/pages/my-data-details/index.tsx | 397 ++++++------------ .../ui/src/router/AuthenticatedAppRouter.tsx | 1 + .../resources/ui/src/utils/TableUtils.tsx | 19 + 12 files changed, 446 insertions(+), 271 deletions(-) create mode 100644 catalog-rest-service/src/main/resources/ui/src/components/common/TabsPane/TabsPane.tsx create mode 100644 catalog-rest-service/src/main/resources/ui/src/components/common/description/Description.tsx create mode 100644 catalog-rest-service/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.tsx diff --git a/catalog-rest-service/src/main/resources/ui/src/components/common/TabsPane/TabsPane.tsx b/catalog-rest-service/src/main/resources/ui/src/components/common/TabsPane/TabsPane.tsx new file mode 100644 index 00000000000..8b17501ba7d --- /dev/null +++ b/catalog-rest-service/src/main/resources/ui/src/components/common/TabsPane/TabsPane.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import SVGIcons from '../../../utils/SvgUtils'; +import NonAdminAction from '../non-admin-action/NonAdminAction'; +type Tab = { + name: string; + icon: { + alt: string; + name: string; + title: string; + }; + isProtected: boolean; + protectedState?: boolean; + position: number; +}; +type Props = { + activeTab: number; + setActiveTab: (value: number) => void; + tabs: Array; +}; +const TabsPane = ({ activeTab, setActiveTab, tabs }: Props) => { + const getTabClasses = (tab: number, activeTab: number) => { + return 'tw-gh-tabs' + (activeTab === tab ? ' active' : ''); + }; + + return ( +
+ +
+ ); +}; + +export default TabsPane; diff --git a/catalog-rest-service/src/main/resources/ui/src/components/common/description/Description.tsx b/catalog-rest-service/src/main/resources/ui/src/components/common/description/Description.tsx new file mode 100644 index 00000000000..7bbbc164aed --- /dev/null +++ b/catalog-rest-service/src/main/resources/ui/src/components/common/description/Description.tsx @@ -0,0 +1,73 @@ +import { TableDetail } from 'Models'; +import React from 'react'; +import SVGIcons from '../../../utils/SvgUtils'; +import { ModalWithMarkdownEditor } from '../../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor'; +import NonAdminAction from '../non-admin-action/NonAdminAction'; +import RichTextEditorPreviewer from '../rich-text-editor/RichTextEditorPreviewer'; + +type Props = { + owner: TableDetail['owner']; + hasEditAccess: boolean; + onDescriptionEdit: () => void; + description: string; + isEdit: boolean; + onCancel: () => void; + onDescriptionUpdate: (value: string) => void; + onSuggest?: (value: string) => void; +}; + +const Description = ({ + owner, + hasEditAccess, + onDescriptionEdit, + description, + isEdit, + onCancel, + onDescriptionUpdate, +}: Props) => { + return ( +
+
+ + Description + +
+ +

You need to be owner to perform this action

+ {!owner ?

Claim ownership in Manage

: null} + + } + isOwner={hasEditAccess}> + +
+
+
+
+
+ {description.trim() ? ( + + ) : ( + No description added + )} +
+ {isEdit && ( + + )} +
+
+ ); +}; + +export default Description; diff --git a/catalog-rest-service/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.tsx b/catalog-rest-service/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.tsx new file mode 100644 index 00000000000..bae465ce0e0 --- /dev/null +++ b/catalog-rest-service/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.tsx @@ -0,0 +1,130 @@ +import classNames from 'classnames'; +import { ColumnTags } from 'Models'; +import React from 'react'; +import { LIST_SIZE } from '../../../constants/constants'; +import Tags from '../../tags/tags'; +import PopOver from '../popover/PopOver'; +import TitleBreadcrumb from '../title-breadcrumb/title-breadcrumb.component'; +import { TitleBreadcrumbProps } from '../title-breadcrumb/title-breadcrumb.interface'; + +type ExtraInfo = { + key: string; + value: string; +}; + +type Props = { + titleLinks: TitleBreadcrumbProps['titleLinks']; + isFollowing: boolean; + followHandler: () => void; + followers: number; + extraInfo: Array; + tier: string; + tags: Array; +}; + +const EntityPageInfo = ({ + titleLinks, + isFollowing, + followHandler, + followers, + extraInfo, + tier, + tags, +}: Props) => { + return ( +
+
+
+ +
+ + + + {followers} + + +
+
+
+
+ {extraInfo.map((info, index) => ( + + + {info.key} : + {' '} + {info.value || '--'} + {extraInfo.length !== 1 && index < extraInfo.length - 1 ? ( + + • + + ) : null} + + ))} +
+
+ {(tags.length > 0 || tier) && ( + + )} + {tier && ( + + )} + {tags.length > 0 && ( + <> + {tags.slice(0, LIST_SIZE).map((tag, index) => ( + + ))} + + {tags.slice(LIST_SIZE).length > 0 && ( + + {tags.slice(LIST_SIZE).map((tag, index) => ( + + ))} + + } + position="bottom" + theme="light" + trigger="click"> + + View more + + + )} + + )} +
+
+ ); +}; + +export default EntityPageInfo; diff --git a/catalog-rest-service/src/main/resources/ui/src/components/common/table-data-card/TableDataCard.tsx b/catalog-rest-service/src/main/resources/ui/src/components/common/table-data-card/TableDataCard.tsx index 2e4c8a64765..0c50040a897 100644 --- a/catalog-rest-service/src/main/resources/ui/src/components/common/table-data-card/TableDataCard.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/components/common/table-data-card/TableDataCard.tsx @@ -17,9 +17,8 @@ import React, { FunctionComponent } from 'react'; import { Link } from 'react-router-dom'; -import { getDatasetDetailsPath } from '../../../constants/constants'; import { stringToHTML } from '../../../utils/StringsUtils'; -import { getUsagePercentile } from '../../../utils/TableUtils'; +import { getEntityLink, getUsagePercentile } from '../../../utils/TableUtils'; import TableDataCardBody from './TableDataCardBody'; type Props = { @@ -32,6 +31,7 @@ type Props = { serviceType?: string; fullyQualifiedName: string; tags?: string[]; + indexType: string; }; const TableDataCard: FunctionComponent = ({ @@ -43,6 +43,7 @@ const TableDataCard: FunctionComponent = ({ serviceType, fullyQualifiedName, tags, + indexType, }: Props) => { const OtherDetails = [ { key: 'Owner', value: owner }, @@ -58,7 +59,7 @@ const TableDataCard: FunctionComponent = ({
- + diff --git a/catalog-rest-service/src/main/resources/ui/src/components/searched-data/SearchedData.tsx b/catalog-rest-service/src/main/resources/ui/src/components/searched-data/SearchedData.tsx index 51d61f41192..d6387c3b4fa 100644 --- a/catalog-rest-service/src/main/resources/ui/src/components/searched-data/SearchedData.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/components/searched-data/SearchedData.tsx @@ -20,6 +20,7 @@ import { FormatedTableData } from 'Models'; import PropTypes from 'prop-types'; import React, { ReactNode } from 'react'; import { PAGE_SIZE } from '../../constants/constants'; +import { SearchIndex } from '../../pages/explore/explore.interface'; import { pluralize } from '../../utils/CommonUtils'; import { getOwnerFromId, @@ -42,6 +43,7 @@ type SearchedDataProp = { showResultCount?: boolean; searchText?: string; showOnboardingTemplate?: boolean; + indexType?: string; }; const SearchedData: React.FC = ({ children, @@ -54,7 +56,8 @@ const SearchedData: React.FC = ({ searchText, totalValue, fetchLeftPanel, -}) => { + indexType = SearchIndex.TABLE, +}: SearchedDataProp) => { const highlightSearchResult = () => { return data.map((table, index) => { const description = isEmpty(table.highlight?.description) @@ -70,6 +73,7 @@ const SearchedData: React.FC = ({ { return path; }; +export const getTopicDetailsPath = (topicFQN: string) => { + let path = ROUTES.TOPIC_DETAILS; + path = path.replace(PLACEHOLDER_ROUTE_TOPIC_FQN, topicFQN); + + return path; +}; + export const LIST_TYPES = ['numbered-list', 'bulleted-list']; export const TIMEOUT = { diff --git a/catalog-rest-service/src/main/resources/ui/src/enums/search.enum.ts b/catalog-rest-service/src/main/resources/ui/src/enums/search.enum.ts index afcaf651a2d..baf56a83fe1 100644 --- a/catalog-rest-service/src/main/resources/ui/src/enums/search.enum.ts +++ b/catalog-rest-service/src/main/resources/ui/src/enums/search.enum.ts @@ -20,3 +20,8 @@ export enum FilterType { PLATFORM = 'platform', CLUSTER = 'cluster', } + +export enum SearchIndex { + TABLE = 'table_search_index', + TOPIC = 'topic_search_index', +} diff --git a/catalog-rest-service/src/main/resources/ui/src/pages/explore/explore.interface.ts b/catalog-rest-service/src/main/resources/ui/src/pages/explore/explore.interface.ts index 44c562260ea..d510b3a4099 100644 --- a/catalog-rest-service/src/main/resources/ui/src/pages/explore/explore.interface.ts +++ b/catalog-rest-service/src/main/resources/ui/src/pages/explore/explore.interface.ts @@ -33,8 +33,3 @@ export type Team = { description: string; href: string; }; - -export enum SearchIndex { - TABLE = 'table_search_index', - TOPIC = 'topic_search_index', -} diff --git a/catalog-rest-service/src/main/resources/ui/src/pages/explore/index.tsx b/catalog-rest-service/src/main/resources/ui/src/pages/explore/index.tsx index 608f9a0ecd8..947cf494b99 100644 --- a/catalog-rest-service/src/main/resources/ui/src/pages/explore/index.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/pages/explore/index.tsx @@ -425,6 +425,7 @@ const ExplorePage: React.FC = (): React.ReactElement => { currentPage={currentPage} data={data} fetchLeftPanel={fetchLeftPanel} + indexType={searchIndex} isLoading={isLoading} paginate={paginate} searchText={searchText} diff --git a/catalog-rest-service/src/main/resources/ui/src/pages/my-data-details/index.tsx b/catalog-rest-service/src/main/resources/ui/src/pages/my-data-details/index.tsx index 97d54a59c19..03c84bce96c 100644 --- a/catalog-rest-service/src/main/resources/ui/src/pages/my-data-details/index.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/pages/my-data-details/index.tsx @@ -16,7 +16,6 @@ */ import { AxiosResponse } from 'axios'; -import classNames from 'classnames'; import { compare } from 'fast-json-patch'; import { isEqual, isNil } from 'lodash'; import { observer } from 'mobx-react'; @@ -38,23 +37,19 @@ import { patchTableDetails, removeFollower, } from '../../axiosAPIs/tableAPI'; -import NonAdminAction from '../../components/common/non-admin-action/NonAdminAction'; -import PopOver from '../../components/common/popover/PopOver'; -import RichTextEditorPreviewer from '../../components/common/rich-text-editor/RichTextEditorPreviewer'; -import TitleBreadcrumb from '../../components/common/title-breadcrumb/title-breadcrumb.component'; +import Description from '../../components/common/description/Description'; +import EntityPageInfo from '../../components/common/entityPageInfo/EntityPageInfo'; +import TabsPane from '../../components/common/TabsPane/TabsPane'; import { TitleBreadcrumbProps } from '../../components/common/title-breadcrumb/title-breadcrumb.interface'; import PageContainer from '../../components/containers/PageContainer'; -import { ModalWithMarkdownEditor } from '../../components/Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor'; import FrequentlyJoinedTables from '../../components/my-data-details/FrequentlyJoinedTables'; import IssuesTab from '../../components/my-data-details/IssuesTab'; import ManageTab from '../../components/my-data-details/ManageTab'; import QualityTab from '../../components/my-data-details/QualityTab'; import SchemaTab from '../../components/my-data-details/SchemaTab'; -import Tags from '../../components/tags/tags'; import { getDatabaseDetailsPath, getServiceDetailsPath, - LIST_SIZE, } from '../../constants/constants'; import useToastContext from '../../hooks/useToastContext'; import { @@ -64,7 +59,6 @@ import { getUserTeams, } from '../../utils/CommonUtils'; import { serviceTypeLogo } from '../../utils/ServiceUtils'; -import SVGIcons from '../../utils/SvgUtils'; import { getOwnerFromId, getTagsWithoutTier, @@ -74,10 +68,6 @@ import { import { getTableTags } from '../../utils/TagsUtils'; import { issues } from './index.mock'; -const getTabClasses = (tab: number, activeTab: number) => { - return 'tw-gh-tabs' + (activeTab === tab ? ' active' : ''); -}; - const MyDataDetailsPage = () => { // User Id for getting followers @@ -115,83 +105,6 @@ const MyDataDetailsPage = () => { const showToast = useToastContext(); - useEffect(() => { - getTableDetailsByFQN( - tableFQN, - 'columns, database, usageSummary, followers, joins, tags, owner,sampleData' - ).then((res: AxiosResponse) => { - const { - description, - id, - name, - // tier, - columns, - database, - owner, - usageSummary, - followers, - joins, - tags, - sampleData, - } = res.data; - setTableDetails(res.data); - setTableId(id); - setTier(getTierFromTableTags(tags)); - setOwner(getOwnerFromId(owner?.id)); - // need to check if already following or not with logedIn user id - setIsFollowing( - !!followers.filter(({ id }: { id: string }) => id === USERId).length - ); - setFollowers(followers?.length); - getDatabase(database.id, 'service').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) - : '', - imgSrc: resService.data.serviceType - ? serviceTypeLogo(resService.data.serviceType) - : undefined, - }, - { - name: database.name, - url: getDatabaseDetailsPath(resDB.data.fullyQualifiedName), - }, - { - name: name, - url: '', - activeTitle: true, - }, - ]); - } - ); - }); - setName(name); - - setDescription(description); - setColumns(columns || []); - setSampleData(sampleData); - setTableTags(getTableTags(columns || [])); - if (!isNil(usageSummary?.weeklyStats.percentileRank)) { - const percentile = getUsagePercentile( - usageSummary.weeklyStats.percentileRank - ); - setUsage(percentile); - } else { - setUsage('--'); - } - setWeeklyUsageCount( - usageSummary?.weeklyStats.count.toLocaleString() || '--' - ); - if (joins) { - setTableJoinData(joins); - } - }); - }, [tableFQN]); - const hasEditAccess = () => { if (owner?.type === 'user') { return owner.id === getCurrentUserId(); @@ -200,6 +113,36 @@ const MyDataDetailsPage = () => { } }; + const tabs = [ + { + name: 'Schema', + icon: { + alt: 'schema', + name: 'icon-schema', + title: 'Schema', + }, + isProtected: false, + position: 1, + }, + { + name: 'Manage', + icon: { + alt: 'manage', + name: 'icon-manage', + title: 'Manage', + }, + isProtected: true, + protectedState: !owner || hasEditAccess(), + position: 6, + }, + ]; + + const extraInfo = [ + { key: 'Owner', value: owner?.name || '' }, + { key: 'Usage', value: usage }, + { key: 'Queries', value: `${weeklyUsageCount} past week` }, + ]; + const onCancel = () => { setIsEdit(false); }; @@ -349,187 +292,117 @@ const MyDataDetailsPage = () => { return freqJoin; }; + useEffect(() => { + getTableDetailsByFQN( + tableFQN, + 'columns, database, usageSummary, followers, joins, tags, owner,sampleData' + ).then((res: AxiosResponse) => { + const { + description, + id, + name, + // tier, + columns, + database, + owner, + usageSummary, + followers, + joins, + tags, + sampleData, + } = res.data; + setTableDetails(res.data); + setTableId(id); + setTier(getTierFromTableTags(tags)); + setOwner(getOwnerFromId(owner?.id)); + // need to check if already following or not with logedIn user id + setIsFollowing( + !!followers.filter(({ id }: { id: string }) => id === USERId).length + ); + setFollowers(followers?.length); + getDatabase(database.id, 'service').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) + : '', + imgSrc: resService.data.serviceType + ? serviceTypeLogo(resService.data.serviceType) + : undefined, + }, + { + name: database.name, + url: getDatabaseDetailsPath(resDB.data.fullyQualifiedName), + }, + { + name: name, + url: '', + activeTitle: true, + }, + ]); + } + ); + }); + setName(name); + + setDescription(description); + setColumns(columns || []); + setSampleData(sampleData); + setTableTags(getTableTags(columns || [])); + if (!isNil(usageSummary?.weeklyStats.percentileRank)) { + const percentile = getUsagePercentile( + usageSummary.weeklyStats.percentileRank + ); + setUsage(percentile); + } else { + setUsage('--'); + } + setWeeklyUsageCount( + usageSummary?.weeklyStats.count.toLocaleString() || '--' + ); + if (joins) { + setTableJoinData(joins); + } + }); + }, [tableFQN]); + return (
-
-
- -
- - - - {followers} - - -
-
-
-
- - Owner :{' '} - - {owner?.name || '--'} - - - - - Usage :{' '} - {usage} - - - - Queries :{' '} - - {weeklyUsageCount} past week - - -
-
- {(tableTags.length > 0 || tier) && ( - - )} - {tier && ( - - )} - {tableTags.length > 0 && ( - <> - {tableTags.slice(0, LIST_SIZE).map((tag, index) => ( - - ))} - - {tableTags.slice(LIST_SIZE).length > 0 && ( - - {tableTags.slice(LIST_SIZE).map((tag, index) => ( - - ))} - - } - position="bottom" - theme="light" - trigger="click"> - - View more - - - )} - - )} -
+
-
- -
+ +
{activeTab === 1 && (
-
-
- - Description - -
- -

You need to be owner to perform this action

- {!owner ? ( -

Claim ownership in Manage

- ) : null} - - } - isOwner={hasEditAccess()}> - -
-
-
-
-
- {description.trim() ? ( - - ) : ( - - No description added - - )} -
- {isEdit && ( - - )} -
-
+
{ + diff --git a/catalog-rest-service/src/main/resources/ui/src/utils/TableUtils.tsx b/catalog-rest-service/src/main/resources/ui/src/utils/TableUtils.tsx index c2ddd2f646c..1927d003083 100644 --- a/catalog-rest-service/src/main/resources/ui/src/utils/TableUtils.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/utils/TableUtils.tsx @@ -2,6 +2,11 @@ import { ColumnTags, TableDetail } from 'Models'; import React from 'react'; import AppState from '../AppState'; import PopOver from '../components/common/popover/PopOver'; +import { + getDatasetDetailsPath, + getTopicDetailsPath, +} from '../constants/constants'; +import { SearchIndex } from '../enums/search.enum'; import { ConstraintTypes } from '../enums/table.enum'; import { ordinalize } from './StringsUtils'; import SVGIcons from './SvgUtils'; @@ -142,3 +147,17 @@ export const getConstraintIcon = (constraint = '') => { ); }; + +export const getEntityLink = ( + indexType: string, + fullyQualifiedName: string +) => { + switch (indexType) { + case SearchIndex.TOPIC: + return getTopicDetailsPath(fullyQualifiedName); + + case SearchIndex.TABLE: + default: + return getDatasetDetailsPath(fullyQualifiedName); + } +};