Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add drag and drop column rearrangement for table viz #19381

Merged
merged 5 commits into from
May 25, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ function loadData(
alignPn = false,
showCellBars = true,
includeSearch = true,
rearrangeColumns = false,
stevetracvc marked this conversation as resolved.
Show resolved Hide resolved
},
): TableChartProps {
if (!props.queriesData || !props.queriesData[0]) return props;
Expand All @@ -86,6 +87,7 @@ function loadData(
page_length: pageLength,
show_cell_bars: showCellBars,
include_search: includeSearch,
rearrange_columns: rearrangeColumns,
stevetracvc marked this conversation as resolved.
Show resolved Hide resolved
},
height: window.innerHeight - 130,
};
Expand Down Expand Up @@ -117,15 +119,17 @@ export const BigTable = ({ width, height }) => {
const cols = number('Columns', 8, { range: true, min: 1, max: 20 });
const pageLength = number('Page size', 50, { range: true, min: 0, max: 100 });
const includeSearch = boolean('Include search', true);
const alignPn = boolean('Algin PosNeg', false);
const alignPn = boolean('Align PosNeg', false);
stevetracvc marked this conversation as resolved.
Show resolved Hide resolved
const showCellBars = boolean('Show Cell Bars', true);
const rearrangeColumns = boolean('Allow user to rearrange columns', false);
const chartProps = loadData(birthNames, {
pageLength,
rows,
cols,
alignPn,
showCellBars,
includeSearch,
rearrangeColumns,
});
return (
<SuperChart
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
usePagination,
useSortBy,
useGlobalFilter,
useColumnOrder,
PluginHook,
TableOptions,
FilterType,
Expand Down Expand Up @@ -63,6 +64,7 @@ export interface DataTableProps<D extends object> extends TableOptions<D> {
sticky?: boolean;
rowCount: number;
wrapperRef?: MutableRefObject<HTMLDivElement>;
onColumnOrderChange: () => void;
}

export interface RenderHTMLCellProps extends HTMLProps<HTMLTableCellElement> {
Expand Down Expand Up @@ -94,12 +96,14 @@ export default function DataTable<D extends object>({
hooks,
serverPagination,
wrapperRef: userWrapperRef,
onColumnOrderChange,
...moreUseTableOptions
}: DataTableProps<D>): JSX.Element {
const tableHooks: PluginHook<D>[] = [
useGlobalFilter,
useSortBy,
usePagination,
useColumnOrder,
doSticky ? useSticky : [],
hooks || [],
].flat();
Expand Down Expand Up @@ -171,6 +175,8 @@ export default function DataTable<D extends object>({
setGlobalFilter,
setPageSize: setPageSize_,
wrapStickyTable,
setColumnOrder,
allColumns,
state: { pageIndex, pageSize, globalFilter: filterValue, sticky = {} },
} = useTable<D>(
{
Expand Down Expand Up @@ -210,6 +216,33 @@ export default function DataTable<D extends object>({

const shouldRenderFooter = columns.some(x => !!x.Footer);

let columnBeingDragged = -1;

const onDragStart = (e: React.DragEvent) => {
const el = e.target as HTMLTableCellElement;
columnBeingDragged = allColumns.findIndex(
col => col.id === el.dataset.columnName,
);
e.dataTransfer.setData('text/plain', `${columnBeingDragged}`);
stevetracvc marked this conversation as resolved.
Show resolved Hide resolved
};

const onDrop = (e: React.DragEvent) => {
const el = e.target as HTMLTableCellElement;
const newPosition = allColumns.findIndex(
col => col.id === el.dataset.columnName,
);

if (newPosition !== -1) {
const currentCols = allColumns.map(c => c.id);
const colToBeMoved = currentCols.splice(columnBeingDragged, 1);
currentCols.splice(newPosition, 0, colToBeMoved[0]);
setColumnOrder(currentCols);
// toggle value in TableChart to trigger column width recalc
onColumnOrderChange();
}
e.preventDefault();
};

const renderTable = () => (
<table {...getTableProps({ className: tableClassName })}>
<thead>
Expand All @@ -222,6 +255,8 @@ export default function DataTable<D extends object>({
column.render('Header', {
key: column.id,
...column.getSortByToggleProps(),
onDragStart,
onDrop,
}),
)}
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ function useInstance<D extends object>(instance: TableInstance<D>) {
data,
page,
rows,
allColumns,
getTableSize = () => undefined,
} = instance;

Expand All @@ -368,7 +369,7 @@ function useInstance<D extends object>(instance: TableInstance<D>) {
useMountedMemo(getTableSize, [getTableSize]) || sticky;
// only change of data should trigger re-render
// eslint-disable-next-line react-hooks/exhaustive-deps
const table = useMemo(renderer, [page, rows]);
const table = useMemo(renderer, [page, rows, allColumns]);

useLayoutEffect(() => {
if (!width || !height) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import {
UseSortByState,
UseTableHooks,
UseSortByHooks,
UseColumnOrderState,
UseColumnOrderInstanceProps,
Renderer,
HeaderProps,
TableFooterProps,
Expand Down Expand Up @@ -64,6 +66,7 @@ declare module 'react-table' {
UseRowSelectInstanceProps<D>,
UseRowStateInstanceProps<D>,
UseSortByInstanceProps<D>,
UseColumnOrderInstanceProps<D>,
UseStickyInstanceProps {}

export interface TableState<D extends object>
Expand All @@ -73,6 +76,7 @@ declare module 'react-table' {
UsePaginationState<D>,
UseRowSelectState<D>,
UseSortByState<D>,
UseColumnOrderState<D>,
UseStickyState {}

// Typing from @types/react-table is incomplete
Expand All @@ -82,12 +86,19 @@ declare module 'react-table' {
onClick?: React.MouseEventHandler;
}

interface TableRearrangeColumnsProps {
onDragStart: (e: React.DragEvent) => void;
onDrop: (e: React.DragEvent) => void;
}

export interface ColumnInterface<D extends object>
extends UseGlobalFiltersColumnOptions<D>,
UseSortByColumnOptions<D> {
// must define as a new property because it's not possible to override
// the existing `Header` renderer option
Header?: Renderer<TableSortByToggleProps & HeaderProps<D>>;
Header?: Renderer<
TableSortByToggleProps & HeaderProps<D> & TableRearrangeColumnsProps
>;
Footer?: Renderer<TableFooterProps<D>>;
}

Expand Down
21 changes: 18 additions & 3 deletions superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { CSSProperties, useCallback, useMemo } from 'react';
import React, { CSSProperties, useCallback, useMemo, useState } from 'react';
import {
ColumnInstance,
ColumnWithLooseAccessor,
Expand Down Expand Up @@ -188,12 +188,16 @@ export default function TableChart<D extends DataRecord = DataRecord>(
filters,
sticky = true, // whether to use sticky header
columnColorFormatters,
allowRearrangeColumns = false,
} = props;
const timestampFormatter = useCallback(
value => getTimeFormatterForGranularity(timeGrain)(value),
[timeGrain],
);

// keep track of whether column order changed, so that column widths can too
const [columnOrderToggle, setColumnOrderToggle] = useState(false);

const handleChange = useCallback(
(filters: { [x: string]: DataRecordValue[] }) => {
if (!emitFilter) {
Expand Down Expand Up @@ -410,7 +414,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
// render `Cell`. This saves some time for large tables.
return <td {...cellProps}>{text}</td>;
},
Header: ({ column: col, onClick, style }) => (
Header: ({ column: col, onClick, style, onDragStart, onDrop }) => (
<th
title="Shift + Click to sort by multiple columns"
className={[className, col.isSorted ? 'is-sorted' : ''].join(' ')}
Expand All @@ -419,6 +423,14 @@ export default function TableChart<D extends DataRecord = DataRecord>(
...style,
}}
onClick={onClick}
data-column-name={col.id}
{...(allowRearrangeColumns && {
draggable: 'true',
onDragStart,
onDragOver: e => e.preventDefault(),
onDragEnter: e => e.preventDefault(),
onDrop,
})}
>
{/* can't use `columnWidth &&` because it may also be zero */}
{config.columnWidth ? (
Expand All @@ -431,12 +443,13 @@ export default function TableChart<D extends DataRecord = DataRecord>(
/>
) : null}
<div
data-column-name={col.id}
css={{
display: 'inline-flex',
alignItems: 'center',
}}
>
<span>{label}</span>
<span data-column-name={col.id}>{label}</span>
<SortIcon column={col} />
</div>
</th>
Expand Down Expand Up @@ -466,6 +479,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
toggleFilter,
totals,
columnColorFormatters,
columnOrderToggle,
],
);

Expand Down Expand Up @@ -495,6 +509,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
height={height}
serverPagination={serverPagination}
onServerPaginationChange={handleServerPaginationChange}
onColumnOrderChange={() => setColumnOrderToggle(!columnOrderToggle)}
// 9 page items in > 340px works well even for 100+ pages
maxPageItemCount={width > 340 ? 9 : 7}
noResults={(filter: string) =>
Expand Down
14 changes: 14 additions & 0 deletions superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,20 @@ const config: ControlPanelConfig = {
},
},
],
[
{
name: 'allow_rearrange_columns',
config: {
type: 'CheckboxControl',
label: t('Allow columns to be rearranged'),
renderTrigger: true,
default: false,
description: t(
"Allow end user to drag-and-drop column headers to rearrange them. Note their changes won't persist for the next time they open the chart.",
),
},
},
],
[
{
name: 'column_config',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const transformProps = (
query_mode: queryMode,
show_totals: showTotals,
conditional_formatting: conditionalFormatting,
allow_rearrange_columns: allowRearrangeColumns,
} = formData;
const timeGrain = extractTimegrain(formData);

Expand Down Expand Up @@ -265,6 +266,7 @@ const transformProps = (
onChangeFilter,
columnColorFormatters,
timeGrain,
allowRearrangeColumns,
};
};

Expand Down
2 changes: 2 additions & 0 deletions superset-frontend/plugins/plugin-chart-table/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export type TableChartFormData = QueryFormData & {
emit_filter?: boolean;
time_grain_sqla?: TimeGranularity;
column_config?: Record<string, ColumnConfig>;
allow_rearrange_columns?: boolean;
};

export interface TableChartProps extends ChartProps {
Expand Down Expand Up @@ -109,6 +110,7 @@ export interface TableChartTransformedProps<D extends DataRecord = DataRecord> {
emitFilter?: boolean;
onChangeFilter?: ChartProps['hooks']['onAddFilter'];
columnColorFormatters?: ColorFormatters;
allowRearrangeColumns?: boolean;
}

export default {};