added sample data support to show sample data on dataset details page. (#274)

* added sample data support to show sample data on dataset details page.

* addressing review comment

* minor fix

* minor fix
This commit is contained in:
Sachin Chaurasiya 2021-08-24 23:58:33 +05:30 committed by GitHub
parent 6ba87a5297
commit a049ec9a4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 264 additions and 254 deletions

View File

@ -15,52 +15,36 @@
* limitations under the License. * limitations under the License.
*/ */
import classNames from 'classnames';
import { lowerCase } from 'lodash';
import React, { FunctionComponent } from 'react'; import React, { FunctionComponent } from 'react';
import { isEven } from '../../utils/CommonUtils'; import { isEven } from '../../utils/CommonUtils';
type Columns = { name: string; dataType: string };
// type Column = {
// columnConstraint?: string;
// columnDataType: string;
// description: string;
// name: string;
// ordinalPosition: number;
// piiTags?: Array<string>;
// };
type MockColumn = {
columnId: number;
name: string;
columnDataType: string;
description?: string;
selected?: boolean;
piiTags?: Array<string>;
};
type Props = { type Props = {
columns: Array<MockColumn>; sampleData: {
data: Array<Record<string, string>>; columns: Array<Columns>;
rows: Array<Array<string>>;
};
}; };
const SampleDataTable: FunctionComponent<Props> = ({ const SampleDataTable: FunctionComponent<Props> = ({ sampleData }: Props) => {
columns,
data,
}: Props) => {
return ( return (
<div className="tw-table-responsive"> <div className="tw-table-responsive">
<table <table
className="tw-min-w-max tw-w-full tw-table-auto" className="tw-min-w-max tw-w-full tw-table-auto"
data-testid="sample-data-table"> data-testid="sample-data-table">
<thead> <thead>
<tr className="tw-border tw-border-main tw-bg-gray-200 tw-text-gray-600 tw-text-sm tw-leading-normal"> <tr className="tableHead-row">
{columns.map((column) => { {sampleData.columns.map((column) => {
return ( return (
<th <th
className="tw-py-3 tw-px-6 tw-text-left tw-whitespace-nowrap" className="tableHead-cell"
data-testid="column-name" data-testid="column-name"
key={column.columnId}> key={column.name}>
<p className="tw-mb-2">{column.name}</p> {column.name}
<span className={'sl-label ' + column.columnDataType}> <span className="tw-py-0.5 tw-px-1 tw-ml-1 tw-rounded tw-text-grey-muted">
{column.columnDataType} ({lowerCase(column.dataType)})
</span> </span>
</th> </th>
); );
@ -68,21 +52,22 @@ const SampleDataTable: FunctionComponent<Props> = ({
</tr> </tr>
</thead> </thead>
<tbody className="tw-text-gray-600 tw-text-sm"> <tbody className="tw-text-gray-600 tw-text-sm">
{data.map((row, rowIndex) => { {sampleData.rows.map((row, rowIndex) => {
return ( return (
<tr <tr
className={`tw-border tw-border-main tableBody-row ${ className={classNames(
!isEven(rowIndex + 1) && 'odd-row' 'tableBody-row',
}`} !isEven(rowIndex + 1) ? 'odd-row' : null
)}
data-testid="row" data-testid="row"
key={rowIndex}> key={rowIndex}>
{columns.map((column) => { {row.map((data) => {
return ( return (
<td <td
className="tw-py-3 tw-px-6 tw-text-left" className="tableBody-cell"
data-testid="cell" data-testid="cell"
key={column.columnId}> key={data}>
{row[column.name]} {data}
</td> </td>
); );
})} })}

View File

@ -16,9 +16,8 @@
*/ */
import { lowerCase } from 'lodash'; import { lowerCase } from 'lodash';
import { ColumnJoins, MockColumn, TableColumn } from 'Models'; import { ColumnJoins, SampleData, TableColumn } from 'Models';
import React, { FunctionComponent, useEffect, useState } from 'react'; import React, { FunctionComponent, useState } from 'react';
import { fetchData } from '../../pages/my-data-details/index.mock';
import Searchbar from '../common/searchbar/Searchbar'; import Searchbar from '../common/searchbar/Searchbar';
import SampleDataTable from './SampleDataTable'; import SampleDataTable from './SampleDataTable';
import SchemaTable from './SchemaTable'; import SchemaTable from './SchemaTable';
@ -27,55 +26,69 @@ type Props = {
columns: Array<TableColumn>; columns: Array<TableColumn>;
joins: Array<ColumnJoins>; joins: Array<ColumnJoins>;
onUpdate: (columns: Array<TableColumn>) => void; onUpdate: (columns: Array<TableColumn>) => void;
sampleData: SampleData;
}; };
const SchemaTab: FunctionComponent<Props> = ({ const SchemaTab: FunctionComponent<Props> = ({
columns, columns,
joins, joins,
onUpdate, onUpdate,
sampleData,
}: Props) => { }: Props) => {
const [data, setData] = useState<Array<Record<string, string>>>([]);
const [searchText, setSearchText] = useState(''); const [searchText, setSearchText] = useState('');
const [checkedValue] = useState('schema'); const [checkedValue, setCheckedValue] = useState('schema');
const [mockColumns, setMockColumns] = useState<Array<MockColumn>>([]);
useEffect(() => {
const schemaDetails = fetchData();
setData(schemaDetails.data);
// TODO remove this to show actual columns from tables api
setMockColumns(schemaDetails.columns);
}, []);
const handleSearchAction = (searchValue: string) => { const handleSearchAction = (searchValue: string) => {
setSearchText(searchValue); setSearchText(searchValue);
}; };
// const handleToggleChange = (value: string) => { const handleToggleChange = (value: string) => {
// setCheckedValue(value); setCheckedValue(value);
// }; };
// const getToggleButtonClasses = (type: string): string => { const getToggleButtonClasses = (type: string): string => {
// return ( return (
// 'tw-flex-1 tw-text-primary tw-font-medium tw-border tw-border-transparent 'tw-flex-1 tw-text-primary tw-font-medium tw-border tw-border-transparent tw-rounded tw-py-1 tw-px-2 focus:tw-outline-none' +
// tw-rounded-md tw-py-1 tw-px-2 focus:tw-outline-none' + (type === checkedValue ? ' tw-bg-primary-hover-lite tw-border-main' : '')
// (type === checkedValue ? ' tw-bg-blue-100 tw-border-blue-100' : '') );
// ); };
// };
const getSampleDataWithType = () => {
const updatedColumns = sampleData.columns.map((column) => {
const matchedColumn = columns.find((col) => col.name === column);
if (matchedColumn) {
return {
name: matchedColumn.name,
dataType: matchedColumn.columnDataType,
};
} else {
return {
name: column,
dataType: '',
};
}
});
return { columns: updatedColumns, rows: sampleData.rows };
};
return ( return (
<div> <div>
<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>
<Searchbar {checkedValue === 'schema' && (
placeholder="Find in table..." <Searchbar
searchValue={searchText} placeholder="Find in table..."
typingInterval={1500} searchValue={searchText}
onSearch={handleSearchAction} typingInterval={1500}
/> onSearch={handleSearchAction}
/>
)}
</div> </div>
{/* <div className="tw-col-span-2 tw-text-right"> <div className="tw-col-span-2 tw-text-right tw-mb-4">
<div <div
className="tw-w-60 tw-inline-flex tw-border tw-border-blue-100 className="tw-w-60 tw-inline-flex tw-border tw-border-main
tw-text-sm tw-rounded-md tw-h-8 tw-bg-white"> tw-text-sm tw-rounded-md tw-h-8 tw-bg-white">
<button <button
className={getToggleButtonClasses('schema')} className={getToggleButtonClasses('schema')}
@ -86,11 +99,13 @@ const SchemaTab: FunctionComponent<Props> = ({
<button <button
className={getToggleButtonClasses('sample-data')} className={getToggleButtonClasses('sample-data')}
data-testid="sample-data-button" data-testid="sample-data-button"
onClick={() => handleToggleChange('sample-data')}> onClick={() => {
handleToggleChange('sample-data');
}}>
Sample Data Sample Data
</button> </button>
</div> </div>
</div> */} </div>
</div> </div>
<div className="row"> <div className="row">
<div className="col-sm-12"> <div className="col-sm-12">
@ -102,20 +117,7 @@ const SchemaTab: FunctionComponent<Props> = ({
onUpdate={onUpdate} onUpdate={onUpdate}
/> />
) : ( ) : (
<SampleDataTable <SampleDataTable sampleData={getSampleDataWithType()} />
columns={mockColumns
.filter((column) => {
return column;
})
.map((column) => {
return {
columnId: column.columnId,
name: column.name,
columnDataType: column.columnDataType,
};
})}
data={data}
/>
)} )}
</div> </div>
</div> </div>

View File

@ -231,177 +231,182 @@ const SchemaTable: FunctionComponent<Props> = ({
return ( return (
<> <>
<table className="tw-w-full" data-testid="schema-table"> <div className="tw-table-responsive">
<thead> <table className="tw-w-full" data-testid="schema-table">
<tr className="tableHead-row"> <thead>
<th className="tableHead-cell">Column Name</th> <tr className="tableHead-row">
<th className="tableHead-cell">Data Type</th> <th className="tableHead-cell">Column Name</th>
<th className="tableHead-cell">Description</th> <th className="tableHead-cell">Data Type</th>
<th className="tableHead-cell tw-w-60">Tags</th> <th className="tableHead-cell">Description</th>
</tr> <th className="tableHead-cell tw-w-60">Tags</th>
</thead> </tr>
<tbody className="tableBody"> </thead>
{searchedColumns.map((column, index) => { <tbody className="tableBody">
return ( {searchedColumns.map((column, index) => {
<tr return (
className={classNames( <tr
'tableBody-row', className={classNames(
!isEven(index + 1) ? 'odd-row' : null 'tableBody-row',
)} !isEven(index + 1) ? 'odd-row' : null
data-testid="column" )}
id={column.name} data-testid="column"
key={index} id={column.name}
ref={rowRef}> key={index}
<td className="tw-relative tableBody-cell"> ref={rowRef}>
{getConstraintIcon(column.columnConstraint)} <td className="tw-relative tableBody-cell">
<span>{column.name}</span> {getConstraintIcon(column.columnConstraint)}
</td> <span>{column.name}</span>
</td>
<td className="tableBody-cell"> <td className="tableBody-cell">
<span> <span>
{column.columnDataType {column.columnDataType
? lowerCase(getDataTypeString(column.columnDataType)) ? lowerCase(getDataTypeString(column.columnDataType))
: ''} : ''}
</span> </span>
</td> </td>
<td className="tw-group tableBody-cell tw-relative"> <td className="tw-group tableBody-cell tw-relative">
<div> <div>
<div <div
className="tw-cursor-pointer hover:tw-underline tw-flex" className="tw-cursor-pointer hover:tw-underline tw-flex"
data-testid="description" data-testid="description"
id={`column-description-${index}`} id={`column-description-${index}`}
onClick={() => handleEditColumn(column, index)}> onClick={() => handleEditColumn(column, index)}>
<div> <div>
{column.description ? ( {column.description ? (
<RichTextEditorPreviewer <RichTextEditorPreviewer
markdown={column.description} markdown={column.description}
/>
) : (
<span className="tw-no-description">
No description added
</span>
)}
</div>
<button className="tw-self-start tw-w-8 tw-h-auto tw-opacity-0 tw-ml-1 group-hover:tw-opacity-100 focus:tw-outline-none">
<SVGIcons
alt="edit"
icon="icon-edit"
title="edit"
width="10px"
/> />
) : ( </button>
<span className="tw-no-description"> </div>
No description added
{checkIfJoinsAvailable(column.name) && (
<div className="tw-mt-3">
<span className="tw-text-gray-400 tw-mr-1">
Frequently joined columns:
</span> </span>
)} {getFrequentlyJoinedWithColumns(column.name)
</div> .slice(0, 3)
<button className="tw-self-start tw-w-8 tw-h-auto tw-opacity-0 tw-ml-1 group-hover:tw-opacity-100 focus:tw-outline-none"> .map((columnJoin, index) => (
<SVGIcons <Fragment key={index}>
alt="edit" {index > 0 && (
icon="icon-edit" <span className="tw-mr-1">,</span>
title="edit" )}
width="10px" <Link
/> className="link-text"
</button> to={getDatasetDetailsPath(
</div> getTableFQNFromColumnFQN(
{checkIfJoinsAvailable(column.name) && ( columnJoin.fullyQualifiedName
<div className="tw-mt-3"> ),
<span className="tw-text-gray-400 tw-mr-1"> getPartialNameFromFQN(
Frequently joined columns: columnJoin.fullyQualifiedName,
</span> ['column']
{getFrequentlyJoinedWithColumns(column.name) )
.slice(0, 3) )}
.map((columnJoin, index) => ( onClick={handleClick}>
<Fragment key={index}> {getPartialNameFromFQN(
{index > 0 && <span className="tw-mr-1">,</span>}
<Link
className="link-text"
to={getDatasetDetailsPath(
getTableFQNFromColumnFQN(
columnJoin.fullyQualifiedName
),
getPartialNameFromFQN(
columnJoin.fullyQualifiedName, columnJoin.fullyQualifiedName,
['column'] ['database', 'table', 'column']
) )}
)} </Link>
onClick={handleClick}> </Fragment>
{getPartialNameFromFQN( ))}
columnJoin.fullyQualifiedName, {getFrequentlyJoinedWithColumns(column.name).length >
['database', 'table', 'column'] 3 && (
)} <PopOver
</Link> html={
</Fragment> <div className="tw-text-left">
))} {getFrequentlyJoinedWithColumns(column.name)
{getFrequentlyJoinedWithColumns(column.name).length > ?.slice(3)
3 && ( .map((columnJoin, index) => (
<PopOver <Fragment key={index}>
html={ <a
<div className="tw-text-left"> className="link-text tw-block tw-py-1"
{getFrequentlyJoinedWithColumns(column.name) href={getDatasetDetailsPath(
?.slice(3) getTableFQNFromColumnFQN(
.map((columnJoin, index) => ( columnJoin.fullyQualifiedName
<Fragment key={index}> ),
<a getPartialNameFromFQN(
className="link-text tw-block tw-py-1" columnJoin.fullyQualifiedName,
href={getDatasetDetailsPath( ['column']
getTableFQNFromColumnFQN( )
columnJoin.fullyQualifiedName )}
), onClick={handleClick}>
getPartialNameFromFQN( {getPartialNameFromFQN(
columnJoin.fullyQualifiedName, columnJoin.fullyQualifiedName,
['column'] ['database', 'table', 'column']
) )}
)} </a>
onClick={handleClick}> </Fragment>
{getPartialNameFromFQN( ))}
columnJoin.fullyQualifiedName, </div>
['database', 'table', 'column'] }
)} position="bottom"
</a> theme="light"
</Fragment> trigger="click">
))} <span className="show-more tw-ml-1">...</span>
</div> </PopOver>
} )}
position="bottom" </div>
theme="light" )}
trigger="click"> </div>
<span className="show-more tw-ml-1">...</span> </td>
</PopOver> <td
)} className="tw-group tw-relative tableBody-cell"
</div> onClick={() => {
)} if (!editColumnTag) {
</div> handleEditColumnTag(column, index);
</td> }
<td
className="tw-group tw-relative tableBody-cell"
onClick={() => {
if (!editColumnTag) {
handleEditColumnTag(column, index);
}
}}>
<TagsContainer
editable={editColumnTag?.index === index}
selectedTags={column.tags}
tagList={allTags}
onCancel={() => {
handleTagSelection();
}}
onSelectionChange={(tags) => {
handleTagSelection(tags);
}}> }}>
{column.tags.length ? ( <TagsContainer
<button className="tw-opacity-0 tw-ml-1 group-hover:tw-opacity-100 focus:tw-outline-none"> editable={editColumnTag?.index === index}
<SVGIcons selectedTags={column.tags}
alt="edit" tagList={allTags}
icon="icon-edit" onCancel={() => {
title="edit" handleTagSelection();
width="10px" }}
/> onSelectionChange={(tags) => {
</button> handleTagSelection(tags);
) : ( }}>
<span className="tw-opacity-0 group-hover:tw-opacity-100"> {column.tags.length ? (
<Tags <button className="tw-opacity-0 tw-ml-1 group-hover:tw-opacity-100 focus:tw-outline-none">
className="tw-border-main" <SVGIcons
tag="+ Add new tag" alt="edit"
type="outlined" icon="icon-edit"
/> title="edit"
</span> width="10px"
)} />
</TagsContainer> </button>
</td> ) : (
</tr> <span className="tw-opacity-0 group-hover:tw-opacity-100">
); <Tags
})} className="tw-border-main"
</tbody> tag="+ Add new tag"
</table> type="outlined"
/>
</span>
)}
</TagsContainer>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
{editColumn && ( {editColumn && (
<ModalWithMarkdownEditor <ModalWithMarkdownEditor
header={`Edit column: "${editColumn.column.name}"`} header={`Edit column: "${editColumn.column.name}"`}

View File

@ -311,4 +311,9 @@ declare module 'Models' {
name: string; name: string;
}; };
}; };
export type SampleData = {
columns: Array<string>;
rows: Array<Array<string>>;
};
} }

View File

@ -20,7 +20,13 @@ import classNames from 'classnames';
import { compare } from 'fast-json-patch'; import { compare } from 'fast-json-patch';
import { isEqual, isNil } from 'lodash'; import { isEqual, isNil } from 'lodash';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { ColumnTags, TableColumn, TableDetail, TableJoinsData } from 'Models'; import {
ColumnTags,
SampleData,
TableColumn,
TableDetail,
TableJoinsData,
} from 'Models';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { getDatabase } from '../../axiosAPIs/databaseAPI'; import { getDatabase } from '../../axiosAPIs/databaseAPI';
@ -89,6 +95,10 @@ const MyDataDetailsPage = () => {
const [usage, setUsage] = useState(''); const [usage, setUsage] = useState('');
const [weeklyUsageCount, setWeeklyUsageCount] = useState(''); const [weeklyUsageCount, setWeeklyUsageCount] = useState('');
const [columns, setColumns] = useState<Array<TableColumn>>([]); const [columns, setColumns] = useState<Array<TableColumn>>([]);
const [sampleData, setSampleData] = useState<SampleData>({
columns: [],
rows: [],
});
const [tableTags, setTableTags] = useState<Array<ColumnTags>>([]); const [tableTags, setTableTags] = useState<Array<ColumnTags>>([]);
const [isEdit, setIsEdit] = useState(false); const [isEdit, setIsEdit] = useState(false);
const [owner, setOwner] = useState<TableDetail['owner']>(); const [owner, setOwner] = useState<TableDetail['owner']>();
@ -108,7 +118,7 @@ const MyDataDetailsPage = () => {
useEffect(() => { useEffect(() => {
getTableDetailsByFQN( getTableDetailsByFQN(
tableFQN, tableFQN,
'columns, database, usageSummary, followers, joins, tags, owner' 'columns, database, usageSummary, followers, joins, tags, owner,sampleData'
).then((res: AxiosResponse) => { ).then((res: AxiosResponse) => {
const { const {
description, description,
@ -122,6 +132,7 @@ const MyDataDetailsPage = () => {
followers, followers,
joins, joins,
tags, tags,
sampleData,
} = res.data; } = res.data;
setTableDetails(res.data); setTableDetails(res.data);
setTableId(id); setTableId(id);
@ -162,6 +173,7 @@ const MyDataDetailsPage = () => {
setDescription(description); setDescription(description);
setColumns(columns || []); setColumns(columns || []);
setSampleData(sampleData);
setTableTags(getTableTags(columns || [])); setTableTags(getTableTags(columns || []));
if (!isNil(usageSummary?.weeklyStats.percentileRank)) { if (!isNil(usageSummary?.weeklyStats.percentileRank)) {
const percentile = getUsagePercentile( const percentile = getUsagePercentile(
@ -529,6 +541,7 @@ const MyDataDetailsPage = () => {
<SchemaTab <SchemaTab
columns={columns} columns={columns}
joins={tableJoinData.columnJoins} joins={tableJoinData.columnJoins}
sampleData={sampleData}
onUpdate={onColumnsUpdate} onUpdate={onColumnsUpdate}
/> />
</div> </div>