Skip to content

Commit c5d9663

Browse files
authored
feat: DH-13515: Support expandable columns in grids v0.85 (#2524)
1 parent e3860a7 commit c5d9663

36 files changed

+781
-67
lines changed

packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ import {
4545
ColumnHeaderGroup,
4646
IrisGridContextMenuData,
4747
PartitionConfig,
48+
IrisGridRenderer,
49+
MouseHandlersProp,
4850
} from '@deephaven/iris-grid';
4951
import {
5052
type RowDataMap,
@@ -151,7 +153,10 @@ export interface OwnProps extends DashboardPanelProps {
151153
/** Load a plugin defined by the table */
152154
loadPlugin: (pluginName: string) => TablePluginComponent;
153155

154-
theme?: IrisGridThemeType;
156+
theme?: Partial<IrisGridThemeType> & Record<string, unknown>;
157+
158+
mouseHandlers?: MouseHandlersProp;
159+
renderer?: IrisGridRenderer;
155160
}
156161

157162
interface StateProps {
@@ -993,6 +998,8 @@ export class IrisGridPanel extends PureComponent<
993998
rollupConfig,
994999
aggregationSettings,
9951000
sorts,
1001+
// TODO:
1002+
// DH-20403: IrisGrid should persist user column widths when the model initializes with a partial column list
9961003
userColumnWidths,
9971004
userRowHeights,
9981005
showSearchBar,
@@ -1103,8 +1110,10 @@ export class IrisGridPanel extends PureComponent<
11031110
inputFilters,
11041111
links,
11051112
metadata,
1113+
mouseHandlers,
11061114
panelState,
11071115
user,
1116+
renderer,
11081117
settings,
11091118
theme,
11101119
} = this.props;
@@ -1206,11 +1215,13 @@ export class IrisGridPanel extends PureComponent<
12061215
isSelectingPartition={isSelectingPartition}
12071216
isStuckToBottom={isStuckToBottom}
12081217
isStuckToRight={isStuckToRight}
1218+
mouseHandlers={mouseHandlers}
12091219
movedColumns={movedColumns}
12101220
movedRows={movedRows}
12111221
partitions={partitions}
12121222
partitionConfig={partitionConfig}
12131223
quickFilters={quickFilters}
1224+
renderer={renderer}
12141225
reverse={reverse}
12151226
rollupConfig={rollupConfig}
12161227
settings={settings}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { isExpandableColumnGridModel } from './ExpandableColumnGridModel';
2+
import type GridModel from './GridModel';
3+
import type ExpandableColumnGridModel from './ExpandableColumnGridModel';
4+
5+
describe('ExpandableColumnGridModel', () => {
6+
describe('isExpandableColumnGridModel', () => {
7+
it('should return true for model with hasExpandableColumns property', () => {
8+
const model = {
9+
hasExpandableColumns: true,
10+
} as ExpandableColumnGridModel;
11+
12+
expect(isExpandableColumnGridModel(model)).toBe(true);
13+
});
14+
15+
it('should return true when hasExpandableColumns is false', () => {
16+
const model = {
17+
hasExpandableColumns: false,
18+
} as ExpandableColumnGridModel;
19+
20+
expect(isExpandableColumnGridModel(model)).toBe(true);
21+
});
22+
23+
it('should return false for model without hasExpandableColumns property', () => {
24+
const model = {} as GridModel;
25+
26+
expect(isExpandableColumnGridModel(model)).toBe(false);
27+
});
28+
});
29+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import GridModel from './GridModel';
2+
import { ModelIndex } from './GridMetrics';
3+
4+
export function isExpandableColumnGridModel(
5+
model: GridModel
6+
): model is ExpandableColumnGridModel {
7+
return (
8+
(model as ExpandableColumnGridModel)?.hasExpandableColumns !== undefined
9+
);
10+
}
11+
12+
/**
13+
* Expandable grid model. Allows for a grid with columns that can expand (e.g. Pivot Table)
14+
*/
15+
export interface ExpandableColumnGridModel extends GridModel {
16+
/** Whether the grid has columns that can be expanded */
17+
hasExpandableColumns: boolean;
18+
19+
/** Whether the grid can expand all columns */
20+
isExpandAllColumnsAvailable: boolean;
21+
22+
/**
23+
* @param column Column to check
24+
* @returns True if the column is expandable
25+
*/
26+
isColumnExpandable: (column: ModelIndex) => boolean;
27+
28+
/**
29+
* @param column Column to check
30+
* @returns True if the column is currently expanded
31+
*/
32+
isColumnExpanded: (column: ModelIndex) => boolean;
33+
34+
/**
35+
* Change the expanded status of an expandable column
36+
* @param column Column to expand
37+
* @param isExpanded True to expand the column, false to collapse
38+
* @param expandDescendants True to expand nested columns, false otherwise
39+
*/
40+
setColumnExpanded: (
41+
column: ModelIndex,
42+
isExpanded: boolean,
43+
expandDescendants?: boolean
44+
) => void;
45+
46+
/**
47+
* Expand all columns
48+
*/
49+
expandAllColumns: () => void;
50+
51+
/**
52+
* Collapse all columns
53+
*/
54+
collapseAllColumns: () => void;
55+
56+
/**
57+
* Get the depth of a column (ie. How indented the column should be)
58+
* @param column Column to check
59+
* @returns Depth of the column
60+
*/
61+
depthForColumn: (column: ModelIndex) => number;
62+
}
63+
64+
export default ExpandableColumnGridModel;

packages/grid/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './ColumnHeaderGroup';
22
export * from './EditableGridModel';
33
export * from './DeletableGridModel';
44
export * from './ExpandableGridModel';
5+
export * from './ExpandableColumnGridModel';
56
export { default as Grid } from './Grid';
67
export * from './Grid';
78
export * from './GridMetricCalculator';

packages/iris-grid/src/ColumnHeaderGroup.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ export function isColumnHeaderGroup(x: unknown): x is ColumnHeaderGroup {
1414
return x instanceof ColumnHeaderGroup;
1515
}
1616

17+
export type ColumnHeaderGroupConfig = {
18+
name: string;
19+
children: string[];
20+
color?: string | null;
21+
depth: number;
22+
childIndexes: ModelIndex[];
23+
parent?: string;
24+
};
25+
1726
export default class ColumnHeaderGroup implements IColumnHeaderGroup {
1827
static NEW_GROUP_PREFIX = ':newGroup';
1928

@@ -36,14 +45,7 @@ export default class ColumnHeaderGroup implements IColumnHeaderGroup {
3645
depth,
3746
childIndexes,
3847
parent,
39-
}: {
40-
name: string;
41-
children: string[];
42-
color?: string | null;
43-
depth: number;
44-
childIndexes: ModelIndex[];
45-
parent?: string;
46-
}) {
48+
}: ColumnHeaderGroupConfig) {
4749
this.name = name;
4850
this.children = children;
4951
this.color = color ?? undefined;

packages/iris-grid/src/IrisGrid.test.tsx

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@ import dh from '@deephaven/jsapi-shim';
44
import { DateUtils, Settings } from '@deephaven/jsapi-utils';
55
import { TestUtils } from '@deephaven/utils';
66
import { TypeValue } from '@deephaven/filters';
7+
import {
8+
ExpandableColumnGridModel,
9+
isExpandableColumnGridModel,
10+
} from '@deephaven/grid';
711
import IrisGrid from './IrisGrid';
812
import IrisGridTestUtils from './IrisGridTestUtils';
13+
import IrisGridProxyModel from './IrisGridProxyModel';
914

1015
class MockPath2D {
1116
// eslint-disable-next-line class-methods-use-this
@@ -14,6 +19,13 @@ class MockPath2D {
1419

1520
window.Path2D = MockPath2D as unknown as new () => Path2D;
1621

22+
jest.mock('@deephaven/grid', () => ({
23+
...jest.requireActual('@deephaven/grid'),
24+
isExpandableColumnGridModel: jest.fn(),
25+
}));
26+
27+
const { asMock } = TestUtils;
28+
1729
const VIEW_SIZE = 5000;
1830

1931
const DEFAULT_SETTINGS: Settings = {
@@ -66,10 +78,12 @@ function createNodeMock(element: ReactElement) {
6678

6779
function makeComponent(
6880
model = irisGridTestUtils.makeModel(),
69-
settings = DEFAULT_SETTINGS
81+
settings = DEFAULT_SETTINGS,
82+
props = {}
7083
) {
7184
const testRenderer = TestRenderer.create(
72-
<IrisGrid model={model} settings={settings} />,
85+
// eslint-disable-next-line react/jsx-props-no-spreading
86+
<IrisGrid model={model} settings={settings} {...props} />,
7387
{
7488
createNodeMock,
7589
}
@@ -222,3 +236,96 @@ it('should set gotoValueSelectedColumnName to empty string if no columns are giv
222236

223237
expect(component.state.gotoValueSelectedColumnName).toEqual('');
224238
});
239+
240+
describe('rebuildFilters', () => {
241+
it('updates state if filters not empty', () => {
242+
const component = makeComponent(undefined, undefined, {
243+
quickFilters: [
244+
[
245+
'2',
246+
{
247+
columnType: IrisGridTestUtils.DEFAULT_TYPE,
248+
filterList: [
249+
{
250+
operator: 'eq',
251+
text: 'null',
252+
value: null,
253+
startColumnIndex: 0,
254+
},
255+
],
256+
},
257+
],
258+
],
259+
});
260+
jest.spyOn(component, 'setState');
261+
expect(component.setState).not.toBeCalled();
262+
component.rebuildFilters();
263+
expect(component.setState).toBeCalled();
264+
});
265+
266+
it('does not update state for empty filters', () => {
267+
const component = makeComponent();
268+
jest.spyOn(component, 'setState');
269+
component.rebuildFilters();
270+
expect(component.setState).not.toBeCalled();
271+
});
272+
});
273+
274+
describe('column expand/collapse', () => {
275+
let model: IrisGridProxyModel & ExpandableColumnGridModel;
276+
let component: IrisGrid;
277+
278+
beforeEach(() => {
279+
model = irisGridTestUtils.makeModel() as IrisGridProxyModel &
280+
ExpandableColumnGridModel;
281+
component = makeComponent(model);
282+
model.setColumnExpanded = jest.fn();
283+
model.isColumnExpanded = jest.fn(() => false);
284+
model.expandAllColumns = jest.fn();
285+
model.collapseAllColumns = jest.fn();
286+
});
287+
288+
afterEach(() => {
289+
jest.clearAllMocks();
290+
});
291+
292+
it('calls setColumnExpanded if model supports expandable columns', () => {
293+
asMock(isExpandableColumnGridModel).mockReturnValue(true);
294+
model.hasExpandableColumns = true;
295+
component.toggleExpandColumn(0);
296+
expect(model.setColumnExpanded).toHaveBeenCalled();
297+
});
298+
299+
it('ignores setColumnExpanded and expand/collapse all if model does not support expandable columns', () => {
300+
asMock(isExpandableColumnGridModel).mockReturnValue(false);
301+
component.toggleExpandColumn(0);
302+
expect(model.setColumnExpanded).not.toHaveBeenCalled();
303+
304+
component.expandAllColumns();
305+
expect(model.expandAllColumns).not.toHaveBeenCalled();
306+
307+
component.collapseAllColumns();
308+
expect(model.collapseAllColumns).not.toHaveBeenCalled();
309+
});
310+
311+
it('calls expandAllColumns if model supports expandable columns and expand all', () => {
312+
asMock(isExpandableColumnGridModel).mockReturnValue(true);
313+
model.isExpandAllColumnsAvailable = true;
314+
component.expandAllColumns();
315+
expect(model.expandAllColumns).toHaveBeenCalled();
316+
317+
component.collapseAllColumns();
318+
expect(model.collapseAllColumns).toHaveBeenCalled();
319+
});
320+
321+
it('ignores expandAllColumns if model does not support expand all', () => {
322+
asMock(isExpandableColumnGridModel).mockReturnValue(true);
323+
model.isExpandAllColumnsAvailable = false;
324+
325+
component.expandAllColumns();
326+
expect(model.expandAllColumns).not.toHaveBeenCalled();
327+
328+
component.collapseAllColumns();
329+
expect(model.collapseAllColumns).not.toHaveBeenCalled();
330+
});
331+
});

0 commit comments

Comments
 (0)