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

View File

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

View File

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

View File

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