From 35d8240c0eadced4e20ab2afcd4beb596cc24628 Mon Sep 17 00:00:00 2001 From: Devin Villarosa Date: Wed, 4 Dec 2024 17:38:28 -0800 Subject: [PATCH] [UI v2] feat: Loads data and url state to concurrency limit route for UX to consume --- .../concurrency/concurrency-constants.ts | 5 -- .../concurrency/concurrency-page.tsx | 10 +-- .../concurrency/concurrency-tabs.tsx | 77 +++++++++++++++---- .../global-concurrency-view/index.tsx | 10 +++ ui-v2/src/hooks/global-concurrency-limits.ts | 19 +++-- ui-v2/src/routes/concurrency-limits.tsx | 25 ++++-- 6 files changed, 106 insertions(+), 40 deletions(-) delete mode 100644 ui-v2/src/components/concurrency/concurrency-constants.ts diff --git a/ui-v2/src/components/concurrency/concurrency-constants.ts b/ui-v2/src/components/concurrency/concurrency-constants.ts deleted file mode 100644 index 2ffc86f414092..0000000000000 --- a/ui-v2/src/components/concurrency/concurrency-constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const TAB_OPTIONS = { - Global: "Global", - "Task Run": "Task Run", -} as const; -export type TabOptions = keyof typeof TAB_OPTIONS; diff --git a/ui-v2/src/components/concurrency/concurrency-page.tsx b/ui-v2/src/components/concurrency/concurrency-page.tsx index 17012f025ba12..d513deec48e74 100644 --- a/ui-v2/src/components/concurrency/concurrency-page.tsx +++ b/ui-v2/src/components/concurrency/concurrency-page.tsx @@ -1,23 +1,15 @@ -import { useState } from "react"; +import { Typography } from "@/components/ui/typography"; -import { Typography } from "@/components//ui/typography"; - -import { TAB_OPTIONS, TabOptions } from "./concurrency-constants"; import { ConcurrencyTabs } from "./concurrency-tabs"; import { GlobalConcurrencyView } from "./global-concurrency-view"; import { TaskRunConcurrencyView } from "./task-run-concurrenct-view"; export const ConcurrencyPage = (): JSX.Element => { - // TODO: Use URL query instead - const [tab, setTab] = useState(TAB_OPTIONS.Global); - return (
Concurrency
} taskRunView={} /> diff --git a/ui-v2/src/components/concurrency/concurrency-tabs.tsx b/ui-v2/src/components/concurrency/concurrency-tabs.tsx index b3396114bca5c..2c7fdd94d6b91 100644 --- a/ui-v2/src/components/concurrency/concurrency-tabs.tsx +++ b/ui-v2/src/components/concurrency/concurrency-tabs.tsx @@ -1,34 +1,81 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { TAB_OPTIONS, TabOptions } from "./concurrency-constants"; +import { TabOptions } from "@/routes/concurrency-limits"; +import { getRouteApi } from "@tanstack/react-router"; + +const routeApi = getRouteApi("/concurrency-limits"); + +type TabOptionValues = { + /** Value of search value in url */ + tabSearchValue: TabOptions; + /** Display value for the UI */ + displayValue: string; +}; + +/** Maps url tab option to visual name */ +const TAB_OPTIONS: Record = { + global: { + tabSearchValue: "global", + displayValue: "Global", + }, + ["task-run"]: { + tabSearchValue: "task-run", + displayValue: "Task Run", + }, +} as const; type Props = { globalView: React.ReactNode; - onValueChange: (value: TabOptions) => void; taskRunView: React.ReactNode; - value: TabOptions; }; // TODO: Move Tabs for navigation to a generic styled component export const ConcurrencyTabs = ({ globalView, - onValueChange, taskRunView, - value, }: Props): JSX.Element => { + const { tab } = routeApi.useSearch(); + const navigate = routeApi.useNavigate(); + return ( - onValueChange(value as TabOptions)} - > + - {TAB_OPTIONS.Global} - {TAB_OPTIONS["Task Run"]} + { + void navigate({ + to: "/concurrency-limits", + search: (prev) => ({ + ...prev, + tab: TAB_OPTIONS.global.tabSearchValue, + }), + }); + }} + > + {TAB_OPTIONS.global.displayValue} + + + { + void navigate({ + to: "/concurrency-limits", + search: (prev) => ({ + ...prev, + tab: TAB_OPTIONS["task-run"].tabSearchValue, + }), + }); + }} + > + {TAB_OPTIONS["task-run"].displayValue} + - {globalView} - {taskRunView} + + {globalView} + + + {taskRunView} + ); }; diff --git a/ui-v2/src/components/concurrency/global-concurrency-view/index.tsx b/ui-v2/src/components/concurrency/global-concurrency-view/index.tsx index 20b5b7b78a635..033d5fb162ed1 100644 --- a/ui-v2/src/components/concurrency/global-concurrency-view/index.tsx +++ b/ui-v2/src/components/concurrency/global-concurrency-view/index.tsx @@ -1,9 +1,13 @@ +import { useListGlobalConcurrencyLimits } from "@/hooks/global-concurrency-limits"; import { useState } from "react"; + import { GlobalConcurrencyLimitsHeader } from "./global-concurrency-limits-header"; export const GlobalConcurrencyView = () => { const [showAddDialog, setShowAddDialog] = useState(false); + const { data } = useListGlobalConcurrencyLimits(); + const openAddDialog = () => setShowAddDialog(true); const closeAddDialog = () => setShowAddDialog(false); @@ -12,6 +16,12 @@ export const GlobalConcurrencyView = () => {
+
TODO
+
    + {data.map((limit) => ( +
  • {JSON.stringify(limit)}
  • + ))} +
{showAddDialog &&
TODO: DIALOG
} ); diff --git a/ui-v2/src/hooks/global-concurrency-limits.ts b/ui-v2/src/hooks/global-concurrency-limits.ts index 1fd78bb4fd239..cf3c5473a62e3 100644 --- a/ui-v2/src/hooks/global-concurrency-limits.ts +++ b/ui-v2/src/hooks/global-concurrency-limits.ts @@ -1,10 +1,11 @@ import type { components } from "@/api/prefect"; import { getQueryService } from "@/api/service"; import { + QueryClient, queryOptions, useMutation, - useQuery, useQueryClient, + useSuspenseQuery, } from "@tanstack/react-query"; export type GlobalConcurrencyLimit = @@ -31,7 +32,7 @@ export const queryKeyFactory = { // ----- 🔑 Queries 🗄️ // ---------------------------- export const buildListGlobalConcurrencyLimitsQuery = ( - filter: GlobalConcurrencyLimitsFilter, + filter: GlobalConcurrencyLimitsFilter = { offset: 0 }, ) => queryOptions({ queryKey: queryKeyFactory.list(filter), @@ -47,11 +48,19 @@ export const buildListGlobalConcurrencyLimitsQuery = ( /** * * @param filter - * @returns list of global concurrency limits as a QueryResult object + * @returns list of global concurrency limits as a SuspenseQueryResult object */ + export const useListGlobalConcurrencyLimits = ( - filter: GlobalConcurrencyLimitsFilter, -) => useQuery(buildListGlobalConcurrencyLimitsQuery(filter)); + filter: GlobalConcurrencyLimitsFilter = { offset: 0 }, +) => useSuspenseQuery(buildListGlobalConcurrencyLimitsQuery(filter)); + +useListGlobalConcurrencyLimits.loader = ({ + context, +}: { + context: { queryClient: QueryClient }; +}) => + context.queryClient.ensureQueryData(buildListGlobalConcurrencyLimitsQuery()); // ----- ✍🏼 Mutations 🗄️ // ---------------------------- diff --git a/ui-v2/src/routes/concurrency-limits.tsx b/ui-v2/src/routes/concurrency-limits.tsx index e425901c1452f..dcfef7f9c4b78 100644 --- a/ui-v2/src/routes/concurrency-limits.tsx +++ b/ui-v2/src/routes/concurrency-limits.tsx @@ -1,11 +1,24 @@ +import { ConcurrencyPage } from "@/components/concurrency/concurrency-page"; +import { useListGlobalConcurrencyLimits } from "@/hooks/global-concurrency-limits"; import { createFileRoute } from "@tanstack/react-router"; +import { zodSearchValidator } from "@tanstack/router-zod-adapter"; +import { z } from "zod"; -import { ConcurrencyPage } from "@/components/concurrency/concurrency-page"; +/** + * Schema for validating URL search parameters for the Concurrency Limits page. + * @property {'Global' | 'Task_Run'} tab used designate which tab view to display + */ +const searchParams = z + .object({ + tab: z.enum(["global", "task-run"]).default("global"), + }) + .strict(); + +export type TabOptions = z.infer["tab"]; export const Route = createFileRoute("/concurrency-limits")({ - component: RouteComponent, + validateSearch: zodSearchValidator(searchParams), + component: ConcurrencyPage, + wrapInSuspense: true, + loader: useListGlobalConcurrencyLimits.loader, }); - -function RouteComponent() { - return ; -}