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

feat(engine+ui): Improve error messages [2/N] #355

Merged
merged 15 commits into from
Aug 25, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export function CreateWorkflowButton() {
</form>
</Form>
{validationErrors && (
<div className="flex h-72 flex-col space-y-2 overflow-auto rounded-md border border-rose-500 bg-rose-100 p-2 font-mono text-xs text-rose-600">
<div className="flex h-36 flex-col space-y-2 overflow-auto rounded-md border border-rose-500 bg-rose-100 p-2 font-mono text-xs text-rose-600">
<span className="font-semibold">Validation errors</span>
<Separator className="bg-rose-400" />
<pre>{validationErrors}</pre>
Expand Down
205 changes: 124 additions & 81 deletions frontend/src/components/nav/workbench-nav.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client"

import React, { useCallback } from "react"
import React from "react"
import Link from "next/link"
import { usePathname, useRouter } from "next/navigation"
import {
Expand Down Expand Up @@ -75,6 +75,7 @@ import {
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import { Separator } from "@/components/ui/separator"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
Tooltip,
Expand Down Expand Up @@ -142,37 +143,10 @@ export function WorkbenchNav() {
{/* Workflow tabs */}
<TabSwitcher workflowId={workflow.id} />
{/* Workflow manual trigger */}
<Popover>
<Tooltip>
<PopoverTrigger asChild>
<TooltipTrigger asChild>
<span>
<Button
type="button"
variant="outline"
className="group flex h-7 items-center px-3 py-0 text-xs text-muted-foreground hover:bg-emerald-500 hover:text-white"
disabled={manualTriggerDisabled}
>
<PlayIcon className="mr-2 size-3 fill-emerald-500 stroke-emerald-500 group-hover:fill-white group-hover:stroke-white" />
<span>Run</span>
</Button>
</span>
</TooltipTrigger>
</PopoverTrigger>
<TooltipContent
side="bottom"
className="max-w-48 border bg-background text-xs text-muted-foreground shadow-lg"
>
{manualTriggerDisabled
? "Please commit changes to enable manual trigger."
: "Run the workflow manually without a webhook. Click to configure inputs."}
</TooltipContent>
<PopoverContent className="w-96 p-3">
<WorkflowExecutionControls workflowId={workflow.id} />
</PopoverContent>
</Tooltip>
</Popover>

<WorkflowManualTrigger
disabled={manualTriggerDisabled}
workflowId={workflow.id}
/>
{/* Commit button */}
<div className="flex items-center space-x-2">
<Tooltip>
Expand Down Expand Up @@ -200,17 +174,22 @@ export function WorkbenchNav() {
className="max-w-72 space-y-2 border bg-background p-0 text-xs text-muted-foreground shadow-lg"
>
{commitErrors ? (
<div className="rounded-md border border-rose-400 bg-rose-100 p-2 font-mono tracking-tighter">
<div className="space-y-2 rounded-md border border-rose-400 bg-rose-100 p-2 font-mono tracking-tighter">
<span className="text-xs font-bold text-rose-500">
Validation errors:
Validation Errors
</span>
<ul className="mt-1 space-y-1">
<div className="mt-1 space-y-1">
{commitErrors.map((error, index) => (
<li key={index} className="text-xs">
{error.message}
</li>
<div className="space-y-2">
<Separator className="bg-rose-400" />
<ErrorMessage
key={index}
message={error.message}
className="text-rose-500"
/>
</div>
))}
</ul>
</div>
</div>
) : (
<div className="p-2">
Expand Down Expand Up @@ -287,6 +266,25 @@ export function WorkbenchNav() {
)
}

function ErrorMessage({
message,
className,
}: { message: string } & React.HTMLAttributes<HTMLPreElement>) {
// Replace newline characters with <br /> tags
const formattedMessage = message.split("\n").map((line, index) => (
<React.Fragment key={index}>
{line}
<br />
</React.Fragment>
))

return (
<pre className={cn("whitespace-pre-wrap text-wrap", className)}>
{formattedMessage}
</pre>
)
}

function TabSwitcher({ workflowId }: { workflowId: string }) {
const pathname = usePathname()
const { workspaceId } = useWorkspace()
Expand Down Expand Up @@ -322,27 +320,43 @@ function TabSwitcher({ workflowId }: { workflowId: string }) {
}

const workflowControlsFormSchema = z.object({
payload: z.string().refine((val) => {
payload: z.string().superRefine((val, ctx) => {
try {
JSON.parse(val)
return true
} catch {
return false
} catch (error) {
if (error instanceof Error) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Invalid JSON format: ${error.message}`,
})
} else {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Invalid JSON format: Unknown error occurred",
})
}
}
}, "Invalid JSON format"),
}),
})
type TWorkflowControlsForm = z.infer<typeof workflowControlsFormSchema>

function WorkflowExecutionControls({ workflowId }: { workflowId: string }) {
function WorkflowManualTrigger({
disabled = true,
workflowId,
}: {
disabled: boolean
workflowId: string
}) {
const [open, setOpen] = React.useState(false)
const { workspaceId } = useWorkspace()
const form = useForm<TWorkflowControlsForm>({
resolver: zodResolver(workflowControlsFormSchema),
defaultValues: { payload: '{"sampleWebhookParam": "sampleValue"}' },
})

const handleSubmit = useCallback(async () => {
const handleSubmit = async (values: TWorkflowControlsForm) => {
// Make the API call to start the workflow
const { payload } = form.getValues()
const { payload } = values
try {
const response = await workflowExecutionsCreateWorkflowExecution({
workspaceId,
Expand All @@ -356,6 +370,7 @@ function WorkflowExecutionControls({ workflowId }: { workflowId: string }) {
title: `Workflow run started`,
description: `${response.wf_exec_id} ${response.message}`,
})
setOpen(false)
} catch (error) {
if (error instanceof ApiError) {
console.error("Error details", error.body)
Expand All @@ -373,44 +388,72 @@ function WorkflowExecutionControls({ workflowId }: { workflowId: string }) {
})
}
}
}, [workflowId, form])
}

return (
<Form {...form}>
<form>
<div className="flex flex-col space-y-2">
<span className="text-xs text-muted-foreground">
Edit the JSON payload below.
</span>
<FormField
control={form.control}
name="payload"
render={({ field }) => (
<FormItem>
<FormControl>
<CustomEditor
className="size-full h-36"
defaultLanguage="yaml"
value={field.value}
onChange={field.onChange}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<Button
type="button"
variant="default"
onClick={handleSubmit}
className="group flex h-7 items-center bg-emerald-500 px-3 py-0 text-xs text-white hover:bg-emerald-500/80 hover:text-white"
<Popover open={open} onOpenChange={setOpen}>
<Tooltip>
<PopoverTrigger asChild>
<TooltipTrigger asChild>
<span>
<Button
type="button"
variant="outline"
className="group flex h-7 items-center px-3 py-0 text-xs text-muted-foreground hover:bg-emerald-500 hover:text-white"
disabled={disabled}
>
<PlayIcon className="mr-2 size-3 fill-emerald-500 stroke-emerald-500 group-hover:fill-white group-hover:stroke-white" />
<span>Run</span>
</Button>
</span>
</TooltipTrigger>
</PopoverTrigger>
<TooltipContent
side="bottom"
className="max-w-48 border bg-background text-xs text-muted-foreground shadow-lg"
>
<PlayIcon className="mr-2 size-3 fill-white stroke-white" />
<span>Run</span>
</Button>
</div>
</form>
{disabled
? "Please commit changes to enable manual trigger."
: "Run the workflow manually without a webhook. Click to configure inputs."}
</TooltipContent>
<PopoverContent className="w-96 p-3">
<form onSubmit={form.handleSubmit(handleSubmit)}>
<div className="flex flex-col space-y-2">
<span className="text-xs text-muted-foreground">
Edit the JSON payload below.
</span>
<FormField
control={form.control}
name="payload"
render={({ field }) => (
<FormItem>
<FormControl>
<CustomEditor
className="size-full h-36"
defaultLanguage="yaml"
value={field.value}
onChange={field.onChange}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<Button
type="submit"
variant="default"
className="group flex h-7 items-center bg-emerald-500 px-3 py-0 text-xs text-white hover:bg-emerald-500/80 hover:text-white"
>
<PlayIcon className="mr-2 size-3 fill-white stroke-white" />
<span>Run</span>
</Button>
</div>
</form>
</PopoverContent>
</Tooltip>
</Popover>
</Form>
)
}
Expand Down
16 changes: 8 additions & 8 deletions frontend/src/components/workbench/panel/udf-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,7 @@ export function UDFActionPanel({
{/* Metadata */}
<Accordion
type="multiple"
defaultValue={[
"action-schema",
"action-inputs"
]}
defaultValue={["action-schema", "action-inputs"]}
className="pb-10"
>
<AccordionItem value="action-settings">
Expand Down Expand Up @@ -400,7 +397,7 @@ export function UDFActionPanel({
)}
/>
{validationErrors && (
<div className="rounded-md border-2 border-rose-500 bg-rose-100 p-4 font-mono text-xs text-rose-600">
<div className="rounded-md border border-rose-400 bg-rose-100 p-4 font-mono text-xs text-rose-500">
<span className="font-bold">Validation Errors</span>
<Separator className="my-2 bg-rose-400" />
<span>{validationErrors.message}</span>
Expand Down Expand Up @@ -438,7 +435,8 @@ export function UDFActionPanel({
</HoverCard>

<span className="text-xs text-muted-foreground">
Define a conditional expression that determines if the action executes.
Define a conditional expression that determines if the
action executes.
</span>
</div>

Expand Down Expand Up @@ -475,7 +473,8 @@ export function UDFActionPanel({
</HoverCard>

<span className="text-xs text-muted-foreground">
Define one or more loop expressions for the action to iterate over.
Define one or more loop expressions for the action to
iterate over.
</span>
</div>

Expand Down Expand Up @@ -510,7 +509,8 @@ function RunIfTooltip() {
</div>
<div className="flex w-full items-center justify-between text-muted-foreground ">
<span>
A run-if expression is a conditional expression that evaluates to a truthy or falsy value:
A run-if expression is a conditional expression that evaluates to a
truthy or falsy value:
</span>
</div>
<div className="rounded-md border bg-muted-foreground/10 p-2">
Expand Down
Loading
Loading