fix: resolve workflow form step auto-open race condition (#21053)
## Summary - Fix intermittent failure where the Quick Lead workflow form step did not auto-open - Root cause: race conditions between SSE events, Apollo cache writes, and the `runWorkflowVersion` mutation timing - Add generic monotonicity guard in the SSE handler that drops stale updates for all records (not just WorkflowRun) ## Changes - **`useTriggerOptimisticEffectFromSseUpdateEvents.ts`**: Compare incoming `updatedAt` with cached record before writing — skip if stale. Moved `upsertRecordsInStore` after the guard so neither Apollo cache nor Jotai store receive stale data. - **`useRunWorkflowVersion.tsx`**: Await mutation before opening side panel; register SSE listener eagerly before mutation - **`useWorkflowRun.ts`**: Simplified back to plain `useFindOneRecord` + schema parse (no extra state needed) - **`generateWorkflowRunDiagram.ts`**: `shouldOpenStep` matches both PENDING and RUNNING for form steps (backend RUNNING means "waiting for user input") - **`WorkflowRunVisualizerEffect.tsx`**: Pass `runStatus` directly without status mapping - **`WorkflowRunStepNodeDetail.tsx`**: Form is interactive when step is PENDING or RUNNING - **Deleted `latestWorkflowRunFamilyState.ts`**: No longer needed — the generic SSE guard replaces it ## Test plan - [x] Hard refresh, run Quick Lead workflow 10+ times — form should always auto-open - [x] Complete the form and verify all subsequent steps execute without getting stuck - [x] Verify the workflow diagram is always visible (never disappears) - [x] Verify other record types still update correctly via SSE (e.g. edit a person in another tab)
This commit is contained in:
@@ -15,7 +15,9 @@ import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useU
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { computeOptimisticCreateRecordBaseRecordInput } from '@/object-record/utils/computeOptimisticCreateRecordBaseRecordInput';
|
||||
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
|
||||
import { useChangeQueryListenState } from '@/sse-db-event/hooks/useChangeQueryListenState';
|
||||
import { RUN_WORKFLOW_VERSION } from '@/workflow/graphql/mutations/runWorkflowVersion';
|
||||
import { getWorkflowRunSseQueryId } from '@/workflow/utils/getWorkflowRunSseQueryId';
|
||||
import { type WorkflowRun } from '@/workflow/types/Workflow';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useCallback } from 'react';
|
||||
@@ -63,6 +65,7 @@ export const useRunWorkflowVersion = () => {
|
||||
});
|
||||
|
||||
const { openRecordInSidePanel } = useOpenRecordInSidePanel();
|
||||
const { changeQueryIdListenState } = useChangeQueryListenState();
|
||||
|
||||
const setRecordInStore = useCallback(
|
||||
(workflowRun: WorkflowRun) => {
|
||||
@@ -137,9 +140,24 @@ export const useRunWorkflowVersion = () => {
|
||||
|
||||
setRecordInStore(recordCreatedInCache);
|
||||
|
||||
await mutate({
|
||||
variables: { input: { workflowVersionId, workflowRunId, payload } },
|
||||
});
|
||||
const sseQueryId = getWorkflowRunSseQueryId(workflowRunId);
|
||||
const sseOperationSignature = {
|
||||
objectNameSingular: CoreObjectNameSingular.WorkflowRun,
|
||||
variables: {
|
||||
filter: { id: { eq: workflowRunId } },
|
||||
},
|
||||
};
|
||||
|
||||
changeQueryIdListenState(true, sseQueryId, sseOperationSignature);
|
||||
|
||||
try {
|
||||
await mutate({
|
||||
variables: { input: { workflowVersionId, workflowRunId, payload } },
|
||||
});
|
||||
} catch (error) {
|
||||
changeQueryIdListenState(false, sseQueryId, sseOperationSignature);
|
||||
throw error;
|
||||
}
|
||||
|
||||
openRecordInSidePanel({
|
||||
objectNameSingular: CoreObjectNameSingular.WorkflowRun,
|
||||
|
||||
Reference in New Issue
Block a user