refactor(ui): replace custom tabs component with ANTD component for dataset details page (#11751)

* refactor(ui): replace custom tabs component with ANTD component for dataset details page

* fixed failing unit test and updated tour page

* fixed failing cypress

* replace custom tabs with antd tabs in all the version details page

* code smell

* refactor table details page

* fixed failing unit test and broken tour page

* fixed broken tour
This commit is contained in:
Shailesh Parmar 2023-05-26 15:15:14 +05:30 committed by GitHub
parent f0f64a7b21
commit bebdb98e68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 491 additions and 648 deletions

View File

@ -594,10 +594,6 @@ describe('Data Quality and Profiler should work properly', () => {
cy.get('[data-testid="Profiler & Data Quality"]')
.should('be.visible')
.click();
cy.get('[data-testid="Profiler & Data Quality"]').should(
'have.class',
'active'
);
interceptURL('GET', '/api/v1/tables/*/columnProfile?*', 'getProfilerInfo');
cy.get('[data-testid="profiler-tab-left-panel"]')
@ -649,10 +645,6 @@ describe('Data Quality and Profiler should work properly', () => {
cy.get('[data-testid="Profiler & Data Quality"]')
.should('be.visible')
.click();
cy.get('[data-testid="Profiler & Data Quality"]').should(
'have.class',
'active'
);
interceptURL(
'GET',
`api/v1/tables/name/${serviceName}.*.${term}?include=all`,

View File

@ -12,6 +12,7 @@
*/
import { EntityUnion } from 'components/Explore/explore.interface';
import { EntityTabs } from 'enums/entity.enum';
import { isEmpty, isNil, isUndefined } from 'lodash';
import { action, makeAutoObservable } from 'mobx';
import { ClientAuth, NewUser } from 'Models';
@ -56,7 +57,7 @@ class AppState {
isTourOpen = false;
currentTourPage: CurrentTourPageType = CurrentTourPageType.MY_DATA_PAGE;
activeTabforTourDatasetPage = 1;
activeTabforTourDatasetPage = EntityTabs.SCHEMA;
constructor() {
makeAutoObservable(this, {

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { Card } from 'antd';
import { Card, Tabs } from 'antd';
import classNames from 'classnames';
import PageContainerV1 from 'components/containers/PageContainerV1';
import PageLayoutV1 from 'components/containers/PageLayoutV1';
@ -29,7 +29,7 @@ import { getEntityName } from 'utils/EntityUtils';
import { bytesToSize } from 'utils/StringsUtils';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { EntityField } from '../../constants/Feeds.constants';
import { EntityInfo, FqnPart } from '../../enums/entity.enum';
import { EntityInfo, EntityTabs, FqnPart } from '../../enums/entity.enum';
import { OwnerType } from '../../enums/user.enum';
import { TagLabel } from '../../generated/type/tagLabel';
import { getPartialNameFromTableFQN } from '../../utils/CommonUtils';
@ -42,7 +42,6 @@ import {
import { TagLabelWithStatus } from '../../utils/EntityVersionUtils.interface';
import Description from '../common/description/Description';
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
import TabsPane from '../common/TabsPane/TabsPane';
import EntityVersionTimeLine from '../EntityVersionTimeLine/EntityVersionTimeLine';
import Loader from '../Loader/Loader';
import VersionTable from '../VersionTable/VersionTable.component';
@ -374,9 +373,8 @@ const ContainerVersion: React.FC<ContainerVersionProp> = ({
const tabs = [
{
name: t('label.schema'),
isProtected: false,
position: 1,
label: t('label.schema'),
key: EntityTabs.SCHEMA,
},
];
@ -411,7 +409,7 @@ const ContainerVersion: React.FC<ContainerVersionProp> = ({
versionHandler={backHandler}
/>
<div className="tw-mt-1 d-flex flex-col flex-grow ">
<TabsPane activeTab={1} className="flex-initial" tabs={tabs} />
<Tabs activeKey={EntityTabs.SCHEMA} items={tabs} />
<Card className="m-y-md">
<div className="tw-grid tw-grid-cols-4 tw-gap-4 tw-w-full">
<div className="tw-col-span-full">

View File

@ -11,11 +11,12 @@
* limitations under the License.
*/
import { Card, Space, Table } from 'antd';
import { Card, Space, Table, Tabs } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import classNames from 'classnames';
import PageContainerV1 from 'components/containers/PageContainerV1';
import PageLayoutV1 from 'components/containers/PageLayoutV1';
import { EntityTabs } from 'enums/entity.enum';
import { isUndefined } from 'lodash';
import { ExtraInfo } from 'Models';
import React, { FC, useEffect, useMemo, useState } from 'react';
@ -42,7 +43,6 @@ import SVGIcons from '../../utils/SvgUtils';
import Description from '../common/description/Description';
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer';
import TabsPane from '../common/TabsPane/TabsPane';
import EntityVersionTimeLine from '../EntityVersionTimeLine/EntityVersionTimeLine';
import Loader from '../Loader/Loader';
import { DashboardVersionProp } from './DashboardVersion.interface';
@ -65,9 +65,8 @@ const DashboardVersion: FC<DashboardVersionProp> = ({
);
const tabs = [
{
name: t('label.detail-plural'),
isProtected: false,
position: 1,
label: t('label.detail-plural'),
key: EntityTabs.SCHEMA,
},
];
@ -294,7 +293,11 @@ const DashboardVersion: FC<DashboardVersionProp> = ({
versionHandler={backHandler}
/>
<div className="tw-mt-1 d-flex flex-col flex-grow ">
<TabsPane activeTab={1} className="flex-initial" tabs={tabs} />
<Tabs
activeKey={EntityTabs.SCHEMA}
data-testid="tabs"
items={tabs}
/>
<Card className="m-y-md">
<div className="tw-grid tw-grid-cols-4 tw-gap-4 tw-w-full">
<div className="tw-col-span-full">

View File

@ -32,10 +32,6 @@ jest.mock('../common/description/Description', () => {
return jest.fn().mockImplementation(() => <div>Description.component</div>);
});
jest.mock('../common/TabsPane/TabsPane', () => {
return jest.fn().mockImplementation(() => <div>TabsPane.component</div>);
});
jest.mock('../EntityVersionTimeLine/EntityVersionTimeLine', () => {
return jest
.fn()
@ -106,7 +102,7 @@ describe('Test DashboardVersion page', () => {
container,
'EntityVersionTimeLine.component'
);
const tabs = await findByText(container, 'TabsPane.component');
const tabs = await findByTestId(container, 'tabs');
const description = await findByText(container, 'Description.component');
const richTextEditorPreviewer = await findByText(
container,
@ -150,7 +146,7 @@ describe('Test DashboardVersion page', () => {
container,
'EntityVersionTimeLine.component'
);
const tabs = await findByText(container, 'TabsPane.component');
const tabs = await findByTestId(container, 'tabs');
const description = await findByText(container, 'Description.component');
const richTextEditorPreviewer = await findByText(
container,
@ -192,7 +188,7 @@ describe('Test DashboardVersion page', () => {
container,
'EntityVersionTimeLine.component'
);
const tabs = await findByText(container, 'TabsPane.component');
const tabs = await findByTestId(container, 'tabs');
const description = await findByText(container, 'Description.component');
expect(dashboardVersionContainer).toBeInTheDocument();

View File

@ -11,12 +11,12 @@
* limitations under the License.
*/
import { Card } from 'antd';
import { Card, Tabs } from 'antd';
import classNames from 'classnames';
import PageContainerV1 from 'components/containers/PageContainerV1';
import PageLayoutV1 from 'components/containers/PageLayoutV1';
import VersionTable from 'components/VersionTable/VersionTable.component';
import { FqnPart } from 'enums/entity.enum';
import { EntityTabs, FqnPart } from 'enums/entity.enum';
import {
ChangeDescription,
Column,
@ -42,7 +42,6 @@ import {
import { TagLabelWithStatus } from '../../utils/EntityVersionUtils.interface';
import Description from '../common/description/Description';
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
import TabsPane from '../common/TabsPane/TabsPane';
import EntityVersionTimeLine from '../EntityVersionTimeLine/EntityVersionTimeLine';
import Loader from '../Loader/Loader';
import { DataModelVersionProp } from './DataModelVersion.interface';
@ -66,9 +65,8 @@ const DataModelVersion: FC<DataModelVersionProp> = ({
);
const tabs = [
{
name: t('label.detail-plural'),
isProtected: false,
position: 1,
label: t('label.model'),
key: EntityTabs.MODEL,
},
];
@ -415,7 +413,7 @@ const DataModelVersion: FC<DataModelVersionProp> = ({
versionHandler={backHandler}
/>
<div className="tw-mt-1 d-flex flex-col flex-grow ">
<TabsPane activeTab={1} className="flex-initial" tabs={tabs} />
<Tabs activeKey={EntityTabs.MODEL} items={tabs} />
<Card className="m-y-md">
<div className="tw-grid tw-grid-cols-4 tw-gap-4 tw-w-full">
<div className="tw-col-span-full">

View File

@ -11,13 +11,15 @@
* limitations under the License.
*/
import { Card, Col, Row, Skeleton, Space, Typography } from 'antd';
import { Card, Col, Row, Skeleton, Space, Tabs, Typography } from 'antd';
import AppState from 'AppState';
import { AxiosError } from 'axios';
import classNames from 'classnames';
import { ActivityFilters } from 'components/ActivityFeed/ActivityFeedList/ActivityFeedList.interface';
import PageLayoutV1 from 'components/containers/PageLayoutV1';
import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.interface';
import { ROUTES } from 'constants/constants';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import { getTableTabPath, ROUTES } from 'constants/constants';
import { mockTablePermission } from 'constants/mockTourData.constants';
import { SearchIndex } from 'enums/search.enum';
import { isEqual, isNil, isUndefined } from 'lodash';
@ -30,7 +32,7 @@ import React, {
useState,
} from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router-dom';
import { useHistory, useLocation, useParams } from 'react-router-dom';
import { searchQuery } from 'rest/searchAPI';
import { restoreTable } from 'rest/tableAPI';
import {
@ -45,7 +47,12 @@ import {
} from '../../constants/char.constants';
import { EntityField } from '../../constants/Feeds.constants';
import { observerOptions } from '../../constants/Mydata.constants';
import { EntityInfo, EntityType, FqnPart } from '../../enums/entity.enum';
import {
EntityInfo,
EntityTabs,
EntityType,
FqnPart,
} from '../../enums/entity.enum';
import { OwnerType } from '../../enums/user.enum';
import {
JoinedWith,
@ -79,7 +86,6 @@ import { CustomPropertyTable } from '../common/CustomPropertyTable/CustomPropert
import { CustomPropertyProps } from '../common/CustomPropertyTable/CustomPropertyTable.interface';
import Description from '../common/description/Description';
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
import TabsPane from '../common/TabsPane/TabsPane';
import PageContainerV1 from '../containers/PageContainerV1';
import EntityLineageComponent from '../EntityLineage/EntityLineage.component';
import FrequentlyJoinedTables from '../FrequentlyJoinedTables/FrequentlyJoinedTables.component';
@ -99,9 +105,6 @@ import './datasetDetails.style.less';
import DbtTab from './DbtTab/DbtTab.component';
const DatasetDetails: React.FC<DatasetDetailsProps> = ({
datasetFQN,
activeTab,
setActiveTabHandler,
tableProfile,
followTableHandler,
unfollowTableHandler,
@ -124,6 +127,9 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
}: DatasetDetailsProps) => {
const { t } = useTranslation();
const location = useLocation();
const { datasetFQN, tab } =
useParams<{ datasetFQN: string; tab: EntityTabs }>();
const history = useHistory();
const [isEdit, setIsEdit] = useState(false);
const [usage, setUsage] = useState('');
const [threadLink, setThreadLink] = useState<string>('');
@ -137,6 +143,8 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
const [tablePermissions, setTablePermissions] = useState<OperationPermission>(
DEFAULT_ENTITY_PERMISSION
);
// For tour we have to maintain state
const [activeTab, setActiveTab] = useState(tab ?? EntityTabs.SCHEMA);
const [activityFilter, setActivityFilter] = useState<ActivityFilters>();
const {
@ -146,11 +154,8 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
tableType,
version,
followers = [],
deleted,
columns,
description,
usageSummary,
joins,
entityName,
} = useMemo(() => {
const { tags } = tableDetails;
@ -235,116 +240,87 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
};
}, [followers]);
const tabs = useMemo(
() => [
const tabs = useMemo(() => {
const allTabs = [
{
name: t('label.schema'),
icon: {
alt: 'schema',
name: 'icon-schema',
title: 'Schema',
selectedName: 'icon-schemacolor',
},
isProtected: false,
position: 1,
label: <TabsLabel name={t('label.schema')} />,
key: EntityTabs.SCHEMA,
},
{
name: t('label.activity-feed-and-task-plural'),
icon: {
alt: 'activity_feed',
name: 'activity_feed',
title: 'Activity Feed',
selectedName: 'activity-feed-color',
},
isProtected: false,
position: 2,
count: feedCount,
label: (
<TabsLabel
count={feedCount}
isActive={activeTab === EntityTabs.ACTIVITY_FEED}
name={t('label.activity-feed-and-task-plural')}
/>
),
key: EntityTabs.ACTIVITY_FEED,
},
{
name: t('label.sample-data'),
icon: {
alt: 'sample_data',
name: 'sample-data',
title: 'Sample Data',
selectedName: 'sample-data-color',
},
isProtected: false,
label: <TabsLabel name={t('label.sample-data')} />,
isHidden: !(
tablePermissions.ViewAll ||
tablePermissions.ViewBasic ||
tablePermissions.ViewSampleData
),
position: 3,
key: EntityTabs.SAMPLE_DATA,
},
{
name: t('label.query-plural'),
icon: {
alt: 'table_queries',
name: 'table_queries',
title: 'Table Queries',
selectedName: '',
},
isProtected: false,
label: (
<TabsLabel
count={queryCount}
isActive={activeTab === EntityTabs.TABLE_QUERIES}
name={t('label.query-plural')}
/>
),
isHidden: !(
tablePermissions.ViewAll ||
tablePermissions.ViewBasic ||
tablePermissions.ViewQueries
),
position: 4,
count: queryCount,
key: EntityTabs.TABLE_QUERIES,
},
{
name: t('label.profiler-amp-data-quality'),
icon: {
alt: 'profiler',
name: 'icon-profiler',
title: 'Profiler',
selectedName: 'icon-profilercolor',
},
isProtected: false,
label: <TabsLabel name={t('label.profiler-amp-data-quality')} />,
isHidden: !(
tablePermissions.ViewAll ||
tablePermissions.ViewBasic ||
tablePermissions.ViewDataProfile ||
tablePermissions.ViewTests
),
position: 5,
key: EntityTabs.PROFILER,
},
{
name: t('label.lineage'),
icon: {
alt: 'lineage',
name: 'icon-lineage',
title: 'Lineage',
selectedName: 'icon-lineagecolor',
},
isProtected: false,
position: 7,
label: <TabsLabel name={t('label.lineage')} />,
key: EntityTabs.LINEAGE,
},
{
name: t('label.dbt-lowercase'),
icon: {
alt: 'dbt-model',
name: 'dbtmodel-light-grey',
title: 'DBT',
selectedName: 'dbtmodel-primery',
},
isProtected: false,
isHidden: !dataModel?.sql,
position: 8,
label: <TabsLabel name={t('label.dbt-lowercase')} />,
isHidden: !(dataModel?.sql ?? dataModel?.rawSql),
key: EntityTabs.DBT,
},
{
name: t('label.custom-property-plural'),
isProtected: false,
position: 9,
label: <TabsLabel name={t('label.custom-property-plural')} />,
key: EntityTabs.CUSTOM_PROPERTIES,
},
],
[tablePermissions, dataModel, feedCount, queryCount]
);
];
return allTabs.filter((data) => !data.isHidden);
}, [tablePermissions, dataModel, feedCount, queryCount, activeTab]);
const handleTabChange = (activeKey: string) => {
if (activeKey !== activeTab) {
if (!isTourPage) {
history.push(getTableTabPath(datasetFQN, activeKey));
}
setActiveTab(activeKey as EntityTabs);
}
};
const getFrequentlyJoinedWithTables = (): Array<
JoinedWith & { name: string }
> => {
const { joins } = tableDetails;
const tableFQNGrouping = [
...(joins?.columnJoins?.flatMap(
(cjs) =>
@ -384,6 +360,8 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
tableProfile?: TableProfile,
numberOfColumns?: number
) => {
const { columns } = tableDetails;
if (isTableProfileLoading) {
return (
<Skeleton active paragraph={{ rows: 1, width: 50 }} title={false} />
@ -464,7 +442,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
EntityInfo.COLUMNS,
isTableProfileLoading,
tableProfile,
columns.length
tableDetails.columns.length
),
},
{
@ -498,7 +476,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
};
const onColumnsUpdate = async (updateColumns: Table['columns']) => {
if (!isEqual(columns, updateColumns)) {
if (!isEqual(tableDetails.columns, updateColumns)) {
const updatedTableDetails = {
...tableDetails,
columns: updateColumns,
@ -627,7 +605,12 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
pagingObj: Paging,
isLoading: boolean
) => {
if (isElementInView && pagingObj?.after && !isLoading && activeTab === 2) {
if (
isElementInView &&
pagingObj?.after &&
!isLoading &&
activeTab === EntityTabs.ACTIVITY_FEED
) {
fetchFeedHandler(
pagingObj.after,
activityFilter?.feedFilter,
@ -649,6 +632,173 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
fetchFeedHandler(undefined, feedType, threadType);
}, []);
useEffect(() => {
if (isTourPage) {
setActiveTab(AppState.activeTabforTourDatasetPage);
} else {
setActiveTab(tab);
}
}, [tab, AppState.activeTabforTourDatasetPage]);
const tabDetails = useMemo(() => {
switch (activeTab) {
case EntityTabs.CUSTOM_PROPERTIES:
return (
<CustomPropertyTable
entityDetails={tableDetails as CustomPropertyProps['entityDetails']}
entityType={EntityType.TABLE}
handleExtensionUpdate={onExtensionUpdate}
hasEditAccess={
tablePermissions.EditAll || tablePermissions.EditCustomFields
}
/>
);
case EntityTabs.DBT:
return <DbtTab dataModel={dataModel} />;
case EntityTabs.LINEAGE:
return (
<Card className="card-body-full m-y-md h-70vh" id="lineageDetails">
<EntityLineageComponent
deleted={tableDetails.deleted}
entityType={EntityType.TABLE}
hasEditAccess={
tablePermissions.EditAll || tablePermissions.EditLineage
}
/>
</Card>
);
case EntityTabs.PROFILER:
return (
<TableProfilerV1
isTableDeleted={tableDetails.deleted}
permissions={tablePermissions}
tableFqn={tableDetails.fullyQualifiedName || ''}
/>
);
case EntityTabs.TABLE_QUERIES:
return (
<TableQueries
isTableDeleted={tableDetails.deleted}
tableId={tableDetails.id}
/>
);
case EntityTabs.SAMPLE_DATA:
return (
<SampleDataTable
isTableDeleted={tableDetails.deleted}
tableId={tableDetails.id}
/>
);
case EntityTabs.ACTIVITY_FEED:
return (
<Card className="m-y-md h-min-full">
<Row>
<Col data-testid="activityfeed" offset={3} span={18}>
<ActivityFeedList
isEntityFeed
withSidePanel
deletePostHandler={deletePostHandler}
entityName={entityName}
feedList={entityThread}
isFeedLoading={isEntityThreadLoading}
postFeedHandler={postFeedHandler}
updateThreadHandler={updateThreadHandler}
onFeedFiltersUpdate={handleFeedFilterChange}
/>
</Col>
</Row>
{loader}
</Card>
);
case EntityTabs.SCHEMA:
default:
return (
<Card className="m-y-md h-full">
<Row id="schemaDetails">
<Col span={17}>
<Description
description={description}
entityFieldTasks={getEntityFieldThreadCounts(
EntityField.DESCRIPTION,
entityFieldTaskCount
)}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.DESCRIPTION,
entityFieldThreadCount
)}
entityFqn={datasetFQN}
entityName={entityName}
entityType={EntityType.TABLE}
hasEditAccess={
tablePermissions.EditAll || tablePermissions.EditDescription
}
isEdit={isEdit}
isReadOnly={tableDetails.deleted}
owner={tableDetails.owner}
onCancel={onCancel}
onDescriptionEdit={onDescriptionEdit}
onDescriptionUpdate={onDescriptionUpdate}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Col>
<Col offset={1} span={6}>
<div className="global-border rounded-4">
<FrequentlyJoinedTables
header={t('label.frequently-joined-table-plural')}
tableList={getFrequentlyJoinedWithTables()}
/>
</div>
</Col>
<Col className="m-t-md" span={24}>
<SchemaTab
columnName={getPartialNameFromTableFQN(
datasetFQN,
[FqnPart['Column']],
FQN_SEPARATOR_CHAR
)}
columns={tableDetails.columns}
entityFieldTasks={getEntityFieldThreadCounts(
EntityField.COLUMNS,
entityFieldTaskCount
)}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.COLUMNS,
entityFieldThreadCount
)}
entityFqn={datasetFQN}
hasDescriptionEditAccess={
tablePermissions.EditAll || tablePermissions.EditDescription
}
hasTagEditAccess={
tablePermissions.EditAll || tablePermissions.EditTags
}
isReadOnly={tableDetails.deleted}
joins={tableDetails.joins?.columnJoins || []}
tableConstraints={tableDetails.tableConstraints}
onThreadLinkSelect={onThreadLinkSelect}
onUpdate={onColumnsUpdate}
/>
</Col>
</Row>
</Card>
);
}
}, [
activeTab,
tableDetails,
tablePermissions,
entityFieldThreadCount,
entityFieldTaskCount,
isEdit,
entityName,
datasetFQN,
description,
entityThread,
isEntityThreadLoading,
dataModel,
]);
return (
<PageContainerV1>
<PageLayoutV1
@ -658,7 +808,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
<EntityPageInfo
canDelete={tablePermissions.Delete}
currentOwner={tableDetails.owner}
deleted={deleted}
deleted={tableDetails.deleted}
displayName={tableDetails.displayName}
entityFieldTasks={getEntityFieldThreadCounts(
EntityField.TAGS,
@ -706,11 +856,11 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
/>
<div className="m-t-md">
<TabsPane
activeTab={activeTab}
className="flex-initial"
setActiveTab={setActiveTabHandler}
tabs={tabs}
<Tabs
activeKey={activeTab ?? EntityTabs.SCHEMA}
data-testid="tabs"
items={tabs}
onChange={handleTabChange}
/>
<div
className={classNames(
@ -719,149 +869,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
isTourPage ? 'h-70vh overflow-hidden' : 'h-full'
)}
id="tab-details">
{activeTab === 1 && (
<Card className="m-y-md h-full">
<Row id="schemaDetails">
<Col span={17}>
<Description
description={description}
entityFieldTasks={getEntityFieldThreadCounts(
EntityField.DESCRIPTION,
entityFieldTaskCount
)}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.DESCRIPTION,
entityFieldThreadCount
)}
entityFqn={datasetFQN}
entityName={entityName}
entityType={EntityType.TABLE}
hasEditAccess={
tablePermissions.EditAll ||
tablePermissions.EditDescription
}
isEdit={isEdit}
isReadOnly={deleted}
owner={owner}
onCancel={onCancel}
onDescriptionEdit={onDescriptionEdit}
onDescriptionUpdate={onDescriptionUpdate}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Col>
<Col offset={1} span={6}>
<div className="global-border rounded-4">
<FrequentlyJoinedTables
header={t('label.frequently-joined-table-plural')}
tableList={getFrequentlyJoinedWithTables()}
/>
</div>
</Col>
<Col className="m-t-md" span={24}>
<SchemaTab
columnName={getPartialNameFromTableFQN(
datasetFQN,
[FqnPart['Column']],
FQN_SEPARATOR_CHAR
)}
columns={columns}
entityFieldTasks={getEntityFieldThreadCounts(
EntityField.COLUMNS,
entityFieldTaskCount
)}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.COLUMNS,
entityFieldThreadCount
)}
entityFqn={datasetFQN}
hasDescriptionEditAccess={
tablePermissions.EditAll ||
tablePermissions.EditDescription
}
hasTagEditAccess={
tablePermissions.EditAll || tablePermissions.EditTags
}
isReadOnly={deleted}
joins={joins?.columnJoins || []}
tableConstraints={tableDetails.tableConstraints}
onThreadLinkSelect={onThreadLinkSelect}
onUpdate={onColumnsUpdate}
/>
</Col>
</Row>
</Card>
)}
{activeTab === 2 && (
<Card className="m-y-md h-min-full">
<Row>
<Col data-testid="activityfeed" offset={3} span={18}>
<ActivityFeedList
isEntityFeed
withSidePanel
className=""
deletePostHandler={deletePostHandler}
entityName={entityName}
feedList={entityThread}
isFeedLoading={isEntityThreadLoading}
postFeedHandler={postFeedHandler}
updateThreadHandler={updateThreadHandler}
onFeedFiltersUpdate={handleFeedFilterChange}
/>
</Col>
</Row>
{loader}
</Card>
)}
{activeTab === 3 && (
<SampleDataTable
isTableDeleted={tableDetails.deleted}
tableId={tableDetails.id}
/>
)}
{activeTab === 4 && (
<TableQueries
isTableDeleted={tableDetails.deleted}
tableId={tableDetails.id}
/>
)}
{activeTab === 5 && (
<TableProfilerV1
isTableDeleted={tableDetails.deleted}
permissions={tablePermissions}
tableFqn={tableDetails.fullyQualifiedName || ''}
/>
)}
{activeTab === 7 && (
<Card
className="card-body-full m-y-md h-70vh"
id="lineageDetails">
<EntityLineageComponent
deleted={deleted}
entityType={EntityType.TABLE}
hasEditAccess={
tablePermissions.EditAll || tablePermissions.EditLineage
}
/>
</Card>
)}
{activeTab === 8 &&
Boolean(dataModel?.sql || dataModel?.rawSql) && (
<DbtTab dataModel={dataModel} />
)}
{activeTab === 9 && (
<CustomPropertyTable
entityDetails={
tableDetails as CustomPropertyProps['entityDetails']
}
entityType={EntityType.TABLE}
handleExtensionUpdate={onExtensionUpdate}
hasEditAccess={
tablePermissions.EditAll || tablePermissions.EditCustomFields
}
/>
)}
{tabDetails}
</div>
<div

View File

@ -13,7 +13,7 @@
import { FeedFilter } from '../../enums/mydata.enum';
import { CreateThread } from '../../generated/api/feed/createThread';
import { Table, TableData } from '../../generated/entity/data/table';
import { Table } from '../../generated/entity/data/table';
import { Thread, ThreadType } from '../../generated/entity/feed/thread';
import { Paging } from '../../generated/type/paging';
import {
@ -24,21 +24,16 @@ import {
export interface DatasetDetailsProps {
entityId?: string;
tableDetails: Table;
datasetFQN: string;
dataModel?: Table['dataModel'];
activeTab: number;
tableProfile: Table['profile'];
sampleData: TableData;
entityThread: Thread[];
isTableProfileLoading?: boolean;
isSampleDataLoading?: boolean;
isEntityThreadLoading: boolean;
feedCount: number;
entityFieldThreadCount: EntityFieldThreadCount[];
entityFieldTaskCount: EntityFieldThreadCount[];
paging: Paging;
createThread: (data: CreateThread) => void;
setActiveTabHandler: (value: number) => void;
followTableHandler: () => void;
unfollowTableHandler: () => void;
versionHandler: () => void;

View File

@ -15,12 +15,13 @@ import {
findByTestId,
findByText,
getByTestId,
queryByTestId,
queryByText,
render,
} from '@testing-library/react';
import { EntityTabs } from 'enums/entity.enum';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { Table } from '../../generated/entity/data/table';
import { ModelType, Table } from '../../generated/entity/data/table';
import { Paging } from '../../generated/type/paging';
import DatasetDetails from './DatasetDetails.component';
import { DatasetDetailsProps } from './DatasetDetails.interface';
@ -86,11 +87,7 @@ const mockThreads = [
];
const datasetDetailsProps: DatasetDetailsProps = {
activeTab: 1,
datasetFQN: '',
followTableHandler: jest.fn(),
sampleData: {},
setActiveTabHandler: jest.fn(),
tableDetails: {
columns: [],
id: '',
@ -113,6 +110,21 @@ const datasetDetailsProps: DatasetDetailsProps = {
onTableUpdate: jest.fn(),
};
const mockParams = {
datasetFQN: 'test',
tab: EntityTabs.SCHEMA,
};
jest.mock('react-router-dom', () => ({
useHistory: jest.fn(),
useLocation: jest.fn().mockReturnValue({ pathname: 'table' }),
useParams: jest.fn().mockImplementation(() => mockParams),
}));
jest.mock('../containers/PageContainerV1', () => {
return jest.fn().mockImplementation(({ children }) => <div>{children}</div>);
});
jest.mock('../EntityLineage/EntityLineage.component', () => {
return jest.fn().mockReturnValue(<p data-testid="lineage">Lineage</p>);
});
@ -159,6 +171,9 @@ jest.mock('../SampleDataTable/SampleDataTable.component', () => {
.fn()
.mockReturnValue(<p data-testid="sample-data">Sample Data</p>);
});
jest.mock('./DbtTab/DbtTab.component', () => {
return jest.fn().mockReturnValue(<div>DbtTab.component</div>);
});
jest.mock('../../utils/CommonUtils', () => ({
addToRecentViewed: jest.fn(),
@ -240,19 +255,19 @@ describe('Test MyDataDetailsPage page', () => {
const EntityPageInfo = await findByText(container, /EntityPageInfo/i);
const description = await findByText(container, /Description/i);
const tabs = await findByTestId(container, 'tabs');
const schemaTab = await findByTestId(tabs, 'label.schema');
const activityFeedTab = await findByTestId(
const schemaTab = await findByText(tabs, 'label.schema');
const activityFeedTab = await findByText(
tabs,
'label.activity-feed-and-task-plural'
);
const sampleDataTab = await findByTestId(tabs, 'label.sample-data');
const queriesTab = await findByTestId(tabs, 'label.query-plural');
const profilerTab = await findByTestId(
const sampleDataTab = await findByText(tabs, 'label.sample-data');
const queriesTab = await findByText(tabs, 'label.query-plural');
const profilerTab = await findByText(
tabs,
'label.profiler-amp-data-quality'
);
const lineageTab = await findByTestId(tabs, 'label.lineage');
const dbtTab = queryByTestId(tabs, 'DBT');
const lineageTab = await findByText(tabs, 'label.lineage');
const dbtTab = queryByText(tabs, 'label.dbt-lowercase');
expect(relatedTables).toBeInTheDocument();
expect(EntityPageInfo).toBeInTheDocument();
@ -277,72 +292,60 @@ describe('Test MyDataDetailsPage page', () => {
});
it('Check if active tab is activity feed', async () => {
const { container } = render(
<DatasetDetails {...datasetDetailsProps} activeTab={2} />,
{
wrapper: MemoryRouter,
}
);
mockParams.tab = EntityTabs.ACTIVITY_FEED;
const { container } = render(<DatasetDetails {...datasetDetailsProps} />, {
wrapper: MemoryRouter,
});
const activityFeedList = await findByText(container, /ActivityFeedList/i);
expect(activityFeedList).toBeInTheDocument();
});
it('Check if active tab is sample data', async () => {
const { container } = render(
<DatasetDetails {...datasetDetailsProps} activeTab={3} />,
{
wrapper: MemoryRouter,
}
);
mockParams.tab = EntityTabs.SAMPLE_DATA;
const { container } = render(<DatasetDetails {...datasetDetailsProps} />, {
wrapper: MemoryRouter,
});
const sampleData = await findByTestId(container, 'sample-data');
expect(sampleData).toBeInTheDocument();
});
it('Check if active tab is queries', async () => {
const { container } = render(
<DatasetDetails {...datasetDetailsProps} activeTab={4} />,
{
wrapper: MemoryRouter,
}
);
mockParams.tab = EntityTabs.TABLE_QUERIES;
const { container } = render(<DatasetDetails {...datasetDetailsProps} />, {
wrapper: MemoryRouter,
});
const tableQueries = await findByText(container, 'TableQueries');
expect(tableQueries).toBeInTheDocument();
});
it('Check if active tab is profiler', async () => {
const { container } = render(
<DatasetDetails {...datasetDetailsProps} activeTab={5} />,
{
wrapper: MemoryRouter,
}
);
mockParams.tab = EntityTabs.PROFILER;
const { container } = render(<DatasetDetails {...datasetDetailsProps} />, {
wrapper: MemoryRouter,
});
const tableProfiler = await findByTestId(container, 'TableProfiler');
expect(tableProfiler).toBeInTheDocument();
});
it('Check if active tab is lineage', async () => {
const { container } = render(
<DatasetDetails {...datasetDetailsProps} activeTab={7} />,
{
wrapper: MemoryRouter,
}
);
mockParams.tab = EntityTabs.LINEAGE;
const { container } = render(<DatasetDetails {...datasetDetailsProps} />, {
wrapper: MemoryRouter,
});
const lineage = await findByTestId(container, 'lineage');
expect(lineage).toBeInTheDocument();
});
it('Check if active tab is custom properties', async () => {
const { container } = render(
<DatasetDetails {...datasetDetailsProps} activeTab={9} />,
{
wrapper: MemoryRouter,
}
);
mockParams.tab = EntityTabs.CUSTOM_PROPERTIES;
const { container } = render(<DatasetDetails {...datasetDetailsProps} />, {
wrapper: MemoryRouter,
});
const customProperties = await findByText(
container,
'CustomPropertyTable.component'
@ -351,13 +354,27 @@ describe('Test MyDataDetailsPage page', () => {
expect(customProperties).toBeInTheDocument();
});
it('Should create an observer if IntersectionObserver is available', async () => {
it('Check if active tab is dbt', async () => {
mockParams.tab = EntityTabs.DBT;
const { container } = render(
<DatasetDetails {...datasetDetailsProps} activeTab={2} />,
<DatasetDetails
{...datasetDetailsProps}
dataModel={{ sql: 'select * from table', modelType: ModelType.Dbt }}
/>,
{
wrapper: MemoryRouter,
}
);
const dbtComponent = await findByText(container, 'DbtTab.component');
expect(dbtComponent).toBeInTheDocument();
});
it('Should create an observer if IntersectionObserver is available', async () => {
mockParams.tab = EntityTabs.ACTIVITY_FEED;
const { container } = render(<DatasetDetails {...datasetDetailsProps} />, {
wrapper: MemoryRouter,
});
const obServerElement = await findByTestId(container, 'observer-element');

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { Card } from 'antd';
import { Card, Tabs } from 'antd';
import classNames from 'classnames';
import PageContainerV1 from 'components/containers/PageContainerV1';
import PageLayoutV1 from 'components/containers/PageLayoutV1';
@ -22,7 +22,7 @@ import { useTranslation } from 'react-i18next';
import { getEntityName } from 'utils/EntityUtils';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { EntityField } from '../../constants/Feeds.constants';
import { FqnPart } from '../../enums/entity.enum';
import { EntityTabs, FqnPart } from '../../enums/entity.enum';
import { OwnerType } from '../../enums/user.enum';
import {
ChangeDescription,
@ -41,7 +41,6 @@ import {
import { TagLabelWithStatus } from '../../utils/EntityVersionUtils.interface';
import Description from '../common/description/Description';
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
import TabsPane from '../common/TabsPane/TabsPane';
import EntityVersionTimeLine from '../EntityVersionTimeLine/EntityVersionTimeLine';
import Loader from '../Loader/Loader';
import VersionTable from '../VersionTable/VersionTable.component';
@ -359,15 +358,8 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
const tabs = [
{
name: t('label.schema'),
icon: {
alt: 'schema',
name: 'icon-schema',
title: 'Schema',
selectedName: 'icon-schemacolor',
},
isProtected: false,
position: 1,
label: t('label.schema'),
key: EntityTabs.SCHEMA,
},
];
@ -402,7 +394,7 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
versionHandler={backHandler}
/>
<div className="tw-mt-1 d-flex flex-col flex-grow ">
<TabsPane activeTab={1} className="flex-initial" tabs={tabs} />
<Tabs activeKey={EntityTabs.SCHEMA} items={tabs} />
<Card className="m-y-md">
<div className="tw-grid tw-grid-cols-4 tw-gap-4 tw-w-full">
<div className="tw-col-span-full">

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { Card, Col, Divider, Row, Space, Typography } from 'antd';
import { Card, Col, Divider, Row, Space, Tabs, Typography } from 'antd';
import classNames from 'classnames';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
@ -19,6 +19,7 @@ import PageContainerV1 from 'components/containers/PageContainerV1';
import PageLayoutV1 from 'components/containers/PageLayoutV1';
import SourceList from 'components/MlModelDetail/SourceList.component';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import { EntityTabs } from 'enums/entity.enum';
import { MlFeature, Mlmodel } from 'generated/entity/data/mlmodel';
import { cloneDeep, isEqual, isUndefined } from 'lodash';
import { ExtraInfo } from 'Models';
@ -44,7 +45,6 @@ import {
import { TagLabelWithStatus } from '../../utils/EntityVersionUtils.interface';
import Description from '../common/description/Description';
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
import TabsPane from '../common/TabsPane/TabsPane';
import EntityVersionTimeLine from '../EntityVersionTimeLine/EntityVersionTimeLine';
import Loader from '../Loader/Loader';
import { MlModelVersionProp } from './MlModelVersion.interface';
@ -68,15 +68,8 @@ const MlModelVersion: FC<MlModelVersionProp> = ({
);
const tabs = [
{
name: 'Features',
icon: {
alt: t('label.feature-plural'),
name: 'icon-schema',
title: t('label.feature-plural'),
selectedName: 'icon-schemacolor',
},
isProtected: false,
position: 1,
label: t('label.feature-plural'),
key: EntityTabs.FEATURES,
},
];
@ -368,7 +361,7 @@ const MlModelVersion: FC<MlModelVersionProp> = ({
versionHandler={backHandler}
/>
<div className="m-t-xss">
<TabsPane activeTab={1} tabs={tabs} />
<Tabs activeKey={EntityTabs.FEATURES} items={tabs} />
<Card className="m-y-md">
<Description
isReadOnly

View File

@ -11,12 +11,13 @@
* limitations under the License.
*/
import { Card, Space, Table } from 'antd';
import { Card, Space, Table, Tabs } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import classNames from 'classnames';
import PageContainerV1 from 'components/containers/PageContainerV1';
import PageLayoutV1 from 'components/containers/PageLayoutV1';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import { EntityTabs } from 'enums/entity.enum';
import { t } from 'i18next';
import { ColumnDiffProps } from 'interface/EntityVersion.interface';
import { cloneDeep, isEqual, isUndefined } from 'lodash';
@ -48,7 +49,6 @@ import SVGIcons from '../../utils/SvgUtils';
import Description from '../common/description/Description';
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer';
import TabsPane from '../common/TabsPane/TabsPane';
import EntityVersionTimeLine from '../EntityVersionTimeLine/EntityVersionTimeLine';
import Loader from '../Loader/Loader';
import { PipelineVersionProp } from './PipelineVersion.interface';
@ -79,15 +79,8 @@ const PipelineVersion: FC<PipelineVersionProp> = ({
const tabs = [
{
name: t('label.detail-plural'),
icon: {
alt: 'schema',
name: 'icon-schema',
title: 'Details',
selectedName: 'icon-schemacolor',
},
isProtected: false,
position: 1,
label: t('label.task-plural'),
key: EntityTabs.TASKS,
},
];
@ -484,7 +477,7 @@ const PipelineVersion: FC<PipelineVersionProp> = ({
versionHandler={backHandler}
/>
<div className="tw-mt-1 d-flex flex-col flex-grow ">
<TabsPane activeTab={1} className="flex-initial" tabs={tabs} />
<Tabs activeKey={EntityTabs.TASKS} items={tabs} />
<Card className="m-y-md">
<div className="tw-grid tw-grid-cols-4 tw-gap-4 tw-w-full">
<div className="tw-col-span-full">

View File

@ -0,0 +1,31 @@
/*
* Copyright 2023 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Space } from 'antd';
import React from 'react';
import { getCountBadge } from 'utils/CommonUtils';
import { TabsLabelProps } from './TabsLabel.interface';
const TabsLabel = ({ name, count, isActive }: TabsLabelProps) => {
return (
<Space className="w-full" data-testid={name}>
{name}
{count && (
<span className="p-l-xs" data-testid="count">
{getCountBadge(count, '', isActive)}
</span>
)}
</Space>
);
};
export default TabsLabel;

View File

@ -0,0 +1,17 @@
/*
* Copyright 2023 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface TabsLabelProps {
name: string;
count?: number;
isActive?: boolean;
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2023 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { render, screen } from '@testing-library/react';
import React from 'react';
import TabsLabel from './TabsLabel.component';
import { TabsLabelProps } from './TabsLabel.interface';
const mockProps: TabsLabelProps = {
name: 'Test tab',
};
describe('TabsLabel component', () => {
it('Component should render', async () => {
render(<TabsLabel {...mockProps} />);
expect(await screen.findByText(mockProps.name)).toBeInTheDocument();
expect(screen.queryByTestId('count')).not.toBeInTheDocument();
});
it('Count should be visible if provided', async () => {
render(<TabsLabel {...mockProps} count={3} />);
const count = await screen.findByTestId('count');
expect(count).toBeVisible();
expect(count.textContent).toStrictEqual('3');
});
});

View File

@ -11,10 +11,11 @@
* limitations under the License.
*/
import { Card } from 'antd';
import { Card, Tabs } from 'antd';
import classNames from 'classnames';
import PageContainerV1 from 'components/containers/PageContainerV1';
import PageLayoutV1 from 'components/containers/PageLayoutV1';
import { EntityTabs } from 'enums/entity.enum';
import { isUndefined } from 'lodash';
import { ExtraInfo } from 'Models';
import React, { FC, useEffect, useState } from 'react';
@ -35,7 +36,6 @@ import { TagLabelWithStatus } from '../../utils/EntityVersionUtils.interface';
import { bytesToSize } from '../../utils/StringsUtils';
import Description from '../common/description/Description';
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
import TabsPane from '../common/TabsPane/TabsPane';
import EntityVersionTimeLine from '../EntityVersionTimeLine/EntityVersionTimeLine';
import Loader from '../Loader/Loader';
import SchemaEditor from '../schema-editor/SchemaEditor';
@ -59,15 +59,8 @@ const TopicVersion: FC<TopicVersionProp> = ({
);
const tabs = [
{
name: t('label.schema'),
icon: {
alt: 'schema',
name: 'icon-schema',
title: 'Schema',
selectedName: 'icon-schemacolor',
},
isProtected: false,
position: 1,
label: t('label.schema'),
key: EntityTabs.SCHEMA,
},
];
@ -285,7 +278,7 @@ const TopicVersion: FC<TopicVersionProp> = ({
versionHandler={backHandler}
/>
<div className="tw-mt-1 d-flex flex-col flex-grow ">
<TabsPane activeTab={1} className="flex-initial" tabs={tabs} />
<Tabs activeKey={EntityTabs.SCHEMA} items={tabs} />
<Card className="m-y-md">
<div className="tw-grid tw-grid-cols-4 tw-gap-4 tw-w-full">
<div className="tw-col-span-full">

View File

@ -129,3 +129,17 @@ export enum EntityInfo {
DATA_MODEL_TYPE = 'data-model-type',
QUERIES = 'queries',
}
export enum EntityTabs {
SCHEMA = 'schema',
ACTIVITY_FEED = 'activity_feed',
SAMPLE_DATA = 'sample_data',
TABLE_QUERIES = 'table_queries',
PROFILER = 'profiler',
LINEAGE = 'lineage',
DBT = 'dbt',
CUSTOM_PROPERTIES = 'custom_properties',
MODEL = 'model',
FEATURES = 'Features',
TASKS = 'Tasks',
}

View File

@ -22,7 +22,7 @@ import {
} from 'components/PermissionProvider/PermissionProvider.interface';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
import { compare, Operation } from 'fast-json-patch';
import { isEmpty, isUndefined } from 'lodash';
import { isEmpty } from 'lodash';
import { observer } from 'mobx-react';
import React, {
FunctionComponent,
@ -41,16 +41,11 @@ import {
removeFollower,
} from 'rest/tableAPI';
import AppState from '../../AppState';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import {
getTableTabPath,
getVersionPath,
pagingObject,
} from '../../constants/constants';
import { EntityType, FqnPart, TabSpecificField } from '../../enums/entity.enum';
import { getVersionPath, pagingObject } from '../../constants/constants';
import { EntityTabs, EntityType } from '../../enums/entity.enum';
import { FeedFilter } from '../../enums/mydata.enum';
import { CreateThread } from '../../generated/api/feed/createThread';
import { Table, TableData } from '../../generated/entity/data/table';
import { Table } from '../../generated/entity/data/table';
import { Post, Thread, ThreadType } from '../../generated/entity/feed/thread';
import { Paging } from '../../generated/type/paging';
import { EntityFieldThreadCount } from '../../interface/feed.interface';
@ -59,15 +54,9 @@ import {
getCurrentUserId,
getEntityMissingError,
getFeedCounts,
getFields,
getPartialNameFromTableFQN,
sortTagsCaseInsensitive,
} from '../../utils/CommonUtils';
import {
datasetTableTabs,
defaultFields,
getCurrentDatasetTab,
} from '../../utils/DatasetDetailsUtils';
import { defaultFields } from '../../utils/DatasetDetailsUtils';
import { getEntityFeedLink, getEntityName } from '../../utils/EntityUtils';
import { deletePost, updateThreadData } from '../../utils/FeedUtils';
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
@ -76,30 +65,17 @@ import { showErrorToast } from '../../utils/ToastUtils';
const DatasetDetailsPage: FunctionComponent = () => {
const history = useHistory();
const { t } = useTranslation();
const { datasetFQN, tab } =
useParams<{ datasetFQN: string; tab: EntityTabs }>();
const { getEntityPermissionByFqn } = usePermissionProvider();
const [isLoading, setIsLoading] = useState<boolean>(true);
const [isSampleDataLoading, setIsSampleDataLoading] =
useState<boolean>(false);
const [isEntityThreadLoading, setIsEntityThreadLoading] =
useState<boolean>(false);
const [isTableProfileLoading, setIsTableProfileLoading] =
useState<boolean>(false);
const USERId = getCurrentUserId();
const [sampleData, setSampleData] = useState<TableData>({
columns: [],
rows: [],
});
const [tableProfile, setTableProfile] = useState<Table['profile']>();
const [tableDetails, setTableDetails] = useState<Table>({} as Table);
const { datasetFQN, tab } = useParams() as Record<string, string>;
const [activeTab, setActiveTab] = useState<number>(getCurrentDatasetTab(tab));
const [tableFQN, setTableFQN] = useState<string>(
getPartialNameFromTableFQN(
datasetFQN,
[FqnPart.Service, FqnPart.Database, FqnPart.Schema, FqnPart.Table],
FQN_SEPARATOR_CHAR
)
);
const [isError, setIsError] = useState(false);
const [entityThread, setEntityThread] = useState<Thread[]>([]);
@ -119,21 +95,6 @@ const DatasetDetailsPage: FunctionComponent = () => {
const { id: tableId, followers, version: currentVersion = '' } = tableDetails;
const activeTabHandler = (tabValue: number) => {
const currentTabIndex = tabValue - 1;
if (datasetTableTabs[currentTabIndex].path !== tab) {
setActiveTab(
getCurrentDatasetTab(datasetTableTabs[currentTabIndex].path)
);
history.push({
pathname: getTableTabPath(
tableFQN,
datasetTableTabs[currentTabIndex].path
),
});
}
};
const getFeedData = async (
after?: string,
feedType?: FeedFilter,
@ -142,7 +103,7 @@ const DatasetDetailsPage: FunctionComponent = () => {
setIsEntityThreadLoading(true);
try {
const { data, paging: pagingObj } = await getAllFeeds(
getEntityFeedLink(EntityType.TABLE, tableFQN),
getEntityFeedLink(EntityType.TABLE, datasetFQN),
after,
threadType,
feedType,
@ -194,10 +155,7 @@ const DatasetDetailsPage: FunctionComponent = () => {
setIsLoading(true);
try {
const res = await getTableDetailsByFQN(
tableFQN,
getFields(defaultFields, datasetTableTabs[activeTab - 1].field ?? '')
);
const res = await getTableDetailsByFQN(datasetFQN, defaultFields);
const { id, fullyQualifiedName, serviceType } = res;
setTableDetails(res);
@ -218,7 +176,7 @@ const DatasetDetailsPage: FunctionComponent = () => {
error as AxiosError,
t('server.entity-details-fetch-error', {
entityType: t('label.pipeline'),
entityName: tableFQN,
entityName: datasetFQN,
})
);
}
@ -250,60 +208,16 @@ const DatasetDetailsPage: FunctionComponent = () => {
}
};
const fetchTabSpecificData = async (tabField = '') => {
switch (tabField) {
case TabSpecificField.SAMPLE_DATA: {
if (!isUndefined(sampleData)) {
break;
} else {
setIsSampleDataLoading(true);
try {
const res = await getTableDetailsByFQN(tableFQN, tabField);
const { sampleData } = res;
setSampleData(sampleData as TableData);
} catch (error) {
showErrorToast(
error as AxiosError,
t('server.entity-details-fetch-error', {
entityType: t('label.table'),
entityName: datasetFQN,
})
);
} finally {
setIsSampleDataLoading(false);
}
break;
}
}
case TabSpecificField.ACTIVITY_FEED: {
getFeedData();
break;
}
default:
break;
}
};
useEffect(() => {
if (datasetTableTabs[activeTab - 1].path !== tab) {
setActiveTab(getCurrentDatasetTab(tab));
if (EntityTabs.ACTIVITY_FEED === tab) {
getFeedData();
}
setEntityThread([]);
}, [tab]);
useEffect(() => {
fetchTabSpecificData(datasetTableTabs[activeTab - 1].field);
}, [activeTab, feedCount]);
}, [tab, feedCount]);
const getEntityFeedCount = () => {
getFeedCounts(
EntityType.TABLE,
tableFQN,
datasetFQN,
setEntityFieldThreadCount,
setEntityFieldTaskCount,
setFeedCount
@ -384,7 +298,7 @@ const DatasetDetailsPage: FunctionComponent = () => {
const versionHandler = () => {
history.push(
getVersionPath(EntityType.TABLE, tableFQN, currentVersion as string)
getVersionPath(EntityType.TABLE, datasetFQN, currentVersion as string)
);
};
@ -454,7 +368,6 @@ const DatasetDetailsPage: FunctionComponent = () => {
useEffect(() => {
if (tablePermissions.ViewAll || tablePermissions.ViewBasic) {
fetchTableDetail();
setActiveTab(getCurrentDatasetTab(tab));
getEntityFeedCount();
}
}, [tablePermissions]);
@ -464,17 +377,7 @@ const DatasetDetailsPage: FunctionComponent = () => {
}, [tableDetails]);
useEffect(() => {
fetchResourcePermission(tableFQN);
}, [tableFQN]);
useEffect(() => {
setTableFQN(
getPartialNameFromTableFQN(
datasetFQN,
[FqnPart.Service, FqnPart.Database, FqnPart.Schema, FqnPart.Table],
FQN_SEPARATOR_CHAR
)
);
fetchResourcePermission(datasetFQN);
}, [datasetFQN]);
if (isLoading) {
@ -483,7 +386,7 @@ const DatasetDetailsPage: FunctionComponent = () => {
if (isError) {
return (
<ErrorPlaceHolder>
{getEntityMissingError('table', tableFQN)}
{getEntityMissingError('table', datasetFQN)}
</ErrorPlaceHolder>
);
}
@ -494,10 +397,8 @@ const DatasetDetailsPage: FunctionComponent = () => {
return (
<DatasetDetails
activeTab={activeTab}
createThread={createThread}
dataModel={tableDetails.dataModel}
datasetFQN={tableFQN}
deletePostHandler={deletePostHandler}
entityFieldTaskCount={entityFieldTaskCount}
entityFieldThreadCount={entityFieldThreadCount}
@ -506,12 +407,9 @@ const DatasetDetailsPage: FunctionComponent = () => {
fetchFeedHandler={handleFeedFetchFromFeedList}
followTableHandler={followTable}
isEntityThreadLoading={isEntityThreadLoading}
isSampleDataLoading={isSampleDataLoading}
isTableProfileLoading={isTableProfileLoading}
paging={paging}
postFeedHandler={postFeedHandler}
sampleData={sampleData}
setActiveTabHandler={activeTabHandler}
tableDetails={tableDetails}
tableProfile={tableProfile}
unfollowTableHandler={unFollowTable}

View File

@ -130,7 +130,6 @@ jest.mock('components/DatasetDetails/DatasetDetails.component', () => {
handleAddColumnTestCase,
handleTestModeChange,
handleShowTestForm,
setActiveTabHandler,
qualityTestFormHandler,
settingsUpdateHandler,
loadNodeHandler,
@ -180,11 +179,6 @@ jest.mock('components/DatasetDetails/DatasetDetails.component', () => {
onClick={handleAddColumnTestCase}>
add column test
</button>
<button
data-testid="change-tab"
onClick={() => setActiveTabHandler(2)}>
change tab
</button>
<button
data-testid="qualityTestFormHandler"
onClick={() => qualityTestFormHandler(6, 'table', 'test')}>
@ -382,7 +376,6 @@ describe('Test DatasetDetails page', () => {
const testForm = await screen.findByTestId('test-form');
const addTableTest = await screen.findByTestId('add-table-test');
const addColumnTest = await screen.findByTestId('add-column-test');
const changeTab = await screen.findByTestId('change-tab');
const settingsUpdateHandler = await screen.findByTestId(
'settingsUpdateHandler'
);
@ -415,7 +408,6 @@ describe('Test DatasetDetails page', () => {
expect(testForm).toBeInTheDocument();
expect(addTableTest).toBeInTheDocument();
expect(addColumnTest).toBeInTheDocument();
expect(changeTab).toBeInTheDocument();
expect(settingsUpdateHandler).toBeInTheDocument();
expect(loadNodeHandler).toBeInTheDocument();
expect(addLineageHandler).toBeInTheDocument();
@ -436,7 +428,6 @@ describe('Test DatasetDetails page', () => {
fireEvent.click(testForm);
fireEvent.click(addTableTest);
fireEvent.click(addColumnTest);
fireEvent.click(changeTab);
fireEvent.click(settingsUpdateHandler);
fireEvent.click(loadNodeHandler);
fireEvent.click(addLineageHandler);

View File

@ -18,6 +18,7 @@ import MyData from 'components/MyData/MyData.component';
import { MyDataProps } from 'components/MyData/MyData.interface';
import NavBar from 'components/nav-bar/NavBar';
import Tour from 'components/tour/Tour';
import { EntityTabs } from 'enums/entity.enum';
import { SearchResponse } from 'interface/search.interface';
import { noop } from 'lodash';
import { observer } from 'mobx-react';
@ -60,9 +61,6 @@ const TourPage = () => {
AppState.currentTourPage
);
const [myDataSearchResult, setMyDataSearchResult] = useState(mockFeedData);
const [datasetActiveTab, setdatasetActiveTab] = useState(
AppState.activeTabforTourDatasetPage
);
const [explorePageCounts, setExplorePageCounts] = useState(exploreCount);
const [searchValue, setSearchValue] = useState('');
@ -101,17 +99,13 @@ const TourPage = () => {
useEffect(() => {
handleIsTourOpen(true);
AppState.currentTourPage = CurrentTourPageType.MY_DATA_PAGE;
AppState.activeTabforTourDatasetPage = 1;
AppState.activeTabforTourDatasetPage = EntityTabs.SCHEMA;
}, []);
useEffect(() => {
setCurrentPage(AppState.currentTourPage);
}, [AppState.currentTourPage]);
useEffect(() => {
setdatasetActiveTab(AppState.activeTabforTourDatasetPage);
}, [AppState.activeTabforTourDatasetPage]);
const getCurrentPage = (page: CurrentTourPageType) => {
switch (page) {
case CurrentTourPageType.MY_DATA_PAGE:
@ -161,9 +155,7 @@ const TourPage = () => {
case CurrentTourPageType.DATASET_PAGE:
return (
<DatasetDetails
activeTab={datasetActiveTab}
createThread={handleCountChange}
datasetFQN={mockDatasetData.datasetFQN}
deletePostHandler={handleCountChange}
entityFieldTaskCount={[]}
entityFieldThreadCount={[]}
@ -174,8 +166,6 @@ const TourPage = () => {
isEntityThreadLoading={false}
paging={{} as Paging}
postFeedHandler={handleCountChange}
sampleData={mockDatasetData.sampleData}
setActiveTabHandler={(tab) => setdatasetActiveTab(tab)}
tableDetails={mockDatasetData.tableDetails as unknown as Table}
tableProfile={
mockDatasetData.tableProfile as unknown as Table['profile']

View File

@ -61,7 +61,7 @@ import {
validEmailRegEx,
} from '../constants/regex.constants';
import { SIZE } from '../enums/common.enum';
import { EntityType, FqnPart, TabSpecificField } from '../enums/entity.enum';
import { EntityType, FqnPart } from '../enums/entity.enum';
import { FilterPatternEnum } from '../enums/filterPattern.enum';
import { ThreadTaskStatus, ThreadType } from '../generated/entity/feed/thread';
import { PipelineType } from '../generated/entity/services/ingestionPipelines/ingestionPipeline';
@ -417,23 +417,6 @@ export const isValidEmail = (email?: string) => {
return isValid;
};
export const getFields = (defaultFields: string, tabSpecificField: string) => {
if (!tabSpecificField) {
return defaultFields;
}
if (!defaultFields) {
return tabSpecificField;
}
if (
tabSpecificField === TabSpecificField.LINEAGE ||
tabSpecificField === TabSpecificField.ACTIVITY_FEED
) {
return defaultFields;
}
return `${defaultFields}, ${tabSpecificField}`;
};
export const getEntityMissingError = (entityType: string, fqn: string) => {
return (
<p>

View File

@ -11,103 +11,8 @@
* limitations under the License.
*/
import i18next from 'i18next';
import { TabSpecificField } from '../enums/entity.enum';
export const defaultFields = `${TabSpecificField.COLUMNS}, ${TabSpecificField.USAGE_SUMMARY},
${TabSpecificField.FOLLOWERS}, ${TabSpecificField.JOINS}, ${TabSpecificField.TAGS}, ${TabSpecificField.OWNER},
${TabSpecificField.DATAMODEL},${TabSpecificField.TABLE_CONSTRAINTS},${TabSpecificField.EXTENSION}`;
export const datasetTableTabs = [
{
name: i18next.t('label.schema'),
path: 'schema',
},
{
name: i18next.t('label.activity-feed-and-task-plural'),
path: 'activity_feed',
field: TabSpecificField.ACTIVITY_FEED,
},
{
name: i18next.t('label.sample-data'),
path: 'sample_data',
},
{
name: i18next.t('label.query-plural'),
path: 'table_queries',
},
{
name: i18next.t('label.profiler'),
path: 'profiler',
},
{
name: i18next.t('label.data-entity', {
entity: i18next.t('label.quality'),
}),
path: 'data-quality',
},
{
name: i18next.t('label.lineage'),
path: 'lineage',
field: TabSpecificField.LINEAGE,
},
{
name: i18next.t('label.dbt-lowercase'),
path: 'dbt',
},
{
name: i18next.t('label.custom-property-plural'),
path: 'custom_properties',
},
];
export const getCurrentDatasetTab = (tab: string) => {
let currentTab = 1;
switch (tab) {
case 'activity_feed':
currentTab = 2;
break;
case 'sample_data':
currentTab = 3;
break;
case 'table_queries':
currentTab = 4;
break;
case 'profiler':
currentTab = 5;
break;
case 'data-quality':
currentTab = 6;
break;
case 'lineage':
currentTab = 7;
break;
case 'dbt':
currentTab = 8;
break;
case 'custom_properties':
currentTab = 9;
break;
case 'schema':
default:
currentTab = 1;
break;
}
return currentTab;
};

View File

@ -11,12 +11,12 @@
* limitations under the License.
*/
import { EntityTabs } from 'enums/entity.enum';
import i18next from 'i18next';
import React from 'react';
import AppState from '../AppState';
import { CurrentTourPageType } from '../enums/tour.enum';
import { Transi18next } from './CommonUtils';
import { getCurrentDatasetTab } from './DatasetDetailsUtils';
export const getSteps = (value: string, clearSearchTerm: () => void) => {
return [
@ -116,7 +116,7 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => {
</p>
),
actionType: 'click',
selector: '[data-testid="BigQuery-dim_address"]',
selector: '[data-testid="sample_data-dim_address"]',
beforeNext: () => {
AppState.currentTourPage = CurrentTourPageType.DATASET_PAGE;
},
@ -186,7 +186,7 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => {
},
{
beforePrev: () => {
AppState.activeTabforTourDatasetPage = getCurrentDatasetTab('schema');
AppState.activeTabforTourDatasetPage = EntityTabs.SCHEMA;
},
actionType: 'click',
content: () => (
@ -200,10 +200,9 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => {
/>
</p>
),
selector: '#sampleData',
selector: '[data-testid="Sample Data"]',
beforeNext: () => {
AppState.activeTabforTourDatasetPage =
getCurrentDatasetTab('sample_data');
AppState.activeTabforTourDatasetPage = EntityTabs.SAMPLE_DATA;
},
},
{
@ -218,15 +217,14 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => {
/>
</p>
),
selector: '#sampleDataDetails',
selector: '#tab-details',
},
{
beforePrev: () => {
AppState.activeTabforTourDatasetPage =
getCurrentDatasetTab('sample_data');
AppState.activeTabforTourDatasetPage = EntityTabs.SAMPLE_DATA;
},
beforeNext: () => {
AppState.activeTabforTourDatasetPage = getCurrentDatasetTab('profiler');
AppState.activeTabforTourDatasetPage = EntityTabs.PROFILER;
},
actionType: 'click',
content: () => (
@ -240,7 +238,7 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => {
/>
</p>
),
selector: '#profilerDataQuality',
selector: '[data-testid="Profiler & Data Quality"]',
},
{
content: () => (
@ -257,14 +255,14 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => {
</p>
),
stepInteraction: false,
selector: '#profilerDetails',
selector: '#tab-details',
},
{
beforePrev: () => {
AppState.activeTabforTourDatasetPage = getCurrentDatasetTab('profiler');
AppState.activeTabforTourDatasetPage = EntityTabs.PROFILER;
},
beforeNext: () => {
AppState.activeTabforTourDatasetPage = getCurrentDatasetTab('lineage');
AppState.activeTabforTourDatasetPage = EntityTabs.LINEAGE;
},
actionType: 'click',
content: () => (
@ -278,7 +276,7 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => {
/>
</p>
),
selector: '#lineage',
selector: '[data-testid="Lineage"]',
},
{
content: () => (
@ -293,7 +291,7 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => {
</p>
),
stepInteraction: false,
selector: '#lineageDetails',
selector: '#tab-details',
},
];
};