mirror of
https://github.com/apache/superset.git
synced 2026-06-09 09:39:25 +00:00
fix(plugin-chart-table): resize and totals formatting bug (#1082)
This commit is contained in:
@@ -73,9 +73,9 @@ const fractionDigits: ControlFormItemSpec<'Slider'> = {
|
||||
|
||||
const columnWidth: ControlFormItemSpec<'InputNumber'> = {
|
||||
controlType: 'InputNumber',
|
||||
label: t('Width'),
|
||||
label: t('Min Width'),
|
||||
description: t(
|
||||
'Default column width in pixels, may still be restricted by the shortest/longest word in the column',
|
||||
"Default minimal column width in pixels, actual width may still be larger than this if other columns don't need much space",
|
||||
),
|
||||
width: 120,
|
||||
placeholder: 'auto',
|
||||
|
||||
@@ -35,7 +35,6 @@ import {
|
||||
IdType,
|
||||
Row,
|
||||
} from 'react-table';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { matchSorter, rankings } from 'match-sorter';
|
||||
import GlobalFilter, { GlobalFilterProps } from './components/GlobalFilter';
|
||||
import SelectPageSize, { SelectPageSizeProps, SizeOption } from './components/SelectPageSize';
|
||||
@@ -45,8 +44,6 @@ import { PAGE_SIZE_OPTIONS } from '../consts';
|
||||
|
||||
export interface DataTableProps<D extends object> extends TableOptions<D> {
|
||||
tableClassName?: string;
|
||||
totals?: { value: string; className?: string }[];
|
||||
totalsHeaderSpan?: number;
|
||||
searchInput?: boolean | GlobalFilterProps<D>['searchInput'];
|
||||
selectPageSize?: boolean | SelectPageSizeProps['selectRenderer'];
|
||||
pageSizeOptions?: SizeOption[]; // available page size options
|
||||
@@ -73,8 +70,6 @@ export default function DataTable<D extends object>({
|
||||
tableClassName,
|
||||
columns,
|
||||
data,
|
||||
totals,
|
||||
totalsHeaderSpan,
|
||||
serverPaginationData,
|
||||
width: initialWidth = '100%',
|
||||
height: initialHeight = 300,
|
||||
@@ -159,6 +154,7 @@ export default function DataTable<D extends object>({
|
||||
getTableBodyProps,
|
||||
prepareRow,
|
||||
headerGroups,
|
||||
footerGroups,
|
||||
page,
|
||||
pageCount,
|
||||
gotoPage,
|
||||
@@ -198,6 +194,8 @@ export default function DataTable<D extends object>({
|
||||
return (wrapStickyTable ? wrapStickyTable(getNoResults) : getNoResults()) as JSX.Element;
|
||||
}
|
||||
|
||||
const shouldRenderFooter = columns.some(x => !!x.Footer);
|
||||
|
||||
const renderTable = () => (
|
||||
<table {...getTableProps({ className: tableClassName })}>
|
||||
<thead>
|
||||
@@ -234,14 +232,16 @@ export default function DataTable<D extends object>({
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
{totals && (
|
||||
{shouldRenderFooter && (
|
||||
<tfoot>
|
||||
<tr key="totals" className="dt-totals">
|
||||
<td colSpan={totalsHeaderSpan}>{t('Totals')}</td>
|
||||
{totals.map(item => (
|
||||
<td className={item.className}>{item.value}</td>
|
||||
))}
|
||||
</tr>
|
||||
{footerGroups.map(footerGroup => {
|
||||
const { key: footerGroupKey, ...footerGroupProps } = footerGroup.getHeaderGroupProps();
|
||||
return (
|
||||
<tr key={footerGroupKey || footerGroup.id} {...footerGroupProps}>
|
||||
{footerGroup.headers.map(column => column.render('Footer', { key: column.id }))}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tfoot>
|
||||
)}
|
||||
</table>
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
UseSortByHooks,
|
||||
Renderer,
|
||||
HeaderProps,
|
||||
TableFooterProps,
|
||||
} from 'react-table';
|
||||
|
||||
import { UseStickyState, UseStickyTableOptions, UseStickyInstanceProps } from '../hooks/useSticky';
|
||||
@@ -64,6 +65,7 @@ declare module 'react-table' {
|
||||
// must define as a new property because it's not possible to override
|
||||
// the existing `Header` renderer option
|
||||
Header?: Renderer<TableSortByToggleProps & HeaderProps<D>>;
|
||||
Footer?: Renderer<TableFooterProps<D>>;
|
||||
}
|
||||
|
||||
export interface ColumnInstance<D extends object>
|
||||
|
||||
@@ -247,24 +247,28 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
[filters, handleChange, isActiveFilterValue],
|
||||
);
|
||||
|
||||
const getSharedStyle = (column: DataColumnMeta): CSSProperties => {
|
||||
const { isNumeric, config = {} } = column;
|
||||
const textAlign = config.horizontalAlign
|
||||
? config.horizontalAlign
|
||||
: isNumeric
|
||||
? 'right'
|
||||
: 'left';
|
||||
return {
|
||||
textAlign,
|
||||
};
|
||||
};
|
||||
|
||||
const getColumnConfigs = useCallback(
|
||||
(column: DataColumnMeta, i: number): ColumnWithLooseAccessor<D> => {
|
||||
const { key, label, dataType, isMetric, config = {} } = column;
|
||||
const isNumber = dataType === GenericDataType.NUMERIC;
|
||||
const isFilter = !isNumber && emitFilter;
|
||||
const textAlign = config.horizontalAlign
|
||||
? config.horizontalAlign
|
||||
: isNumber
|
||||
? 'right'
|
||||
: 'left';
|
||||
const { key, label, isNumeric, dataType, isMetric, config = {} } = column;
|
||||
const isFilter = !isNumeric && emitFilter;
|
||||
const columnWidth = Number.isNaN(Number(config.columnWidth))
|
||||
? config.columnWidth
|
||||
: Number(config.columnWidth);
|
||||
|
||||
// inline style for both th and td cell
|
||||
const sharedStyle: CSSProperties = {
|
||||
textAlign,
|
||||
};
|
||||
const sharedStyle: CSSProperties = getSharedStyle(column);
|
||||
|
||||
const alignPositiveNegative =
|
||||
config.alignPositiveNegative === undefined ? defaultAlignPN : config.alignPositiveNegative;
|
||||
@@ -287,7 +291,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
// typing is incorrect in current version of `@types/react-table`
|
||||
// so we ask TS not to check.
|
||||
accessor: ((datum: D) => datum[key]) as never,
|
||||
Cell: ({ value }: { column: ColumnInstance<D>; value: DataRecordValue }) => {
|
||||
Cell: ({ value }: { value: DataRecordValue }) => {
|
||||
const [isHtml, text] = formatColumnValue(column, value);
|
||||
const html = isHtml ? { __html: text } : undefined;
|
||||
const cellProps = {
|
||||
@@ -343,6 +347,15 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
<SortIcon column={col} />
|
||||
</th>
|
||||
),
|
||||
Footer: totals ? (
|
||||
i === 0 ? (
|
||||
<th>{t('Totals')}</th>
|
||||
) : (
|
||||
<td style={sharedStyle}>
|
||||
<strong>{formatColumnValue(column, totals[key])[1]}</strong>
|
||||
</td>
|
||||
)
|
||||
) : undefined,
|
||||
sortDescFirst: sortDesc,
|
||||
sortType: getSortTypeByDataType(dataType),
|
||||
};
|
||||
@@ -357,6 +370,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
showCellBars,
|
||||
sortDesc,
|
||||
toggleFilter,
|
||||
totals,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -366,37 +380,17 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
||||
updateExternalFormData(setDataMask, pageNumber, pageSize);
|
||||
};
|
||||
|
||||
const totalsFormatted =
|
||||
totals &&
|
||||
columnsMeta
|
||||
.filter(column => Object.keys(totals).includes(column.key))
|
||||
.reduce(
|
||||
(acc: { value: string; className: string }[], column) => [
|
||||
...acc,
|
||||
{
|
||||
value: formatColumnValue(column, totals[column.key])[1],
|
||||
className: column.dataType === GenericDataType.NUMERIC ? 'dt-metric' : '',
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
const totalsHeaderSpan =
|
||||
totalsFormatted &&
|
||||
columnsMeta.filter(column => !column.isPercentMetric).length - totalsFormatted.length;
|
||||
|
||||
return (
|
||||
<Styles>
|
||||
<DataTable<D>
|
||||
columns={columns}
|
||||
totals={totalsFormatted}
|
||||
totalsHeaderSpan={totalsHeaderSpan}
|
||||
data={data}
|
||||
rowCount={rowCount}
|
||||
tableClassName="table table-striped table-condensed"
|
||||
pageSize={pageSize}
|
||||
serverPaginationData={serverPaginationData}
|
||||
pageSizeOptions={pageSizeOptions}
|
||||
width={width}
|
||||
height={height}
|
||||
serverPagination={serverPagination}
|
||||
onServerPaginationChange={handleServerPaginationChange}
|
||||
|
||||
@@ -37,9 +37,9 @@ const translations: Partial<Record<Locale, typeof en>> = {
|
||||
'Show Cell Bars': ['为指标添加条状图背景'],
|
||||
'page_size.show': ['每页显示'],
|
||||
'page_size.all': ['全部'],
|
||||
'page_size.entries': ['条'],
|
||||
'table.previous_page': ['以前的'],
|
||||
'table.next_page': ['下一個'],
|
||||
'page_size.entries': ['上一页'],
|
||||
'table.previous_page': ['上一页'],
|
||||
'table.next_page': ['下一页'],
|
||||
'search.num_records': ['%s条记录...'],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -146,6 +146,7 @@ const processColumns = memoizeOne(function processColumns(props: TableChartProps
|
||||
key,
|
||||
label,
|
||||
dataType,
|
||||
isNumeric: dataType === GenericDataType.NUMERIC,
|
||||
isMetric,
|
||||
isPercentMetric,
|
||||
formatter,
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface DataColumnMeta {
|
||||
formatter?: TimeFormatter | NumberFormatter | CustomFormatter;
|
||||
isMetric?: boolean;
|
||||
isPercentMetric?: boolean;
|
||||
isNumeric?: boolean;
|
||||
config?: ColumnConfig;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,12 @@ function formatValue(
|
||||
formatter: DataColumnMeta['formatter'],
|
||||
value: DataRecordValue,
|
||||
): [boolean, string] {
|
||||
if (value === null || typeof value === 'undefined') {
|
||||
// render undefined as empty string
|
||||
if (value === undefined) {
|
||||
return [false, ''];
|
||||
}
|
||||
// render null as `N/A`
|
||||
if (value === null) {
|
||||
return [false, 'N/A'];
|
||||
}
|
||||
if (formatter) {
|
||||
|
||||
Reference in New Issue
Block a user