Skip to content

Commit d6826ce

Browse files
authored
feat: Add IrisGridCacheUtils for memoizing iris grid state (#2416)
Adds `IrisGridCacheUtils` which provides 3 methods to create memoized dehydrators: `makeMemoizedGridStateDehydrator`, `makeMemoizedIrisGridStateDehydrator`, and `makeMemoizedCombinedGridStateDehydrator`. The first 2 are used for `IrisGridPanel` which saves the state under separate keys for `gridState` and `irisGridState`. The 3rd is for newer uses like `UITable` which can just save the combined state and spread it to `IrisGrid` since `IrisGrid` accepts both `GridState` and `IrisGridState` props. Added tests for `dehydrateIrisGridState`. Improved the `memoize-one` type override to infer the types of the equality function based on the actual function args.
1 parent 2533670 commit d6826ce

7 files changed

Lines changed: 453 additions & 190 deletions

File tree

@types/memoize-one/index.d.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
export declare type EqualityFn = (
2-
newArgs: unknown[],
3-
lastArgs: unknown[]
4-
) => boolean;
1+
export declare type EqualityFn<P> = (newArgs: P, lastArgs: P) => boolean;
52

63
// eslint-disable-next-line @typescript-eslint/ban-types
74
declare function memoizeOne<ResultFn extends Function>(
85
resultFn: ResultFn,
9-
isEqual?: EqualityFn
6+
isEqual?: EqualityFn<Parameters<ResultFn>>
107
): ResultFn;
118

129
export default memoizeOne;

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

Lines changed: 12 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
IrisGrid,
2222
type IrisGridType,
2323
IrisGridModel,
24+
IrisGridCacheUtils,
2425
IrisGridUtils,
2526
isIrisGridTableModelTemplate,
2627
type ColumnName,
@@ -103,11 +104,11 @@ export interface PanelState {
103104
gridState: {
104105
isStuckToBottom: boolean;
105106
isStuckToRight: boolean;
106-
movedColumns: {
107+
movedColumns: readonly {
107108
from: string | ModelIndex | [string, string] | [ModelIndex, ModelIndex];
108109
to: string | ModelIndex;
109110
}[];
110-
movedRows: MoveOperation[];
111+
movedRows: readonly MoveOperation[];
111112
};
112113
irisGridState: DehydratedIrisGridState;
113114
irisGridPanelState: DehydratedIrisGridPanelState;
@@ -368,6 +369,12 @@ export class IrisGridPanel extends PureComponent<
368369

369370
private irisGridUtils: IrisGridUtils | null;
370371

372+
private gridStateDehydrator =
373+
IrisGridCacheUtils.makeMemoizedGridStateDehydrator();
374+
375+
private irisGridStateDehydrator =
376+
IrisGridCacheUtils.makeMemoizedIrisGridStateDehydrator();
377+
371378
getTableName(): string {
372379
const { metadata } = this.props;
373380
return getTableNameFromMetadata(metadata);
@@ -461,76 +468,6 @@ export class IrisGridPanel extends PureComponent<
461468
})
462469
);
463470

464-
getDehydratedIrisGridState = memoize(
465-
(
466-
model: IrisGridModel,
467-
sorts: readonly dh.Sort[],
468-
advancedFilters: ReadonlyAdvancedFilterMap,
469-
customColumnFormatMap: Map<ColumnName, FormattingRule>,
470-
isFilterBarShown: boolean,
471-
quickFilters: ReadonlyQuickFilterMap,
472-
customColumns: readonly ColumnName[],
473-
reverse: boolean,
474-
rollupConfig: UIRollupConfig | undefined,
475-
showSearchBar: boolean,
476-
searchValue: string,
477-
selectDistinctColumns: readonly ColumnName[],
478-
selectedSearchColumns: readonly ColumnName[],
479-
invertSearchColumns: boolean,
480-
userColumnWidths: ModelSizeMap,
481-
userRowHeights: ModelSizeMap,
482-
aggregationSettings: AggregationSettings,
483-
pendingDataMap: PendingDataMap<UIRow>,
484-
frozenColumns: readonly ColumnName[],
485-
conditionalFormats: readonly SidebarFormattingRule[],
486-
columnHeaderGroups: readonly ColumnHeaderGroup[],
487-
partitionConfig: PartitionConfig | undefined
488-
) => {
489-
assertNotNull(this.irisGridUtils);
490-
return this.irisGridUtils.dehydrateIrisGridState(model, {
491-
advancedFilters,
492-
aggregationSettings,
493-
customColumnFormatMap,
494-
isFilterBarShown,
495-
metrics: {
496-
userColumnWidths,
497-
userRowHeights,
498-
},
499-
quickFilters,
500-
customColumns,
501-
reverse,
502-
rollupConfig,
503-
showSearchBar,
504-
searchValue,
505-
selectDistinctColumns,
506-
selectedSearchColumns,
507-
sorts,
508-
invertSearchColumns,
509-
pendingDataMap,
510-
frozenColumns,
511-
conditionalFormats,
512-
columnHeaderGroups,
513-
partitionConfig,
514-
});
515-
}
516-
);
517-
518-
getDehydratedGridState = memoize(
519-
(
520-
model: IrisGridModel,
521-
movedColumns: readonly MoveOperation[],
522-
movedRows: readonly MoveOperation[],
523-
isStuckToBottom: boolean,
524-
isStuckToRight: boolean
525-
) =>
526-
IrisGridUtils.dehydrateGridState(model, {
527-
isStuckToBottom,
528-
isStuckToRight,
529-
movedColumns,
530-
movedRows,
531-
})
532-
);
533-
534471
getCachedPanelState = memoize(
535472
(
536473
irisGridPanelState: PanelState['irisGridPanelState'],
@@ -1124,34 +1061,9 @@ export class IrisGridPanel extends PureComponent<
11241061
partitions,
11251062
advancedSettings,
11261063
} = this.state;
1127-
const {
1128-
advancedFilters,
1129-
aggregationSettings,
1130-
customColumnFormatMap,
1131-
isFilterBarShown,
1132-
quickFilters,
1133-
customColumns,
1134-
reverse,
1135-
rollupConfig,
1136-
showSearchBar,
1137-
searchValue,
1138-
selectDistinctColumns,
1139-
selectedSearchColumns,
1140-
sorts,
1141-
invertSearchColumns,
1142-
metrics,
1143-
pendingDataMap,
1144-
frozenColumns,
1145-
conditionalFormats,
1146-
columnHeaderGroups,
1147-
partitionConfig,
1148-
} = irisGridState;
1064+
assertNotNull(this.irisGridUtils);
11491065
assertNotNull(model);
1150-
assertNotNull(metrics);
1151-
const { userColumnWidths, userRowHeights } = metrics;
11521066
assertNotNull(gridState);
1153-
const { isStuckToBottom, isStuckToRight, movedColumns, movedRows } =
1154-
gridState;
11551067

11561068
const panelState = this.getCachedPanelState(
11571069
this.getDehydratedIrisGridPanelState(
@@ -1160,37 +1072,8 @@ export class IrisGridPanel extends PureComponent<
11601072
partitions,
11611073
advancedSettings
11621074
),
1163-
this.getDehydratedIrisGridState(
1164-
model,
1165-
sorts,
1166-
advancedFilters,
1167-
customColumnFormatMap,
1168-
isFilterBarShown,
1169-
quickFilters,
1170-
customColumns,
1171-
reverse,
1172-
rollupConfig,
1173-
showSearchBar,
1174-
searchValue,
1175-
selectDistinctColumns,
1176-
selectedSearchColumns,
1177-
invertSearchColumns,
1178-
userColumnWidths,
1179-
userRowHeights,
1180-
aggregationSettings,
1181-
pendingDataMap,
1182-
frozenColumns,
1183-
conditionalFormats,
1184-
columnHeaderGroups,
1185-
partitionConfig
1186-
),
1187-
this.getDehydratedGridState(
1188-
model,
1189-
movedColumns,
1190-
movedRows,
1191-
isStuckToBottom,
1192-
isStuckToRight
1193-
),
1075+
this.irisGridStateDehydrator(model, irisGridState),
1076+
this.gridStateDehydrator(model, gridState),
11941077
pluginState
11951078
);
11961079

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import { type GridMetrics } from '@deephaven/grid';
2+
import dh from '@deephaven/jsapi-shim';
3+
import IrisGridTestUtils from './IrisGridTestUtils';
4+
import {
5+
type HydratedGridState,
6+
type HydratedIrisGridState,
7+
} from './IrisGridUtils';
8+
import { IrisGridCacheUtils } from './IrisGridCacheUtils';
9+
10+
const irisGridTestUtils = new IrisGridTestUtils(dh);
11+
12+
const gridState = {
13+
isStuckToBottom: false,
14+
isStuckToRight: false,
15+
movedRows: [],
16+
movedColumns: [],
17+
} satisfies HydratedGridState;
18+
19+
const irisGridState = {
20+
advancedFilters: new Map(),
21+
partitionConfig: {
22+
partitions: [],
23+
mode: 'merged',
24+
},
25+
aggregationSettings: {
26+
aggregations: [],
27+
showOnTop: false,
28+
},
29+
customColumnFormatMap: new Map(),
30+
isFilterBarShown: false,
31+
quickFilters: new Map(),
32+
customColumns: [],
33+
reverse: false,
34+
rollupConfig: {
35+
columns: [],
36+
showConstituents: false,
37+
showNonAggregatedColumns: false,
38+
includeDescriptions: true,
39+
},
40+
showSearchBar: false,
41+
searchValue: '',
42+
selectDistinctColumns: [],
43+
selectedSearchColumns: [],
44+
sorts: [],
45+
invertSearchColumns: false,
46+
pendingDataMap: new Map(),
47+
frozenColumns: [],
48+
conditionalFormats: [],
49+
columnHeaderGroups: [],
50+
metrics: {
51+
userColumnWidths: new Map(),
52+
userRowHeights: new Map(),
53+
} as GridMetrics,
54+
} satisfies HydratedIrisGridState;
55+
56+
describe('makeMemoizedGridStateDehydrator', () => {
57+
test('creates a new memoization function with each call', () => {
58+
const cacheA = IrisGridCacheUtils.makeMemoizedGridStateDehydrator();
59+
const cacheB = IrisGridCacheUtils.makeMemoizedGridStateDehydrator();
60+
expect(cacheA).not.toBe(cacheB);
61+
});
62+
63+
test('memoizes dehydration', () => {
64+
const model = irisGridTestUtils.makeModel();
65+
66+
const dehydrate = IrisGridCacheUtils.makeMemoizedGridStateDehydrator();
67+
68+
// Same state in different objects
69+
expect(dehydrate(model, gridState)).toBe(
70+
dehydrate(model, { ...gridState })
71+
);
72+
73+
const differentModel = irisGridTestUtils.makeModel();
74+
expect(dehydrate(model, gridState)).not.toBe(
75+
dehydrate(differentModel, gridState)
76+
);
77+
78+
const differentState = {
79+
...gridState,
80+
isStuckToBottom: true,
81+
};
82+
expect(dehydrate(model, gridState)).not.toBe(
83+
dehydrate(model, differentState)
84+
);
85+
86+
const extraneousState = {
87+
...gridState,
88+
lastLeft: 10,
89+
};
90+
expect(dehydrate(model, gridState)).toBe(dehydrate(model, extraneousState));
91+
});
92+
});
93+
94+
describe('makeMemoizedIrisGridStateDehydrator', () => {
95+
test('creates a new memoization function with each call', () => {
96+
const cacheA = IrisGridCacheUtils.makeMemoizedIrisGridStateDehydrator();
97+
const cacheB = IrisGridCacheUtils.makeMemoizedIrisGridStateDehydrator();
98+
expect(cacheA).not.toBe(cacheB);
99+
});
100+
101+
test('memoizes dehydration', () => {
102+
const model = irisGridTestUtils.makeModel();
103+
104+
const dehydrate = IrisGridCacheUtils.makeMemoizedIrisGridStateDehydrator();
105+
106+
// Same state in different objects
107+
expect(dehydrate(model, irisGridState)).toBe(
108+
dehydrate(model, { ...irisGridState })
109+
);
110+
111+
const differentModel = irisGridTestUtils.makeModel();
112+
expect(dehydrate(model, irisGridState)).not.toBe(
113+
dehydrate(differentModel, irisGridState)
114+
);
115+
116+
const differentState = {
117+
...irisGridState,
118+
isFilterBarShown: true,
119+
};
120+
expect(dehydrate(model, irisGridState)).not.toBe(
121+
dehydrate(model, differentState)
122+
);
123+
124+
const extraneousState = {
125+
...irisGridState,
126+
lastLeft: 10,
127+
};
128+
expect(dehydrate(model, irisGridState)).toBe(
129+
dehydrate(model, extraneousState)
130+
);
131+
});
132+
});
133+
134+
describe('makeMemoizedCombinedGridStateDehydrator', () => {
135+
test('creates a new memoization function with each call', () => {
136+
const cacheA = IrisGridCacheUtils.makeMemoizedCombinedGridStateDehydrator();
137+
const cacheB = IrisGridCacheUtils.makeMemoizedCombinedGridStateDehydrator();
138+
expect(cacheA).not.toBe(cacheB);
139+
});
140+
test('memoizes dehydration', () => {
141+
const model = irisGridTestUtils.makeModel();
142+
143+
const dehydrate =
144+
IrisGridCacheUtils.makeMemoizedCombinedGridStateDehydrator();
145+
146+
// Same state in different objects
147+
expect(dehydrate(model, irisGridState, gridState)).toBe(
148+
dehydrate(model, { ...irisGridState }, { ...gridState })
149+
);
150+
151+
const differentModel = irisGridTestUtils.makeModel();
152+
expect(dehydrate(model, irisGridState, gridState)).not.toBe(
153+
dehydrate(differentModel, irisGridState, gridState)
154+
);
155+
156+
const differentGridState = {
157+
...gridState,
158+
isStuckToBottom: true,
159+
};
160+
expect(dehydrate(model, irisGridState, gridState)).not.toBe(
161+
dehydrate(model, irisGridState, differentGridState)
162+
);
163+
164+
const extraneousGridState = {
165+
...gridState,
166+
lastLeft: 10,
167+
};
168+
expect(dehydrate(model, irisGridState, gridState)).toBe(
169+
dehydrate(model, irisGridState, extraneousGridState)
170+
);
171+
172+
const differentIrisGridState = {
173+
...irisGridState,
174+
isFilterBarShown: true,
175+
};
176+
expect(dehydrate(model, irisGridState, gridState)).not.toBe(
177+
dehydrate(model, differentIrisGridState, gridState)
178+
);
179+
180+
const extraneousIrisGridState = {
181+
...irisGridState,
182+
lastLeft: 10,
183+
};
184+
expect(dehydrate(model, irisGridState, gridState)).toBe(
185+
dehydrate(model, extraneousIrisGridState, gridState)
186+
);
187+
188+
expect(dehydrate(model, irisGridState, gridState)).toBe(
189+
dehydrate(model, extraneousIrisGridState, extraneousGridState)
190+
);
191+
});
192+
});

0 commit comments

Comments
 (0)