refactoring mydata details page (#326)

* refactoring mydata details page

* addressing review comment
This commit is contained in:
Sachin Chaurasiya 2021-08-31 11:26:05 +05:30 committed by GitHub
parent 150f70353c
commit 32b8bea8c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 446 additions and 271 deletions

View File

@ -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<Tab>;
};
const TabsPane = ({ activeTab, setActiveTab, tabs }: Props) => {
const getTabClasses = (tab: number, activeTab: number) => {
return 'tw-gh-tabs' + (activeTab === tab ? ' active' : '');
};
return (
<div className="tw-bg-transparent tw--mx-4">
<nav className="tw-flex tw-flex-row tw-gh-tabs-container tw-px-4">
{tabs.map((tab) =>
tab.isProtected ? (
<NonAdminAction
isOwner={tab.protectedState}
title="You need to be owner to perform this action">
<button
className={getTabClasses(tab.position, activeTab)}
data-testid="tab"
onClick={() => setActiveTab(tab.position)}>
<SVGIcons
alt={tab.icon.alt}
icon={tab.icon.name}
title={tab.icon.title}
/>{' '}
{tab.name}
</button>
</NonAdminAction>
) : (
<button
className={getTabClasses(tab.position, activeTab)}
data-testid="tab"
onClick={() => setActiveTab(tab.position)}>
<SVGIcons
alt={tab.icon.alt}
icon={tab.icon.name}
title={tab.icon.title}
/>{' '}
{tab.name}
</button>
)
)}
</nav>
</div>
);
};
export default TabsPane;

View File

@ -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 (
<div className="schema-description tw-flex tw-flex-col tw-h-full tw-min-h-168 tw-relative tw-border tw-border-main tw-rounded-md">
<div className="tw-flex tw-items-center tw-px-3 tw-py-1 tw-border-b tw-border-main">
<span className="tw-flex-1 tw-leading-8 tw-m-0 tw-text-sm tw-font-normal">
Description
</span>
<div className="tw-flex-initial">
<NonAdminAction
html={
<>
<p>You need to be owner to perform this action</p>
{!owner ? <p>Claim ownership in Manage </p> : null}
</>
}
isOwner={hasEditAccess}>
<button
className="focus:tw-outline-none"
onClick={onDescriptionEdit}>
<SVGIcons alt="edit" icon="icon-edit" title="edit" />
</button>
</NonAdminAction>
</div>
</div>
<div className="tw-px-3 tw-py-2 tw-overflow-y-auto">
<div className="tw-pl-3" data-testid="description" id="description">
{description.trim() ? (
<RichTextEditorPreviewer markdown={description} />
) : (
<span className="tw-no-description">No description added</span>
)}
</div>
{isEdit && (
<ModalWithMarkdownEditor
header={`Edit description for ${name}`}
placeholder="Enter Description"
value={description}
onCancel={onCancel}
onSave={onDescriptionUpdate}
/>
)}
</div>
</div>
);
};
export default Description;

View File

@ -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<ExtraInfo>;
tier: string;
tags: Array<ColumnTags>;
};
const EntityPageInfo = ({
titleLinks,
isFollowing,
followHandler,
followers,
extraInfo,
tier,
tags,
}: Props) => {
return (
<div>
<div className="tw-flex tw-flex-col">
<div className="tw-flex tw-flex-initial tw-justify-between tw-items-center">
<TitleBreadcrumb titleLinks={titleLinks} />
<div className="tw-flex tw-h-6 tw-ml-2 tw-mt-2">
<span
className={classNames(
'tw-flex tw-border tw-border-primary tw-rounded',
isFollowing ? 'tw-bg-primary tw-text-white' : 'tw-text-primary'
)}>
<button
className={classNames(
'tw-text-xs tw-border-r tw-font-normal tw-py-1 tw-px-2 tw-rounded-l focus:tw-outline-none',
isFollowing ? 'tw-border-white' : 'tw-border-primary'
)}
data-testid="follow-button"
onClick={followHandler}>
{isFollowing ? (
<>
<i className="fas fa-star" /> Unfollow
</>
) : (
<>
<i className="far fa-star" /> Follow
</>
)}
</button>
<span className="tw-text-xs tw-border-l-0 tw-font-normal tw-py-1 tw-px-2 tw-rounded-r">
{followers}
</span>
</span>
</div>
</div>
</div>
<div className="tw-flex tw-gap-1 tw-mb-2 tw-mt-1">
{extraInfo.map((info, index) => (
<span key={index}>
<span className="tw-text-grey-muted tw-font-normal">
{info.key} :
</span>{' '}
<span className="tw-pl-1tw-font-normal ">{info.value || '--'}</span>
{extraInfo.length !== 1 && index < extraInfo.length - 1 ? (
<span className="tw-mx-3 tw-inline-block tw-text-gray-400">
</span>
) : null}
</span>
))}
</div>
<div className="tw-flex tw-flex-wrap tw-pt-1">
{(tags.length > 0 || tier) && (
<i className="fas fa-tags tw-px-1 tw-mt-2 tw-text-grey-muted" />
)}
{tier && (
<Tags className="tw-bg-gray-200" tag={`#${tier.split('.')[1]}`} />
)}
{tags.length > 0 && (
<>
{tags.slice(0, LIST_SIZE).map((tag, index) => (
<Tags
className="tw-bg-gray-200"
key={index}
tag={`#${tag.tagFQN}`}
/>
))}
{tags.slice(LIST_SIZE).length > 0 && (
<PopOver
className="tw-py-1"
html={
<>
{tags.slice(LIST_SIZE).map((tag, index) => (
<Tags
className="tw-bg-gray-200 tw-px-2"
key={index}
tag={`#${tag.tagFQN}`}
/>
))}
</>
}
position="bottom"
theme="light"
trigger="click">
<span className="tw-cursor-pointer tw-text-xs link-text">
View more
</span>
</PopOver>
)}
</>
)}
</div>
</div>
);
};
export default EntityPageInfo;

View File

@ -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<Props> = ({
@ -43,6 +43,7 @@ const TableDataCard: FunctionComponent<Props> = ({
serviceType,
fullyQualifiedName,
tags,
indexType,
}: Props) => {
const OtherDetails = [
{ key: 'Owner', value: owner },
@ -58,7 +59,7 @@ const TableDataCard: FunctionComponent<Props> = ({
<div className="tw-bg-white tw-p-3 tw-border tw-border-main tw-rounded-md">
<div>
<h6 className="tw-flex tw-items-center tw-m-0 tw-heading">
<Link to={getDatasetDetailsPath(fullyQualifiedName)}>
<Link to={getEntityLink(indexType, fullyQualifiedName)}>
<button className="tw-text-grey-body tw-font-medium">
{stringToHTML(name)}
</button>

View File

@ -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<SearchedDataProp> = ({
children,
@ -54,7 +56,8 @@ const SearchedData: React.FC<SearchedDataProp> = ({
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<SearchedDataProp> = ({
<TableDataCard
description={description}
fullyQualifiedName={table.fullyQualifiedName}
indexType={indexType}
name={name}
owner={getOwnerFromId(table.owner)?.name}
serviceType={table.serviceType || '--'}

View File

@ -33,6 +33,7 @@ export const imageTypes = {
export const ERROR404 = 'No data found';
export const ERROR500 = 'Something went wrong';
const PLACEHOLDER_ROUTE_DATASET_FQN = ':datasetFQN';
const PLACEHOLDER_ROUTE_TOPIC_FQN = ':topicFQN';
const PLACEHOLDER_ROUTE_DATABASE_FQN = ':databaseFQN';
const PLACEHOLDER_ROUTE_SERVICE_FQN = ':serviceFQN';
const PLACEHOLDER_ROUTE_SEARCHQUERY = ':searchQuery';
@ -109,6 +110,7 @@ export const ROUTES = {
SIGNUP: '/signup',
SIGNIN: '/signin',
DATASET_DETAILS: `/dataset/${PLACEHOLDER_ROUTE_DATASET_FQN}`,
TOPIC_DETAILS: `/topic/${PLACEHOLDER_ROUTE_TOPIC_FQN}`,
DATABASE_DETAILS: `/database/${PLACEHOLDER_ROUTE_DATABASE_FQN}`,
ONBOARDING: '/onboarding',
};
@ -148,6 +150,13 @@ export const getDatabaseDetailsPath = (databaseFQN: string) => {
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 = {

View File

@ -20,3 +20,8 @@ export enum FilterType {
PLATFORM = 'platform',
CLUSTER = 'cluster',
}
export enum SearchIndex {
TABLE = 'table_search_index',
TOPIC = 'topic_search_index',
}

View File

@ -33,8 +33,3 @@ export type Team = {
description: string;
href: string;
};
export enum SearchIndex {
TABLE = 'table_search_index',
TOPIC = 'topic_search_index',
}

View File

@ -425,6 +425,7 @@ const ExplorePage: React.FC = (): React.ReactElement => {
currentPage={currentPage}
data={data}
fetchLeftPanel={fetchLeftPanel}
indexType={searchIndex}
isLoading={isLoading}
paginate={paginate}
searchText={searchText}

View File

@ -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 (
<PageContainer>
<div className="tw-px-4 w-full">
<div className="tw-flex tw-flex-col">
<div className="tw-flex tw-flex-initial tw-justify-between tw-items-center">
<TitleBreadcrumb titleLinks={slashedTableName} />
<div className="tw-flex tw-h-6 tw-ml-2 tw-mt-2">
<span
className={classNames(
'tw-flex tw-border tw-border-primary tw-rounded',
isFollowing
? 'tw-bg-primary tw-text-white'
: 'tw-text-primary'
)}>
<button
className={classNames(
'tw-text-xs tw-border-r tw-font-normal tw-py-1 tw-px-2 tw-rounded-l focus:tw-outline-none',
isFollowing ? 'tw-border-white' : 'tw-border-primary'
)}
data-testid="follow-button"
onClick={followTable}>
{isFollowing ? (
<>
<i className="fas fa-star" /> Unfollow
</>
) : (
<>
<i className="far fa-star" /> Follow
</>
)}
</button>
<span className="tw-text-xs tw-border-l-0 tw-font-normal tw-py-1 tw-px-2 tw-rounded-r">
{followers}
</span>
</span>
</div>
</div>
</div>
<div className="tw-flex tw-gap-1 tw-mb-2 tw-mt-1">
<span>
<span className="tw-text-grey-muted tw-font-normal">Owner :</span>{' '}
<span className="tw-pl-1tw-font-normal ">
{owner?.name || '--'}
</span>
<span className="tw-mx-3 tw-inline-block tw-text-gray-400"></span>
</span>
<span>
<span className="tw-text-grey-muted tw-font-normal">Usage :</span>{' '}
<span className="tw-pl-1 tw-font-normal"> {usage}</span>
<span className="tw-mx-3 tw-inline-block tw-text-gray-400"></span>
</span>
<span>
<span className="tw-text-grey-muted tw-font-normal">Queries :</span>{' '}
<span className="tw-pl-1 tw-font-normal">
{weeklyUsageCount} past week
</span>
</span>
</div>
<div className="tw-flex tw-flex-wrap tw-pt-1">
{(tableTags.length > 0 || tier) && (
<i className="fas fa-tags tw-px-1 tw-mt-2 tw-text-grey-muted" />
)}
{tier && (
<Tags className="tw-bg-gray-200" tag={`#${tier.split('.')[1]}`} />
)}
{tableTags.length > 0 && (
<>
{tableTags.slice(0, LIST_SIZE).map((tag, index) => (
<Tags
className="tw-bg-gray-200"
key={index}
tag={`#${tag.tagFQN}`}
/>
))}
{tableTags.slice(LIST_SIZE).length > 0 && (
<PopOver
className="tw-py-1"
html={
<>
{tableTags.slice(LIST_SIZE).map((tag, index) => (
<Tags
className="tw-bg-gray-200 tw-px-2"
key={index}
tag={`#${tag.tagFQN}`}
/>
))}
</>
}
position="bottom"
theme="light"
trigger="click">
<span className="tw-cursor-pointer tw-text-xs link-text">
View more
</span>
</PopOver>
)}
</>
)}
</div>
<EntityPageInfo
extraInfo={extraInfo}
followers={followers}
followHandler={followTable}
isFollowing={isFollowing}
tags={tableTags}
tier={tier || ''}
titleLinks={slashedTableName}
/>
<div className="tw-block tw-mt-1">
<div className="tw-bg-transparent tw--mx-4">
<nav className="tw-flex tw-flex-row tw-gh-tabs-container tw-px-4">
<button
className={getTabClasses(1, activeTab)}
data-testid="tab"
onClick={() => setActiveTab(1)}>
<SVGIcons alt="schema" icon="icon-schema" title="schema" />{' '}
{'Schema '}
</button>
<NonAdminAction
isOwner={!owner || hasEditAccess()}
title="You need to be owner to perform this action">
<button
className={getTabClasses(6, activeTab)}
data-testid="tab"
onClick={() => setActiveTab(6)}>
<SVGIcons alt="manage" icon="icon-manage" title="manage" />{' '}
{'Manage '}
</button>
</NonAdminAction>
</nav>
</div>
<TabsPane
activeTab={activeTab}
setActiveTab={setActiveTab}
tabs={tabs}
/>
<div className="tw-bg-white tw--mx-4 tw-p-4">
{activeTab === 1 && (
<div className="tw-grid tw-grid-cols-4 tw-gap-4 w-full">
<div className="tw-col-span-3">
<div className="schema-description tw-flex tw-flex-col tw-h-full tw-min-h-168 tw-relative tw-border tw-border-main tw-rounded-md">
<div className="tw-flex tw-items-center tw-px-3 tw-py-1 tw-border-b tw-border-main">
<span className="tw-flex-1 tw-leading-8 tw-m-0 tw-text-sm tw-font-normal">
Description
</span>
<div className="tw-flex-initial">
<NonAdminAction
html={
<>
<p>You need to be owner to perform this action</p>
{!owner ? (
<p>Claim ownership in Manage </p>
) : null}
</>
}
isOwner={hasEditAccess()}>
<button
className="focus:tw-outline-none"
onClick={onDescriptionEdit}>
<SVGIcons
alt="edit"
icon="icon-edit"
title="edit"
/>
</button>
</NonAdminAction>
</div>
</div>
<div className="tw-px-3 tw-py-2 tw-overflow-y-auto">
<div
className="tw-pl-3"
data-testid="description"
id="description">
{description.trim() ? (
<RichTextEditorPreviewer markdown={description} />
) : (
<span className="tw-no-description">
No description added
</span>
)}
</div>
{isEdit && (
<ModalWithMarkdownEditor
header={`Edit description for ${name}`}
placeholder="Enter Description"
value={description}
onCancel={onCancel}
onSave={onDescriptionUpdate}
onSuggest={onSuggest}
/>
)}
</div>
</div>
<Description
description={description}
hasEditAccess={hasEditAccess()}
isEdit={isEdit}
owner={owner}
onCancel={onCancel}
onDescriptionEdit={onDescriptionEdit}
onDescriptionUpdate={onDescriptionUpdate}
onSuggest={onSuggest}
/>
</div>
<div className="tw-col-span-1 tw-border tw-border-main tw-rounded-md">
<FrequentlyJoinedTables

View File

@ -62,6 +62,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => {
<Route exact component={TagsPage} path={ROUTES.TAGS} />
<Route component={DatabaseDetails} path={ROUTES.DATABASE_DETAILS} />
<Route component={MyDataDetailsPage} path={ROUTES.DATASET_DETAILS} />
<Route component={MyDataDetailsPage} path={ROUTES.TOPIC_DETAILS} />
<Route component={Onboarding} path={ROUTES.ONBOARDING} />
<Redirect to={ROUTES.NOT_FOUND} />
</Switch>

View File

@ -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 = '') => {
</PopOver>
);
};
export const getEntityLink = (
indexType: string,
fullyQualifiedName: string
) => {
switch (indexType) {
case SearchIndex.TOPIC:
return getTopicDetailsPath(fullyQualifiedName);
case SearchIndex.TABLE:
default:
return getDatasetDetailsPath(fullyQualifiedName);
}
};