fix(autolayout): type issue if workflow deployed + remove dead state code (#1524)

* fix(autolayout): type issue if workflow deployed

* remove dead code hasActiveWebhook field
This commit is contained in:
Vikhyath Mondreti
2025-10-01 20:18:29 -07:00
committed by GitHub
parent 7aae108b87
commit 3509ce8ce4
19 changed files with 14 additions and 111 deletions

View File

@@ -237,7 +237,6 @@ describe('Copilot Checkpoints Revert API Route', () => {
parallels: {},
isDeployed: true,
deploymentStatuses: { production: 'deployed' },
hasActiveWebhook: false,
},
}
@@ -287,7 +286,6 @@ describe('Copilot Checkpoints Revert API Route', () => {
parallels: {},
isDeployed: true,
deploymentStatuses: { production: 'deployed' },
hasActiveWebhook: false,
lastSaved: 1640995200000,
},
},
@@ -309,7 +307,6 @@ describe('Copilot Checkpoints Revert API Route', () => {
parallels: {},
isDeployed: true,
deploymentStatuses: { production: 'deployed' },
hasActiveWebhook: false,
lastSaved: 1640995200000,
}),
}
@@ -445,7 +442,6 @@ describe('Copilot Checkpoints Revert API Route', () => {
parallels: {},
isDeployed: false,
deploymentStatuses: {},
hasActiveWebhook: false,
lastSaved: 1640995200000,
})
})
@@ -722,7 +718,6 @@ describe('Copilot Checkpoints Revert API Route', () => {
production: 'deployed',
staging: 'pending',
},
hasActiveWebhook: true,
deployedAt: '2024-01-01T10:00:00.000Z',
},
}
@@ -769,7 +764,6 @@ describe('Copilot Checkpoints Revert API Route', () => {
production: 'deployed',
staging: 'pending',
},
hasActiveWebhook: true,
deployedAt: '2024-01-01T10:00:00.000Z',
lastSaved: 1640995200000,
})

View File

@@ -73,7 +73,6 @@ export async function POST(request: NextRequest) {
parallels: checkpointState?.parallels || {},
isDeployed: checkpointState?.isDeployed || false,
deploymentStatuses: checkpointState?.deploymentStatuses || {},
hasActiveWebhook: checkpointState?.hasActiveWebhook || false,
lastSaved: Date.now(),
// Only include deployedAt if it's a valid date string that can be converted
...(checkpointState?.deployedAt &&

View File

@@ -76,7 +76,6 @@ export async function POST(
isDeployed: true,
deployedAt: new Date(),
deploymentStatuses: deployedState.deploymentStatuses || {},
hasActiveWebhook: deployedState.hasActiveWebhook || false,
})
if (!saveResult.success) {

View File

@@ -133,7 +133,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
state: {
// Default values for expected properties
deploymentStatuses: {},
hasActiveWebhook: false,
// Data from normalized tables
blocks: normalizedData.blocks,
edges: normalizedData.edges,

View File

@@ -89,13 +89,6 @@ const ParallelSchema = z.object({
parallelType: z.enum(['count', 'collection']).optional(),
})
const DeploymentStatusSchema = z.object({
id: z.string(),
status: z.enum(['deploying', 'deployed', 'failed', 'stopping', 'stopped']),
deployedAt: z.date().optional(),
error: z.string().optional(),
})
const WorkflowStateSchema = z.object({
blocks: z.record(BlockStateSchema),
edges: z.array(EdgeSchema),
@@ -103,9 +96,7 @@ const WorkflowStateSchema = z.object({
parallels: z.record(ParallelSchema).optional(),
lastSaved: z.number().optional(),
isDeployed: z.boolean().optional(),
deployedAt: z.date().optional(),
deploymentStatuses: z.record(DeploymentStatusSchema).optional(),
hasActiveWebhook: z.boolean().optional(),
deployedAt: z.coerce.date().optional(),
})
/**
@@ -204,8 +195,6 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
lastSaved: state.lastSaved || Date.now(),
isDeployed: state.isDeployed || false,
deployedAt: state.deployedAt,
deploymentStatuses: state.deploymentStatuses || {},
hasActiveWebhook: state.hasActiveWebhook || false,
}
const saveResult = await saveWorkflowToNormalizedTables(workflowId, workflowState as any)

View File

@@ -89,7 +89,6 @@ export async function GET(request: NextRequest) {
// Use normalized table data - construct state from normalized tables
workflowState = {
deploymentStatuses: {},
hasActiveWebhook: false,
blocks: normalizedData.blocks,
edges: normalizedData.edges,
loops: normalizedData.loops,

View File

@@ -86,7 +86,6 @@ export function DiffControls() {
lastSaved: rawState.lastSaved || Date.now(),
isDeployed: rawState.isDeployed || false,
deploymentStatuses: rawState.deploymentStatuses || {},
hasActiveWebhook: rawState.hasActiveWebhook || false,
// Only include deployedAt if it's a valid date, never include null/undefined
...(rawState.deployedAt && rawState.deployedAt instanceof Date
? { deployedAt: rawState.deployedAt }

View File

@@ -19,7 +19,6 @@ export interface CurrentWorkflow {
deployedAt?: Date
deploymentStatuses?: Record<string, DeploymentStatus>
needsRedeployment?: boolean
hasActiveWebhook?: boolean
// Mode information
isDiffMode: boolean
@@ -66,7 +65,6 @@ export function useCurrentWorkflow(): CurrentWorkflow {
deployedAt: activeWorkflow.deployedAt,
deploymentStatuses: activeWorkflow.deploymentStatuses,
needsRedeployment: activeWorkflow.needsRedeployment,
hasActiveWebhook: activeWorkflow.hasActiveWebhook,
// Mode information - update to reflect ready state
isDiffMode: shouldUseDiff,

View File

@@ -204,16 +204,19 @@ export async function applyAutoLayoutAndUpdateStore(
useWorkflowStore.getState().updateLastSaved()
// Clean up the workflow state for API validation
// Destructure out UI-only fields that shouldn't be persisted
const { deploymentStatuses, needsRedeployment, dragStartPosition, ...stateToSave } =
newWorkflowState
const cleanedWorkflowState = {
...newWorkflowState,
...stateToSave,
// Convert null dates to undefined (since they're optional)
deployedAt: newWorkflowState.deployedAt ? new Date(newWorkflowState.deployedAt) : undefined,
deployedAt: stateToSave.deployedAt ? new Date(stateToSave.deployedAt) : undefined,
// Ensure other optional fields are properly handled
loops: newWorkflowState.loops || {},
parallels: newWorkflowState.parallels || {},
deploymentStatuses: newWorkflowState.deploymentStatuses || {},
loops: stateToSave.loops || {},
parallels: stateToSave.parallels || {},
// Sanitize edges: remove null/empty handle fields to satisfy schema (optional strings)
edges: (newWorkflowState.edges || []).map((edge: any) => {
edges: (stateToSave.edges || []).map((edge: any) => {
const { sourceHandle, targetHandle, ...rest } = edge || {}
const sanitized: any = { ...rest }
if (typeof sourceHandle === 'string' && sourceHandle.length > 0) {

View File

@@ -382,7 +382,6 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
isDeployed: workflowState.isDeployed ?? false,
deployedAt: workflowState.deployedAt,
deploymentStatuses: workflowState.deploymentStatuses || {},
hasActiveWebhook: workflowState.hasActiveWebhook ?? false,
})
// Replace subblock store values for this workflow

View File

@@ -479,7 +479,6 @@ export function useCollaborativeWorkflow() {
isDeployed: workflowData.state.isDeployed || false,
deployedAt: workflowData.state.deployedAt,
lastSaved: workflowData.state.lastSaved || Date.now(),
hasActiveWebhook: workflowData.state.hasActiveWebhook || false,
deploymentStatuses: workflowData.state.deploymentStatuses || {},
})

View File

@@ -214,7 +214,6 @@ const mockWorkflowState: WorkflowState = {
lastSaved: Date.now(),
isDeployed: false,
deploymentStatuses: {},
hasActiveWebhook: false,
}
describe('Database Helpers', () => {
@@ -452,11 +451,6 @@ describe('Database Helpers', () => {
)
expect(result.success).toBe(true)
expect(result.jsonBlob).toBeDefined()
expect(result.jsonBlob.blocks).toEqual(mockWorkflowState.blocks)
expect(result.jsonBlob.edges).toEqual(mockWorkflowState.edges)
expect(result.jsonBlob.loops).toEqual(mockWorkflowState.loops)
expect(result.jsonBlob.parallels).toEqual(mockWorkflowState.parallels)
// Verify transaction was called
expect(mockTransaction).toHaveBeenCalledTimes(1)
@@ -471,7 +465,6 @@ describe('Database Helpers', () => {
lastSaved: Date.now(),
isDeployed: false,
deploymentStatuses: {},
hasActiveWebhook: false,
}
const mockTransaction = vi.fn().mockImplementation(async (callback) => {
@@ -494,10 +487,6 @@ describe('Database Helpers', () => {
)
expect(result.success).toBe(true)
expect(result.jsonBlob.blocks).toEqual({})
expect(result.jsonBlob.edges).toEqual([])
expect(result.jsonBlob.loops).toEqual({})
expect(result.jsonBlob.parallels).toEqual({})
})
it('should return error when transaction fails', async () => {
@@ -650,7 +639,6 @@ describe('Database Helpers', () => {
lastSaved: Date.now(),
isDeployed: false,
deploymentStatuses: {},
hasActiveWebhook: false,
}
it('should successfully migrate workflow from JSON to normalized tables', async () => {
@@ -737,7 +725,6 @@ describe('Database Helpers', () => {
lastSaved: Date.now(),
isDeployed: false,
deploymentStatuses: {},
hasActiveWebhook: false,
}
// Create 1000 blocks
@@ -782,8 +769,6 @@ describe('Database Helpers', () => {
)
expect(result.success).toBe(true)
expect(Object.keys(result.jsonBlob.blocks)).toHaveLength(1000)
expect(result.jsonBlob.edges).toHaveLength(999)
})
})
@@ -1020,7 +1005,6 @@ describe('Database Helpers', () => {
loops: {},
parallels: {},
deploymentStatuses: {},
hasActiveWebhook: false,
}
// Mock the transaction for save operation
@@ -1058,10 +1042,6 @@ describe('Database Helpers', () => {
)
expect(saveResult.success).toBe(true)
// Step 6: Verify the JSON blob also preserves advancedMode
expect(saveResult.jsonBlob?.blocks['agent-original'].advancedMode).toBe(true)
expect(saveResult.jsonBlob?.blocks['agent-duplicate'].advancedMode).toBe(true)
// Verify the database insert was called with the correct values
expect(mockTransaction).toHaveBeenCalled()
})
@@ -1161,7 +1141,6 @@ describe('Database Helpers', () => {
loops: {},
parallels: {},
deploymentStatuses: {},
hasActiveWebhook: false,
}
// Mock successful save

View File

@@ -216,12 +216,11 @@ export async function loadWorkflowFromNormalizedTables(
/**
* Save workflow state to normalized tables
* Also returns the JSON blob for backward compatibility
*/
export async function saveWorkflowToNormalizedTables(
workflowId: string,
state: WorkflowState
): Promise<{ success: boolean; jsonBlob?: any; error?: string }> {
): Promise<{ success: boolean; error?: string }> {
try {
// Start a transaction
await db.transaction(async (tx) => {
@@ -297,27 +296,9 @@ export async function saveWorkflowToNormalizedTables(
if (subflowInserts.length > 0) {
await tx.insert(workflowSubflows).values(subflowInserts)
}
return { success: true }
})
// Create JSON blob for backward compatibility
const jsonBlob = {
blocks: state.blocks,
edges: state.edges,
loops: state.loops || {},
parallels: state.parallels || {},
lastSaved: Date.now(),
isDeployed: state.isDeployed,
deployedAt: state.deployedAt,
deploymentStatuses: state.deploymentStatuses,
hasActiveWebhook: state.hasActiveWebhook,
}
return {
success: true,
jsonBlob,
}
return { success: true }
} catch (error) {
logger.error(`Error saving workflow ${workflowId} to normalized tables:`, error)
return {
@@ -354,6 +335,7 @@ export async function migrateWorkflowToNormalizedTables(
): Promise<{ success: boolean; error?: string }> {
try {
// Convert JSON state to WorkflowState format
// Only include fields that are actually persisted to normalized tables
const workflowState: WorkflowState = {
blocks: jsonState.blocks || {},
edges: jsonState.edges || [],
@@ -362,16 +344,9 @@ export async function migrateWorkflowToNormalizedTables(
lastSaved: jsonState.lastSaved,
isDeployed: jsonState.isDeployed,
deployedAt: jsonState.deployedAt,
deploymentStatuses: jsonState.deploymentStatuses || {},
hasActiveWebhook: jsonState.hasActiveWebhook,
}
const result = await saveWorkflowToNormalizedTables(workflowId, workflowState)
if (result.success) {
return { success: true }
}
return { success: false, error: result.error }
return await saveWorkflowToNormalizedTables(workflowId, workflowState)
} catch (error) {
logger.error(`Error migrating workflow ${workflowId} to normalized tables:`, error)
return {

View File

@@ -68,7 +68,6 @@ export function useWorkflowDiff(): UseWorkflowDiffReturn {
isDeployed: currentState.isDeployed,
deployedAt: currentState.deployedAt,
deploymentStatuses: { ...currentState.deploymentStatuses },
hasActiveWebhook: currentState.hasActiveWebhook,
},
subblockValues: JSON.parse(JSON.stringify(currentSubblockValues)), // Deep copy
timestamp: Date.now(),
@@ -107,7 +106,6 @@ export function useWorkflowDiff(): UseWorkflowDiffReturn {
isDeployed: backup.workflowState.isDeployed,
deployedAt: backup.workflowState.deployedAt,
deploymentStatuses: backup.workflowState.deploymentStatuses,
hasActiveWebhook: backup.workflowState.hasActiveWebhook,
})
// Restore subblock values

View File

@@ -365,6 +365,5 @@ export function mergeWithUIState(
isDeployed: fullState.isDeployed,
deployedAt: fullState.deployedAt,
deploymentStatuses: fullState.deploymentStatuses,
hasActiveWebhook: fullState.hasActiveWebhook,
}
}

View File

@@ -133,7 +133,6 @@ export async function getWorkflowState(workflowId: string) {
const finalState = {
// Default values for expected properties
deploymentStatuses: {},
hasActiveWebhook: false,
// Data from normalized tables
blocks: normalizedData.blocks,
edges: normalizedData.edges,

View File

@@ -1953,7 +1953,6 @@ export const useCopilotStore = create<CopilotStore>()(
isDeployed: !!reverted.isDeployed,
...(reverted.deployedAt ? { deployedAt: new Date(reverted.deployedAt) } : {}),
deploymentStatuses: reverted.deploymentStatuses || {},
hasActiveWebhook: !!reverted.hasActiveWebhook,
})
// Extract and apply subblock values

View File

@@ -38,7 +38,6 @@ const initialState = {
// New field for per-workflow deployment tracking
deploymentStatuses: {},
needsRedeployment: false,
hasActiveWebhook: false,
history: {
past: [],
present: {
@@ -475,7 +474,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
lastSaved: Date.now(),
isDeployed: false,
isPublished: false,
hasActiveWebhook: false,
}
set(newState)
// Note: Socket.IO handles real-time sync automatically
@@ -500,7 +498,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
deployedAt: state.deployedAt,
deploymentStatuses: state.deploymentStatuses,
needsRedeployment: state.needsRedeployment,
hasActiveWebhook: state.hasActiveWebhook,
}
},
@@ -902,15 +899,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
}))
},
setWebhookStatus: (hasActiveWebhook: boolean) => {
// Only update if the status has changed to avoid unnecessary rerenders
if (get().hasActiveWebhook !== hasActiveWebhook) {
set({ hasActiveWebhook })
get().updateLastSaved()
// Note: Socket.IO handles real-time sync automatically
}
},
revertToDeployedState: async (deployedState: WorkflowState) => {
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
@@ -931,7 +919,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
parallels: deployedState.parallels || {},
isDeployed: true,
needsRedeployment: false,
hasActiveWebhook: false, // Reset webhook status
// Keep existing deployment statuses and update for the active workflow if needed
deploymentStatuses: {
...get().deploymentStatuses,
@@ -966,14 +953,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
},
})
// Check if there's an active webhook in the deployed state
const starterBlock = Object.values(deployedState.blocks).find(
(block) => block.type === 'starter'
)
if (starterBlock && starterBlock.subBlocks?.startWorkflow?.value === 'webhook') {
set({ hasActiveWebhook: true })
}
pushHistory(set, get, newState, 'Reverted to deployed state')
get().updateLastSaved()

View File

@@ -154,7 +154,6 @@ export interface WorkflowState {
// New field for per-workflow deployment status
deploymentStatuses?: Record<string, DeploymentStatus>
needsRedeployment?: boolean
hasActiveWebhook?: boolean
// Drag state for undo/redo
dragStartPosition?: DragStartPosition | null
}
@@ -214,7 +213,6 @@ export interface WorkflowActions {
generateLoopBlocks: () => Record<string, Loop>
generateParallelBlocks: () => Record<string, Parallel>
setNeedsRedeploymentFlag: (needsRedeployment: boolean) => void
setWebhookStatus: (hasActiveWebhook: boolean) => void
revertToDeployedState: (deployedState: WorkflowState) => void
toggleBlockAdvancedMode: (id: string) => void
toggleBlockTriggerMode: (id: string) => void