Skip to content

Commit 190f688

Browse files
authored
feat: Cherry-pick DH-20363: Pivot filter support #2602 (#2608)
Added support for filtering on pivot columns (e.g., ColumnBy) that use negative column indices, and makes filter input positioning extensible for plugins. Added two new methods to `IrisGridMetricCalculator`: `getFilterInputCoordinates`: Returns positioning for the filter input field `getAdvancedFilterButtonCoordinates`: Returns positioning for the advanced filter button Both methods return null for negative indices by default, allowing plugins to override for custom column types Added `showAdvancedFilterButton` prop to `FilterInputField` to hide the advanced filter button for columns that don't support advanced filters.
1 parent 549e4e9 commit 190f688

6 files changed

Lines changed: 713 additions & 113 deletions

File tree

packages/grid/src/GridMetricCalculator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ export class GridMetricCalculator {
604604
visibleRows,
605605
visibleColumns,
606606

607-
// Map of the height/width of visible rows/columns
607+
// Map of the height/width of columns in the viewport (excluding floating columns)
608608
visibleRowHeights,
609609
visibleColumnWidths,
610610

@@ -616,7 +616,7 @@ export class GridMetricCalculator {
616616
allRows,
617617
allColumns,
618618

619-
// Map of the height/width of visible rows/columns
619+
// Map of the height/width of all rendered columns (visible + floating + dragging)
620620
allRowHeights,
621621
allColumnWidths,
622622

packages/iris-grid/src/FilterInputField.tsx

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface FilterInputFieldProps {
1111
className: string;
1212
style: React.CSSProperties;
1313
value: string;
14+
showAdvancedFilterButton: boolean;
1415
isAdvancedFilterSet: boolean;
1516
onAdvancedFiltersTriggered: React.MouseEventHandler<HTMLButtonElement>;
1617
onChange: (value: string) => void;
@@ -35,6 +36,7 @@ class FilterInputField extends PureComponent<
3536
style: {},
3637
className: '',
3738
value: '',
39+
showAdvancedFilterButton: false,
3840
isAdvancedFilterSet: false,
3941
onAdvancedFiltersTriggered: (): void => undefined,
4042
onChange: (): void => undefined,
@@ -203,6 +205,7 @@ class FilterInputField extends PureComponent<
203205
const {
204206
className,
205207
style,
208+
showAdvancedFilterButton,
206209
isAdvancedFilterSet,
207210
onAdvancedFiltersTriggered,
208211
} = this.props;
@@ -230,21 +233,26 @@ class FilterInputField extends PureComponent<
230233
autoCapitalize="off"
231234
spellCheck="false"
232235
/>
233-
<div className="advanced-filter-button-container">
234-
<Button
235-
kind="ghost"
236-
className={classNames('btn-link-icon advanced-filter-button', {
237-
'filter-set': isAdvancedFilterSet,
238-
})}
239-
onClick={onAdvancedFiltersTriggered}
240-
onContextMenu={this.handleContextMenu}
241-
>
242-
<div className="fa-layers ">
243-
<FontAwesomeIcon icon={dhFilterFilled} className="filter-solid" />
244-
<FontAwesomeIcon icon={vsFilter} className="filter-light" />
245-
</div>
246-
</Button>
247-
</div>
236+
{showAdvancedFilterButton && (
237+
<div className="advanced-filter-button-container">
238+
<Button
239+
kind="ghost"
240+
className={classNames('btn-link-icon advanced-filter-button', {
241+
'filter-set': isAdvancedFilterSet,
242+
})}
243+
onClick={onAdvancedFiltersTriggered}
244+
onContextMenu={this.handleContextMenu}
245+
>
246+
<div className="fa-layers ">
247+
<FontAwesomeIcon
248+
icon={dhFilterFilled}
249+
className="filter-solid"
250+
/>
251+
<FontAwesomeIcon icon={vsFilter} className="filter-light" />
252+
</div>
253+
</Button>
254+
</div>
255+
)}
248256
</div>
249257
);
250258
}

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

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import React, { ReactElement } from 'react';
2-
import TestRenderer from 'react-test-renderer';
2+
import TestRenderer, { act } from 'react-test-renderer';
33
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';
77
import {
8-
ExpandableColumnGridModel,
8+
type ExpandableColumnGridModel,
99
isExpandableColumnGridModel,
1010
} from '@deephaven/grid';
1111
import IrisGrid from './IrisGrid';
@@ -92,9 +92,13 @@ function makeComponent(
9292
IrisGrid;
9393
}
9494

95-
function keyDown(key, component, extraArgs?) {
95+
function keyDown(
96+
key: string,
97+
component: IrisGrid,
98+
extraArgs?: KeyboardEventInit
99+
): void {
96100
const args = { key, ...extraArgs };
97-
component.grid.notifyKeyboardHandlers(
101+
component.grid?.notifyKeyboardHandlers(
98102
'onDown',
99103
new KeyboardEvent('keydown', args)
100104
);
@@ -329,3 +333,73 @@ describe('column expand/collapse', () => {
329333
expect(model.collapseAllColumns).not.toHaveBeenCalled();
330334
});
331335
});
336+
337+
describe('rebuildFilters', () => {
338+
it('updates state if filters not empty', () => {
339+
const testComponent = makeComponent(undefined, undefined, {
340+
quickFilters: [
341+
[
342+
'2',
343+
{
344+
columnType: IrisGridTestUtils.DEFAULT_TYPE,
345+
filterList: [
346+
{
347+
operator: 'eq',
348+
text: 'null',
349+
value: null,
350+
startColumnIndex: 0,
351+
},
352+
],
353+
},
354+
],
355+
],
356+
});
357+
jest.spyOn(testComponent, 'setState');
358+
expect(testComponent.setState).not.toBeCalled();
359+
act(() => {
360+
testComponent.rebuildFilters();
361+
});
362+
expect(testComponent.setState).toBeCalled();
363+
});
364+
365+
it('does not update state for empty filters', () => {
366+
const testComponent = makeComponent();
367+
jest.spyOn(testComponent, 'setState');
368+
testComponent.rebuildFilters();
369+
expect(testComponent.setState).not.toBeCalled();
370+
});
371+
});
372+
373+
describe('Advanced Filter', () => {
374+
it.each([
375+
{ columnIndex: -1, expectedVisibility: false },
376+
{ columnIndex: 0, expectedVisibility: true },
377+
{ columnIndex: 1, expectedVisibility: true },
378+
])(
379+
'advanced filter button for column index $columnIndex should be rendered: $expectedVisibility',
380+
({ columnIndex, expectedVisibility }) => {
381+
const model = irisGridTestUtils.makeModel();
382+
const testRenderer = TestRenderer.create(
383+
<IrisGrid model={model} settings={DEFAULT_SETTINGS} />,
384+
{ createNodeMock }
385+
);
386+
const component =
387+
testRenderer.getInstance() as TestRenderer.ReactTestInstance & IrisGrid;
388+
389+
act(() => {
390+
component.setState({
391+
hoverAdvancedFilter: columnIndex,
392+
isFilterBarShown: true,
393+
});
394+
});
395+
396+
const advancedFilterButtons = testRenderer.root.findAll(
397+
el =>
398+
el.props?.className?.includes('advanced-filter-button') === true &&
399+
el.props?.className?.includes('container') !== true
400+
);
401+
402+
expect(advancedFilterButtons.length > 0).toBe(expectedVisibility);
403+
}
404+
);
405+
});

0 commit comments

Comments
 (0)