feat(code refactoring): code refactoring

This commit is contained in:
Conglei Shi 2019-05-22 20:09:35 -07:00 committed by Yongjie Zhao
parent aa7ef1ad90
commit c87a74841e
3 changed files with 175 additions and 140 deletions

View File

@ -1,17 +1,7 @@
import React, { CSSProperties } from 'react';
import React from 'react';
import DataTable from '@airbnb/lunar/lib/components/DataTable';
import { Renderers, RendererProps } from '@airbnb/lunar/lib/components/DataTable/types';
import { HEIGHT_TO_PX } from '@airbnb/lunar/lib/components/DataTable/constants';
import { FormDataMetric, Metric } from '@superset-ui/chart';
type ColumnType = {
key: string;
label: string;
format?: (value: any) => string;
type: 'metric' | 'string';
maxValue?: number;
minValue?: number;
};
import { Renderers } from '@airbnb/lunar/lib/components/DataTable/types';
import { getRenderer, ColumnType, heightType, Cell } from './renderer';
type Props = {
data: any[];
@ -23,15 +13,9 @@ type Props = {
[key: string]: any[];
};
includeSearch?: boolean;
metrics: FormDataMetric[];
onAddFilter?: (key: string, value: number[]) => void;
onRemoveFilter?: (key: string, value: number[]) => void;
orderDesc: boolean;
pageLength: number | string;
percentMetrics: string[];
tableFilter: boolean;
tableTimestampFormat: string;
timeseriesLimitMetric: FormDataMetric;
};
function NOOP(key: string, value: []) {}
@ -47,17 +31,13 @@ const defaultProps = {
export type TableProps = Props & Readonly<typeof defaultProps>;
type Cell = {
key: string;
value: any;
};
type TableState = {
selectedCells: Set<string>;
};
const NEGATIVE_COLOR = '#ff8787';
const POSITIVE_COLOR = '#ced4da';
function getCellHash(cell: Cell) {
return `${cell.key}#${cell.value}`;
}
class TableVis extends React.Component<TableProps, TableState> {
static defaultProps = defaultProps;
@ -77,7 +57,7 @@ class TableVis extends React.Component<TableProps, TableState> {
return;
}
const newSelectedCells = new Set(Array.from(selectedCells));
const cellHash = `${cell.key}#${cell.value}`;
const cellHash = getCellHash(cell);
if (newSelectedCells.has(cellHash)) {
newSelectedCells.delete(cellHash);
onRemoveFilter(cell.key, [cell.value]);
@ -90,6 +70,11 @@ class TableVis extends React.Component<TableProps, TableState> {
});
};
isSelected = (cell: Cell) => {
const { selectedCells } = this.state;
return selectedCells.has(getCellHash(cell));
};
static getDerivedStateFromProps: React.GetDerivedStateFromProps<TableProps, TableState> = (
props: TableProps,
state: TableState,
@ -99,7 +84,12 @@ class TableVis extends React.Component<TableProps, TableState> {
const newSelectedCells = new Set(Array.from(selectedCells));
Object.keys(filters).forEach(key => {
filters[key].forEach(value => {
newSelectedCells.add(`${key}#${value}`);
newSelectedCells.add(
getCellHash({
key,
value,
}),
);
});
});
return {
@ -110,122 +100,30 @@ class TableVis extends React.Component<TableProps, TableState> {
render() {
const {
metrics,
timeseriesLimitMetric,
orderDesc,
data,
columns,
alignPositiveNegative,
colorPositiveNegative,
columns,
height,
tableFilter,
} = this.props;
const { selectedCells } = this.state;
const sortByKey =
timeseriesLimitMetric &&
((timeseriesLimitMetric as Metric).label || (timeseriesLimitMetric as string));
let formattedData = data.map(row => ({
data: row,
}));
if (sortByKey) {
formattedData = formattedData.sort((a, b) => {
const delta = a.data[sortByKey] - b.data[sortByKey];
if (orderDesc) {
return -delta;
}
return delta;
});
if (metrics.indexOf(sortByKey) < 0) {
formattedData = formattedData.map(row => {
const data = { ...row.data };
delete data[sortByKey];
return {
data,
};
});
}
}
const heightType = 'small';
const getRenderer = (column: ColumnType) => (props: RendererProps) => {
const { key } = props;
let value = props.row.rowData.data[key];
const cellHash = `${key}#${value}`;
const isMetric = metrics.indexOf(key) >= 0;
let Parent;
if (isMetric) {
let left = 0;
let width = 0;
if (alignPositiveNegative) {
width = Math.abs(
Math.round((value / Math.max(column.maxValue!, Math.abs(column.minValue!))) * 100),
);
} else {
const posExtent = Math.abs(Math.max(column.maxValue!, 0));
const negExtent = Math.abs(Math.min(column.minValue!, 0));
const tot = posExtent + negExtent;
left = Math.round((Math.min(negExtent + value, negExtent) / tot) * 100);
width = Math.round((Math.abs(value) / tot) * 100);
}
const color = colorPositiveNegative && value < 0 ? NEGATIVE_COLOR : POSITIVE_COLOR;
Parent = ({ children }: { children: React.ReactNode }) => {
const boxStyle: CSSProperties = {
margin: '0px -16px',
backgroundColor: tableFilter && selectedCells.has(cellHash) ? '#ffec99' : undefined,
};
const boxContainerStyle: CSSProperties = {
position: 'relative',
height: HEIGHT_TO_PX[heightType],
textAlign: isMetric ? 'right' : 'left',
display: 'flex',
alignItems: 'center',
margin: '0px 16px',
};
const barStyle: CSSProperties = {
background: color,
width: `${width}%`,
left: `${left}%`,
position: 'absolute',
height: 24,
};
return (
<div style={boxStyle}>
<div style={boxContainerStyle}>
<div style={barStyle} />
<div style={{ zIndex: 10, marginLeft: 'auto' }}>{children}</div>
</div>
</div>
);
};
} else {
Parent = ({ children }: { children: React.ReactNode }) => (
<React.Fragment>{children}</React.Fragment>
);
}
return (
<div
onClick={this.handleCellSelected({
key,
value,
})}
>
<Parent>{column.format ? column.format(value) : value}</Parent>
</div>
);
};
const renderers: Renderers = {};
columns.forEach(column => {
renderers[column.key] = getRenderer(column);
renderers[column.key] = getRenderer({
column,
alignPositiveNegative,
colorPositiveNegative,
enableFilter: tableFilter,
isSelected: this.isSelected,
handleCellSelected: this.handleCellSelected,
});
});
return <DataTable data={formattedData} zebra rowHeight={heightType} renderers={renderers} />;
return (
<DataTable data={data} zebra rowHeight={heightType} renderers={renderers} height={height} />
);
}
}

View File

@ -22,6 +22,12 @@ import { ChartProps, FormDataMetric, Metric } from '@superset-ui/chart';
import { getNumberFormatter, NumberFormats, NumberFormatter } from '@superset-ui/number-format';
import { getTimeFormatter, TimeFormatter } from '@superset-ui/time-format';
const DTTM_ALIAS = '__timestamp';
type PlainObject = {
[key: string]: any;
};
export default function transformProps(chartProps: ChartProps) {
const { height, datasource, filters, formData, onAddFilter, payload } = chartProps;
const {
@ -47,9 +53,38 @@ export default function transformProps(chartProps: ChartProps) {
.filter(m => typeof records[0][m as string] === 'number');
const dataArray: {
[key: string]: any;
[key: string]: any[];
} = {};
const sortByKey =
timeseriesLimitMetric &&
((timeseriesLimitMetric as Metric).label || (timeseriesLimitMetric as string));
let formattedData: {
data: PlainObject;
}[] = records.map((row: PlainObject) => ({
data: row,
}));
if (sortByKey) {
formattedData = formattedData.sort((a, b) => {
const delta = a.data[sortByKey] - b.data[sortByKey];
if (orderDesc) {
return -delta;
}
return delta;
});
if (metrics.indexOf(sortByKey) < 0) {
formattedData = formattedData.map(row => {
const data = { ...row.data };
delete data[sortByKey];
return {
data,
};
});
}
}
metrics.forEach(metric => {
const arr = [];
for (let i = 0; i < records.length; i += 1) {
@ -91,7 +126,7 @@ export default function transformProps(chartProps: ChartProps) {
}
}
if (key === '__timestamp') {
if (key === DTTM_ALIAS) {
formatFunction = tsFormatter;
}
@ -116,19 +151,15 @@ export default function transformProps(chartProps: ChartProps) {
return {
height,
data: records,
data: formattedData,
alignPositiveNegative: alignPn,
colorPositiveNegative: colorPn,
columns: processedColumns,
filters,
includeSearch,
metrics,
onAddFilter,
orderDesc,
pageLength: pageLength && parseInt(pageLength, 10),
percentMetrics,
tableFilter,
tableTimestampFormat,
timeseriesLimitMetric,
};
}

View File

@ -0,0 +1,106 @@
import React, { CSSProperties } from 'react';
import { HEIGHT_TO_PX } from '@airbnb/lunar/lib/components/DataTable/constants';
import { RendererProps } from '@airbnb/lunar/lib/components/DataTable/types';
const NEGATIVE_COLOR = '#ff8787';
const POSITIVE_COLOR = '#ced4da';
const SELECTION_COLOR = '#ffec99';
export const heightType = 'micro';
export type ColumnType = {
key: string;
label: string;
format?: (value: any) => string;
type: 'metric' | 'string';
maxValue?: number;
minValue?: number;
};
export type Cell = {
key: string;
value: any;
};
export const getRenderer = ({
column,
alignPositiveNegative,
colorPositiveNegative,
enableFilter,
isSelected,
handleCellSelected,
}: {
column: ColumnType;
alignPositiveNegative: boolean;
colorPositiveNegative: boolean;
enableFilter: boolean;
isSelected: (cell: Cell) => boolean;
handleCellSelected: (cell: Cell) => any;
}) => (props: RendererProps) => {
const { key } = props;
let value = props.row.rowData.data[key];
const isMetric = column.type === 'metric';
let Parent;
if (isMetric) {
let left = 0;
let width = 0;
if (alignPositiveNegative) {
width = Math.abs(
Math.round((value / Math.max(column.maxValue!, Math.abs(column.minValue!))) * 100),
);
} else {
const posExtent = Math.abs(Math.max(column.maxValue!, 0));
const negExtent = Math.abs(Math.min(column.minValue!, 0));
const tot = posExtent + negExtent;
left = Math.round((Math.min(negExtent + value, negExtent) / tot) * 100);
width = Math.round((Math.abs(value) / tot) * 100);
}
const color = colorPositiveNegative && value < 0 ? NEGATIVE_COLOR : POSITIVE_COLOR;
Parent = ({ children }: { children: React.ReactNode }) => {
const boxStyle: CSSProperties = {
margin: '0px -16px',
backgroundColor: enableFilter && isSelected({ key, value }) ? SELECTION_COLOR : undefined,
};
const boxContainerStyle: CSSProperties = {
position: 'relative',
height: HEIGHT_TO_PX[heightType],
textAlign: isMetric ? 'right' : 'left',
display: 'flex',
alignItems: 'center',
margin: '0px 16px',
};
const barStyle: CSSProperties = {
background: color,
width: `${width}%`,
left: `${left}%`,
position: 'absolute',
height: HEIGHT_TO_PX[heightType] / 2,
};
return (
<div style={boxStyle}>
<div style={boxContainerStyle}>
<div style={barStyle} />
<div style={{ zIndex: 10, marginLeft: 'auto' }}>{children}</div>
</div>
</div>
);
};
} else {
Parent = ({ children }: { children: React.ReactNode }) => (
<React.Fragment>{children}</React.Fragment>
);
}
return (
<div
onClick={handleCellSelected({
key,
value,
})}
>
<Parent>{column.format ? column.format(value) : value}</Parent>
</div>
);
};