Skip to content

Commit

Permalink
feat: Add show_search and show_quick_filters to ui.table (#461)
Browse files Browse the repository at this point in the history
Fixes #443, Fixes #444. Also removes `ui.table.quick_filter` to align
with design doc. Drops `can_search` for now since it was not implemented
according to spec

```py
from deephaven import empty_table
import deephaven.ui as ui

t = empty_table(1000).update("X=ii")

ui_table = ui.table(t, quick_filters={"X": ">50", "Y": "=20"}, show_quick_filters=True)
ui_with_search = ui.table(t, show_search=True)
ui_with_search_and_filter = ui.table(t, show_search=True, show_quick_filters=True)
```
  • Loading branch information
mattrunyon authored May 29, 2024
1 parent 6732ad6 commit 4923017
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 175 deletions.
160 changes: 84 additions & 76 deletions plugins/ui/DESIGN.md

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions plugins/ui/src/deephaven/ui/components/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from ..elements import UITable
from ..types import (
CellPressCallback,
ColumnName,
ColumnPressCallback,
QuickFilterExpression,
RowPressCallback,
)

Expand All @@ -18,6 +20,9 @@ def table(
on_cell_double_press: CellPressCallback | None = None,
on_column_press: ColumnPressCallback | None = None,
on_column_double_press: ColumnPressCallback | None = None,
quick_filters: dict[ColumnName, QuickFilterExpression] | None = None,
show_quick_filters: bool = False,
show_search: bool = False,
) -> UITable:
"""
Customization to how a table is displayed, how it behaves, and listen to UI events.
Expand All @@ -40,6 +45,9 @@ def table(
The first parameter is the column name.
on_column_double_press: The callback function to run when a column is double clicked.
The first parameter is the column name.
quick_filters: The quick filters to apply to the table. Dictionary of column name to filter value.
show_quick_filters: Whether to show the quick filter bar by default.
show_search: Whether to show the search bar by default.
"""
props = locals()
del props["table"]
Expand Down
47 changes: 3 additions & 44 deletions plugins/ui/src/deephaven/ui/elements/UITable.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from ..types import (
ColumnName,
AggregationOperation,
SearchMode,
QuickFilterExpression,
Color,
ContextMenuAction,
Expand Down Expand Up @@ -58,11 +57,6 @@ def remap_sort_direction(direction: TableSortDirection | str) -> Literal["ASC",


class UITableProps(TypedDict):
can_search: NotRequired[bool]
"""
Whether the search bar is accessible or not. Use the system default if no value set.
"""

on_row_press: NotRequired[RowPressCallback]
"""
Callback function to run when a row is clicked.
Expand Down Expand Up @@ -190,7 +184,9 @@ def _with_dict_prop(self, key: str, value: dict[str, Any]) -> "UITable":
A new UITable with the passed in prop added to the existing props
"""
logger.debug("_with_dict_prop(%s, %s)", key, value)
existing = self._props.get(key, {})
existing = (
self._props.get(key) or {}
) # Turn missing or explicit None into empty dict
return UITable(**{**self._props, key: {**existing, **value}}) # type: ignore

def render(self, context: RenderContext) -> dict[str, Any]:
Expand Down Expand Up @@ -250,29 +246,6 @@ def back_columns(self, columns: str | list[str]) -> "UITable":
"""
raise NotImplementedError()

def can_search(self, mode: SearchMode) -> "UITable":
"""
Set the search bar to explicitly be accessible or inaccessible, or use the system default.
Args:
mode: Set the search bar to explicitly be accessible or inaccessible,
or use the system default.
Returns:
A new UITable
"""
if mode == "SHOW":
return self._with_prop("can_search", True)
elif mode == "HIDE":
return self._with_prop("can_search", False)
elif mode == "DEFAULT":
new = self._with_prop("can_search", None)
# pop current can_search value if it exists
new._props.pop("can_search", None)
return new

raise ValueError(f"Invalid search mode: {mode}")

def column_group(
self, name: str, children: list[str], color: str | None
) -> "UITable":
Expand Down Expand Up @@ -477,20 +450,6 @@ def on_row_double_press(self, callback: RowPressCallback) -> "UITable":
)
return self._with_prop("on_row_double_press", callback)

def quick_filter(
self, filter: dict[ColumnName, QuickFilterExpression]
) -> "UITable":
"""
Add a quick filter for the UI to apply to the table.
Args:
filter: The quick filter to apply to the table.
Returns:
A new UITable
"""
return self._with_dict_prop("filters", filter)

def selection_mode(self, mode: SelectionMode) -> "UITable":
"""
Set the selection mode for the table.
Expand Down
1 change: 0 additions & 1 deletion plugins/ui/src/deephaven/ui/types/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ class RowDataValue(CellData):
RowData = Dict[ColumnName, Any]
ColumnData = List[Any]
TableData = Dict[ColumnName, ColumnData]
SearchMode = Literal["SHOW", "HIDE", "DEFAULT"]
SelectionArea = Literal["CELL", "ROW", "COLUMN"]
SelectionMode = Literal["SINGLE", "MULTIPLE"]
Sentinel = Any
Expand Down
40 changes: 24 additions & 16 deletions plugins/ui/src/js/src/elements/UITable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ function UITable({
onColumnDoublePress,
onRowPress,
onRowDoublePress,
canSearch,
filters,
quickFilters,
sorts,
alwaysFetchColumns,
table: exportedTable,
showSearch: showSearchBar,
showQuickFilters,
}: UITableProps): JSX.Element | null {
const dh = useApi();
const [model, setModel] = useState<IrisGridModel>();
Expand All @@ -47,12 +48,16 @@ function UITable({
}, [columns, utils, sorts]);

const hydratedQuickFilters = useMemo(() => {
if (filters !== undefined && model !== undefined && columns !== undefined) {
log.debug('Hydrating filters', filters);
if (
quickFilters !== undefined &&
model !== undefined &&
columns !== undefined
) {
log.debug('Hydrating filters', quickFilters);

const dehydratedQuickFilters: DehydratedQuickFilter[] = [];

Object.entries(filters).forEach(([columnName, filter]) => {
Object.entries(quickFilters).forEach(([columnName, filter]) => {
const columnIndex = model.getColumnIndexByName(columnName);
if (columnIndex !== undefined) {
dehydratedQuickFilters.push([columnIndex, { text: filter }]);
Expand All @@ -62,7 +67,7 @@ function UITable({
return utils.hydrateQuickFilters(columns, dehydratedQuickFilters);
}
return undefined;
}, [filters, model, columns, utils]);
}, [quickFilters, model, columns, utils]);

// Just load the object on mount
useEffect(() => {
Expand Down Expand Up @@ -110,19 +115,22 @@ function UITable({
]
);

const irisGridProps: Partial<IrisGridProps> = useMemo(
() => ({
mouseHandlers,
alwaysFetchColumns,
showSearchBar: canSearch,
sorts: hydratedSorts,
quickFilters: hydratedQuickFilters,
settings,
}),
const irisGridProps = useMemo(
() =>
({
mouseHandlers,
alwaysFetchColumns,
showSearchBar,
sorts: hydratedSorts,
quickFilters: hydratedQuickFilters,
isFilterBarShown: showQuickFilters,
settings,
}) satisfies Partial<IrisGridProps>,
[
mouseHandlers,
alwaysFetchColumns,
canSearch,
showSearchBar,
showQuickFilters,
hydratedSorts,
hydratedQuickFilters,
settings,
Expand Down
5 changes: 3 additions & 2 deletions plugins/ui/src/js/src/elements/UITableUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ export interface UITableProps {
onColumnPress?: (columnName: ColumnName) => void;
onColumnDoublePress?: (columnName: ColumnName) => void;
alwaysFetchColumns?: string[];
canSearch?: boolean;
filters?: Record<string, string>;
quickFilters?: Record<string, string>;
sorts?: DehydratedSort[];
showSearch: boolean;
showQuickFilters: boolean;
[key: string]: unknown;
}

Expand Down
60 changes: 24 additions & 36 deletions plugins/ui/test/deephaven/ui/test_ui_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ def expect_render(self, ui_table, expected_props: dict[str, Any]):
context = RenderContext(on_change, on_queue)
result = ui_table.render(context)

self.assertDictEqual(result, expected_props)
# Can replace 2nd param with result | expected_props after dropping Python 3.8
# https://stackoverflow.com/questions/20050913/python-unittests-assertdictcontainssubset-recommended-alternative
self.assertDictEqual(result, {**result, **expected_props})

def test_empty_ui_table(self):
import deephaven.ui as ui
Expand All @@ -39,7 +41,6 @@ def callback(row):
self.expect_render(
t,
{
"table": self.source,
"onRowDoublePress": callback,
},
)
Expand All @@ -54,7 +55,6 @@ def test_always_fetch_columns(self):
self.expect_render(
t,
{
"table": self.source,
"alwaysFetchColumns": ["X"],
},
)
Expand All @@ -64,7 +64,6 @@ def test_always_fetch_columns(self):
self.expect_render(
t,
{
"table": self.source,
"alwaysFetchColumns": ["X", "Y"],
},
)
Expand All @@ -74,95 +73,88 @@ def test_always_fetch_columns(self):
self.expect_render(
t,
{
"table": self.source,
"alwaysFetchColumns": ["X", "Y"],
},
)

def test_can_search(self):
def test_quick_filters(self):
import deephaven.ui as ui

ui_table = ui.table(self.source)

t = ui_table.can_search("SHOW")
t = ui.table(self.source, quick_filters={"X": "X > 1"})

self.expect_render(
t,
{
"table": self.source,
"canSearch": True,
"quickFilters": {"X": "X > 1"},
},
)

t = ui_table.can_search("HIDE")
t = ui.table(self.source, quick_filters={"X": "X > 1", "Y": "Y < 2"})

self.expect_render(
t,
{
"table": self.source,
"canSearch": False,
"quickFilters": {"X": "X > 1", "Y": "Y < 2"},
},
)

t = ui_table.can_search("DEFAULT")
def test_show_quick_filters(self):
import deephaven.ui as ui

t = ui.table(self.source)

self.expect_render(
t,
{
"table": self.source,
"showQuickFilters": False,
},
)

t = ui_table.can_search("SHOW").can_search("DEFAULT")
t = ui.table(self.source, show_quick_filters=True)

self.expect_render(
t,
{
"table": self.source,
"showQuickFilters": True,
},
)

t = ui_table.can_search("HIDE").can_search("DEFAULT")
t = ui.table(self.source, show_quick_filters=False)

self.expect_render(
t,
{
"table": self.source,
"showQuickFilters": False,
},
)

def test_quick_filter(self):
def test_show_search(self):
import deephaven.ui as ui

ui_table = ui.table(self.source)

t = ui_table.quick_filter({"X": "X > 1"})
t = ui.table(self.source)

self.expect_render(
t,
{
"table": self.source,
"filters": {"X": "X > 1"},
"showSearch": False,
},
)

t = ui_table.quick_filter({"X": "X > 1"}).quick_filter({"X": "X > 2"})
t = ui.table(self.source, show_search=True)

self.expect_render(
t,
{
"table": self.source,
"filters": {"X": "X > 2"},
"showSearch": True,
},
)

t = ui_table.quick_filter({"X": "X > 1", "Y": "Y < 2"})
t = ui.table(self.source, show_search=False)

self.expect_render(
t,
{
"table": self.source,
"filters": {"X": "X > 1", "Y": "Y < 2"},
"showSearch": False,
},
)

Expand All @@ -176,7 +168,6 @@ def test_sort(self):
self.expect_render(
t,
{
"table": self.source,
"sorts": [{"column": "X", "direction": "ASC", "is_abs": False}],
},
)
Expand All @@ -185,7 +176,6 @@ def test_sort(self):
self.expect_render(
t,
{
"table": self.source,
"sorts": [{"column": "X", "direction": "DESC", "is_abs": False}],
},
)
Expand All @@ -208,7 +198,6 @@ def test_sort(self):
self.expect_render(
t,
{
"table": self.source,
"sorts": [
{"column": "X", "direction": "ASC", "is_abs": False},
{"column": "Y", "direction": "DESC", "is_abs": False},
Expand All @@ -221,7 +210,6 @@ def test_sort(self):
self.expect_render(
t,
{
"table": self.source,
"sorts": [
{"column": "X", "direction": "DESC", "is_abs": False},
{"column": "Y", "direction": "ASC", "is_abs": False},
Expand Down

0 comments on commit 4923017

Please sign in to comment.