Merge branch 'fix/table-bugs' into 'release'

Fixing table bugs

This MR fixes a bunch of bugs associated with the react-base-table. Some of them include:
1. Show loading state for the table 
2. Handling nested objects when displaying table data
3. Truncate long text instead of overlapping it with the next column
4. Displaying Youtube video URLs in the table column

See merge request theappsmith/internal-tools-client!704
This commit is contained in:
Arpit Mohan 2020-06-16 07:37:39 +00:00
commit 0abd0fb682
6 changed files with 159 additions and 123 deletions

View File

@ -216,8 +216,10 @@ export class ReactTableComponent extends React.Component<
Cell: (props: any) => {
return renderCell(
props.cell.value,
props.cell.row.index,
columnType.type,
isHidden,
this.props.widgetId,
columnType.format,
);
},
@ -232,7 +234,10 @@ export class ReactTableComponent extends React.Component<
columns = this.reorderColumns(columns);
if (this.props.columnActions?.length) {
columns.push({
Header: "Actions",
Header:
this.props.columnNameMap && this.props.columnNameMap["actions"]
? this.props.columnNameMap["actions"]
: "Actions",
accessor: "actions",
width: 150,
minWidth: 60,

View File

@ -11,10 +11,10 @@ export const TableWrapper = styled.div<{ width: number; height: number }>`
justify-content: space-between;
flex-direction: column;
.tableWrap {
height: 100%;
display: block;
overflow-x: auto;
overflow-y: hidden;
border-bottom: 1px solid ${Colors.GEYSER_LIGHT};
}
.table {
border-spacing: 0;
@ -29,16 +29,11 @@ export const TableWrapper = styled.div<{ width: number; height: number }>`
height: ${props => props.height - 5 - 102}px;
}
.tr {
:last-child {
.td {
border-bottom: 0;
}
}
:nth-child(even) {
background: ${Colors.ATHENS_GRAY_DARKER};
}
&.selected-row {
background: ${Colors.ATHENS_GRAY};
background: ${Colors.POLAR};
}
&:hover {
background: ${Colors.ATHENS_GRAY};
@ -237,7 +232,7 @@ export const CellWrapper = styled.div<{ isHidden: boolean }>`
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
white-space: nowrap;
opacity: ${props => (props.isHidden ? "0.6" : "1")};
.image-cell {
width: 40px;
@ -251,4 +246,11 @@ export const CellWrapper = styled.div<{ isHidden: boolean }>`
video {
border-radius: 4px;
}
&.video-cell {
height: 100%;
iframe {
border: none;
border-radius: 4px;
}
}
`;

View File

@ -8,6 +8,7 @@ import {
} from "./TableStyledWrappers";
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
import { ColumnMenuOptionProps } from "./ReactTableComponent";
import { isString } from "lodash";
interface MenuOptionProps {
columnAccessor?: string;
@ -272,8 +273,10 @@ export const getMenuOptions = (props: MenuOptionProps) => {
export const renderCell = (
value: any,
rowIndex: number,
columnType: string,
isHidden: boolean,
widgetId: string,
format?: string,
) => {
if (!value) {
@ -281,6 +284,13 @@ export const renderCell = (
}
switch (columnType) {
case "image":
if (!isString(value)) {
return (
<CellWrapper isHidden={isHidden}>
<div>Invalid Image </div>
</CellWrapper>
);
}
return (
<CellWrapper isHidden={isHidden}>
{value
@ -302,13 +312,25 @@ export const renderCell = (
</CellWrapper>
);
case "video":
return (
<CellWrapper isHidden={isHidden}>
<video width="56" height="32" autoPlay={false}>
<source src={`${value}`} type="video/mp4" />
</video>
</CellWrapper>
const youtubeRegex = new RegExp(
"^(https?://)?(www.)?(youtube.com|youtu.?be)/embed/.+$",
);
if (isString(value) && youtubeRegex.test(value)) {
return (
<CellWrapper isHidden={isHidden} className="video-cell">
<iframe
title={`video-${widgetId}-${rowIndex}`}
width="56"
height="32"
src={`${value}`}
></iframe>
</CellWrapper>
);
} else {
return (
<CellWrapper isHidden={isHidden}>Invalid Video Link</CellWrapper>
);
}
case "currency":
if (!isNaN(value)) {
return (
@ -352,9 +374,11 @@ export const renderCell = (
return <CellWrapper isHidden={isHidden}>Invalid Time</CellWrapper>;
}
case "text":
return <CellWrapper isHidden={isHidden}>{value}</CellWrapper>;
const text = isString(value) ? value : JSON.stringify(value);
return <CellWrapper isHidden={isHidden}>{text}</CellWrapper>;
default:
return <CellWrapper isHidden={isHidden}>{value}</CellWrapper>;
const data = isString(value) ? value : JSON.stringify(value);
return <CellWrapper isHidden={isHidden}>{data}</CellWrapper>;
}
};

View File

@ -50,13 +50,13 @@ export function InputText(props: {
class InputTextControl extends BaseControl<InputControlProps> {
render() {
const {
errorMessage,
expected,
propertyValue,
isValid,
label,
placeholderText,
dataTreePath,
validationMessage,
} = this.props;
return (
<InputText
@ -64,7 +64,7 @@ class InputTextControl extends BaseControl<InputControlProps> {
value={propertyValue}
onChange={this.onTextChange}
isValid={isValid}
errorMessage={errorMessage}
errorMessage={validationMessage}
expected={expected}
placeholder={placeholderText}
dataTreePath={dataTreePath}
@ -101,6 +101,7 @@ class InputTextControl extends BaseControl<InputControlProps> {
export interface InputControlProps extends ControlProps {
placeholderText: string;
inputType: InputType;
validationMessage?: string;
isDisabled?: boolean;
}

View File

@ -245,22 +245,24 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
if (!isValid) {
return {
isValid,
parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Table Data`,
parsed: [],
transformed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: [{ "Col1" : "val1", "Col2" : "val2" }]`,
};
} else if (
!_.every(parsed, datum => {
return (
_.isObject(datum) &&
Object.keys(datum).filter(key => _.isString(key) && key.length === 0)
.length === 0
);
})
) {
}
const isValidTableData = _.every(parsed, datum => {
return (
_.isObject(datum) &&
Object.keys(datum).filter(key => _.isString(key) && key.length === 0)
.length === 0
);
});
if (!isValidTableData) {
return {
isValid: false,
parsed: [],
message: `${WIDGET_TYPE_VALIDATION_ERROR}: [{ "key1" : "val1", "key2" : "val2" }, { "key1" : "val3", "key2" : "val4" }]`,
transformed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: [{ "Col1" : "val1", "Col2" : "val2" }]`,
};
}
return { isValid, parsed };

View File

@ -1,8 +1,8 @@
import React, { lazy, Suspense } from "react";
import React, { Suspense } from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import { EventType } from "constants/ActionConstants";
import { forIn } from "lodash";
// import { forIn } from "lodash";
import ReactTableComponent from "components/designSystems/appsmith/ReactTableComponent";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
@ -10,43 +10,44 @@ import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
import { ColumnModel } from "@syncfusion/ej2-grids";
import { ColumnDirTypecast } from "@syncfusion/ej2-react-grids";
// import { ColumnModel } from "@syncfusion/ej2-grids";
// import { ColumnDirTypecast } from "@syncfusion/ej2-react-grids";
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
import Skeleton from "components/utils/Skeleton";
import { Classes } from "@blueprintjs/core";
const TableComponent = lazy(() =>
import(
/* webpackPrefetch: true, webpackChunkName: "table" */ "components/designSystems/syncfusion/TableComponent"
),
);
// const TableComponent = React.lazy(() =>
// import(
// /* webpackPrefetch: true, webpackChunkName: "table" */ "components/designSystems/syncfusion/TableComponent"
// ),
// );
const ROW_HEIGHT = 37;
const TABLE_HEADER_HEIGHT = 39;
const TABLE_FOOTER_HEIGHT = 48;
const TABLE_EXPORT_HEIGHT = 43;
// const ROW_HEIGHT = 37;
// const TABLE_HEADER_HEIGHT = 39;
// const TABLE_FOOTER_HEIGHT = 48;
// const TABLE_EXPORT_HEIGHT = 43;
function constructColumns(
data: object[],
hiddenColumns?: string[],
): ColumnModel[] | ColumnDirTypecast[] {
let cols: ColumnModel[] | ColumnDirTypecast[] = [];
const listItemWithAllProperties = {};
data.forEach(dataItem => {
Object.assign(listItemWithAllProperties, dataItem);
});
forIn(listItemWithAllProperties, (value: any, key: string) => {
cols.push({
field: key,
visible: !hiddenColumns?.includes(key),
});
});
cols = (cols as any[]).filter(col => col.field !== "_color") as
| ColumnModel[]
| ColumnDirTypecast[];
return cols;
}
// function constructColumns(
// data: object[],
// hiddenColumns?: string[],
// ): ColumnModel[] | ColumnDirTypecast[] {
// let cols: ColumnModel[] | ColumnDirTypecast[] = [];
// const listItemWithAllProperties = {};
// data.forEach(dataItem => {
// Object.assign(listItemWithAllProperties, dataItem);
// });
// forIn(listItemWithAllProperties, (value: any, key: string) => {
// cols.push({
// field: key,
// visible: !hiddenColumns?.includes(key),
// });
// });
// cols = (cols as any[]).filter(col => col.field !== "_color") as
// | ColumnModel[]
// | ColumnDirTypecast[];
// return cols;
// }
class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
static getPropertyValidationMap(): WidgetPropertyValidationType {
@ -85,7 +86,7 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
getPageView() {
const { tableData, hiddenColumns } = this.props;
const columns = constructColumns(tableData, hiddenColumns);
// const columns = constructColumns(tableData, hiddenColumns);
const serverSidePaginationEnabled = (this.props
.serverSidePaginationEnabled &&
@ -98,14 +99,14 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
}
const { componentWidth, componentHeight } = this.getComponentDimensions();
const exportHeight =
this.props.exportCsv || this.props.exportPDF || this.props.exportCsv
? TABLE_EXPORT_HEIGHT
: 0;
const tableHeaderHeight =
this.props.tableData.length === 0 ? 2 : TABLE_HEADER_HEIGHT;
const tableContentHeight =
componentHeight - TABLE_FOOTER_HEIGHT - tableHeaderHeight - exportHeight;
// const exportHeight =
// this.props.exportCsv || this.props.exportPDF || this.props.exportCsv
// ? TABLE_EXPORT_HEIGHT
// : 0;
// const tableHeaderHeight =
// this.props.tableData.length === 0 ? 2 : TABLE_HEADER_HEIGHT;
// const tableContentHeight =
// componentHeight - TABLE_FOOTER_HEIGHT - tableHeaderHeight - exportHeight;
// Use below code to calculate page size for old table component
// const pageSize = Math.floor(tableContentHeight / ROW_HEIGHT);
const pageSize = Math.floor((componentHeight - 104) / 52);
@ -113,59 +114,60 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
if (pageSize !== this.props.pageSize) {
super.updateWidgetMetaProperty("pageSize", pageSize);
}
// /*
return (
<Suspense fallback={<Skeleton />}>
<ReactTableComponent
height={componentHeight}
width={componentWidth}
tableData={tableData}
widgetId={this.props.widgetId}
renderMode={this.props.renderMode}
hiddenColumns={hiddenColumns}
columnActions={this.props.columnActions}
columnNameMap={this.props.columnNameMap}
columnTypeMap={this.props.columnTypeMap}
columnOrder={this.props.columnOrder}
pageSize={pageSize}
onCommandClick={this.onCommandClick}
selectedRowIndex={
this.props.selectedRowIndex === undefined
? -1
: this.props.selectedRowIndex
}
serverSidePaginationEnabled={serverSidePaginationEnabled}
onRowClick={this.handleRowClick}
pageNo={pageNo}
nextPageClick={this.handleNextPageClick}
prevPageClick={this.handlePrevPageClick}
updatePageNo={(pageNo: number) => {
super.updateWidgetMetaProperty("pageNo", pageNo);
}}
updateHiddenColumns={(hiddenColumns?: string[]) => {
super.updateWidgetProperty("hiddenColumns", hiddenColumns);
}}
updateColumnType={(columnTypeMap: {
[key: string]: { type: string; format: string };
}) => {
super.updateWidgetProperty("columnTypeMap", columnTypeMap);
}}
updateColumnName={(columnNameMap: { [key: string]: string }) => {
super.updateWidgetProperty("columnNameMap", columnNameMap);
}}
handleResizeColumn={(columnSizeMap: { [key: string]: number }) => {
super.updateWidgetProperty("columnSizeMap", columnSizeMap);
}}
handleReorderColumn={(columnOrder: string[]) => {
super.updateWidgetProperty("columnOrder", columnOrder);
}}
columnSizeMap={this.props.columnSizeMap}
resetSelectedRowIndex={this.resetSelectedRowIndex}
disableDrag={(disable: boolean) => {
this.disableDrag(disable);
}}
/>
<div className={this.props.isLoading ? Classes.SKELETON : ""}>
<ReactTableComponent
height={componentHeight}
width={componentWidth}
tableData={tableData}
widgetId={this.props.widgetId}
renderMode={this.props.renderMode}
hiddenColumns={hiddenColumns}
columnActions={this.props.columnActions}
columnNameMap={this.props.columnNameMap}
columnTypeMap={this.props.columnTypeMap}
columnOrder={this.props.columnOrder}
pageSize={pageSize}
onCommandClick={this.onCommandClick}
selectedRowIndex={
this.props.selectedRowIndex === undefined
? -1
: this.props.selectedRowIndex
}
serverSidePaginationEnabled={serverSidePaginationEnabled}
onRowClick={this.handleRowClick}
pageNo={pageNo}
nextPageClick={this.handleNextPageClick}
prevPageClick={this.handlePrevPageClick}
updatePageNo={(pageNo: number) => {
super.updateWidgetMetaProperty("pageNo", pageNo);
}}
updateHiddenColumns={(hiddenColumns?: string[]) => {
super.updateWidgetProperty("hiddenColumns", hiddenColumns);
}}
updateColumnType={(columnTypeMap: {
[key: string]: { type: string; format: string };
}) => {
super.updateWidgetProperty("columnTypeMap", columnTypeMap);
}}
updateColumnName={(columnNameMap: { [key: string]: string }) => {
super.updateWidgetProperty("columnNameMap", columnNameMap);
}}
handleResizeColumn={(columnSizeMap: { [key: string]: number }) => {
super.updateWidgetProperty("columnSizeMap", columnSizeMap);
}}
handleReorderColumn={(columnOrder: string[]) => {
super.updateWidgetProperty("columnOrder", columnOrder);
}}
columnSizeMap={this.props.columnSizeMap}
resetSelectedRowIndex={this.resetSelectedRowIndex}
disableDrag={(disable: boolean) => {
this.disableDrag(disable);
}}
/>
</div>
</Suspense>
);
// */