-
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 16eeace
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
128 changes: 128 additions & 0 deletions
128
.../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,128 @@ | ||
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 { form, isLoading, saveOrUpdate } = useCreateOrEditLimitDialog({ | ||
limitToUpdate, | ||
onSubmit, | ||
}); | ||
|
||
const dialogTitle = limitToUpdate | ||
? `Update ${limitToUpdate.name}` | ||
: "Add Concurrency Limit"; | ||
|
||
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> | ||
); | ||
}; |
121 changes: 121 additions & 0 deletions
121
...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,121 @@ | ||
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, | ||
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.