This is an automated email from the ASF dual-hosted git repository.
bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new f2e0cbbd0d9 Adds support for hiding columns in datatable (#47826)
f2e0cbbd0d9 is described below
commit f2e0cbbd0d9bd38c59b053199a37db1abfca99ca
Author: Aritra Basu <[email protected]>
AuthorDate: Wed Mar 26 19:37:37 2025 +0530
Adds support for hiding columns in datatable (#47826)
* Add support for hiding columns in datatable
Closes: #47423
Currently defaulting to always allow filtering
* Moved filter to outside the table
* Update hide condition
* Rebased and used hasRow
* Updated title
* Add margin
---
.../ui/src/components/DataTable/DataTable.tsx | 28 ++++++---
.../src/components/DataTable/FilterMenuButton.tsx | 70 ++++++++++++++++++++++
.../ui/src/components/DataTable/TableList.tsx | 1 -
.../airflow/ui/src/components/DataTable/types.ts | 3 +-
4 files changed, 92 insertions(+), 10 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/components/DataTable/DataTable.tsx
b/airflow-core/src/airflow/ui/src/components/DataTable/DataTable.tsx
index d4ed640a46f..8168ac28e45 100644
--- a/airflow-core/src/airflow/ui/src/components/DataTable/DataTable.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DataTable/DataTable.tsx
@@ -16,27 +16,30 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { HStack, Text } from "@chakra-ui/react";
+import { HStack, Spacer, Text } from "@chakra-ui/react";
import {
getCoreRowModel,
getExpandedRowModel,
getPaginationRowModel,
useReactTable,
+ type VisibilityState,
type OnChangeFn,
type TableState as ReactTableState,
type Row,
type Table as TanStackTable,
type Updater,
} from "@tanstack/react-table";
-import React, { type ReactNode, useCallback, useRef } from "react";
+import React, { type ReactNode, useCallback, useRef, useState } from "react";
-import { ProgressBar, Pagination, Toaster } from "../ui";
-import { CardList } from "./CardList";
-import { TableList } from "./TableList";
-import { createSkeletonMock } from "./skeleton";
-import type { CardDef, MetaColumn, TableState } from "./types";
+import { CardList } from "src/components/DataTable/CardList";
+import FilterMenuButton from "src/components/DataTable/FilterMenuButton";
+import { TableList } from "src/components/DataTable/TableList";
+import { createSkeletonMock } from "src/components/DataTable/skeleton";
+import type { CardDef, MetaColumn, TableState } from
"src/components/DataTable/types";
+import { ProgressBar, Pagination, Toaster } from "src/components/ui";
type DataTableProps<TData> = {
+ readonly allowFiltering?: boolean;
readonly cardDef?: CardDef<TData>;
readonly columns: Array<MetaColumn<TData>>;
readonly data: Array<TData>;
@@ -57,6 +60,7 @@ type DataTableProps<TData> = {
const defaultGetRowCanExpand = () => false;
export const DataTable = <TData,>({
+ allowFiltering = true,
cardDef,
columns,
data,
@@ -83,6 +87,7 @@ export const DataTable = <TData,>({
// Only use the controlled state
const nextState = {
+ columnVisibility: next.columnVisibility,
pagination: next.pagination,
sorting: next.sorting,
};
@@ -92,21 +97,24 @@ export const DataTable = <TData,>({
},
[onStateChange],
);
+ const [columnVisibility, setColumnVisibility] =
useState<VisibilityState>({});
const rest = Boolean(isLoading) ? createSkeletonMock(displayMode,
skeletonCount, columns) : {};
const table = useReactTable({
columns,
data,
+ enableHiding: true,
getCoreRowModel: getCoreRowModel(),
getExpandedRowModel: getExpandedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getRowCanExpand,
manualPagination: true,
manualSorting: true,
+ onColumnVisibilityChange: setColumnVisibility,
onStateChange: handleStateChange,
rowCount: total,
- state: initialState,
+ state: { ...initialState, columnVisibility },
...rest,
});
@@ -125,6 +133,10 @@ export const DataTable = <TData,>({
<>
<ProgressBar size="xs" visibility={Boolean(isFetching) &&
!Boolean(isLoading) ? "visible" : "hidden"} />
<Toaster />
+ <HStack>
+ <Spacer display="flow" />
+ {allowFiltering && hasRows && display === "table" ? <FilterMenuButton
table={table} /> : undefined}
+ </HStack>
{errorMessage}
{hasRows && display === "table" ? <TableList table={table} /> :
undefined}
{hasRows && display === "card" && cardDef !== undefined ? (
diff --git
a/airflow-core/src/airflow/ui/src/components/DataTable/FilterMenuButton.tsx
b/airflow-core/src/airflow/ui/src/components/DataTable/FilterMenuButton.tsx
new file mode 100644
index 00000000000..cbc9855a922
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/DataTable/FilterMenuButton.tsx
@@ -0,0 +1,70 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { IconButton } from "@chakra-ui/react";
+import { flexRender, type Header, type Table } from "@tanstack/react-table";
+import { MdFilterList } from "react-icons/md";
+
+import { Menu } from "src/components/ui";
+import { Checkbox } from "src/components/ui/Checkbox";
+
+type Props<TData> = {
+ readonly table: Table<TData>;
+};
+
+const FilterMenuButton = <TData,>({ table }: Props<TData>) => (
+ <Menu.Root closeOnSelect={false} positioning={{ placement: "bottom" }}>
+ <Menu.Trigger asChild>
+ <IconButton
+ aria-label="Filter table columns"
+ margin={1}
+ padding={0}
+ title="Filter table columns"
+ variant="plain"
+ >
+ <MdFilterList size="1" />
+ </IconButton>
+ </Menu.Trigger>
+ <Menu.Content>
+ {table.getAllLeafColumns().map((column) => {
+ const text = flexRender(column.columnDef.header, {
+ column,
+ header: { column } as Header<TData, unknown>,
+ table,
+ });
+
+ return text?.toString ? (
+ <Menu.Item asChild key={column.id} value={column.id}>
+ <Checkbox
+ checked={column.getIsVisible()}
+ // At least one item needs to be visible
+ disabled={table.getVisibleFlatColumns().length < 2 &&
column.getIsVisible()}
+ onChange={() => {
+ column.toggleVisibility();
+ }}
+ >
+ {text}
+ </Checkbox>
+ </Menu.Item>
+ ) : undefined;
+ })}
+ </Menu.Content>
+ </Menu.Root>
+);
+
+export default FilterMenuButton;
diff --git a/airflow-core/src/airflow/ui/src/components/DataTable/TableList.tsx
b/airflow-core/src/airflow/ui/src/components/DataTable/TableList.tsx
index 5e18fabc837..d2027dcc5a1 100644
--- a/airflow-core/src/airflow/ui/src/components/DataTable/TableList.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DataTable/TableList.tsx
@@ -35,7 +35,6 @@ export const TableList = <TData,>({ renderSubComponent, table
}: DataTableProps<
const sort = column.getIsSorted();
const canSort = column.getCanSort();
const text = flexRender(column.columnDef.header, getContext());
-
let rightIcon;
if (canSort) {
diff --git a/airflow-core/src/airflow/ui/src/components/DataTable/types.ts
b/airflow-core/src/airflow/ui/src/components/DataTable/types.ts
index 50383f3624c..38a8b0e36b8 100644
--- a/airflow-core/src/airflow/ui/src/components/DataTable/types.ts
+++ b/airflow-core/src/airflow/ui/src/components/DataTable/types.ts
@@ -17,10 +17,11 @@
* under the License.
*/
import type { SimpleGridProps } from "@chakra-ui/react";
-import type { ColumnDef, PaginationState, SortingState } from
"@tanstack/react-table";
+import type { ColumnDef, PaginationState, SortingState, VisibilityState } from
"@tanstack/react-table";
import type { ReactNode } from "react";
export type TableState = {
+ columnVisibility?: VisibilityState;
pagination: PaginationState;
sorting: SortingState;
};