diff --git a/studio/frontend/src/features/recipe-studio/components/runtime/execution-progress-island.tsx b/studio/frontend/src/features/recipe-studio/components/runtime/execution-progress-island.tsx new file mode 100644 index 0000000000..9cf2791416 --- /dev/null +++ b/studio/frontend/src/features/recipe-studio/components/runtime/execution-progress-island.tsx @@ -0,0 +1,146 @@ +import { + ArrowDown01Icon, + ArrowUp01Icon, + CheckmarkCircle02Icon, + Flag02Icon, +} from "@hugeicons/core-free-icons"; +import { HugeiconsIcon } from "@hugeicons/react"; +import type { ReactElement } from "react"; +import { Button } from "@/components/ui/button"; +import { Progress } from "@/components/ui/progress"; +import { Spinner } from "@/components/ui/spinner"; +import { cn } from "@/lib/utils"; +import type { RecipeExecutionRecord } from "../../execution-types"; +import { isExecutionInProgress } from "../../executions/execution-helpers"; +import { + formatMetricValue, + formatPercent, +} from "../executions/executions-view-helpers"; + +type ExecutionProgressIslandProps = { + execution: RecipeExecutionRecord; + currentColumnIcon: typeof Flag02Icon; + minimized: boolean; + onMinimizedChange: (value: boolean) => void; + onViewExecutions: () => void; +}; + +function formatEta(value: number | null | undefined): string { + const metric = formatMetricValue(value); + if (metric === "--") { + return "--"; + } + return `${metric}s`; +} + +function statusLabel(input: { + complete: boolean; + inProgress: boolean; +}): string { + if (input.complete) { + return "Run completed"; + } + if (input.inProgress) { + return "Run in progress"; + } + return "Run status"; +} + +export function ExecutionProgressIsland({ + execution, + currentColumnIcon, + minimized, + onMinimizedChange, + onViewExecutions, +}: ExecutionProgressIslandProps): ReactElement { + const complete = execution.status === "completed"; + const inProgress = isExecutionInProgress(execution.status); + const progressPercent = execution.progress?.percent ?? (complete ? 100 : 0); + const hasProgressSignal = Boolean( + execution.progress && + (typeof execution.progress.done === "number" || + typeof execution.progress.total === "number" || + typeof execution.progress.percent === "number" || + typeof execution.progress.rate === "number" || + typeof execution.progress.eta_sec === "number"), + ); + const showLoadingSpinner = inProgress && !hasProgressSignal; + const batchTotal = execution.batch?.total ?? null; + const showBatch = typeof batchTotal === "number" && batchTotal > 1; + + return ( +
+ {statusLabel({ complete, inProgress })} +
+Done: {formatMetricValue(execution.progress?.done)}
+Total: {formatMetricValue(execution.progress?.total)}
+Rate: {formatMetricValue(execution.progress?.rate)}
+ETA: {formatEta(execution.progress?.eta_sec)}
+Column: {execution.current_column ?? "--"}
+Drop from final dataset
diff --git a/studio/frontend/src/features/recipe-studio/dialogs/validators/validator-dialog.tsx b/studio/frontend/src/features/recipe-studio/dialogs/validators/validator-dialog.tsx index 57c9b0772a..be87c7b912 100644 --- a/studio/frontend/src/features/recipe-studio/dialogs/validators/validator-dialog.tsx +++ b/studio/frontend/src/features/recipe-studio/dialogs/validators/validator-dialog.tsx @@ -1,3 +1,8 @@ +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; import { Input } from "@/components/ui/input"; import { Select, @@ -6,7 +11,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { type ReactElement, useMemo } from "react"; +import { type ReactElement, useMemo, useState } from "react"; import { useRecipeStudioStore } from "../../stores/recipe-studio"; import type { ValidatorConfig } from "../../types"; import { isValidatorCodeLang } from "../../utils/validators/code-lang"; @@ -27,6 +32,7 @@ export function ValidatorDialog({ const configs = useRecipeStudioStore((state) => state.configs); const targetColumnId = `${config.id}-target-column`; const batchSizeId = `${config.id}-batch-size`; + const [advancedOpen, setAdvancedOpen] = useState(false); const codeOptions = useMemo( () => Object.values(configs) @@ -46,7 +52,6 @@ export function ValidatorDialog({ [configs], ); const currentTarget = config.target_columns[0] ?? ""; - const engineLabel = config.code_lang.startsWith("sql:") ? "SQL" : "Python"; return (
- Batch {runtimeVisualState.batch.idx ?? "--"}/ - {runtimeVisualState.batch.total} -
- {activeExecution?.current_column && ( -Column: {activeExecution.current_column}
-