-
Notifications
You must be signed in to change notification settings - Fork 59
Add create run form #308
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
Add create run form #308
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,236 @@ | ||||||||||||||||||
| import { Button } from "@/components/ui/button"; | ||||||||||||||||||
| import { | ||||||||||||||||||
| Field, | ||||||||||||||||||
| FieldDescription, | ||||||||||||||||||
| FieldError, | ||||||||||||||||||
| FieldGroup, | ||||||||||||||||||
| FieldLabel, | ||||||||||||||||||
| } from "@/components/ui/field"; | ||||||||||||||||||
| import { Input } from "@/components/ui/input"; | ||||||||||||||||||
| import { Textarea } from "@/components/ui/textarea"; | ||||||||||||||||||
| import { createWorkflowRunServerFn } from "@/lib/api"; | ||||||||||||||||||
| import { useNavigate } from "@tanstack/react-router"; | ||||||||||||||||||
| import { useState } from "react"; | ||||||||||||||||||
|
|
||||||||||||||||||
| interface CreateRunFormProps { | ||||||||||||||||||
| onCancel?: () => void; | ||||||||||||||||||
| onSuccess?: () => void; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function normalizeOptionalField(value: string): string | null { | ||||||||||||||||||
| const trimmed = value.trim(); | ||||||||||||||||||
| return trimmed.length > 0 ? trimmed : null; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function toIsoDateTime(value: string, fieldName: string): string { | ||||||||||||||||||
| const parsed = new Date(value); | ||||||||||||||||||
| if (Number.isNaN(parsed.getTime())) { | ||||||||||||||||||
| throw new TypeError(`${fieldName} must be a valid date and time`); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| return parsed.toISOString(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function getErrorMessage(error: unknown): string { | ||||||||||||||||||
| if (error instanceof Error && error.message) { | ||||||||||||||||||
| return error.message; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| return "Unable to create workflow run"; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| export function CreateRunForm({ onCancel, onSuccess }: CreateRunFormProps) { | ||||||||||||||||||
| const navigate = useNavigate(); | ||||||||||||||||||
|
|
||||||||||||||||||
| const [workflowName, setWorkflowName] = useState(""); | ||||||||||||||||||
| const [version, setVersion] = useState(""); | ||||||||||||||||||
| const [input, setInput] = useState(""); | ||||||||||||||||||
| const [availableAt, setAvailableAt] = useState(""); | ||||||||||||||||||
| const [deadlineAt, setDeadlineAt] = useState(""); | ||||||||||||||||||
| const [inputError, setInputError] = useState<string | null>(null); | ||||||||||||||||||
| const [submitError, setSubmitError] = useState<string | null>(null); | ||||||||||||||||||
| const [isSubmitting, setIsSubmitting] = useState(false); | ||||||||||||||||||
|
|
||||||||||||||||||
| async function submitForm() { | ||||||||||||||||||
| if (!workflowName.trim()) { | ||||||||||||||||||
| setSubmitError("Workflow name is required"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| const normalizedAvailableAt = normalizeOptionalField(availableAt); | ||||||||||||||||||
| const normalizedDeadlineAt = normalizeOptionalField(deadlineAt); | ||||||||||||||||||
| let availableAtIso: string | null = null; | ||||||||||||||||||
| let deadlineAtIso: string | null = null; | ||||||||||||||||||
|
|
||||||||||||||||||
| try { | ||||||||||||||||||
| if (normalizedAvailableAt) { | ||||||||||||||||||
| availableAtIso = toIsoDateTime(normalizedAvailableAt, "Schedule for"); | ||||||||||||||||||
| } | ||||||||||||||||||
| if (normalizedDeadlineAt) { | ||||||||||||||||||
| deadlineAtIso = toIsoDateTime(normalizedDeadlineAt, "Deadline"); | ||||||||||||||||||
| } | ||||||||||||||||||
| } catch (error) { | ||||||||||||||||||
| setSubmitError(getErrorMessage(error)); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| const normalizedInput = normalizeOptionalField(input); | ||||||||||||||||||
| if (normalizedInput) { | ||||||||||||||||||
| try { | ||||||||||||||||||
| JSON.parse(normalizedInput); | ||||||||||||||||||
| } catch { | ||||||||||||||||||
| setInputError("Input must be valid JSON"); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+78
to
+85
|
||||||||||||||||||
| if (normalizedInput) { | |
| try { | |
| JSON.parse(normalizedInput); | |
| } catch { | |
| setInputError("Input must be valid JSON"); | |
| return; | |
| } | |
| } |
Copilot
AI
Feb 14, 2026
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.
After successfully creating a workflow run, the form navigates to the run detail page, but the homepage data won't be immediately refreshed. While the usePolling hook will eventually update the list (every 2 seconds), consider calling router.invalidate() before navigation to ensure the homepage shows the new run when users navigate back. This would provide a better user experience.
Copilot
AI
Feb 14, 2026
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.
When the dialog closes via onSuccess or onCancel, the form state (workflowName, version, input, etc.) is not reset. This means if a user opens the dialog again after creating a run, the form will still contain the previous values. Consider resetting the form state when the dialog closes or when it opens to provide a clean form experience.
Copilot
AI
Feb 14, 2026
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.
The datetime-local input type is used for availableAt and deadlineAt fields, but there's no validation to ensure that deadlineAt is after availableAt if both are provided. Consider adding this logical validation to prevent users from setting a deadline before the scheduled start time, which would be an invalid configuration.
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.
The JSON.parse call at line 80 is wrapped in a try-catch, but the error is caught and a generic message "Input must be valid JSON" is set. Consider providing more specific error feedback that includes the parsing error details (e.g., the specific JSON syntax error) to help users fix their input more easily. For example: "Input must be valid JSON: unexpected token at position X".