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 Entitylineage from '../EntityLineage/EntityLineage.component';
import FrequentlyJoinedTables from '../FrequentlyJoinedTables/FrequentlyJoinedTables.component'; import FrequentlyJoinedTables from '../FrequentlyJoinedTables/FrequentlyJoinedTables.component';
import ManageTab from '../ManageTab/ManageTab.component'; import ManageTab from '../ManageTab/ManageTab.component';
import SampleDataTable, {
SampleColumns,
} from '../SampleDataTable/SampleDataTable.component';
import SchemaEditor from '../schema-editor/SchemaEditor'; import SchemaEditor from '../schema-editor/SchemaEditor';
import SchemaTab from '../SchemaTab/SchemaTab.component'; import SchemaTab from '../SchemaTab/SchemaTab.component';
import TableProfiler from '../TableProfiler/TableProfiler.component'; import TableProfiler from '../TableProfiler/TableProfiler.component';
@ -164,6 +167,17 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
isHidden: !dataModel?.sql, isHidden: !dataModel?.sql,
position: 4, 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', name: 'Manage',
icon: { icon: {
@ -174,7 +188,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
}, },
isProtected: true, isProtected: true,
protectedState: !owner || hasEditAccess(), 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(() => { useEffect(() => {
if (isAuthDisabled && users.length && followers.length) { if (isAuthDisabled && users.length && followers.length) {
setFollowersData(followers); setFollowersData(followers);
@ -450,6 +487,11 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
</div> </div>
)} )}
{activeTab === 5 && ( {activeTab === 5 && (
<div className="tw-mt-4">
<SampleDataTable sampleData={getSampleDataWithType()} />
</div>
)}
{activeTab === 6 && (
<div className="tw-mt-4"> <div className="tw-mt-4">
<ManageTab <ManageTab
currentTier={tier?.tagFQN} currentTier={tier?.tagFQN}

View File

@ -13,7 +13,13 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { lowerCase } from 'lodash'; 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 { TableData } from '../../generated/entity/data/table';
import { isEven } from '../../utils/CommonUtils'; import { isEven } from '../../utils/CommonUtils';
@ -27,59 +33,108 @@ type Props = {
}; };
const SampleDataTable: FunctionComponent<Props> = ({ sampleData }: 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 ( return (
<div className="tw-table-responsive"> <div
{sampleData?.rows?.length && sampleData?.columns?.length ? ( className="tw-relative tw-flex tw-justify-between"
<table onScrollCapture={() => {
className="tw-min-w-max tw-w-full tw-table-auto" setScrollOffSet(tableRef.current?.scrollLeft ?? 0);
data-testid="sample-data-table"> }}>
<thead> {scrollHandle.left ? (
<tr className="tableHead-row"> <button
{sampleData.columns.map((column) => { 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 ( return (
<th <tr
className="tableHead-cell" className={classNames(
data-testid="column-name" 'tableBody-row',
key={column.name}> !isEven(rowIndex + 1) ? 'odd-row' : null
{column.name} )}
<span className="tw-py-0.5 tw-px-1 tw-ml-1 tw-rounded tw-text-grey-muted"> data-testid="row"
({lowerCase(column.dataType)}) key={rowIndex}>
</span> {row.map((data, index) => {
</th> return (
<td
className="tableBody-cell"
data-testid="cell"
key={index}>
{data ? data.toString() : '--'}
</td>
);
})}
</tr>
); );
})} })}
</tr> </tbody>
</thead> </table>
<tbody className="tw-text-gray-600 tw-text-sm"> ) : (
{sampleData?.rows?.map((row, rowIndex) => { <div className="tw-flex tw-justify-center tw-font-medium tw-items-center tw-border tw-border-main tw-rounded-md tw-p-8">
return ( No sample data available
<tr </div>
className={classNames( )}
'tableBody-row', </div>
!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>
)}
</div> </div>
); );
}; };

View File

@ -11,11 +11,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { isNil, isUndefined, lowerCase } from 'lodash'; import { lowerCase } from 'lodash';
import { DatasetSchemaTableTab } from 'Models'; import React, { Fragment, FunctionComponent, useState } from 'react';
import React, { FunctionComponent, useEffect, useState } from 'react';
import { useHistory, useParams } from 'react-router';
import { getDatasetTabPath } from '../../constants/constants';
import { import {
ColumnJoins, ColumnJoins,
Table, Table,
@ -23,9 +20,6 @@ import {
} from '../../generated/entity/data/table'; } from '../../generated/entity/data/table';
import Searchbar from '../common/searchbar/Searchbar'; import Searchbar from '../common/searchbar/Searchbar';
import EntityTable from '../EntityTable/EntityTable.component'; import EntityTable from '../EntityTable/EntityTable.component';
import SampleDataTable, {
SampleColumns,
} from '../SampleDataTable/SampleDataTable.component';
type Props = { type Props = {
owner?: Table['owner']; owner?: Table['owner'];
@ -42,127 +36,46 @@ const SchemaTab: FunctionComponent<Props> = ({
columns, columns,
joins, joins,
onUpdate, onUpdate,
sampleData,
columnName, columnName,
hasEditAccess, hasEditAccess,
owner, owner,
isReadOnly = false, isReadOnly = false,
}: Props) => { }: Props) => {
const history = useHistory();
const { datasetFQN: tableFQN, tab } = useParams() as Record<string, string>;
const [searchText, setSearchText] = useState(''); const [searchText, setSearchText] = useState('');
const [checkedValue, setCheckedValue] =
useState<DatasetSchemaTableTab>('schema');
const handleSearchAction = (searchValue: string) => { const handleSearchAction = (searchValue: string) => {
setSearchText(searchValue); 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 ( return (
<div> <Fragment>
<div className="tw-grid tw-grid-cols-3 tw-gap-x-2"> <div className="tw-grid tw-grid-cols-3 tw-gap-x-2">
<div> <div>
{checkedValue === 'schema' && ( <Searchbar
<Searchbar placeholder="Find in table..."
placeholder="Find in table..." searchValue={searchText}
searchValue={searchText} typingInterval={1500}
typingInterval={1500} onSearch={handleSearchAction}
onSearch={handleSearchAction} />
/>
)}
</div> </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>
<div className="row"> <div className="row">
{columns?.length > 0 ? ( {columns?.length > 0 ? (
<div className="col-sm-12"> <div className="col-sm-12">
{checkedValue === 'schema' ? ( <EntityTable
<EntityTable columnName={columnName}
columnName={columnName} hasEditAccess={Boolean(hasEditAccess)}
hasEditAccess={Boolean(hasEditAccess)} isReadOnly={isReadOnly}
isReadOnly={isReadOnly} joins={joins}
joins={joins} owner={owner}
owner={owner} searchText={lowerCase(searchText)}
searchText={lowerCase(searchText)} tableColumns={columns}
tableColumns={columns} onUpdate={onUpdate}
onUpdate={onUpdate} />
/>
) : (
<SampleDataTable sampleData={getSampleDataWithType()} />
)}
</div> </div>
) : null} ) : null}
</div> </div>
</div> </Fragment>
); );
}; };

View File

@ -28,6 +28,10 @@ export const datasetTableTabs = [
name: 'DBT', name: 'DBT',
path: 'dbt', path: 'dbt',
}, },
{
name: 'Sample Data',
path: 'sample_data',
},
{ {
name: 'Manage', name: 'Manage',
path: 'manage', path: 'manage',
@ -50,13 +54,17 @@ export const getCurrentDatasetTab = (tab: string) => {
break; break;
case 'manage': case 'sample_data':
currentTab = 5; currentTab = 5;
break; break;
case 'manage':
currentTab = 6;
break;
case 'schema': case 'schema':
case 'sample_data':
default: default:
currentTab = 1; 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 IconVersionWhite from '../assets/svg/version-white.svg';
import IconVersion from '../assets/svg/version.svg'; import IconVersion from '../assets/svg/version.svg';
import IconWarning from '../assets/svg/warning.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 = { type Props = {
alt: string; alt: string;
@ -199,6 +201,8 @@ export const Icons = {
PROFILERCOLOR: 'icon-profilercolor', PROFILERCOLOR: 'icon-profilercolor',
MANAGECOLOR: 'icon-managecolor', MANAGECOLOR: 'icon-managecolor',
SEARCHV1COLOR: 'icon-searchv1color', SEARCHV1COLOR: 'icon-searchv1color',
SAMPLE_DATA: 'sample-data',
SAMPLE_DATA_COLOR: 'sample-data-color',
}; };
const SVGIcons: FunctionComponent<Props> = ({ const SVGIcons: FunctionComponent<Props> = ({
@ -568,6 +572,14 @@ const SVGIcons: FunctionComponent<Props> = ({
case Icons.SEARCHV1COLOR: case Icons.SEARCHV1COLOR:
IconComponent = IconSearchV1Color; IconComponent = IconSearchV1Color;
break;
case Icons.SAMPLE_DATA:
IconComponent = IconSampleData;
break;
case Icons.SAMPLE_DATA_COLOR:
IconComponent = IconSampleDataColor;
break; break;
default: default: