Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[UI v2] feat: Loads data and url state to concurrency limit route for UX to consume #16227

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions ui-v2/src/components/concurrency/concurrency-constants.ts

This file was deleted.

10 changes: 1 addition & 9 deletions ui-v2/src/components/concurrency/concurrency-page.tsx
Original file line number Diff line number Diff line change
@@ -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<TabOptions>(TAB_OPTIONS.Global);

return (
<div className="flex flex-col gap-4">
<Typography variant="h2">Concurrency</Typography>
<div className="flex flex-col gap-6">
<ConcurrencyTabs
value={tab}
onValueChange={setTab}
globalView={<GlobalConcurrencyView />}
taskRunView={<TaskRunConcurrencyView />}
/>
Expand Down
77 changes: 62 additions & 15 deletions ui-v2/src/components/concurrency/concurrency-tabs.tsx
Original file line number Diff line number Diff line change
@@ -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<TabOptions, TabOptionValues> = {
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 (
<Tabs
defaultValue="Global"
className="w-[400px]"
value={value}
onValueChange={(value) => onValueChange(value as TabOptions)}
>
<Tabs defaultValue="Global" value={tab}>
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="Global">{TAB_OPTIONS.Global}</TabsTrigger>
<TabsTrigger value="Task Run">{TAB_OPTIONS["Task Run"]}</TabsTrigger>
<TabsTrigger
value={TAB_OPTIONS.global.tabSearchValue}
onClick={() => {
void navigate({
to: "/concurrency-limits",
search: (prev) => ({
...prev,
tab: TAB_OPTIONS.global.tabSearchValue,
}),
});
}}
>
{TAB_OPTIONS.global.displayValue}
</TabsTrigger>

<TabsTrigger
value={TAB_OPTIONS["task-run"].tabSearchValue}
onClick={() => {
void navigate({
to: "/concurrency-limits",
search: (prev) => ({
...prev,
tab: TAB_OPTIONS["task-run"].tabSearchValue,
}),
});
}}
>
{TAB_OPTIONS["task-run"].displayValue}
</TabsTrigger>
</TabsList>
<TabsContent value={TAB_OPTIONS.Global}>{globalView}</TabsContent>
<TabsContent value={TAB_OPTIONS["Task Run"]}>{taskRunView}</TabsContent>
<TabsContent value={TAB_OPTIONS.global.tabSearchValue}>
{globalView}
</TabsContent>
<TabsContent value={TAB_OPTIONS["task-run"].tabSearchValue}>
{taskRunView}
</TabsContent>
</Tabs>
);
};
Original file line number Diff line number Diff line change
@@ -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);

Expand All @@ -12,6 +16,12 @@ export const GlobalConcurrencyView = () => {
<div className="flex flex-col gap-2">
<GlobalConcurrencyLimitsHeader onAdd={openAddDialog} />
</div>
<div>TODO</div>
<ul>
{data.map((limit) => (
<li key={limit.id}>{JSON.stringify(limit)}</li>
))}
</ul>
{showAddDialog && <div onClick={closeAddDialog}>TODO: DIALOG</div>}
</>
);
Expand Down
19 changes: 14 additions & 5 deletions ui-v2/src/hooks/global-concurrency-limits.ts
Original file line number Diff line number Diff line change
@@ -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 =
Expand All @@ -31,7 +32,7 @@ export const queryKeyFactory = {
// ----- 🔑 Queries 🗄️
// ----------------------------
export const buildListGlobalConcurrencyLimitsQuery = (
filter: GlobalConcurrencyLimitsFilter,
filter: GlobalConcurrencyLimitsFilter = { offset: 0 },
) =>
queryOptions({
queryKey: queryKeyFactory.list(filter),
Expand All @@ -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 = ({
Copy link
Contributor Author

@devinvillarosa devinvillarosa Dec 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ended up using this .loader style for consistency with Variable.

But one principal I'm trying to keep in mind is

How do we keep our server data reusable throughout the webapp as it grows and evolve?

I think only exposing queryOptions and mutations exposed to the webapp gives this flexibility because you can feed it into different loaders, useQuery, useQueries, useSuspenseQuery, and useSuspenseQueries for different scenarios.

But let keep building more stuff to see some similar patterns

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could end up with multiple entry points. In most cases, you can use the prebuilt hooks (a la useVariables), but developers can import and use the query options factory in cases where more customization is needed.

context,
}: {
context: { queryClient: QueryClient };
}) =>
context.queryClient.ensureQueryData(buildListGlobalConcurrencyLimitsQuery());

// ----- ✍🏼 Mutations 🗄️
// ----------------------------
Expand Down
25 changes: 19 additions & 6 deletions ui-v2/src/routes/concurrency-limits.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof searchParams>["tab"];

export const Route = createFileRoute("/concurrency-limits")({
component: RouteComponent,
validateSearch: zodSearchValidator(searchParams),
component: ConcurrencyPage,
wrapInSuspense: true,
loader: useListGlobalConcurrencyLimits.loader,
});

function RouteComponent() {
return <ConcurrencyPage />;
}
Loading