UI : Moving Sample data to its separate tab. (#2045)

* UI : Moving Sample data to its separate tab.

* Adding horizontal scrolling indicator for sample table
This commit is contained in:
Sachin Chaurasiya 2022-01-05 21:50:57 +05:30 committed by GitHub
parent dea0f2117d
commit 83c452431e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 192 additions and 160 deletions

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="512" height="512" x="0" y="0" viewBox="0 0 32 32" style="enable-background:new 0 0 512 512" xml:space="preserve" class=""><g><g xmlns="http://www.w3.org/2000/svg" id="Layer_2" data-name="Layer 2"><path d="m26.4 31h-16.63a2.69 2.69 0 0 1 -2.69-2.69v-24.62a2.69 2.69 0 0 1 2.69-2.69h7.63a2.78 2.78 0 0 1 1.85.71l8.9 8a2.79 2.79 0 0 1 .93 2.08v16.52a2.69 2.69 0 0 1 -2.68 2.69zm-16.63-28a.69.69 0 0 0 -.69.69v24.62a.69.69 0 0 0 .69.69h16.63a.69.69 0 0 0 .69-.69v-16.57a.78.78 0 0 0 -.26-.58l-8.9-8a.78.78 0 0 0 -.53-.16z" fill="#7147E8" data-original="#000000" class=""></path><path d="m28.08 11.94h-8.81a2.19 2.19 0 0 1 -2.19-2.19v-7.19h2v7.19a.19.19 0 0 0 .19.19h8.81z" fill="#7147E8" data-original="#000000" class=""></path><rect height="2" rx="1" width="10" x="13.42" y="24" fill="#7147E8" data-original="#000000" class=""></rect><rect height="2" rx="1" width="4" x="13.42" y="19.69" fill="#7147E8" data-original="#000000" class=""></rect><path d="m5.41 26.66a2.52 2.52 0 0 1 -2.49-2.51v-16.3a2.5 2.5 0 0 1 3-2.45l2.15.36-.33 2-2.17-.39a.53.53 0 0 0 -.46.09.5.5 0 0 0 -.19.39v16.3a.51.51 0 0 0 .61.49l2.26-.3.26 2-2.19.29a2.11 2.11 0 0 1 -.45.03z" fill="#7147E8" data-original="#000000" class=""></path></g></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="512" height="512" x="0" y="0" viewBox="0 0 32 32" style="enable-background:new 0 0 512 512" xml:space="preserve" class=""><g><g xmlns="http://www.w3.org/2000/svg" id="Layer_2" data-name="Layer 2"><path d="m26.4 31h-16.63a2.69 2.69 0 0 1 -2.69-2.69v-24.62a2.69 2.69 0 0 1 2.69-2.69h7.63a2.78 2.78 0 0 1 1.85.71l8.9 8a2.79 2.79 0 0 1 .93 2.08v16.52a2.69 2.69 0 0 1 -2.68 2.69zm-16.63-28a.69.69 0 0 0 -.69.69v24.62a.69.69 0 0 0 .69.69h16.63a.69.69 0 0 0 .69-.69v-16.57a.78.78 0 0 0 -.26-.58l-8.9-8a.78.78 0 0 0 -.53-.16z" fill="#6b7280" data-original="#000000" class=""></path><path d="m28.08 11.94h-8.81a2.19 2.19 0 0 1 -2.19-2.19v-7.19h2v7.19a.19.19 0 0 0 .19.19h8.81z" fill="#6b7280" data-original="#000000" class=""></path><rect height="2" rx="1" width="10" x="13.42" y="24" fill="#6b7280" data-original="#000000" class=""></rect><rect height="2" rx="1" width="4" x="13.42" y="19.69" fill="#6b7280" data-original="#000000" class=""></rect><path d="m5.41 26.66a2.52 2.52 0 0 1 -2.49-2.51v-16.3a2.5 2.5 0 0 1 3-2.45l2.15.36-.33 2-2.17-.39a.53.53 0 0 0 -.46.09.5.5 0 0 0 -.19.39v16.3a.51.51 0 0 0 .61.49l2.26-.3.26 2-2.19.29a2.11 2.11 0 0 1 -.45.03z" fill="#6b7280" data-original="#000000" class=""></path></g></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -40,6 +40,9 @@ import PageContainer from '../containers/PageContainer';
import Entitylineage from '../EntityLineage/EntityLineage.component';
import FrequentlyJoinedTables from '../FrequentlyJoinedTables/FrequentlyJoinedTables.component';
import ManageTab from '../ManageTab/ManageTab.component';
import SampleDataTable, {
SampleColumns,
} from '../SampleDataTable/SampleDataTable.component';
import SchemaEditor from '../schema-editor/SchemaEditor';
import SchemaTab from '../SchemaTab/SchemaTab.component';
import TableProfiler from '../TableProfiler/TableProfiler.component';
@ -164,6 +167,17 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
isHidden: !dataModel?.sql,
position: 4,
},
{
name: 'Sample Data',
icon: {
alt: 'sample_data',
name: 'sample-data',
title: 'Sample Data',
selectedName: 'sample-data-color',
},
isProtected: false,
position: 5,
},
{
name: 'Manage',
icon: {
@ -174,7 +188,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
},
isProtected: true,
protectedState: !owner || hasEditAccess(),
position: 5,
position: 6,
},
];
@ -331,6 +345,29 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
}
};
const getSampleDataWithType = () => {
const updatedColumns = sampleData?.columns?.map((column) => {
const matchedColumn = columns.find((col) => col.name === column);
if (matchedColumn) {
return {
name: matchedColumn.name,
dataType: matchedColumn.dataType,
};
} else {
return {
name: column,
dataType: '',
};
}
});
return {
columns: updatedColumns as SampleColumns[] | undefined,
rows: sampleData?.rows,
};
};
useEffect(() => {
if (isAuthDisabled && users.length && followers.length) {
setFollowersData(followers);
@ -450,6 +487,11 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
</div>
)}
{activeTab === 5 && (
<div className="tw-mt-4">
<SampleDataTable sampleData={getSampleDataWithType()} />
</div>
)}
{activeTab === 6 && (
<div className="tw-mt-4">
<ManageTab
currentTier={tier?.tagFQN}

View File

@ -13,7 +13,13 @@
import classNames from 'classnames';
import { lowerCase } from 'lodash';
import React, { FunctionComponent } from 'react';
import React, {
FunctionComponent,
useEffect,
useLayoutEffect,
useRef,
useState,
} from 'react';
import { TableData } from '../../generated/entity/data/table';
import { isEven } from '../../utils/CommonUtils';
@ -27,59 +33,108 @@ type Props = {
};
const SampleDataTable: FunctionComponent<Props> = ({ sampleData }: Props) => {
const tableRef = useRef<HTMLDivElement>(null);
const [scrollOffset, setScrollOffSet] = useState<number>(0);
const [containerWidth, setContainerWidth] = useState<number>(0);
const [scrollHandle, setScrollHandle] = useState<{
left: boolean;
right: boolean;
}>({ left: true, right: true });
const scrollHandler = (scrollOffset: number) => {
if (tableRef.current) {
tableRef.current.scrollLeft += scrollOffset;
setScrollOffSet(tableRef.current.scrollLeft);
}
};
useLayoutEffect(() => {
setContainerWidth(
(tableRef.current?.scrollWidth ?? 0) -
(tableRef.current?.clientWidth ?? 0)
);
}, []);
useEffect(() => {
const rFlag = scrollOffset !== containerWidth;
const lFlag = scrollOffset > 0;
setScrollHandle((pre) => ({ ...pre, right: rFlag, left: lFlag }));
}, [scrollOffset, containerWidth]);
return (
<div className="tw-table-responsive">
{sampleData?.rows?.length && sampleData?.columns?.length ? (
<table
className="tw-min-w-max tw-w-full tw-table-auto"
data-testid="sample-data-table">
<thead>
<tr className="tableHead-row">
{sampleData.columns.map((column) => {
<div
className="tw-relative tw-flex tw-justify-between"
onScrollCapture={() => {
setScrollOffSet(tableRef.current?.scrollLeft ?? 0);
}}>
{scrollHandle.left ? (
<button
className="tw-border tw-border-main tw-fixed tw-left-7 tw-top-2/3 tw-rounded-full tw-shadow-md tw-z-50 tw-bg-body-main tw-w-8 tw-h-8"
onClick={() => scrollHandler(-50)}>
<i className="fas fa-chevron-left tw-text-grey-muted" />
</button>
) : null}
{scrollHandle.right ? (
<button
className="tw-border tw-border-main tw-fixed tw-right-7 tw-top-2/3 tw-rounded-full tw-shadow-md tw-z-50 tw-bg-body-main tw-w-8 tw-h-8"
onClick={() => scrollHandler(50)}>
<i className="fas fa-chevron-right tw-text-grey-muted" />
</button>
) : null}
<div className="tw-table-responsive" ref={tableRef}>
{sampleData?.rows?.length && sampleData?.columns?.length ? (
<table
className="tw-min-w-max tw-w-full tw-table-auto"
data-testid="sample-data-table">
<thead>
<tr className="tableHead-row">
{sampleData.columns.map((column) => {
return (
<th
className="tableHead-cell"
data-testid="column-name"
key={column.name}>
{column.name}
<span className="tw-py-0.5 tw-px-1 tw-ml-1 tw-rounded tw-text-grey-muted">
({lowerCase(column.dataType)})
</span>
</th>
);
})}
</tr>
</thead>
<tbody className="tw-text-gray-600 tw-text-sm">
{sampleData?.rows?.map((row, rowIndex) => {
return (
<th
className="tableHead-cell"
data-testid="column-name"
key={column.name}>
{column.name}
<span className="tw-py-0.5 tw-px-1 tw-ml-1 tw-rounded tw-text-grey-muted">
({lowerCase(column.dataType)})
</span>
</th>
<tr
className={classNames(
'tableBody-row',
!isEven(rowIndex + 1) ? 'odd-row' : null
)}
data-testid="row"
key={rowIndex}>
{row.map((data, index) => {
return (
<td
className="tableBody-cell"
data-testid="cell"
key={index}>
{data ? data.toString() : '--'}
</td>
);
})}
</tr>
);
})}
</tr>
</thead>
<tbody className="tw-text-gray-600 tw-text-sm">
{sampleData?.rows?.map((row, rowIndex) => {
return (
<tr
className={classNames(
'tableBody-row',
!isEven(rowIndex + 1) ? 'odd-row' : null
)}
data-testid="row"
key={rowIndex}>
{row.map((data, index) => {
return (
<td
className="tableBody-cell"
data-testid="cell"
key={index}>
{data ? data.toString() : '--'}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
) : (
<div className="tw-flex tw-justify-center tw-font-medium tw-items-center tw-border tw-border-main tw-rounded-md tw-p-8">
No sample data available
</div>
)}
</tbody>
</table>
) : (
<div className="tw-flex tw-justify-center tw-font-medium tw-items-center tw-border tw-border-main tw-rounded-md tw-p-8">
No sample data available
</div>
)}
</div>
</div>
);
};

View File

@ -11,11 +11,8 @@
* limitations under the License.
*/
import { isNil, isUndefined, lowerCase } from 'lodash';
import { DatasetSchemaTableTab } from 'Models';
import React, { FunctionComponent, useEffect, useState } from 'react';
import { useHistory, useParams } from 'react-router';
import { getDatasetTabPath } from '../../constants/constants';
import { lowerCase } from 'lodash';
import React, { Fragment, FunctionComponent, useState } from 'react';
import {
ColumnJoins,
Table,
@ -23,9 +20,6 @@ import {
} from '../../generated/entity/data/table';
import Searchbar from '../common/searchbar/Searchbar';
import EntityTable from '../EntityTable/EntityTable.component';
import SampleDataTable, {
SampleColumns,
} from '../SampleDataTable/SampleDataTable.component';
type Props = {
owner?: Table['owner'];
@ -42,127 +36,46 @@ const SchemaTab: FunctionComponent<Props> = ({
columns,
joins,
onUpdate,
sampleData,
columnName,
hasEditAccess,
owner,
isReadOnly = false,
}: Props) => {
const history = useHistory();
const { datasetFQN: tableFQN, tab } = useParams() as Record<string, string>;
const [searchText, setSearchText] = useState('');
const [checkedValue, setCheckedValue] =
useState<DatasetSchemaTableTab>('schema');
const handleSearchAction = (searchValue: string) => {
setSearchText(searchValue);
};
const handleToggleChange = (value: DatasetSchemaTableTab) => {
setCheckedValue(value);
history.push({
pathname: getDatasetTabPath(tableFQN, value),
});
};
useEffect(() => {
if (tab && ['schema', 'sample_data'].includes(tab)) {
const activeTab = isUndefined(tab)
? 'schema'
: (tab as DatasetSchemaTableTab);
setCheckedValue(activeTab);
}
}, [tab]);
const getToggleButtonClasses = (type: string): string => {
return (
'tw-flex-1 tw-font-medium tw-border tw-border-transparent tw-rounded tw-py-1 tw-px-2 focus:tw-outline-none' +
(type === checkedValue
? ' tw-bg-primary tw-text-white tw-border-main'
: '')
);
};
const getSampleDataWithType = () => {
const updatedColumns = sampleData?.columns?.map((column) => {
const matchedColumn = columns.find((col) => col.name === column);
if (matchedColumn) {
return {
name: matchedColumn.name,
dataType: matchedColumn.dataType,
};
} else {
return {
name: column,
dataType: '',
};
}
});
return {
columns: updatedColumns as SampleColumns[] | undefined,
rows: sampleData?.rows,
};
};
return (
<div>
<Fragment>
<div className="tw-grid tw-grid-cols-3 tw-gap-x-2">
<div>
{checkedValue === 'schema' && (
<Searchbar
placeholder="Find in table..."
searchValue={searchText}
typingInterval={1500}
onSearch={handleSearchAction}
/>
)}
<Searchbar
placeholder="Find in table..."
searchValue={searchText}
typingInterval={1500}
onSearch={handleSearchAction}
/>
</div>
{!isReadOnly && !isNil(sampleData) ? (
<div className="tw-col-span-2 tw-text-right tw-mb-4">
<div
className="tw-w-60 tw-inline-flex tw-border tw-border-main
tw-text-sm tw-rounded-md tw-h-8 tw-bg-white">
<button
className={getToggleButtonClasses('schema')}
data-testid="schema-button"
onClick={() => handleToggleChange('schema')}>
Schema
</button>
<button
className={getToggleButtonClasses('sample_data')}
data-testid="sample-data-button"
onClick={() => {
handleToggleChange('sample_data');
}}>
Sample Data
</button>
</div>
</div>
) : null}
</div>
<div className="row">
{columns?.length > 0 ? (
<div className="col-sm-12">
{checkedValue === 'schema' ? (
<EntityTable
columnName={columnName}
hasEditAccess={Boolean(hasEditAccess)}
isReadOnly={isReadOnly}
joins={joins}
owner={owner}
searchText={lowerCase(searchText)}
tableColumns={columns}
onUpdate={onUpdate}
/>
) : (
<SampleDataTable sampleData={getSampleDataWithType()} />
)}
<EntityTable
columnName={columnName}
hasEditAccess={Boolean(hasEditAccess)}
isReadOnly={isReadOnly}
joins={joins}
owner={owner}
searchText={lowerCase(searchText)}
tableColumns={columns}
onUpdate={onUpdate}
/>
</div>
) : null}
</div>
</div>
</Fragment>
);
};

View File

@ -28,6 +28,10 @@ export const datasetTableTabs = [
name: 'DBT',
path: 'dbt',
},
{
name: 'Sample Data',
path: 'sample_data',
},
{
name: 'Manage',
path: 'manage',
@ -50,13 +54,17 @@ export const getCurrentDatasetTab = (tab: string) => {
break;
case 'manage':
case 'sample_data':
currentTab = 5;
break;
case 'manage':
currentTab = 6;
break;
case 'schema':
case 'sample_data':
default:
currentTab = 1;

View File

@ -101,6 +101,8 @@ import IconVersionBlack from '../assets/svg/version-black.svg';
import IconVersionWhite from '../assets/svg/version-white.svg';
import IconVersion from '../assets/svg/version.svg';
import IconWarning from '../assets/svg/warning.svg';
import IconSampleDataColor from '../assets/svg/sample-data-colored.svg';
import IconSampleData from '../assets/svg/sample-data.svg';
type Props = {
alt: string;
@ -199,6 +201,8 @@ export const Icons = {
PROFILERCOLOR: 'icon-profilercolor',
MANAGECOLOR: 'icon-managecolor',
SEARCHV1COLOR: 'icon-searchv1color',
SAMPLE_DATA: 'sample-data',
SAMPLE_DATA_COLOR: 'sample-data-color',
};
const SVGIcons: FunctionComponent<Props> = ({
@ -568,6 +572,14 @@ const SVGIcons: FunctionComponent<Props> = ({
case Icons.SEARCHV1COLOR:
IconComponent = IconSearchV1Color;
break;
case Icons.SAMPLE_DATA:
IconComponent = IconSampleData;
break;
case Icons.SAMPLE_DATA_COLOR:
IconComponent = IconSampleDataColor;
break;
default: