This is an automated email from the ASF dual-hosted git repository.
eladkal 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 94f6fcc6c5 Upgrade react-table package. Use with Audit Log table
(#38092)
94f6fcc6c5 is described below
commit 94f6fcc6c555f106059f57e04afaf8bbf6b18a83
Author: Brent Bovenzi <[email protected]>
AuthorDate: Mon Mar 18 01:39:47 2024 -0700
Upgrade react-table package. Use with Audit Log table (#38092)
---
airflow/www/package.json | 1 +
airflow/www/static/js/api/useEventLogs.tsx | 1 +
.../www/static/js/components/NewTable/NewCells.tsx | 33 +++
.../www/static/js/components/NewTable/NewTable.tsx | 269 +++++++++++++++++++++
.../js/components/NewTable/createSkeleton.tsx | 47 ++++
.../js/components/NewTable/searchParams.test.ts | 63 +++++
.../static/js/components/NewTable/searchParams.ts | 126 ++++++++++
.../js/components/NewTable/useTableUrlState.ts | 69 ++++++
airflow/www/static/js/dag/details/AuditLog.tsx | 102 ++++----
airflow/www/static/js/dag/useSelection.ts | 23 ++
airflow/www/yarn.lock | 12 +
11 files changed, 695 insertions(+), 51 deletions(-)
diff --git a/airflow/www/package.json b/airflow/www/package.json
index e6149c8bd1..0f6fb546c9 100644
--- a/airflow/www/package.json
+++ b/airflow/www/package.json
@@ -103,6 +103,7 @@
"@emotion/cache": "^11.9.3",
"@emotion/react": "^11.9.3",
"@emotion/styled": "^11",
+ "@tanstack/react-table": "^8.13.2",
"@visx/group": "^2.10.0",
"@visx/shape": "^2.12.2",
"ansi_up": "^6.0.2",
diff --git a/airflow/www/static/js/api/useEventLogs.tsx
b/airflow/www/static/js/api/useEventLogs.tsx
index 409fd930b0..3a04eac393 100644
--- a/airflow/www/static/js/api/useEventLogs.tsx
+++ b/airflow/www/static/js/api/useEventLogs.tsx
@@ -67,6 +67,7 @@ export default function useEventLogs({
},
{
refetchInterval: isRefreshOn && (autoRefreshInterval || 1) * 1000,
+ keepPreviousData: true,
}
);
}
diff --git a/airflow/www/static/js/components/NewTable/NewCells.tsx
b/airflow/www/static/js/components/NewTable/NewCells.tsx
new file mode 100644
index 0000000000..40cf40a53c
--- /dev/null
+++ b/airflow/www/static/js/components/NewTable/NewCells.tsx
@@ -0,0 +1,33 @@
+/*!
+ * 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 React from "react";
+import { Code } from "@chakra-ui/react";
+
+import Time from "src/components/Time";
+
+export const TimeCell = ({ getValue }: any) => {
+ const value = getValue();
+ return <Time dateTime={value} />;
+};
+
+export const CodeCell = ({ getValue }: any) => {
+ const value = getValue();
+ return value ? <Code>{JSON.stringify(value)}</Code> : null;
+};
diff --git a/airflow/www/static/js/components/NewTable/NewTable.tsx
b/airflow/www/static/js/components/NewTable/NewTable.tsx
new file mode 100644
index 0000000000..1b45aaa8ea
--- /dev/null
+++ b/airflow/www/static/js/components/NewTable/NewTable.tsx
@@ -0,0 +1,269 @@
+/*!
+ * 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.
+ */
+
+/*
+ * Custom wrapper of react-table using Chakra UI components
+ */
+
+import React, { useRef, useCallback } from "react";
+import {
+ Table as ChakraTable,
+ Thead,
+ Tbody,
+ Tr,
+ Th,
+ Td,
+ useColorModeValue,
+ TableProps as ChakraTableProps,
+ HStack,
+ Box,
+ ButtonGroup,
+ Button,
+ Spinner,
+} from "@chakra-ui/react";
+import {
+ useReactTable,
+ getCoreRowModel,
+ getPaginationRowModel,
+ getSortedRowModel,
+ PaginationState,
+ SortingState,
+ flexRender,
+ ColumnDef,
+ TableState as ReactTableState,
+ RowData,
+ OnChangeFn,
+ Updater,
+ Table as ReactTable,
+} from "@tanstack/react-table";
+import { MdKeyboardArrowLeft, MdKeyboardArrowRight } from "react-icons/md";
+import {
+ TiArrowUnsorted,
+ TiArrowSortedDown,
+ TiArrowSortedUp,
+} from "react-icons/ti";
+import { isEqual, pick } from "lodash";
+
+import createSkeleton from "./createSkeleton";
+
+export interface TableProps<TData extends RowData> extends ChakraTableProps {
+ data: TData[];
+ columns: ColumnDef<TData, any>[];
+ initialState?: TableState;
+ onStateChange?: (state: TableState) => void;
+ resultCount?: number;
+ isLoading?: boolean;
+ isFetching?: boolean;
+ onRowClicked?: (row: any, e: unknown) => void;
+ skeletonCount?: number;
+}
+
+const normalizeState = (state: ReactTableState): TableState =>
+ pick(state, "pagination", "sorting", "globalFilter");
+
+export interface TableState {
+ pagination: PaginationState;
+ sorting: SortingState;
+}
+
+export const NewTable = <TData extends RowData>({
+ data,
+ columns,
+ initialState,
+ onStateChange,
+ resultCount,
+ isLoading = false,
+ isFetching = false,
+ onRowClicked,
+ skeletonCount = 10,
+}: TableProps<TData>) => {
+ // ref current table instance so that we can "compute" the state
+ const ref = useRef<{ tableRef: ReactTable<TData> | undefined }>({
+ tableRef: undefined,
+ });
+
+ const oddColor = useColorModeValue("gray.50", "gray.900");
+ const hoverColor = useColorModeValue("gray.100", "gray.700");
+
+ const handleStateChange = useCallback<OnChangeFn<ReactTableState>>(
+ (updater: Updater<ReactTableState>) => {
+ // this is why we need ref, the state is NOT pass out as plain object
but a updater function.
+ if (ref.current.tableRef && onStateChange) {
+ // nah, this is a ugly piece of code.
+ const current = ref.current.tableRef.getState();
+ const nextState =
+ typeof updater === "function" ? updater(current) : updater;
+ const old = normalizeState(current);
+ const updated = normalizeState(nextState);
+ // react table call onStateChange on every update even the state does
not change.
+ // so we need to do a quick compare here.
+ if (!isEqual(old, updated)) onStateChange(updated);
+ }
+ },
+ [onStateChange]
+ );
+
+ const tableInstance = useReactTable({
+ columns,
+ data,
+ initialState: {
+ ...initialState,
+ pagination: {
+ pageIndex: 0,
+ pageSize: 25, // default page size to 25
+ ...initialState?.pagination,
+ },
+ },
+ ...(isLoading ? createSkeleton(skeletonCount, columns) : {}),
+ rowCount: resultCount,
+ manualPagination: true,
+ manualSorting: true,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ onStateChange: handleStateChange,
+ });
+
+ ref.current.tableRef = tableInstance;
+
+ const total = resultCount || data.length;
+ const { pageIndex, pageSize } = tableInstance.getState().pagination;
+ const lowerCount = Math.min(pageIndex * pageSize + 1, total);
+ const upperCount = Math.min(
+ (pageIndex + 1) * pageSize,
+ pageIndex * pageSize + data.length,
+ total
+ );
+ const canPrevious = tableInstance.getCanPreviousPage();
+ const canNext = tableInstance.getCanNextPage() && data.length === pageSize;
+ const handlePrevious = () => {
+ tableInstance.previousPage();
+ };
+ const handleNext = () => {
+ tableInstance.nextPage();
+ };
+
+ return (
+ <>
+ <ChakraTable>
+ <Thead>
+ {tableInstance.getHeaderGroups().map((headerGroup) => (
+ <Tr key={headerGroup.id}>
+ {headerGroup.headers.map(
+ ({ id, colSpan, column, isPlaceholder, getContext }) => {
+ const sort = column.getIsSorted();
+ const canSort = column.getCanSort();
+ return (
+ <Th
+ key={id}
+ colSpan={colSpan}
+ whiteSpace="nowrap"
+ cursor={column.getCanSort() ? "pointer" : undefined}
+ onClick={column.getToggleSortingHandler()}
+ >
+ {isPlaceholder
+ ? null
+ : flexRender(column.columnDef.header, getContext())}
+ {canSort && !sort && (
+ <TiArrowUnsorted
+ aria-label="unsorted"
+ style={{ display: "inline" }}
+ size="1em"
+ />
+ )}
+ {canSort &&
+ sort &&
+ (sort === "desc" ? (
+ <TiArrowSortedDown
+ aria-label="sorted descending"
+ style={{ display: "inline" }}
+ size="1em"
+ />
+ ) : (
+ <TiArrowSortedUp
+ aria-label="sorted ascending"
+ style={{ display: "inline" }}
+ size="1em"
+ />
+ ))}
+ </Th>
+ );
+ }
+ )}
+ </Tr>
+ ))}
+ </Thead>
+ <Tbody>
+ {tableInstance.getRowModel().rows.map((row) => (
+ <Tr
+ key={row.id}
+ _odd={{ backgroundColor: oddColor }}
+ _hover={
+ onRowClicked && {
+ backgroundColor: hoverColor,
+ cursor: "pointer",
+ }
+ }
+ onClick={
+ onRowClicked ? (e: unknown) => onRowClicked(row, e) : undefined
+ }
+ >
+ {row.getVisibleCells().map((cell) => (
+ <Td key={cell.id} py={3}>
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+ </Td>
+ ))}
+ </Tr>
+ ))}
+ </Tbody>
+ </ChakraTable>
+ <HStack spacing={4} mt={4}>
+ {upperCount && (
+ <Box fontSize="sm">
+ {lowerCount}-{upperCount}
+ {upperCount !== total && ` out of ${total} total`}
+ </Box>
+ )}
+ {(canPrevious || canNext) && (
+ <ButtonGroup size="sm" isAttached variant="outline">
+ <Button
+ leftIcon={<MdKeyboardArrowLeft />}
+ colorScheme="gray"
+ aria-label="Previous Page"
+ isDisabled={!canPrevious}
+ onClick={handlePrevious}
+ >
+ Prev
+ </Button>
+ <Button
+ rightIcon={<MdKeyboardArrowRight />}
+ colorScheme="gray"
+ aria-label="Next Page"
+ isDisabled={!canNext}
+ onClick={handleNext}
+ >
+ Next
+ </Button>
+ {isFetching && <Spinner ml={2} mt={4} size="sm" />}
+ </ButtonGroup>
+ )}
+ </HStack>
+ </>
+ );
+};
diff --git a/airflow/www/static/js/components/NewTable/createSkeleton.tsx
b/airflow/www/static/js/components/NewTable/createSkeleton.tsx
new file mode 100644
index 0000000000..cb0a228c76
--- /dev/null
+++ b/airflow/www/static/js/components/NewTable/createSkeleton.tsx
@@ -0,0 +1,47 @@
+/*!
+ * 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 React from "react";
+
+import { Skeleton } from "@chakra-ui/react";
+import type { ColumnDef } from "@tanstack/react-table";
+
+function createSkeleton<TData, TValue>(
+ skeletonCount: number,
+ columnDefs: ColumnDef<TData, TValue>[]
+) {
+ const colDefs = columnDefs.map((colDef) => ({
+ ...colDef,
+ cell: () => (
+ <Skeleton
+ data-testid="skeleton"
+ // @ts-ignore
+ width={colDef.meta?.skeletonWidth || 200}
+ height="10px"
+ display="inline-block"
+ />
+ ),
+ }));
+
+ const data = [...Array(skeletonCount)].map(() => ({} as TData));
+
+ return { columns: colDefs, data };
+}
+
+export default createSkeleton;
diff --git a/airflow/www/static/js/components/NewTable/searchParams.test.ts
b/airflow/www/static/js/components/NewTable/searchParams.test.ts
new file mode 100644
index 0000000000..edff0cc0d5
--- /dev/null
+++ b/airflow/www/static/js/components/NewTable/searchParams.test.ts
@@ -0,0 +1,63 @@
+/*!
+ * 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 type { TableState } from "./NewTable";
+import { searchParamsToState, stateToSearchParams } from "./searchParams";
+
+describe("searchParams", () => {
+ describe("stateToSearchParams", () => {
+ it("can serialize table state to search params", () => {
+ const state: TableState = {
+ pagination: {
+ pageIndex: 1,
+ pageSize: 20,
+ },
+ sorting: [{ id: "name", desc: false }],
+ };
+ expect(stateToSearchParams(state).toString()).toEqual(
+ "limit=20&offset=1&sort.name=asc"
+ );
+ });
+ });
+ describe("searchParamsToState", () => {
+ it("can parse search params back to table state", () => {
+ expect(
+ searchParamsToState(
+ new URLSearchParams("limit=20&offset=0&sort.name=asc&sort.age=desc"),
+ {
+ pagination: {
+ pageIndex: 1,
+ pageSize: 5,
+ },
+ sorting: [],
+ }
+ )
+ ).toEqual({
+ pagination: {
+ pageIndex: 0,
+ pageSize: 20,
+ },
+ sorting: [
+ { id: "name", desc: false },
+ { id: "age", desc: true },
+ ],
+ });
+ });
+ });
+});
diff --git a/airflow/www/static/js/components/NewTable/searchParams.ts
b/airflow/www/static/js/components/NewTable/searchParams.ts
new file mode 100644
index 0000000000..ad01c50b9e
--- /dev/null
+++ b/airflow/www/static/js/components/NewTable/searchParams.ts
@@ -0,0 +1,126 @@
+/*!
+ * 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 type { SortingState } from "@tanstack/react-table";
+import { isEqual } from "lodash";
+
+import type { TableState } from "./NewTable";
+
+export const LIMIT_PARAM = "limit";
+export const OFFSET_PARAM = "offset";
+export const SORT_PARAM = "sort.";
+
+export const stateToSearchParams = (
+ state: TableState,
+ defaultTableState?: TableState
+): URLSearchParams => {
+ const queryParams = new URLSearchParams(window.location.search);
+ if (isEqual(state.pagination, defaultTableState?.pagination)) {
+ queryParams.delete(LIMIT_PARAM);
+ queryParams.delete(OFFSET_PARAM);
+ } else if (state.pagination) {
+ queryParams.set(LIMIT_PARAM, `${state.pagination.pageSize}`);
+ queryParams.set(OFFSET_PARAM, `${state.pagination.pageIndex}`);
+ }
+
+ if (isEqual(state.sorting, defaultTableState?.sorting)) {
+ state.sorting.forEach(({ id }) => {
+ queryParams.delete(`${SORT_PARAM}${id}`);
+ });
+ } else if (state.sorting) {
+ state.sorting.forEach(({ id, desc }) => {
+ queryParams.set(`${SORT_PARAM}${id}`, desc ? "desc" : "asc");
+ });
+ }
+
+ return queryParams;
+};
+
+export const searchParamsToState = (
+ searchParams: URLSearchParams,
+ defaultState: TableState
+) => {
+ let urlState: Partial<TableState> = {};
+ const pageIndex = searchParams.get(OFFSET_PARAM);
+ const pageSize = searchParams.get(LIMIT_PARAM);
+ if (pageIndex && pageSize) {
+ urlState = {
+ ...urlState,
+ pagination: {
+ pageIndex: parseInt(pageIndex, 10),
+ pageSize: parseInt(pageSize, 10),
+ },
+ };
+ }
+ const sorting: SortingState = [];
+ searchParams.forEach((v, k) => {
+ if (k.startsWith(SORT_PARAM)) {
+ sorting.push({
+ id: k.replace(SORT_PARAM, ""),
+ desc: v === "desc",
+ });
+ }
+ });
+ if (sorting.length) {
+ urlState = {
+ ...urlState,
+ sorting,
+ };
+ }
+ return {
+ ...defaultState,
+ ...urlState,
+ };
+};
+
+interface CoreServiceQueryParams {
+ offset?: number;
+ limit?: number;
+ sorts?: any[];
+ search?: string;
+}
+
+/**
+ * helper function that build query params that core services rely on
+ * @param state
+ * @returns
+ */
+export const buildQueryParams = (
+ state?: Partial<TableState>
+): CoreServiceQueryParams => {
+ let queryParams = {};
+ if (state?.pagination) {
+ const { pageIndex, pageSize } = state.pagination;
+ queryParams = {
+ ...queryParams,
+ limit: pageSize,
+ offset: pageIndex * pageSize,
+ };
+ }
+ if (state?.sorting) {
+ const sorts = state.sorting.map(
+ ({ id, desc }) => `${id}:${desc ? "desc" : "asc"}`
+ ) as any[];
+ queryParams = {
+ ...queryParams,
+ sorts,
+ };
+ }
+ return queryParams;
+};
diff --git a/airflow/www/static/js/components/NewTable/useTableUrlState.ts
b/airflow/www/static/js/components/NewTable/useTableUrlState.ts
new file mode 100644
index 0000000000..8f1caeb406
--- /dev/null
+++ b/airflow/www/static/js/components/NewTable/useTableUrlState.ts
@@ -0,0 +1,69 @@
+/*!
+ * 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 { useCallback, useMemo } from "react";
+
+import { useSearchParams } from "react-router-dom";
+import type { TableState } from "./NewTable";
+import {
+ buildQueryParams,
+ searchParamsToState,
+ stateToSearchParams,
+} from "./searchParams";
+
+export const defaultTableState: TableState = {
+ pagination: {
+ pageIndex: 0,
+ pageSize: 25,
+ },
+ sorting: [],
+};
+
+export const useTableURLState = (defaultState?: Partial<TableState>) => {
+ const [searchParams, setSearchParams] = useSearchParams();
+
+ const handleStateChange = useCallback(
+ (state: TableState) => {
+ setSearchParams(stateToSearchParams(state, defaultTableState), {
+ replace: true,
+ });
+ },
+ [setSearchParams]
+ );
+
+ const tableURLState = useMemo(
+ () =>
+ searchParamsToState(searchParams, {
+ ...defaultTableState,
+ ...defaultState,
+ }),
+ [searchParams, defaultState]
+ );
+
+ const requestParams = useMemo(
+ () => buildQueryParams(tableURLState),
+ [tableURLState]
+ );
+
+ return {
+ tableURLState,
+ requestParams,
+ setTableURLState: handleStateChange,
+ };
+};
diff --git a/airflow/www/static/js/dag/details/AuditLog.tsx
b/airflow/www/static/js/dag/details/AuditLog.tsx
index 0e4a0f0474..15f0900741 100644
--- a/airflow/www/static/js/dag/details/AuditLog.tsx
+++ b/airflow/www/static/js/dag/details/AuditLog.tsx
@@ -19,7 +19,7 @@
/* global moment */
-import React, { useMemo, useRef, useState } from "react";
+import React, { useMemo, useRef } from "react";
import {
Box,
Flex,
@@ -29,14 +29,17 @@ import {
Input,
HStack,
} from "@chakra-ui/react";
-import type { SortingRule } from "react-table";
+import { createColumnHelper } from "@tanstack/react-table";
import { snakeCase } from "lodash";
-import { CodeCell, Table, TimeCell } from "src/components/Table";
import { useEventLogs } from "src/api";
import { getMetaValue, useOffsetTop } from "src/utils";
import type { DagRun } from "src/types";
import LinkButton from "src/components/LinkButton";
+import type { EventLog } from "src/types/api-generated";
+import { NewTable } from "src/components/NewTable/NewTable";
+import { useTableURLState } from "src/components/NewTable/useTableUrlState";
+import { CodeCell, TimeCell } from "src/components/NewTable/NewCells";
interface Props {
taskId?: string;
@@ -45,57 +48,61 @@ interface Props {
const dagId = getMetaValue("dag_id") || undefined;
+const columnHelper = createColumnHelper<EventLog>();
+
const AuditLog = ({ taskId, run }: Props) => {
const logRef = useRef<HTMLDivElement>(null);
const offsetTop = useOffsetTop(logRef);
- const limit = 25;
- const [offset, setOffset] = useState(0);
- const [sortBy, setSortBy] = useState<SortingRule<object>[]>([
- { id: "when", desc: true },
- ]);
+ const { tableURLState, setTableURLState } = useTableURLState({
+ sorting: [{ id: "when", desc: true }],
+ });
- const sort = sortBy[0];
+ const sort = tableURLState.sorting[0];
const orderBy = sort ? `${sort.desc ? "-" : ""}${snakeCase(sort.id)}` : "";
- const { data, isLoading } = useEventLogs({
+ const { data, isLoading, isFetching } = useEventLogs({
dagId,
taskId,
runId: run?.runId || undefined,
before: run?.lastSchedulingDecision || undefined,
after: run?.queuedAt || undefined,
orderBy,
- limit,
- offset,
+ limit: tableURLState.pagination.pageSize,
+ offset:
+ tableURLState.pagination.pageIndex * tableURLState.pagination.pageSize,
});
const columns = useMemo(() => {
- const when = {
- Header: "When",
- accessor: "when",
- Cell: TimeCell,
- };
- const task = {
- Header: "Task ID",
- accessor: "taskId",
- };
- const runId = {
- Header: "Run ID",
- accessor: "runId",
- };
- const rest = [
- {
- Header: "Event",
- accessor: "event",
- },
- {
- Header: "Owner",
- accessor: "owner",
- },
- {
- Header: "Extra",
- accessor: "extra",
- Cell: CodeCell,
+ const when = columnHelper.accessor("when", {
+ header: "When",
+ cell: TimeCell,
+ });
+ const task = columnHelper.accessor("taskId", {
+ header: "Task Id",
+ meta: {
+ skeletonWidth: 20,
},
+ });
+ const runId = columnHelper.accessor("runId", {
+ header: "Run Id",
+ });
+ const rest = [
+ columnHelper.accessor("event", {
+ header: "Event",
+ meta: {
+ skeletonWidth: 40,
+ },
+ }),
+ columnHelper.accessor("owner", {
+ header: "Owner",
+ meta: {
+ skeletonWidth: 20,
+ },
+ }),
+ columnHelper.accessor("extra", {
+ header: "Extra",
+ cell: CodeCell,
+ }),
];
return [
when,
@@ -106,7 +113,6 @@ const AuditLog = ({ taskId, run }: Props) => {
}, [taskId, run]);
const memoData = useMemo(() => data?.eventLogs, [data?.eventLogs]);
- const memoSort = useMemo(() => sortBy, [sortBy]);
return (
<Box
@@ -162,21 +168,15 @@ const AuditLog = ({ taskId, run }: Props) => {
<FormHelperText />
</FormControl>
</HStack>
- <Table
+ <NewTable
+ key={`${taskId}-${run?.runId}`}
data={memoData || []}
columns={columns}
isLoading={isLoading}
- manualPagination={{
- offset,
- setOffset,
- totalEntries: data?.totalEntries || 0,
- }}
- manualSort={{
- setSortBy,
- sortBy,
- initialSortBy: memoSort,
- }}
- pageSize={limit}
+ isFetching={isFetching}
+ initialState={tableURLState}
+ onStateChange={setTableURLState}
+ resultCount={data?.totalEntries}
/>
</Box>
);
diff --git a/airflow/www/static/js/dag/useSelection.ts
b/airflow/www/static/js/dag/useSelection.ts
index 7464c254ae..00ead40c52 100644
--- a/airflow/www/static/js/dag/useSelection.ts
+++ b/airflow/www/static/js/dag/useSelection.ts
@@ -19,6 +19,11 @@
import { createContext, useContext } from "react";
import { useSearchParams } from "react-router-dom";
+import {
+ LIMIT_PARAM,
+ OFFSET_PARAM,
+ SORT_PARAM,
+} from "src/components/NewTable/searchParams";
export const RUN_ID = "dag_run_id";
const TASK_ID = "task_id";
@@ -40,9 +45,18 @@ const useSelection = () => {
// Clear selection, but keep other search params
const clearSelection = () => {
+ const params = new URLSearchParams(window.location.search);
searchParams.delete(RUN_ID);
searchParams.delete(TASK_ID);
searchParams.delete(MAP_INDEX);
+ [...searchParams.keys()].forEach((key) => {
+ if (
+ key === OFFSET_PARAM ||
+ key === LIMIT_PARAM ||
+ key.includes(SORT_PARAM)
+ )
+ params.delete(key);
+ });
setSearchParams(searchParams);
};
@@ -50,6 +64,15 @@ const useSelection = () => {
// Check the window, in case params have changed since this hook was loaded
const params = new URLSearchParams(window.location.search);
+ [...searchParams.keys()].forEach((key) => {
+ if (
+ key === OFFSET_PARAM ||
+ key === LIMIT_PARAM ||
+ key.includes(SORT_PARAM)
+ )
+ params.delete(key);
+ });
+
if (runId) params.set(RUN_ID, runId);
else params.delete(RUN_ID);
diff --git a/airflow/www/yarn.lock b/airflow/www/yarn.lock
index 25693116bb..62dd14d358 100644
--- a/airflow/www/yarn.lock
+++ b/airflow/www/yarn.lock
@@ -2905,6 +2905,18 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
+"@tanstack/react-table@^8.13.2":
+ version "8.13.2"
+ resolved
"https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.13.2.tgz#a3aa737ae464abc651f68daa7e82dca17813606c"
+ integrity
sha512-b6mR3mYkjRtJ443QZh9sc7CvGTce81J35F/XMr0OoWbx0KIM7TTTdyNP2XKObvkLpYnLpCrYDwI3CZnLezWvpg==
+ dependencies:
+ "@tanstack/table-core" "8.13.2"
+
+"@tanstack/[email protected]":
+ version "8.13.2"
+ resolved
"https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.13.2.tgz#2512574dd3d20dc94b7db1f9f48090f0c18b5c85"
+ integrity
sha512-/2saD1lWBUV6/uNAwrsg2tw58uvMJ07bO2F1IWMxjFRkJiXKQRuc3Oq2aufeobD3873+4oIM/DRySIw7+QsPPw==
+
"@testing-library/dom@^8.5.0":
version "8.13.0"
resolved
"https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.13.0.tgz#bc00bdd64c7d8b40841e27a70211399ad3af46f5"