diff --git a/ui-v2/src/api/task-runs/index.ts b/ui-v2/src/api/task-runs/index.ts index 1f63d844cace..7461d743fff5 100644 --- a/ui-v2/src/api/task-runs/index.ts +++ b/ui-v2/src/api/task-runs/index.ts @@ -83,7 +83,7 @@ export const buildListTaskRunsQuery = ( * const { data } = useSuspenseQuery(buildListTaskRunsQuery(["id-0", "id-1"])); * ``` */ -export const buildGetFlowRusTaskRunsCountQuery = ( +export const buildGetFlowRunsTaskRunsCountQuery = ( flow_run_ids: Array, ) => { return queryOptions({ diff --git a/ui-v2/src/api/task-runs/task-runs.test.ts b/ui-v2/src/api/task-runs/task-runs.test.ts index 8b662023c548..95344e2f6703 100644 --- a/ui-v2/src/api/task-runs/task-runs.test.ts +++ b/ui-v2/src/api/task-runs/task-runs.test.ts @@ -7,7 +7,7 @@ import { describe, expect, it } from "vitest"; import { TaskRun, TaskRunsFilter, - buildGetFlowRusTaskRunsCountQuery, + buildGetFlowRunsTaskRunsCountQuery, buildListTaskRunsQuery, } from "."; @@ -75,7 +75,7 @@ describe("task runs api", () => { }); }); - describe("buildGetFlowRusTaskRunsCountQuery", () => { + describe("buildGetFlowRunsTaskRunsCountQuery", () => { const mockGetFlowRunsTaskRunsCountAPI = ( response: Record, ) => { @@ -93,7 +93,7 @@ describe("task runs api", () => { const queryClient = new QueryClient(); const { result } = renderHook( - () => useSuspenseQuery(buildGetFlowRusTaskRunsCountQuery(mockIds)), + () => useSuspenseQuery(buildGetFlowRunsTaskRunsCountQuery(mockIds)), { wrapper: createWrapper({ queryClient }) }, ); diff --git a/ui-v2/src/components/flow-runs/data-table/data-table.stories.tsx b/ui-v2/src/components/flow-runs/data-table/data-table.stories.tsx index 08e5d58fd833..fef7b9e9fb05 100644 --- a/ui-v2/src/components/flow-runs/data-table/data-table.stories.tsx +++ b/ui-v2/src/components/flow-runs/data-table/data-table.stories.tsx @@ -7,19 +7,34 @@ import { toastDecorator, } from "@/storybook/utils"; import { faker } from "@faker-js/faker"; +import { fn } from "@storybook/test"; import { buildApiUrl } from "@tests/utils/handlers"; import { http, HttpResponse } from "msw"; +import { useMemo, useState } from "react"; import { FlowRunsDataTable } from "./data-table"; +import { FlowRunState } from "./state-filter"; const MOCK_DATA = [ createFakeFlowRunWithDeploymentAndFlow({ id: "0", - state: { type: "SCHEDULED", id: "0" }, + state: { type: "SCHEDULED", name: "Late", id: "0" }, + }), + createFakeFlowRunWithDeploymentAndFlow({ + id: "1", + state: { type: "COMPLETED", name: "Cached", id: "0" }, + }), + createFakeFlowRunWithDeploymentAndFlow({ + id: "2", + state: { type: "SCHEDULED", name: "Scheduled", id: "0" }, + }), + createFakeFlowRunWithDeploymentAndFlow({ + id: "3", + state: { type: "COMPLETED", name: "Completed", id: "0" }, + }), + createFakeFlowRunWithDeploymentAndFlow({ + id: "4", + state: { type: "FAILED", name: "Failed", id: "0" }, }), - createFakeFlowRunWithDeploymentAndFlow({ id: "1" }), - createFakeFlowRunWithDeploymentAndFlow({ id: "2" }), - createFakeFlowRunWithDeploymentAndFlow({ id: "3" }), - createFakeFlowRunWithDeploymentAndFlow({ id: "4" }), ]; const MOCK_FLOW_RUNS_TASK_COUNT = { @@ -34,7 +49,7 @@ const meta: Meta = { title: "Components/FlowRuns/DataTable/FlowRunsDataTable", decorators: [routerDecorator, reactQueryDecorator, toastDecorator], args: { flowRuns: MOCK_DATA, flowRunsCount: MOCK_DATA.length }, - component: FlowRunsDataTable, + render: () => , parameters: { msw: { handlers: [ @@ -48,3 +63,46 @@ const meta: Meta = { export default meta; export const story: StoryObj = { name: "FlowRunsDataTable" }; + +const FlowRunDataTableStory = () => { + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(10); + + const [search, setSearch] = useState(""); + const [filters, setFilters] = useState>(new Set()); + + const flowRuns = useMemo(() => { + return MOCK_DATA.filter((flowRun) => + flowRun.name?.toLocaleLowerCase().includes(search.toLowerCase()), + ).filter((flowRun) => + filters.size === 0 + ? flowRun + : filters.has(flowRun.state?.name as FlowRunState), + ); + }, [filters, search]); + + return ( + { + setPageIndex(pagination.pageIndex); + setPageSize(pagination.pageSize); + }} + filter={{ + value: filters, + onSelect: setFilters, + }} + search={{ + value: search, + onChange: setSearch, + }} + sort={{ + value: "NAME_ASC", + onSelect: fn(), + }} + /> + ); +}; diff --git a/ui-v2/src/components/flow-runs/data-table/data-table.test.tsx b/ui-v2/src/components/flow-runs/data-table/data-table.test.tsx index 6256f2bc3cde..0ab7a250b047 100644 --- a/ui-v2/src/components/flow-runs/data-table/data-table.test.tsx +++ b/ui-v2/src/components/flow-runs/data-table/data-table.test.tsx @@ -10,7 +10,7 @@ import { import { render, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { createWrapper } from "@tests/utils"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { FlowRunsDataTable, type FlowRunsDataTableProps } from "./data-table"; // Wraps component in test with a Tanstack router provider @@ -46,6 +46,12 @@ describe("Flow Runs DataTable", () => { , { wrapper: createWrapper() }, @@ -75,6 +81,12 @@ describe("Flow Runs DataTable", () => { , { wrapper: createWrapper() }, diff --git a/ui-v2/src/components/flow-runs/data-table/data-table.tsx b/ui-v2/src/components/flow-runs/data-table/data-table.tsx index 6525134cc116..ad80336dfcac 100644 --- a/ui-v2/src/components/flow-runs/data-table/data-table.tsx +++ b/ui-v2/src/components/flow-runs/data-table/data-table.tsx @@ -9,15 +9,6 @@ import { DataTable } from "@/components/ui/data-table"; import { StateBadge } from "@/components/ui/state-badge"; import { TagBadgeGroup } from "@/components/ui/tag-badge-group"; -import { - RowSelectionState, - createColumnHelper, - getCoreRowModel, - getPaginationRowModel, - useReactTable, -} from "@tanstack/react-table"; -import { Suspense, useMemo, useState } from "react"; - import { Flow } from "@/api/flows"; import { Checkbox } from "@/components/ui/checkbox"; import { DeleteConfirmationDialog } from "@/components/ui/delete-confirmation-dialog"; @@ -26,14 +17,24 @@ import { Skeleton } from "@/components/ui/skeleton"; import { Typography } from "@/components/ui/typography"; import { pluralize } from "@/utils"; import { CheckedState } from "@radix-ui/react-checkbox"; +import { + OnChangeFn, + PaginationState, + RowSelectionState, + createColumnHelper, + getCoreRowModel, + useReactTable, +} from "@tanstack/react-table"; +import { Suspense, useCallback, useMemo, useState } from "react"; + import { DeploymentCell } from "./deployment-cell"; import { DurationCell } from "./duration-cell"; import { NameCell } from "./name-cell"; import { ParametersCell } from "./parameters-cell"; import { RunNameSearch } from "./run-name-search"; -import { SortFilter } from "./sort-filter"; +import { SortFilter, SortFilters } from "./sort-filter"; import { StartTimeCell } from "./start-time-cell"; -import { StateFilter } from "./state-filter"; +import { FlowRunState, StateFilter } from "./state-filter"; import { TasksCell } from "./tasks-cell"; import { useDeleteFlowRunsDialog } from "./use-delete-flow-runs-dialog"; @@ -163,11 +164,40 @@ const createColumns = ({ return ret; }; +type PaginationProps = { + pageCount: number; + pagination: PaginationState; + onPaginationChange: (pagination: PaginationState) => void; +}; +type SearchProps = { + onChange: (value: string) => void; + value: string; +}; +type FilterProps = { + defaultValue?: Set; + value: Set; + onSelect: (filters: Set) => void; +}; +type SortProps = { + defaultValue?: SortFilters; + value: SortFilters; + onSelect: (sort: SortFilters) => void; +}; + export type FlowRunsDataTableProps = { + search?: SearchProps; + filter?: FilterProps; + sort?: SortProps; flowRunsCount: number; flowRuns: Array; -}; +} & PaginationProps; export const FlowRunsDataTable = ({ + pageCount, + pagination, + onPaginationChange, + search, + sort, + filter, flowRunsCount, flowRuns, }: FlowRunsDataTableProps) => { @@ -181,16 +211,32 @@ export const FlowRunsDataTable = ({ [flowRuns], ); + const handlePaginationChange: OnChangeFn = useCallback( + (updater) => { + let newPagination = pagination; + if (typeof updater === "function") { + newPagination = updater(pagination); + } else { + newPagination = updater; + } + onPaginationChange(newPagination); + }, + [pagination, onPaginationChange], + ); + const table = useReactTable({ getRowId: (row) => row.id, onRowSelectionChange: setRowSelection, - state: { rowSelection }, + state: { pagination, rowSelection }, data: flowRuns, columns: createColumns({ showDeployment, }), getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), // TODO: use server-side pagination + pageCount, + manualPagination: true, + defaultColumn: { maxSize: 300 }, + onPaginationChange: handlePaginationChange, }); const selectedRows = Object.keys(rowSelection); @@ -223,26 +269,28 @@ export const FlowRunsDataTable = ({ )} -
- -
-
- {}} - /> -
-
- {}} - /> -
+ {search && ( +
+ search.onChange(e.target.value)} + placeholder="Search by run name" + /> +
+ )} + {filter && ( +
+ +
+ )} + {sort && ( +
+ +
+ )} diff --git a/ui-v2/src/components/flow-runs/data-table/run-name-search.tsx b/ui-v2/src/components/flow-runs/data-table/run-name-search.tsx index 7309340c01ed..95fe261ba8b7 100644 --- a/ui-v2/src/components/flow-runs/data-table/run-name-search.tsx +++ b/ui-v2/src/components/flow-runs/data-table/run-name-search.tsx @@ -4,7 +4,12 @@ import { Input, type InputProps } from "@/components/ui/input"; export const RunNameSearch = (props: InputProps) => { return (
- + void; value: undefined | SortFilters; }; -export const SortFilter = ({ value, onSelect }: SortFilterProps) => { +export const SortFilter = ({ + defaultValue, + value, + onSelect, +}: SortFilterProps) => { return ( - diff --git a/ui-v2/src/components/flow-runs/data-table/state-filter.tsx b/ui-v2/src/components/flow-runs/data-table/state-filter.tsx index 6a0aca18de74..293fc5575502 100644 --- a/ui-v2/src/components/flow-runs/data-table/state-filter.tsx +++ b/ui-v2/src/components/flow-runs/data-table/state-filter.tsx @@ -58,12 +58,14 @@ const FLOW_RUN_STATES_MAP = { const MAX_FILTERS_DISPLAYED = 4; type StateFilterProps = { + defaultValue?: Set; selectedFilters: Set | undefined; onSelectFilter: (filters: Set) => void; }; export const StateFilter = ({ - selectedFilters = new Set(), + defaultValue, + selectedFilters = defaultValue || new Set(), onSelectFilter, }: StateFilterProps) => { const [open, setOpen] = useState(false); diff --git a/ui-v2/src/components/flow-runs/data-table/tasks-cell.tsx b/ui-v2/src/components/flow-runs/data-table/tasks-cell.tsx index 5bb36961fd26..db6cd548a1d6 100644 --- a/ui-v2/src/components/flow-runs/data-table/tasks-cell.tsx +++ b/ui-v2/src/components/flow-runs/data-table/tasks-cell.tsx @@ -2,7 +2,7 @@ import { type FlowRunWithDeploymentAndFlow, type FlowRunWithFlow, } from "@/api/flow-runs"; -import { buildGetFlowRusTaskRunsCountQuery } from "@/api/task-runs"; +import { buildGetFlowRunsTaskRunsCountQuery } from "@/api/task-runs"; import { Icon } from "@/components/ui/icons"; import { Typography } from "@/components/ui/typography"; @@ -16,7 +16,7 @@ type TasksCellsProp = { export const TasksCell = ({ flowRun }: TasksCellsProp) => { const { data } = useSuspenseQuery( - buildGetFlowRusTaskRunsCountQuery([flowRun.id]), + buildGetFlowRunsTaskRunsCountQuery([flowRun.id]), ); const taskRunsCount = data[flowRun.id];