-
Notifications
You must be signed in to change notification settings - Fork 1
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
fix: Variable ui cleanup #203
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,198 @@ | ||||||||||||||||||||||||||||||||
"use client"; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
import type { | ||||||||||||||||||||||||||||||||
JobAgent, | ||||||||||||||||||||||||||||||||
Runbook, | ||||||||||||||||||||||||||||||||
RunbookVariable, | ||||||||||||||||||||||||||||||||
Workspace, | ||||||||||||||||||||||||||||||||
} from "@ctrlplane/db/schema"; | ||||||||||||||||||||||||||||||||
import { useState } from "react"; | ||||||||||||||||||||||||||||||||
import { useRouter } from "next/navigation"; | ||||||||||||||||||||||||||||||||
import { z } from "zod"; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
import { createRunbookVariable } from "@ctrlplane/db/schema"; | ||||||||||||||||||||||||||||||||
import { Button } from "@ctrlplane/ui/button"; | ||||||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||||||
Dialog, | ||||||||||||||||||||||||||||||||
DialogContent, | ||||||||||||||||||||||||||||||||
DialogHeader, | ||||||||||||||||||||||||||||||||
DialogTitle, | ||||||||||||||||||||||||||||||||
DialogTrigger, | ||||||||||||||||||||||||||||||||
} from "@ctrlplane/ui/dialog"; | ||||||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||||||
Form, | ||||||||||||||||||||||||||||||||
FormControl, | ||||||||||||||||||||||||||||||||
FormField, | ||||||||||||||||||||||||||||||||
FormItem, | ||||||||||||||||||||||||||||||||
FormLabel, | ||||||||||||||||||||||||||||||||
FormMessage, | ||||||||||||||||||||||||||||||||
FormRootError, | ||||||||||||||||||||||||||||||||
useForm, | ||||||||||||||||||||||||||||||||
} from "@ctrlplane/ui/form"; | ||||||||||||||||||||||||||||||||
import { Input } from "@ctrlplane/ui/input"; | ||||||||||||||||||||||||||||||||
import { Textarea } from "@ctrlplane/ui/textarea"; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
import { JobAgentConfig } from "~/components/form/job-agent/JobAgentConfig"; | ||||||||||||||||||||||||||||||||
import { JobAgentSelector } from "~/components/form/job-agent/JobAgentSelector"; | ||||||||||||||||||||||||||||||||
import { api } from "~/trpc/react"; | ||||||||||||||||||||||||||||||||
import { RunbookVariablesEditor } from "./create/RunbookVariableEditor"; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
const updateRunbookSchema = z.object({ | ||||||||||||||||||||||||||||||||
name: z.string().min(1), | ||||||||||||||||||||||||||||||||
description: z.string(), | ||||||||||||||||||||||||||||||||
variables: z.array(createRunbookVariable), | ||||||||||||||||||||||||||||||||
jobAgentId: z.string().uuid({ message: "Must be a valid job agent ID" }), | ||||||||||||||||||||||||||||||||
jobAgentConfig: z.record(z.any()), | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
export const EditRunbookDialog: React.FC<{ | ||||||||||||||||||||||||||||||||
workspace: Workspace; | ||||||||||||||||||||||||||||||||
jobAgents: JobAgent[]; | ||||||||||||||||||||||||||||||||
runbook: Runbook & { variables: RunbookVariable[] }; | ||||||||||||||||||||||||||||||||
children: React.ReactNode; | ||||||||||||||||||||||||||||||||
}> = ({ workspace, jobAgents, runbook, children }) => { | ||||||||||||||||||||||||||||||||
const [open, setOpen] = useState(false); | ||||||||||||||||||||||||||||||||
const update = api.runbook.update.useMutation(); | ||||||||||||||||||||||||||||||||
const form = useForm({ | ||||||||||||||||||||||||||||||||
schema: updateRunbookSchema, | ||||||||||||||||||||||||||||||||
disabled: update.isPending, | ||||||||||||||||||||||||||||||||
defaultValues: { | ||||||||||||||||||||||||||||||||
...runbook, | ||||||||||||||||||||||||||||||||
description: runbook.description ?? "", | ||||||||||||||||||||||||||||||||
jobAgentId: runbook.jobAgentId ?? "", | ||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
const router = useRouter(); | ||||||||||||||||||||||||||||||||
const onSubmit = form.handleSubmit(async (data) => | ||||||||||||||||||||||||||||||||
update | ||||||||||||||||||||||||||||||||
.mutateAsync({ id: runbook.id, data }) | ||||||||||||||||||||||||||||||||
.then(() => router.refresh()) | ||||||||||||||||||||||||||||||||
.then(() => setOpen(false)), | ||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||
Comment on lines
+68
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle errors in form submission The Apply this diff to handle errors using -const onSubmit = form.handleSubmit(async (data) =>
- update
- .mutateAsync({ id: runbook.id, data })
- .then(() => router.refresh())
- .then(() => setOpen(false)),
-);
+const onSubmit = form.handleSubmit(async (data) => {
+ try {
+ await update.mutateAsync({ id: runbook.id, data });
+ router.refresh();
+ setOpen(false);
+ } catch (error) {
+ // Handle error, e.g., display error message to the user
+ form.setError("root", { message: "Failed to update runbook." });
+ }
+}); 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
const jobAgentId = form.watch("jobAgentId"); | ||||||||||||||||||||||||||||||||
const jobAgent = jobAgents.find((j) => j.id === jobAgentId); | ||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||
<Dialog open={open} onOpenChange={setOpen}> | ||||||||||||||||||||||||||||||||
<DialogTrigger asChild>{children}</DialogTrigger> | ||||||||||||||||||||||||||||||||
<DialogContent className="scrollbar-thin scrollbar-track-neutral-900 scrollbar-thumb-neutral-800 max-h-[95vh] max-w-3xl overflow-y-auto"> | ||||||||||||||||||||||||||||||||
<DialogHeader> | ||||||||||||||||||||||||||||||||
<DialogTitle>Edit Runbook</DialogTitle> | ||||||||||||||||||||||||||||||||
</DialogHeader> | ||||||||||||||||||||||||||||||||
<Form {...form}> | ||||||||||||||||||||||||||||||||
<form onSubmit={onSubmit} className="space-y-8"> | ||||||||||||||||||||||||||||||||
<div className="space-y-3"> | ||||||||||||||||||||||||||||||||
<div>General</div> | ||||||||||||||||||||||||||||||||
<FormField | ||||||||||||||||||||||||||||||||
control={form.control} | ||||||||||||||||||||||||||||||||
name="name" | ||||||||||||||||||||||||||||||||
render={({ field }) => ( | ||||||||||||||||||||||||||||||||
<FormItem> | ||||||||||||||||||||||||||||||||
<FormLabel>Name</FormLabel> | ||||||||||||||||||||||||||||||||
<FormControl> | ||||||||||||||||||||||||||||||||
<Input | ||||||||||||||||||||||||||||||||
placeholder="Deploy Hotfix, Rollback Release, Scale Service..." | ||||||||||||||||||||||||||||||||
{...field} | ||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||
</FormControl> | ||||||||||||||||||||||||||||||||
<FormMessage /> | ||||||||||||||||||||||||||||||||
</FormItem> | ||||||||||||||||||||||||||||||||
)} | ||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||
<FormField | ||||||||||||||||||||||||||||||||
control={form.control} | ||||||||||||||||||||||||||||||||
name="description" | ||||||||||||||||||||||||||||||||
render={({ field }) => ( | ||||||||||||||||||||||||||||||||
<FormItem> | ||||||||||||||||||||||||||||||||
<FormLabel>Description</FormLabel> | ||||||||||||||||||||||||||||||||
<FormControl> | ||||||||||||||||||||||||||||||||
<Textarea | ||||||||||||||||||||||||||||||||
placeholder="Describe the purpose of this runbook..." | ||||||||||||||||||||||||||||||||
{...field} | ||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||
</FormControl> | ||||||||||||||||||||||||||||||||
<FormMessage /> | ||||||||||||||||||||||||||||||||
</FormItem> | ||||||||||||||||||||||||||||||||
)} | ||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
<div className="space-y-3"> | ||||||||||||||||||||||||||||||||
<div>Variables</div> | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
<div className="text-sm text-muted-foreground"> | ||||||||||||||||||||||||||||||||
Variables in runbooks make automation flexible and reusable. | ||||||||||||||||||||||||||||||||
They let you customize runbooks with user inputs and use | ||||||||||||||||||||||||||||||||
environment-specific values without hardcoding. This allows | ||||||||||||||||||||||||||||||||
runbooks to adapt to different scenarios without changing their | ||||||||||||||||||||||||||||||||
core logic. | ||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
<FormField | ||||||||||||||||||||||||||||||||
control={form.control} | ||||||||||||||||||||||||||||||||
name="variables" | ||||||||||||||||||||||||||||||||
render={({ field }) => ( | ||||||||||||||||||||||||||||||||
<FormItem> | ||||||||||||||||||||||||||||||||
<FormControl> | ||||||||||||||||||||||||||||||||
<RunbookVariablesEditor | ||||||||||||||||||||||||||||||||
value={field.value} | ||||||||||||||||||||||||||||||||
onChange={field.onChange} | ||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||
</FormControl> | ||||||||||||||||||||||||||||||||
<FormMessage /> | ||||||||||||||||||||||||||||||||
</FormItem> | ||||||||||||||||||||||||||||||||
)} | ||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
<div className="space-y-3"> | ||||||||||||||||||||||||||||||||
<div>Agent</div> | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
<FormField | ||||||||||||||||||||||||||||||||
control={form.control} | ||||||||||||||||||||||||||||||||
name="jobAgentId" | ||||||||||||||||||||||||||||||||
render={({ field: { value, onChange } }) => ( | ||||||||||||||||||||||||||||||||
<FormItem> | ||||||||||||||||||||||||||||||||
<FormLabel>Job Agent</FormLabel> | ||||||||||||||||||||||||||||||||
<FormControl> | ||||||||||||||||||||||||||||||||
<JobAgentSelector | ||||||||||||||||||||||||||||||||
jobAgents={jobAgents} | ||||||||||||||||||||||||||||||||
workspace={workspace} | ||||||||||||||||||||||||||||||||
value={value} | ||||||||||||||||||||||||||||||||
onChange={onChange} | ||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||
</FormControl> | ||||||||||||||||||||||||||||||||
<FormMessage /> | ||||||||||||||||||||||||||||||||
</FormItem> | ||||||||||||||||||||||||||||||||
)} | ||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
<FormField | ||||||||||||||||||||||||||||||||
control={form.control} | ||||||||||||||||||||||||||||||||
name="jobAgentConfig" | ||||||||||||||||||||||||||||||||
render={({ field: { value, onChange } }) => ( | ||||||||||||||||||||||||||||||||
<FormItem> | ||||||||||||||||||||||||||||||||
<FormLabel>Config</FormLabel> | ||||||||||||||||||||||||||||||||
<FormControl> | ||||||||||||||||||||||||||||||||
<JobAgentConfig | ||||||||||||||||||||||||||||||||
jobAgent={jobAgent} | ||||||||||||||||||||||||||||||||
workspace={workspace} | ||||||||||||||||||||||||||||||||
value={value} | ||||||||||||||||||||||||||||||||
onChange={onChange} | ||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||
</FormControl> | ||||||||||||||||||||||||||||||||
<FormMessage /> | ||||||||||||||||||||||||||||||||
</FormItem> | ||||||||||||||||||||||||||||||||
)} | ||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
<Button type="submit">Save</Button> | ||||||||||||||||||||||||||||||||
<FormRootError /> | ||||||||||||||||||||||||||||||||
</form> | ||||||||||||||||||||||||||||||||
</Form> | ||||||||||||||||||||||||||||||||
</DialogContent> | ||||||||||||||||||||||||||||||||
</Dialog> | ||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid spreading
runbook
intodefaultValues
Spreading
...runbook
intodefaultValues
may include unexpected fields that are not defined in the form schema, potentially causing validation issues or unintended behavior. It's better to explicitly set only the fields defined in the schema.Apply this diff to only include necessary fields:
📝 Committable suggestion