-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[UI v2] Adds Create or edit dialog for concurrency limit
- Loading branch information
1 parent
09412d1
commit e9d76c9
Showing
6 changed files
with
354 additions
and
15 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
125 changes: 125 additions & 0 deletions
125
.../src/components/concurrency/global-concurrency-view/create-or-edit-limit-dialog/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import { Button } from "@/components/ui/button"; | ||
import { | ||
Dialog, | ||
DialogContent, | ||
DialogFooter, | ||
DialogHeader, | ||
DialogTitle, | ||
DialogTrigger, | ||
} from "@/components/ui/dialog"; | ||
import { | ||
Form, | ||
FormControl, | ||
FormField, | ||
FormItem, | ||
FormLabel, | ||
FormMessage, | ||
} from "@/components/ui/form"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Switch } from "@/components/ui/switch"; | ||
|
||
import { type GlobalConcurrencyLimit } from "@/hooks/global-concurrency-limits"; | ||
import { useCreateOrEditLimitDialog } from "./use-create-or-edit-limit-dialog"; | ||
|
||
type Props = { | ||
limitToUpdate: undefined | GlobalConcurrencyLimit; | ||
onOpenChange: (open: boolean) => void; | ||
onSubmit: () => void; | ||
open: boolean; | ||
}; | ||
|
||
export const CreateOrEditLimitDialog = ({ | ||
limitToUpdate, | ||
onOpenChange, | ||
onSubmit, | ||
open, | ||
}: Props) => { | ||
const { dialogTitle, form, isLoading, saveOrUpdate } = | ||
useCreateOrEditLimitDialog({ | ||
limitToUpdate, | ||
onSubmit, | ||
}); | ||
|
||
return ( | ||
<Dialog open={open} onOpenChange={onOpenChange}> | ||
<DialogContent> | ||
<DialogHeader> | ||
<DialogTitle>{dialogTitle}</DialogTitle> | ||
</DialogHeader> | ||
|
||
<Form {...form}> | ||
<form | ||
onSubmit={(e) => void form.handleSubmit(saveOrUpdate)(e)} | ||
className="space-y-4" | ||
> | ||
<FormMessage>{form.formState.errors.root?.message}</FormMessage> | ||
<FormField | ||
control={form.control} | ||
name="name" | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormLabel>Name</FormLabel> | ||
<FormControl> | ||
<Input type="text" autoComplete="off" {...field} /> | ||
</FormControl> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
<FormField | ||
control={form.control} | ||
name="limit" | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormLabel>Concurrency Limit</FormLabel> | ||
<FormControl> | ||
<Input type="number" {...field} /> | ||
</FormControl> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
<FormField | ||
control={form.control} | ||
name="slot_decay_per_second" | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormLabel>Slot Decay Per Second</FormLabel> | ||
<FormControl> | ||
<Input type="number" {...field} /> | ||
</FormControl> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
<FormField | ||
control={form.control} | ||
name="active" | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormLabel>Active</FormLabel> | ||
<FormControl> | ||
<Switch | ||
className="block" | ||
checked={field.value} | ||
onCheckedChange={field.onChange} | ||
/> | ||
</FormControl> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
<DialogFooter> | ||
<DialogTrigger asChild> | ||
<Button variant="outline">Close</Button> | ||
</DialogTrigger> | ||
<Button type="submit" loading={isLoading}> | ||
{limitToUpdate ? "Update" : "Save"} | ||
</Button> | ||
</DialogFooter> | ||
</form> | ||
</Form> | ||
</DialogContent> | ||
</Dialog> | ||
); | ||
}; |
124 changes: 124 additions & 0 deletions
124
...cy/global-concurrency-view/create-or-edit-limit-dialog/use-create-or-edit-limit-dialog.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import { | ||
GlobalConcurrencyLimit, | ||
useCreateGlobalConcurrencyLimit, | ||
useUpdateGlobalConcurrencyLimit, | ||
} from "@/hooks/global-concurrency-limits"; | ||
import { useToast } from "@/hooks/use-toast"; | ||
import { zodResolver } from "@hookform/resolvers/zod"; | ||
import { useEffect } from "react"; | ||
import { useForm } from "react-hook-form"; | ||
import { z } from "zod"; | ||
|
||
const formSchema = z.object({ | ||
active: z.boolean().default(true), | ||
/** Coerce to solve common issue of transforming a string number to a number type */ | ||
denied_slots: z.number().default(0).or(z.string()).pipe(z.coerce.number()), | ||
/** Coerce to solve common issue of transforming a string number to a number type */ | ||
limit: z.number().default(0).or(z.string()).pipe(z.coerce.number()), | ||
name: z | ||
.string() | ||
.min(2, { message: "Name must be at least 2 characters" }) | ||
.default(""), | ||
/** Coerce to solve common issue of transforming a string number to a number type */ | ||
slot_decay_per_second: z | ||
.number() | ||
.default(0) | ||
.or(z.string()) | ||
.pipe(z.coerce.number()), | ||
/** Additional fields post creation. Coerce to solve common issue of transforming a string number to a number type */ | ||
active_slots: z.number().default(0).or(z.string()).pipe(z.coerce.number()), | ||
}); | ||
|
||
const DEFAULT_VALUES = { | ||
active: true, | ||
name: "", | ||
limit: 0, | ||
slot_decay_per_second: 0, | ||
denied_slots: 0, | ||
active_slots: 0, | ||
} as const; | ||
|
||
type UseCreateOrEditLimitDialog = { | ||
/** Limit to edit. Pass undefined if creating a new limit */ | ||
limitToUpdate: GlobalConcurrencyLimit | undefined; | ||
/** Callback after hitting Save or Update */ | ||
onSubmit: () => void; | ||
}; | ||
|
||
export const useCreateOrEditLimitDialog = ({ | ||
limitToUpdate, | ||
onSubmit, | ||
}: UseCreateOrEditLimitDialog) => { | ||
const { toast } = useToast(); | ||
|
||
const { createGlobalConcurrencyLimit, status: createStatus } = | ||
useCreateGlobalConcurrencyLimit(); | ||
const { updateGlobalConcurrencyLimit, status: updateStatus } = | ||
useUpdateGlobalConcurrencyLimit(); | ||
|
||
const form = useForm<z.infer<typeof formSchema>>({ | ||
resolver: zodResolver(formSchema), | ||
defaultValues: DEFAULT_VALUES, | ||
}); | ||
|
||
// Sync form data with limit-to-edit data | ||
useEffect(() => { | ||
if (limitToUpdate) { | ||
const { active, name, limit, slot_decay_per_second, active_slots } = | ||
limitToUpdate; | ||
form.reset({ active, name, limit, slot_decay_per_second, active_slots }); | ||
} else { | ||
form.reset(DEFAULT_VALUES); | ||
} | ||
}, [form, limitToUpdate]); | ||
|
||
const saveOrUpdate = (values: z.infer<typeof formSchema>) => { | ||
const onSettled = () => { | ||
form.reset(DEFAULT_VALUES); | ||
onSubmit(); | ||
}; | ||
|
||
if (limitToUpdate?.id) { | ||
updateGlobalConcurrencyLimit( | ||
{ | ||
id_or_name: limitToUpdate.id, | ||
...values, | ||
}, | ||
{ | ||
onSuccess: () => { | ||
toast({ title: "Limit updated" }); | ||
}, | ||
onError: (error) => { | ||
const message = | ||
error.message || "Unknown error while updating limit."; | ||
form.setError("root", { message }); | ||
}, | ||
onSettled, | ||
}, | ||
); | ||
} else { | ||
createGlobalConcurrencyLimit(values, { | ||
onSuccess: () => { | ||
toast({ title: "Limit created" }); | ||
}, | ||
onError: (error) => { | ||
const message = | ||
error.message || "Unknown error while creating variable."; | ||
form.setError("root", { | ||
message, | ||
}); | ||
}, | ||
onSettled, | ||
}); | ||
} | ||
}; | ||
|
||
return { | ||
form, | ||
saveOrUpdate, | ||
dialogTitle: limitToUpdate | ||
? `Update ${limitToUpdate.name}` | ||
: "Add Concurrency Limit", | ||
isLoading: createStatus === "pending" || updateStatus === "pending", | ||
}; | ||
}; |
60 changes: 45 additions & 15 deletions
60
ui-v2/src/components/concurrency/global-concurrency-view/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,58 @@ | ||
import { useListGlobalConcurrencyLimits } from "@/hooks/global-concurrency-limits"; | ||
import { useState } from "react"; | ||
|
||
import { useMemo, useState } from "react"; | ||
import { CreateOrEditLimitDialog } from "./create-or-edit-limit-dialog"; | ||
import { GlobalConcurrencyLimitEmptyState } from "./global-concurrency-limit-empty-state"; | ||
import { GlobalConcurrencyLimitsHeader } from "./global-concurrency-limits-header"; | ||
|
||
type AddOrEditDialogState = { | ||
open: boolean; | ||
limitIdToEdit?: string; | ||
}; | ||
|
||
export const GlobalConcurrencyView = () => { | ||
const [showAddDialog, setShowAddDialog] = useState(false); | ||
const [openAddOrEditDialog, setOpenAddOrEditDialog] = | ||
useState<AddOrEditDialogState>({ | ||
open: false, | ||
}); | ||
|
||
const { data } = useListGlobalConcurrencyLimits(); | ||
|
||
const openAddDialog = () => setShowAddDialog(true); | ||
const closeAddDialog = () => setShowAddDialog(false); | ||
const selectedlimitToUpdate = useMemo(() => { | ||
if (!openAddOrEditDialog.limitIdToEdit) { | ||
return undefined; | ||
} | ||
return data.find((limit) => limit.id === openAddOrEditDialog.limitIdToEdit); | ||
}, [data, openAddOrEditDialog.limitIdToEdit]); | ||
|
||
const openAddDialog = () => | ||
setOpenAddOrEditDialog((curr) => ({ ...curr, open: true })); | ||
|
||
// close and deselect any selected limits | ||
const closeAddDialog = () => setOpenAddOrEditDialog({ open: false }); | ||
|
||
return ( | ||
<> | ||
<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>} | ||
{data.length === 0 ? ( | ||
<GlobalConcurrencyLimitEmptyState onAdd={openAddDialog} /> | ||
) : ( | ||
<div className="flex flex-col gap-2"> | ||
<GlobalConcurrencyLimitsHeader onAdd={openAddDialog} /> | ||
<div>TODO</div> | ||
<ul> | ||
{data.map((limit) => ( | ||
<li key={limit.id}>{JSON.stringify(limit)}</li> | ||
))} | ||
</ul> | ||
</div> | ||
)} | ||
<CreateOrEditLimitDialog | ||
open={openAddOrEditDialog.open} | ||
onOpenChange={(open) => | ||
setOpenAddOrEditDialog((curr) => ({ ...curr, open })) | ||
} | ||
limitToUpdate={selectedlimitToUpdate} | ||
onSubmit={closeAddDialog} | ||
/> | ||
</> | ||
); | ||
}; |
Oops, something went wrong.