Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
20 changes: 19 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions packages/dashboard-core-plugins/src/FilterEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { makeEventFunctions } from '@deephaven/golden-layout';
import { type dh } from '@deephaven/jsapi-types';
import { InputFilterEvent } from './events';
import {
type FilterChangeEvent,
type FilterColumnSourceId,
} from './FilterPlugin';
Comment thread
mattrunyon marked this conversation as resolved.
Outdated

export const {
listen: listenForFilterColumnsChanged,
emit: emitFilterColumnsChanged,
useListener: useFilterColumnsChangedListener,
} = makeEventFunctions<
[
sourceId: FilterColumnSourceId,
columns: readonly { name: string; type: string }[] | null,
Comment thread
mattrunyon marked this conversation as resolved.
]
>(InputFilterEvent.COLUMNS_CHANGED);

export const {
listen: listenForFilterTableChanged,
emit: emitFilterTableChanged,
useListener: useFilterTableChangedListener,
} = makeEventFunctions<
[sourceId: FilterColumnSourceId, table: dh.Table | null]
>(InputFilterEvent.TABLE_CHANGED);

export const {
listen: listenForFilterChanged,
emit: emitFilterChanged,
useListener: useFilterChangedListener,
} = makeEventFunctions<
[
sourceId: FilterColumnSourceId,
filterChange: FilterChangeEvent | FilterChangeEvent[] | null,
Comment thread
mattrunyon marked this conversation as resolved.
]
>(InputFilterEvent.FILTERS_CHANGED);
87 changes: 49 additions & 38 deletions packages/dashboard-core-plugins/src/FilterPlugin.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
import { type Component, useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { nanoid } from 'nanoid';
import {
assertIsDashboardPluginProps,
type DashboardPluginComponentProps,
LayoutUtils,
type PanelComponent,
PanelEvent,
type PanelId,
updateDashboardData,
useListener,
} from '@deephaven/dashboard';
import Log from '@deephaven/log';
import { TextUtils } from '@deephaven/utils';
import { type dh } from '@deephaven/jsapi-types';
import { InputFilterEvent } from './events';
import {
DropdownFilterPanel,
FilterSetManagerPanel,
InputFilterPanel,
type WidgetId,
} from './panels';
import {
useFilterChangedListener,
useFilterColumnsChangedListener,
useFilterTableChangedListener,
} from './FilterEvents';

const log = Log.module('FilterPlugin');

Expand All @@ -38,10 +45,6 @@ export type FilterChangeEvent = Column & {

export type FilterPluginProps = Partial<DashboardPluginComponentProps>;

function flattenArray<T>(accumulator: T[], currentValue: T | T[]): T[] {
return accumulator.concat(currentValue);
}

export function FilterPlugin(props: FilterPluginProps): JSX.Element | null {
assertIsDashboardPluginProps(props);
const { id: localDashboardId, layout, registerComponent } = props;
Expand All @@ -50,13 +53,15 @@ export function FilterPlugin(props: FilterPluginProps): JSX.Element | null {
() => new Map<FilterColumnSourceId, Column[]>()
);
const [panelFilters] = useState(
() => new Map<Component, FilterChangeEvent[]>()
() => new Map<FilterColumnSourceId, FilterChangeEvent[]>()
);
const [panelTables] = useState(
() => new Map<FilterColumnSourceId, dh.Table>()
);
const [panelTables] = useState(() => new Map());

const sendUpdate = useCallback(() => {
const columns = Array.from(panelColumns.values())
.reduce(flattenArray, [] as Column[])
.flat()
.sort((a, b) => {
const aName = TextUtils.toLower(a.name);
const bName = TextUtils.toLower(b.name);
Expand Down Expand Up @@ -87,7 +92,7 @@ export function FilterPlugin(props: FilterPluginProps): JSX.Element | null {
}, [] as Column[]);

const filters = Array.from(panelFilters.values())
.reduce(flattenArray, [] as FilterChangeEvent[])
.flat()
.sort((a, b) => a.timestamp - b.timestamp);
const tableMap = new Map(panelTables);

Expand All @@ -100,49 +105,67 @@ export function FilterPlugin(props: FilterPluginProps): JSX.Element | null {
/**
* Handler for the COLUMNS_CHANGED event.
* @param sourceId The id of the component that's emitting the filter change
* @param columns The columns in this panel
* @param columns The columns in this panel. Null to clear the columns.
*/
const handleColumnsChanged = useCallback(
(sourceId: FilterColumnSourceId, columns: Column | Column[]) => {
(sourceId: FilterColumnSourceId, columns: readonly Column[] | null) => {
log.debug2('handleColumnsChanged', sourceId, columns);
panelColumns.set(sourceId, ([] as Column[]).concat(columns));
if (columns == null) {
panelColumns.delete(sourceId);
} else {
panelColumns.set(sourceId, ([] as Column[]).concat(columns));
}
sendUpdate();
},
[panelColumns, sendUpdate]
);

/**
* Handler for the FILTERS_CHANGED event.
* @param {Component} panel The component that's emitting the filter change
* @param {FilterChangeEvent|Array<FilterChangeEvent>} filters The input filters set by the panel
* @param sourceId The id of the component that's emitting the filter change
* @param filters The input filters set by the panel
*/
const handleFiltersChanged = useCallback(
(panel, filters) => {
log.debug2('handleFiltersChanged', panel, filters);
panelFilters.set(panel, [].concat(filters) as FilterChangeEvent[]);
(
sourceId: FilterColumnSourceId,
filters: FilterChangeEvent | FilterChangeEvent[] | null
) => {
log.debug2('handleFiltersChanged', sourceId, filters);
if (filters == null) {
panelFilters.delete(sourceId);
} else {
panelFilters.set(
sourceId,
([] as FilterChangeEvent[]).concat(filters ?? [])
);
}
sendUpdate();
},
[panelFilters, sendUpdate]
);

const handleTableChanged = useCallback(
(panel, table) => {
log.debug2('handleTableChanged', panel, table);
panelTables.set(LayoutUtils.getIdFromPanel(panel), table);
(sourceId: FilterColumnSourceId, table: dh.Table | null) => {
log.debug2('handleTableChanged', sourceId, table);
if (table == null) {
panelTables.delete(sourceId);
} else {
panelTables.set(sourceId, table);
}
sendUpdate();
},
[panelTables, sendUpdate]
);

const handlePanelUnmount = useCallback(
panel => {
(panel: PanelComponent) => {
log.debug2('handlePanelUnmount', panel);
const panelId = LayoutUtils.getIdFromPanel(panel);
if (panelId != null) {
panelColumns.delete(panelId);
panelTables.delete(panelId);
panelFilters.delete(panelId);
}
panelFilters.delete(panel);
panelTables.delete(panelId);
sendUpdate();
},
[panelColumns, panelFilters, panelTables, sendUpdate]
Expand Down Expand Up @@ -256,21 +279,9 @@ export function FilterPlugin(props: FilterPluginProps): JSX.Element | null {
[registerComponent]
);

useListener(
layout.eventHub,
InputFilterEvent.COLUMNS_CHANGED,
handleColumnsChanged
);
useListener(
layout.eventHub,
InputFilterEvent.FILTERS_CHANGED,
handleFiltersChanged
);
useListener(
layout.eventHub,
InputFilterEvent.TABLE_CHANGED,
handleTableChanged
);
useFilterColumnsChangedListener(layout.eventHub, handleColumnsChanged);
useFilterChangedListener(layout.eventHub, handleFiltersChanged);
useFilterTableChangedListener(layout.eventHub, handleTableChanged);
useListener(
layout.eventHub,
InputFilterEvent.OPEN_DROPDOWN,
Expand Down
11 changes: 10 additions & 1 deletion packages/dashboard-core-plugins/src/GridWidgetPlugin.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ import { createMockStore } from '@deephaven/redux';
import dh from '@deephaven/jsapi-shim';
import GridWidgetPlugin from './GridWidgetPlugin';

const mockGoldenLayout = { eventHub: TestUtils.createMockProxy() };

jest.mock('@deephaven/dashboard', () => ({
...(jest.requireActual('@deephaven/dashboard') as Record<string, unknown>),
useLayoutManager: jest.fn(() => mockGoldenLayout),
useDashboardId: jest.fn(() => 'test'),
useDhId: jest.fn(() => ({})),
}));

const MockIrisGrid: React.FC & jest.Mock = jest.fn(() => (
<div>MockIrisGrid</div>
));
Expand All @@ -22,7 +31,7 @@ jest.mock('@deephaven/iris-grid', () => {
});

it('mounts without crashing', async () => {
const table = TestUtils.createMockProxy<DhType.Table>();
const table = TestUtils.createMockProxy<DhType.Table>({ columns: [] });
Comment thread
mofojed marked this conversation as resolved.
const fetch = jest.fn(() => Promise.resolve(table));

const store = createMockStore();
Expand Down
30 changes: 28 additions & 2 deletions packages/dashboard-core-plugins/src/GridWidgetPlugin.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useMemo, useRef } from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import {
type WidgetComponentProps,
usePersistentState,
Expand All @@ -10,20 +10,25 @@ import {
IrisGrid,
IrisGridCacheUtils,
type IrisGridState,
type IrisGridType,
IrisGridUtils,
} from '@deephaven/iris-grid';
import { useSelector } from 'react-redux';
import { getSettings, type RootState } from '@deephaven/redux';
import { LoadingOverlay } from '@deephaven/components';
import { getErrorMessage } from '@deephaven/utils';
import { useLayoutManager, useListener } from '@deephaven/dashboard';
import { EMPTY_ARRAY, getErrorMessage } from '@deephaven/utils';
import { useApi } from '@deephaven/jsapi-bootstrap';
import { type GridState } from '@deephaven/grid';
import { useIrisGridModel } from './useIrisGridModel';
import useDashboardColumnFilters from './useDashboardColumnFilters';
import { InputFilterEvent } from './events';

export function GridWidgetPlugin({
fetch,
}: WidgetComponentProps<DhType.Table>): JSX.Element | null {
const settings = useSelector(getSettings<RootState>);
const { eventHub } = useLayoutManager();

const fetchResult = useIrisGridModel(fetch);

Expand Down Expand Up @@ -82,6 +87,25 @@ export function GridWidgetPlugin({
[fetchResult, setState, dehydrateIrisGridState]
);

const inputFilters = useDashboardColumnFilters(
Comment thread
mattrunyon marked this conversation as resolved.
fetchResult.status === 'success' ? fetchResult.model.columns : EMPTY_ARRAY
);

const [irisGrid, setIrisGrid] = useState<IrisGridType | null>(null);
Comment thread
mattrunyon marked this conversation as resolved.
Outdated

const handleClearAllFilters = useCallback(() => {
if (irisGrid == null) {
return;
}
irisGrid.clearAllFilters();
}, [irisGrid]);

useListener(
eventHub,
InputFilterEvent.CLEAR_ALL_FILTERS,
handleClearAllFilters
);

if (fetchResult.status === 'loading') {
return <LoadingOverlay isLoading />;
}
Expand All @@ -98,11 +122,13 @@ export function GridWidgetPlugin({
const { model } = fetchResult;
return (
<IrisGrid
ref={ref => setIrisGrid(ref)}
Comment thread
mattrunyon marked this conversation as resolved.
Outdated
model={model}
settings={settings}
onStateChange={handleIrisGridChange}
// eslint-disable-next-line react/jsx-props-no-spreading
{...hydratedState}
inputFilters={inputFilters}
/>
);
}
Expand Down
1 change: 1 addition & 0 deletions packages/dashboard-core-plugins/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export { default as LinkerUtils } from './linker/LinkerUtils';
export type { Link } from './linker/LinkerUtils';
export { default as ToolType } from './linker/ToolType';
export * from './useConfigureRuff';
export * from './useDashboardColumnFilters';
export * from './useLoadTablePlugin';

export * from './events';
Expand Down
Loading
Loading