From 557e0b6d020eb1c4a1d8a0dff6ab832592d696ce Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 13 Jun 2026 09:23:35 -0700 Subject: [PATCH 1/2] =?UTF-8?q?improvement(react-query):=20codebase-wide?= =?UTF-8?q?=20audit=20=E2=80=94=20server-state=20hooks,=20webhook=20cohere?= =?UTF-8?q?nce,=20resume=20migration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../demo-request/demo-request-modal.tsx | 5 +- .../[executionId]/[contextId]/route.ts | 7 +- .../[executionId]/resume-page-client.tsx | 541 +++++++----------- .../workflow-sidebar/workflow-sidebar.tsx | 6 +- .../workflow-block/hooks/use-webhook-info.ts | 68 +-- apps/sim/ee/data-drains/hooks/data-drains.ts | 25 +- apps/sim/ee/sso/hooks/sso.ts | 4 +- apps/sim/ee/whitelabeling/hooks/whitelabel.ts | 3 +- apps/sim/hooks/queries/a2a/agents.ts | 8 +- apps/sim/hooks/queries/academy.ts | 5 +- apps/sim/hooks/queries/admin-users.ts | 41 +- apps/sim/hooks/queries/api-keys.ts | 12 +- apps/sim/hooks/queries/byok-keys.ts | 19 +- apps/sim/hooks/queries/copilot-keys.ts | 8 +- apps/sim/hooks/queries/credential-sets.ts | 52 +- apps/sim/hooks/queries/custom-tools.ts | 3 +- apps/sim/hooks/queries/deployments.ts | 4 +- apps/sim/hooks/queries/environment.ts | 18 +- apps/sim/hooks/queries/general-settings.ts | 2 +- apps/sim/hooks/queries/inbox.ts | 12 +- apps/sim/hooks/queries/invitations.ts | 5 +- apps/sim/hooks/queries/kb/connectors.ts | 15 +- apps/sim/hooks/queries/kb/knowledge.ts | 11 +- apps/sim/hooks/queries/mothership-admin.ts | 4 +- .../hooks/queries/oauth/oauth-connections.ts | 11 +- .../hooks/queries/oauth/oauth-credentials.ts | 7 +- apps/sim/hooks/queries/providers.ts | 7 +- apps/sim/hooks/queries/resume-execution.ts | 197 +++++++ apps/sim/hooks/queries/schedules.ts | 75 +-- apps/sim/hooks/queries/skills.ts | 2 + apps/sim/hooks/queries/subscription.ts | 58 +- apps/sim/hooks/queries/tables.ts | 6 +- apps/sim/hooks/queries/user-profile.ts | 2 +- .../hooks/queries/utils/custom-tool-keys.ts | 3 +- .../queries/utils/optimistic-mutation.ts | 2 +- apps/sim/hooks/queries/utils/workflow-keys.ts | 4 +- apps/sim/hooks/queries/webhooks.ts | 35 +- .../sim/hooks/queries/workflow-mcp-servers.ts | 72 +-- apps/sim/hooks/queries/workflows.ts | 26 +- apps/sim/hooks/use-permission-config.ts | 7 +- apps/sim/lib/api/contracts/workflows.ts | 25 + 41 files changed, 775 insertions(+), 642 deletions(-) create mode 100644 apps/sim/hooks/queries/resume-execution.ts diff --git a/apps/sim/app/(landing)/components/demo-request/demo-request-modal.tsx b/apps/sim/app/(landing)/components/demo-request/demo-request-modal.tsx index 3c254742348..f274e39c614 100644 --- a/apps/sim/app/(landing)/components/demo-request/demo-request-modal.tsx +++ b/apps/sim/app/(landing)/components/demo-request/demo-request-modal.tsx @@ -1,6 +1,7 @@ 'use client' import { useState } from 'react' +import { getErrorMessage } from '@sim/utils/errors' import { useMutation } from '@tanstack/react-query' import { ChipCombobox, @@ -128,9 +129,7 @@ export function DemoRequestModal({ children, theme = 'dark' }: DemoRequestModalP } const submitError = demoMutation.isError - ? demoMutation.error instanceof Error - ? demoMutation.error.message - : 'Failed to submit demo request. Please try again.' + ? getErrorMessage(demoMutation.error, 'Failed to submit demo request. Please try again.') : null return ( diff --git a/apps/sim/app/api/resume/[workflowId]/[executionId]/[contextId]/route.ts b/apps/sim/app/api/resume/[workflowId]/[executionId]/[contextId]/route.ts index 47f2f381168..cfc05b5a1e9 100644 --- a/apps/sim/app/api/resume/[workflowId]/[executionId]/[contextId]/route.ts +++ b/apps/sim/app/api/resume/[workflowId]/[executionId]/[contextId]/route.ts @@ -2,7 +2,10 @@ import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' -import { resumeWorkflowExecutionContextContract } from '@/lib/api/contracts/workflows' +import { + getPauseContextDetailContract, + resumeWorkflowExecutionContextContract, +} from '@/lib/api/contracts/workflows' import { parseRequest } from '@/lib/api/server' import { AuthType } from '@/lib/auth/hybrid' import { getJobQueue } from '@/lib/core/async-jobs' @@ -310,7 +313,7 @@ export const GET = withRouteHandler( params: Promise<{ workflowId: string; executionId: string; contextId: string }> } ) => { - const parsed = await parseRequest(resumeWorkflowExecutionContextContract, request, context) + const parsed = await parseRequest(getPauseContextDetailContract, request, context) if (!parsed.success) return parsed.response const { workflowId, executionId, contextId } = parsed.data.params diff --git a/apps/sim/app/resume/[workflowId]/[executionId]/resume-page-client.tsx b/apps/sim/app/resume/[workflowId]/[executionId]/resume-page-client.tsx index 4f962ccce10..ca81b92f807 100644 --- a/apps/sim/app/resume/[workflowId]/[executionId]/resume-page-client.tsx +++ b/apps/sim/app/resume/[workflowId]/[executionId]/resume-page-client.tsx @@ -1,12 +1,9 @@ 'use client' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { createLogger } from '@sim/logger' +import { useQueryClient } from '@tanstack/react-query' import { RefreshCw } from 'lucide-react' import { useRouter } from 'next/navigation' - -const logger = createLogger('ResumePage') - import { Badge, Button, @@ -29,19 +26,17 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select' -import { requestJson } from '@/lib/api/client/request' -import { resumeWorkflowExecutionContract } from '@/lib/api/contracts/workflows' import Navbar from '@/app/(landing)/components/navbar/navbar' import { useBrandConfig } from '@/ee/whitelabeling' -import type { ResumeStatus } from '@/executor/types' - -interface ResumeLinks { - apiUrl: string - uiUrl: string - contextId: string - executionId: string - workflowId: string -} +import { + type PauseContextDetail, + type PausedExecutionDetail, + type PausePointWithQueue, + resumeKeys, + usePauseContextDetail, + useResumeContext, + useResumeExecutionDetail, +} from '@/hooks/queries/resume-execution' interface NormalizedInputField { id: string @@ -63,62 +58,6 @@ interface ResponseStructureRow { value: any } -interface ResumeQueueEntrySummary { - id: string - contextId: string - status: string - queuedAt: string | null - claimedAt: string | null - completedAt: string | null - failureReason: string | null - newExecutionId: string - resumeInput: any -} - -interface PausePointWithQueue { - contextId: string - triggerBlockId?: string - blockId?: string - response: any - registeredAt: string - resumeStatus: ResumeStatus - snapshotReady: boolean - resumeLinks?: ResumeLinks - queuePosition?: number | null - latestResumeEntry?: ResumeQueueEntrySummary | null - parallelScope?: any - loopScope?: any - pauseKind?: 'human' | 'time' - resumeAt?: string -} - -interface PausedExecutionSummary { - id: string - workflowId: string - executionId: string - status: string - totalPauseCount: number - resumedCount: number - pausedAt: string | null - updatedAt: string | null - expiresAt: string | null - metadata: Record | null - triggerIds: string[] - pausePoints: PausePointWithQueue[] -} - -interface PauseContextDetail { - execution: PausedExecutionSummary - pausePoint: PausePointWithQueue - queue: ResumeQueueEntrySummary[] - activeResumeEntry?: ResumeQueueEntrySummary | null -} - -interface PausedExecutionDetail extends PausedExecutionSummary { - executionSnapshot: any - queue: ResumeQueueEntrySummary[] -} - interface ResumeExecutionPageProps { params: { workflowId: string; executionId: string } initialExecutionDetail: PausedExecutionDetail | null @@ -205,10 +144,13 @@ export default function ResumeExecutionPage({ const { workflowId, executionId } = params const router = useRouter() const brandConfig = useBrandConfig() + const queryClient = useQueryClient() - const [executionDetail, setExecutionDetail] = useState( - initialExecutionDetail - ) + const { + data: executionDetail, + isFetching: refreshingExecution, + refetch: refetchExecutionDetail, + } = useResumeExecutionDetail(workflowId, executionId, initialExecutionDetail ?? undefined) const pausePoints = executionDetail?.pausePoints ?? [] const defaultContextId = useMemo(() => { @@ -222,7 +164,11 @@ export default function ResumeExecutionPage({ const [selectedContextId, setSelectedContextId] = useState( defaultContextId ?? null ) - const [selectedDetail, setSelectedDetail] = useState(null) + const { data: selectedDetail, isLoading: loadingDetail } = usePauseContextDetail( + workflowId, + executionId, + selectedContextId ?? undefined + ) const [selectedStatus, setSelectedStatus] = useState('paused') const [queuePosition, setQueuePosition] = useState(undefined) @@ -233,12 +179,12 @@ export default function ResumeExecutionPage({ >({}) const [formValues, setFormValues] = useState>({}) const [formErrors, setFormErrors] = useState>({}) - const [loadingDetail, setLoadingDetail] = useState(false) const [loadingAction, setLoadingAction] = useState(false) - const [refreshingExecution, setRefreshingExecution] = useState(false) const [error, setError] = useState(null) const [message, setMessage] = useState(null) + const resumeMutation = useResumeContext() + const normalizeInputFormatFields = useCallback((raw: any): NormalizedInputField[] => { if (!Array.isArray(raw)) return [] return raw @@ -513,296 +459,193 @@ export default function ResumeExecutionPage({ .filter((row): row is ResponseStructureRow => row !== null) }, [selectedDetail]) + const seedFormFromDetail = useCallback( + (detail: PauseContextDetail) => { + const responseData = detail.pausePoint.response?.data ?? {} + const operation = responseData.operation || 'human' + const fetchedInputFields = normalizeInputFormatFields(responseData.inputFormat) + const submission = + responseData && + typeof responseData.submission === 'object' && + !Array.isArray(responseData.submission) + ? (responseData.submission as Record) + : undefined + if (operation === 'human' && fetchedInputFields.length > 0) { + const baseValues = buildInitialFormValues(fetchedInputFields, submission) + let mergedValues = baseValues + setFormValuesByContext((prev) => { + const existingValues = prev[detail.pausePoint.contextId] + if (existingValues) mergedValues = { ...baseValues, ...existingValues } + return { ...prev, [detail.pausePoint.contextId]: mergedValues } + }) + setFormValues(mergedValues) + setFormErrors({}) + if (resumeInputsRef.current[detail.pausePoint.contextId] !== undefined) { + delete resumeInputsRef.current[detail.pausePoint.contextId] + } + setResumeInput('') + } else { + const initialValue = + typeof responseData === 'string' + ? responseData + : JSON.stringify(responseData ?? {}, null, 2) + if (resumeInputsRef.current[detail.pausePoint.contextId] !== undefined) { + setResumeInput(resumeInputsRef.current[detail.pausePoint.contextId]) + } else { + setResumeInput(initialValue) + resumeInputsRef.current = { + ...resumeInputsRef.current, + [detail.pausePoint.contextId]: initialValue, + } + } + setFormValues({}) + setFormErrors({}) + } + }, + [normalizeInputFormatFields, buildInitialFormValues] + ) + useEffect(() => { + if (!selectedDetail) return + setSelectedStatus(selectedDetail.pausePoint.resumeStatus) + setQueuePosition(selectedDetail.pausePoint.queuePosition) + seedFormFromDetail(selectedDetail) + }, [selectedDetail, seedFormFromDetail]) + + const handleRefreshExecution = useCallback(async () => { + const { data } = await refetchExecutionDetail() if (!selectedContextId) { - setSelectedDetail(null) - return + const firstPaused = + data?.pausePoints.find((point) => point.resumeStatus === 'paused')?.contextId ?? null + setSelectedContextId(firstPaused) } - const controller = new AbortController() - const loadDetail = async () => { - setLoadingDetail(true) - try { - // boundary-raw-fetch: GET /api/resume/[workflowId]/[executionId]/[contextId] has no contract (server route only models POST resume submission) - const response = await fetch( - `/api/resume/${workflowId}/${executionId}/${selectedContextId}`, - { - method: 'GET', - credentials: 'include', - cache: 'no-store', - signal: controller.signal, + }, [refetchExecutionDetail, selectedContextId]) + + const handleResume = useCallback( + async () => { + if (!selectedContextId || !selectedDetail) return + setLoadingAction(true) + setError(null) + setMessage(null) + let resumePayload: any + if (isHumanMode && hasInputFormat) { + const errors: Record = {} + const submission: Record = {} + for (const field of inputFormatFields) { + const rawValue = formValues[field.name] ?? '' + const hasValue = + field.type === 'boolean' + ? rawValue === 'true' || rawValue === 'false' + : rawValue.trim().length > 0 && rawValue !== '__unset__' + if (!hasValue || rawValue === '__unset__') { + if (field.required) errors[field.name] = 'This field is required.' + continue } - ) - if (!response.ok) { - setSelectedDetail(null) + const { value, error: parseError } = parseFormValue(field, rawValue) + if (parseError) { + errors[field.name] = parseError + continue + } + if (value !== undefined) submission[field.name] = value + } + if (Object.keys(errors).length > 0) { + setFormErrors(errors) + setLoadingAction(false) return } - const data: PauseContextDetail = await response.json() - setSelectedDetail(data) - setSelectedStatus(data.pausePoint.resumeStatus) - setQueuePosition(data.pausePoint.queuePosition) - const responseData = data.pausePoint.response?.data ?? {} - const operation = responseData.operation || 'human' - const fetchedInputFields = normalizeInputFormatFields(responseData.inputFormat) - const submission = - responseData && - typeof responseData.submission === 'object' && - !Array.isArray(responseData.submission) - ? (responseData.submission as Record) - : undefined - if (operation === 'human' && fetchedInputFields.length > 0) { - const baseValues = buildInitialFormValues(fetchedInputFields, submission) - let mergedValues = baseValues - setFormValuesByContext((prev) => { - const existingValues = prev[data.pausePoint.contextId] - if (existingValues) mergedValues = { ...baseValues, ...existingValues } - return { ...prev, [data.pausePoint.contextId]: mergedValues } - }) - setFormValues(mergedValues) - setFormErrors({}) - if (resumeInputsRef.current[data.pausePoint.contextId] !== undefined) { - delete resumeInputsRef.current[data.pausePoint.contextId] - } - setResumeInput('') - } else { - const initialValue = - typeof responseData === 'string' - ? responseData - : JSON.stringify(responseData ?? {}, null, 2) - if (resumeInputsRef.current[data.pausePoint.contextId] !== undefined) { - setResumeInput(resumeInputsRef.current[data.pausePoint.contextId]) - } else { - setResumeInput(initialValue) - resumeInputsRef.current = { - ...resumeInputsRef.current, - [data.pausePoint.contextId]: initialValue, - } + setFormErrors({}) + resumePayload = { submission } + } else { + let parsedInput: any + if (resumeInput && resumeInput.trim().length > 0) { + try { + parsedInput = JSON.parse(resumeInput) + } catch { + setError('Resume input must be valid JSON.') + setLoadingAction(false) + return } - setFormValues({}) - setFormErrors({}) } - } catch (err) { - if ((err as any)?.name !== 'AbortError') { - logger.error('Failed to load pause context detail', err) - } - } finally { - setLoadingDetail(false) + resumePayload = parsedInput } - } - loadDetail() - return () => controller.abort() - }, [ - workflowId, - executionId, - selectedContextId, - normalizeInputFormatFields, - buildInitialFormValues, - ]) - - const refreshExecutionDetail = useCallback(async () => { - setRefreshingExecution(true) - try { - const raw = await requestJson(resumeWorkflowExecutionContract, { - params: { workflowId, executionId }, - }) - // double-cast-allowed: contract pause-point shape is z.record(z.string(), z.unknown()) but the page works against the more specific local PausedExecutionDetail / PausePointWithQueue interfaces - const data = raw as unknown as PausedExecutionDetail - setExecutionDetail(data) - if (!selectedContextId) { - const first = - data.pausePoints?.find((point: PausePointWithQueue) => point.resumeStatus === 'paused') - ?.contextId ?? null - setSelectedContextId(first) - } - } catch (err) { - logger.error('Failed to refresh execution detail', err) - } finally { - setRefreshingExecution(false) - } - }, [workflowId, executionId, selectedContextId]) - - const refreshSelectedDetail = useCallback( - async (contextId: string, showLoader = true) => { try { - if (showLoader) setLoadingDetail(true) - // boundary-raw-fetch: GET /api/resume/[workflowId]/[executionId]/[contextId] has no contract (server route only models POST resume submission) - const response = await fetch(`/api/resume/${workflowId}/${executionId}/${contextId}`, { - method: 'GET', - credentials: 'include', - cache: 'no-store', + const { ok, payload } = await resumeMutation.mutateAsync({ + workflowId, + executionId, + contextId: selectedContextId, + input: resumePayload, }) - if (!response.ok) return - const data: PauseContextDetail = await response.json() - setSelectedDetail(data) - setSelectedStatus(data.pausePoint.resumeStatus) - setQueuePosition(data.pausePoint.queuePosition) - const responseData = data.pausePoint.response?.data ?? {} - const operation = responseData.operation || 'human' - const fetchedInputFields = normalizeInputFormatFields(responseData.inputFormat) - const submission = - responseData && - typeof responseData.submission === 'object' && - !Array.isArray(responseData.submission) - ? (responseData.submission as Record) - : undefined - if (operation === 'human' && fetchedInputFields.length > 0) { - const baseValues = buildInitialFormValues(fetchedInputFields, submission) - let mergedValues = baseValues - setFormValuesByContext((prev) => { - const existingValues = prev[data.pausePoint.contextId] - if (existingValues) mergedValues = { ...baseValues, ...existingValues } - return { ...prev, [data.pausePoint.contextId]: mergedValues } - }) - setFormValues(mergedValues) - setFormErrors({}) - if (resumeInputsRef.current[data.pausePoint.contextId] !== undefined) { - delete resumeInputsRef.current[data.pausePoint.contextId] + if (!ok) { + setError(payload.error || 'Failed to resume execution.') + setSelectedStatus(selectedDetail.pausePoint.resumeStatus) + return + } + const nextStatus = payload.status === 'queued' ? 'queued' : 'resuming' + const nextQueuePosition = payload.queuePosition ?? null + const fallbackContextId = + executionDetail?.pausePoints.find( + (point) => point.contextId !== selectedContextId && point.resumeStatus === 'paused' + )?.contextId ?? null + // Optimistically reflect the new status in the cache; the mutation's + // onSettled invalidation refetches both queries to reconcile with the server. + queryClient.setQueryData( + resumeKeys.execution(workflowId, executionId), + (prev) => { + if (!prev) return prev + return { + ...prev, + pausePoints: prev.pausePoints.map((point) => + point.contextId === selectedContextId + ? { ...point, resumeStatus: nextStatus, queuePosition: nextQueuePosition } + : point + ), + } } - setResumeInput('') - } else { - const initialValue = - typeof responseData === 'string' - ? responseData - : JSON.stringify(responseData ?? {}, null, 2) - if (resumeInputsRef.current[data.pausePoint.contextId] !== undefined) { - setResumeInput(resumeInputsRef.current[data.pausePoint.contextId]) - } else { - setResumeInput(initialValue) - resumeInputsRef.current = { - ...resumeInputsRef.current, - [data.pausePoint.contextId]: initialValue, + ) + queryClient.setQueryData( + resumeKeys.context(workflowId, executionId, selectedContextId), + (prev) => { + if (!prev || prev.pausePoint.contextId !== selectedContextId) return prev + return { + ...prev, + pausePoint: { + ...prev.pausePoint, + resumeStatus: nextStatus, + queuePosition: nextQueuePosition, + }, } } - setFormValues({}) - setFormErrors({}) - } - } catch (err) { - logger.error('Failed to refresh pause context detail', err) + ) + setSelectedStatus(nextStatus) + setQueuePosition(nextQueuePosition) + setSelectedContextId((prev) => (prev !== selectedContextId ? prev : fallbackContextId)) + setMessage( + payload.status === 'queued' ? 'Resume request queued.' : 'Resume started successfully.' + ) + } catch (err: any) { + setError(err.message || 'Unexpected error while resuming execution.') } finally { - if (showLoader) setLoadingDetail(false) + setLoadingAction(false) } }, - [workflowId, executionId, normalizeInputFormatFields, buildInitialFormValues] + // resumeMutation.mutateAsync is referentially stable in TanStack Query v5 + // eslint-disable-next-line react-hooks/exhaustive-deps + [ + workflowId, + executionId, + selectedContextId, + isHumanMode, + hasInputFormat, + inputFormatFields, + formValues, + parseFormValue, + resumeInput, + selectedDetail, + executionDetail, + queryClient, + ] ) - const handleResume = useCallback(async () => { - if (!selectedContextId || !selectedDetail) return - setLoadingAction(true) - setError(null) - setMessage(null) - let resumePayload: any - if (isHumanMode && hasInputFormat) { - const errors: Record = {} - const submission: Record = {} - for (const field of inputFormatFields) { - const rawValue = formValues[field.name] ?? '' - const hasValue = - field.type === 'boolean' - ? rawValue === 'true' || rawValue === 'false' - : rawValue.trim().length > 0 && rawValue !== '__unset__' - if (!hasValue || rawValue === '__unset__') { - if (field.required) errors[field.name] = 'This field is required.' - continue - } - const { value, error: parseError } = parseFormValue(field, rawValue) - if (parseError) { - errors[field.name] = parseError - continue - } - if (value !== undefined) submission[field.name] = value - } - if (Object.keys(errors).length > 0) { - setFormErrors(errors) - setLoadingAction(false) - return - } - setFormErrors({}) - resumePayload = { submission } - } else { - let parsedInput: any - if (resumeInput && resumeInput.trim().length > 0) { - try { - parsedInput = JSON.parse(resumeInput) - } catch { - setError('Resume input must be valid JSON.') - setLoadingAction(false) - return - } - } - resumePayload = parsedInput - } - try { - // boundary-raw-fetch: resume-context POST contract has no body schema (route uses tolerant raw JSON parse for resume input forwarded to PauseResumeManager) - const response = await fetch( - `/api/resume/${workflowId}/${executionId}/${selectedContextId}`, - { - method: 'POST', - credentials: 'include', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(resumePayload ? { input: resumePayload } : {}), - } - ) - const payload = await response.json() - if (!response.ok) { - setError(payload.error || 'Failed to resume execution.') - setSelectedStatus(selectedDetail.pausePoint.resumeStatus) - return - } - const nextStatus = payload.status === 'queued' ? 'queued' : 'resuming' - const nextQueuePosition = payload.queuePosition ?? null - const fallbackContextId = - executionDetail?.pausePoints.find( - (point) => point.contextId !== selectedContextId && point.resumeStatus === 'paused' - )?.contextId ?? null - setExecutionDetail((prev) => { - if (!prev) return prev - return { - ...prev, - pausePoints: prev.pausePoints.map((point) => - point.contextId === selectedContextId - ? { ...point, resumeStatus: nextStatus, queuePosition: nextQueuePosition } - : point - ), - } - }) - setSelectedDetail((prev) => { - if (!prev || prev.pausePoint.contextId !== selectedContextId) return prev - return { - ...prev, - pausePoint: { - ...prev.pausePoint, - resumeStatus: nextStatus, - queuePosition: nextQueuePosition, - }, - } - }) - setSelectedStatus(nextStatus) - setQueuePosition(nextQueuePosition) - setSelectedContextId((prev) => (prev !== selectedContextId ? prev : fallbackContextId)) - setMessage( - payload.status === 'queued' ? 'Resume request queued.' : 'Resume started successfully.' - ) - await Promise.all([refreshExecutionDetail(), refreshSelectedDetail(selectedContextId, false)]) - } catch (err: any) { - setError(err.message || 'Unexpected error while resuming execution.') - } finally { - setLoadingAction(false) - } - }, [ - workflowId, - executionId, - selectedContextId, - isHumanMode, - hasInputFormat, - inputFormatFields, - formValues, - parseFormValue, - resumeInput, - selectedDetail, - executionDetail, - refreshExecutionDetail, - refreshSelectedDetail, - ]) - const isFormComplete = useMemo(() => { if (!isHumanMode || !hasInputFormat) return true return inputFormatFields.every((field) => { @@ -902,7 +745,7 @@ export default function ResumeExecutionPage({