mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-12-02 18:46:02 +00:00
refactoring mydata details page (#326)
* refactoring mydata details page * addressing review comment
This commit is contained in:
parent
150f70353c
commit
32b8bea8c2
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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>
|
||||
|
||||
@ -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 || '--'}
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -20,3 +20,8 @@ export enum FilterType {
|
||||
PLATFORM = 'platform',
|
||||
CLUSTER = 'cluster',
|
||||
}
|
||||
|
||||
export enum SearchIndex {
|
||||
TABLE = 'table_search_index',
|
||||
TOPIC = 'topic_search_index',
|
||||
}
|
||||
|
||||
@ -33,8 +33,3 @@ export type Team = {
|
||||
description: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
export enum SearchIndex {
|
||||
TABLE = 'table_search_index',
|
||||
TOPIC = 'topic_search_index',
|
||||
}
|
||||
|
||||
@ -425,6 +425,7 @@ const ExplorePage: React.FC = (): React.ReactElement => {
|
||||
currentPage={currentPage}
|
||||
data={data}
|
||||
fetchLeftPanel={fetchLeftPanel}
|
||||
indexType={searchIndex}
|
||||
isLoading={isLoading}
|
||||
paginate={paginate}
|
||||
searchText={searchText}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user