Skip to content

Commit

Permalink
[UI v2] feat: Start transition flow runs list form data table to cards (
Browse files Browse the repository at this point in the history
  • Loading branch information
devinvillarosa authored Feb 21, 2025
1 parent 6d0ec74 commit 27e85f9
Show file tree
Hide file tree
Showing 19 changed files with 1,148 additions and 50 deletions.
144 changes: 94 additions & 50 deletions ui-v2/src/components/deployments/deployment-details-upcoming-tab.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { getRouteApi } from "@tanstack/react-router";
import { PaginationState } from "@tanstack/react-table";

import { usePaginateFlowRunswithFlows } from "@/api/flow-runs/use-paginate-flow-runs-with-flows";
import { FlowRunState, SortFilters } from "@/components/flow-runs/data-table";
import {
FlowRunState,
FlowRunsDataTable,
FlowRunsFilters,
RowSelectionProvider,
SortFilters,
} from "@/components/flow-runs/data-table";

import { useCallback, useMemo } from "react";
FlowRunsList,
FlowRunsPagination,
FlowRunsRowCount,
type PaginationState,
} from "@/components/flow-runs/flow-runs-list";
import { useCallback, useMemo, useState } from "react";

const routeApi = getRouteApi("/deployments/deployment/$id");

Expand All @@ -21,10 +20,12 @@ type DeploymentDetailsUpcomingTabProps = {
export const DeploymentDetailsUpcomingTab = ({
deploymentId,
}: DeploymentDetailsUpcomingTabProps) => {
const [pagination, onPaginationChange] = usePagination();
const [selectedRows, setSelectedRows] = useState<Set<string>>(new Set());
const [pagination, onChangePagination] = usePagination();
const [search, setSearch] = useSearch();
const [sort, setSort] = useSort();
const [filter, setFilter] = useFilter();
const resetFilters = useResetFilters();

const { data } = usePaginateFlowRunswithFlows({
deployments: {
Expand All @@ -39,62 +40,98 @@ export const DeploymentDetailsUpcomingTab = ({
},
operator: "and_",
},
limit: pagination.pageSize,
page: pagination.pageIndex + 1, // + 1 for to account for react table's 0 index
limit: pagination.limit,
page: pagination.page,
sort,
});

const handleResetFilters = () => {
resetFilters();
setSelectedRows(new Set());
};

const addRow = (id: string) =>
setSelectedRows((curr) => new Set(curr).add(id));
const removeRow = (id: string) =>
setSelectedRows((curr) => {
const newValue = new Set(curr);
newValue.delete(id);
return newValue;
});

const handleSelectRow = (id: string, checked: boolean) => {
if (checked) {
addRow(id);
} else {
removeRow(id);
}
};

return (
<RowSelectionProvider>
<FlowRunsFilters
flowRunsCount={data?.count}
search={{ value: search, onChange: setSearch }}
sort={{ value: sort, onSelect: setSort }}
stateFilter={{
value: new Set(filter),
onSelect: setFilter,
}}
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<FlowRunsRowCount
count={data?.count}
results={data?.results}
selectedRows={selectedRows}
setSelectedRows={setSelectedRows}
/>
<FlowRunsFilters
search={{ value: search, onChange: setSearch }}
sort={{ value: sort, onSelect: setSort }}
stateFilter={{
value: new Set(filter),
onSelect: setFilter,
}}
/>
</div>

<FlowRunsList
flowRuns={data?.results}
selectedRows={selectedRows}
onSelect={handleSelectRow}
onClearFilters={handleResetFilters}
/>
{data ? (
<FlowRunsDataTable
flowRuns={data.results}
flowRunsCount={data.count}

{data && data.results.length > 0 && (
<FlowRunsPagination
pagination={pagination}
pageCount={data.pages}
onPaginationChange={onPaginationChange}
onChangePagination={onChangePagination}
pages={data.pages}
/>
) : null}
</RowSelectionProvider>
)}
</div>
);
};

function useResetFilters() {
const navigate = routeApi.useNavigate();
const resetFilters = useCallback(() => {
void navigate({
to: ".",
search: (prev) => ({
...prev,
upcoming: undefined,
}),
replace: true,
});
}, [navigate]);
return resetFilters;
}

function usePagination() {
const { upcoming } = routeApi.useSearch();
const navigate = routeApi.useNavigate();

// React Table uses 0-based pagination, so we need to subtract 1 from the page number
const pageIndex = (upcoming?.page ?? 1) - 1;
const pageSize = upcoming?.limit ?? 5;
const pagination: PaginationState = useMemo(
() => ({
pageIndex,
pageSize,
}),
[pageIndex, pageSize],
);

const onPaginationChange = useCallback(
(newPagination: PaginationState) => {
const onChangePagination = useCallback(
(pagination?: PaginationState) => {
void navigate({
to: ".",
search: (prev) => ({
...prev,
page: newPagination.pageIndex + 1,
limit: newPagination.pageSize,
upcoming: {
...upcoming,
page: newPagination.pageIndex + 1,
limit: newPagination.pageSize,
...pagination,
},
}),
replace: true,
Expand All @@ -103,15 +140,22 @@ function usePagination() {
[navigate, upcoming],
);

return [pagination, onPaginationChange] as const;
const pagination = useMemo(() => {
return {
page: upcoming?.page ?? 1,
limit: upcoming?.limit ?? 5,
};
}, [upcoming?.limit, upcoming?.page]);

return [pagination, onChangePagination] as const;
}

function useSearch() {
const { upcoming } = routeApi.useSearch();
const navigate = routeApi.useNavigate();

const onSearch = useCallback(
(value: string) => {
(value?: string) => {
void navigate({
to: ".",
search: (prev) => ({
Expand Down Expand Up @@ -141,7 +185,7 @@ function useSort() {
const navigate = routeApi.useNavigate();

const onSort = useCallback(
(value: SortFilters | undefined) => {
(value?: SortFilters) => {
void navigate({
to: ".",
search: (prev) => ({
Expand All @@ -168,7 +212,7 @@ function useFilter() {
const navigate = routeApi.useNavigate();

const onFilter = useCallback(
(value: Set<FlowRunState>) => {
(value?: Set<FlowRunState>) => {
void navigate({
to: ".",
search: (prev) => ({
Expand All @@ -177,7 +221,7 @@ function useFilter() {
...upcoming,
flowRuns: {
...upcoming?.flowRuns,
state: Array.from(value),
state: value ? Array.from(value) : undefined,
},
},
}),
Expand Down
48 changes: 48 additions & 0 deletions ui-v2/src/components/flow-runs/flow-runs-list/flow-run-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { components } from "@/api/prefect";
import { Card } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { Typography } from "@/components/ui/typography";
import { cva } from "class-variance-authority";
import type { FlowRunRow } from "./types";

type FlowRunCardProps =
| {
flowRun: FlowRunRow;
}
| {
flowRun: FlowRunRow;
checked: boolean;
onCheckedChange: (checked: boolean) => void;
};

export const FlowRunCard = ({ flowRun, ...props }: FlowRunCardProps) => {
return (
<Card className={stateCardVariants({ state: flowRun.state?.type })}>
<div className="flex items-center gap-2">
{"checked" in props && "onCheckedChange" in props && (
<Checkbox
checked={props.checked}
onCheckedChange={props.onCheckedChange}
/>
)}
<Typography>{flowRun.name}</Typography>
</div>
</Card>
);
};

const stateCardVariants = cva("flex flex-col gap-2 p-4 border-l-8", {
variants: {
state: {
COMPLETED: "border-l-green-600",
FAILED: "border-l-red-600",
RUNNING: "border-l-blue-700",
CANCELLED: "border-l-gray-800",
CANCELLING: "border-l-gray-800",
CRASHED: "border-l-orange-600",
PAUSED: "border-l-gray-800",
PENDING: "border-l-gray-800",
SCHEDULED: "border-l-yellow-700",
} satisfies Record<components["schemas"]["StateType"], string>,
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { RunNameSearch } from "./flow-runs-filters/run-name-search";
import { SortFilter } from "./flow-runs-filters/sort-filter";
import type { SortFilters } from "./flow-runs-filters/sort-filter.constants";
import { StateFilter } from "./flow-runs-filters/state-filter";
import type { FlowRunState } from "./flow-runs-filters/state-filters.constants";

export type FlowRunsFiltersProps = {
search: {
onChange: (value: string) => void;
value: string;
};
stateFilter: {
value: Set<FlowRunState>;
onSelect: (filters: Set<FlowRunState>) => void;
};
sort: {
value: SortFilters | undefined;
onSelect: (sort: SortFilters) => void;
};
};

export const FlowRunsFilters = ({
search,
sort,
stateFilter,
}: FlowRunsFiltersProps) => {
return (
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 pr-2 border-r-2">
<div className="min-w-56">
<RunNameSearch
value={search.value}
onChange={(e) => search.onChange(e.target.value)}
placeholder="Search by run name"
/>
</div>
<div className="min-w-56">
<StateFilter
selectedFilters={stateFilter.value}
onSelectFilter={stateFilter.onSelect}
/>
</div>
</div>
<SortFilter value={sort.value} onSelect={sort.onSelect} />
</div>
);
};
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Icon } from "@/components/ui/icons";
import { Input, type InputProps } from "@/components/ui/input";

export const RunNameSearch = (props: InputProps) => {
return (
<div className="relative">
<Input
aria-label="search by run name"
placeholder="Search by run name"
className="pl-10"
{...props}
/>
<Icon
id="Search"
className="absolute left-3 top-2.5 text-muted-foreground"
size={18}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const SORT_FILTERS = [
"START_TIME_ASC",
"START_TIME_DESC",
"NAME_ASC",
"NAME_DESC",
] as const;
export type SortFilters = (typeof SORT_FILTERS)[number];
Loading

0 comments on commit 27e85f9

Please sign in to comment.