mirror of
https://github.com/simstudioai/sim.git
synced 2026-08-31 01:11:53 +08:00
improvement(stores): remove deployment state from Zustand in favor of React Query (#3923)
This commit is contained in:
@@ -304,7 +304,6 @@ describe('Copilot Checkpoints Revert API Route', () => {
|
||||
loops: {},
|
||||
parallels: {},
|
||||
isDeployed: true,
|
||||
deploymentStatuses: { production: 'deployed' },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -349,7 +348,6 @@ describe('Copilot Checkpoints Revert API Route', () => {
|
||||
loops: {},
|
||||
parallels: {},
|
||||
isDeployed: true,
|
||||
deploymentStatuses: { production: 'deployed' },
|
||||
lastSaved: 1640995200000,
|
||||
},
|
||||
},
|
||||
@@ -370,7 +368,6 @@ describe('Copilot Checkpoints Revert API Route', () => {
|
||||
loops: {},
|
||||
parallels: {},
|
||||
isDeployed: true,
|
||||
deploymentStatuses: { production: 'deployed' },
|
||||
lastSaved: 1640995200000,
|
||||
}),
|
||||
}
|
||||
@@ -473,7 +470,6 @@ describe('Copilot Checkpoints Revert API Route', () => {
|
||||
edges: undefined,
|
||||
loops: null,
|
||||
parallels: undefined,
|
||||
deploymentStatuses: null,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -508,7 +504,6 @@ describe('Copilot Checkpoints Revert API Route', () => {
|
||||
loops: {},
|
||||
parallels: {},
|
||||
isDeployed: false,
|
||||
deploymentStatuses: {},
|
||||
lastSaved: 1640995200000,
|
||||
})
|
||||
})
|
||||
@@ -768,10 +763,6 @@ describe('Copilot Checkpoints Revert API Route', () => {
|
||||
parallel1: { branches: ['branch1', 'branch2'] },
|
||||
},
|
||||
isDeployed: true,
|
||||
deploymentStatuses: {
|
||||
production: 'deployed',
|
||||
staging: 'pending',
|
||||
},
|
||||
deployedAt: '2024-01-01T10:00:00.000Z',
|
||||
},
|
||||
}
|
||||
@@ -816,10 +807,6 @@ describe('Copilot Checkpoints Revert API Route', () => {
|
||||
parallel1: { branches: ['branch1', 'branch2'] },
|
||||
},
|
||||
isDeployed: true,
|
||||
deploymentStatuses: {
|
||||
production: 'deployed',
|
||||
staging: 'pending',
|
||||
},
|
||||
deployedAt: '2024-01-01T10:00:00.000Z',
|
||||
lastSaved: 1640995200000,
|
||||
})
|
||||
|
||||
@@ -82,7 +82,6 @@ export async function POST(request: NextRequest) {
|
||||
loops: checkpointState?.loops || {},
|
||||
parallels: checkpointState?.parallels || {},
|
||||
isDeployed: checkpointState?.isDeployed || false,
|
||||
deploymentStatuses: checkpointState?.deploymentStatuses || {},
|
||||
lastSaved: Date.now(),
|
||||
...(checkpointState?.deployedAt &&
|
||||
checkpointState.deployedAt !== null &&
|
||||
|
||||
@@ -79,7 +79,6 @@ export async function POST(
|
||||
loops: deployedState.loops || {},
|
||||
parallels: deployedState.parallels || {},
|
||||
lastSaved: Date.now(),
|
||||
deploymentStatuses: deployedState.deploymentStatuses || {},
|
||||
})
|
||||
|
||||
if (!saveResult.success) {
|
||||
|
||||
@@ -89,7 +89,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||
const finalWorkflowData = {
|
||||
...workflowData,
|
||||
state: {
|
||||
deploymentStatuses: {},
|
||||
blocks: normalizedData.blocks,
|
||||
edges: normalizedData.edges,
|
||||
loops: normalizedData.loops,
|
||||
@@ -115,7 +114,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||
const emptyWorkflowData = {
|
||||
...workflowData,
|
||||
state: {
|
||||
deploymentStatuses: {},
|
||||
blocks: {},
|
||||
edges: [],
|
||||
loops: {},
|
||||
|
||||
@@ -108,8 +108,6 @@ function normalizeWorkflowState(input?: any): WorkflowState | null {
|
||||
lastUpdate: input.lastUpdate,
|
||||
metadata: input.metadata,
|
||||
variables: input.variables,
|
||||
deploymentStatuses: input.deploymentStatuses,
|
||||
needsRedeployment: input.needsRedeployment,
|
||||
dragStartPosition: input.dragStartPosition ?? null,
|
||||
}
|
||||
|
||||
|
||||
@@ -1407,17 +1407,6 @@ export function useChat(
|
||||
const output = tc.result?.output as Record<string, unknown> | undefined
|
||||
const deployedWorkflowId = (output?.workflowId as string) ?? undefined
|
||||
if (deployedWorkflowId && typeof output?.isDeployed === 'boolean') {
|
||||
const isDeployed = output.isDeployed as boolean
|
||||
const serverDeployedAt = output.deployedAt
|
||||
? new Date(output.deployedAt as string)
|
||||
: undefined
|
||||
useWorkflowRegistry
|
||||
.getState()
|
||||
.setDeploymentStatus(
|
||||
deployedWorkflowId,
|
||||
isDeployed,
|
||||
isDeployed ? (serverDeployedAt ?? new Date()) : undefined
|
||||
)
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: deploymentKeys.info(deployedWorkflowId),
|
||||
})
|
||||
|
||||
@@ -111,8 +111,6 @@ function normalizeWorkflowState(input?: any): WorkflowState | null {
|
||||
lastUpdate: input.lastUpdate,
|
||||
metadata: input.metadata,
|
||||
variables: input.variables,
|
||||
deploymentStatuses: input.deploymentStatuses,
|
||||
needsRedeployment: input.needsRedeployment,
|
||||
dragStartPosition: input.dragStartPosition ?? null,
|
||||
}
|
||||
|
||||
|
||||
+1
-7
@@ -40,7 +40,6 @@ import { useWorkflowMap } from '@/hooks/queries/workflows'
|
||||
import { useWorkspaceSettings } from '@/hooks/queries/workspace'
|
||||
import { usePermissionConfig } from '@/hooks/use-permission-config'
|
||||
import { useSettingsNavigation } from '@/hooks/use-settings-navigation'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { mergeSubblockState } from '@/stores/workflows/utils'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import type { WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
@@ -90,10 +89,7 @@ export function DeployModal({
|
||||
const params = useParams()
|
||||
const workspaceId = params?.workspaceId as string
|
||||
const { navigateToSettings } = useSettingsNavigation()
|
||||
const deploymentStatus = useWorkflowRegistry((state) =>
|
||||
state.getWorkflowDeploymentStatus(workflowId)
|
||||
)
|
||||
const isDeployed = deploymentStatus?.isDeployed ?? isDeployedProp
|
||||
const isDeployed = isDeployedProp
|
||||
const { data: workflowMap = {} } = useWorkflowMap(workspaceId)
|
||||
const workflowMetadata = workflowId ? workflowMap[workflowId] : undefined
|
||||
const workflowWorkspaceId = workflowMetadata?.workspaceId ?? null
|
||||
@@ -381,8 +377,6 @@ export function DeployModal({
|
||||
|
||||
invalidateDeploymentQueries(queryClient, workflowId)
|
||||
|
||||
useWorkflowRegistry.getState().setWorkflowNeedsRedeployment(workflowId, false)
|
||||
|
||||
if (chatSuccessTimeoutRef.current) {
|
||||
clearTimeout(chatSuccessTimeoutRef.current)
|
||||
}
|
||||
|
||||
+5
-5
@@ -9,7 +9,7 @@ import {
|
||||
useDeployment,
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/hooks'
|
||||
import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow'
|
||||
import { useDeployedWorkflowState } from '@/hooks/queries/deployments'
|
||||
import { useDeployedWorkflowState, useDeploymentInfo } from '@/hooks/queries/deployments'
|
||||
import type { WorkspaceUserPermissions } from '@/hooks/use-user-permissions'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
|
||||
@@ -25,10 +25,10 @@ export function Deploy({ activeWorkflowId, userPermissions, className }: DeployP
|
||||
const isRegistryLoading = hydrationPhase === 'idle' || hydrationPhase === 'state-loading'
|
||||
const { hasBlocks } = useCurrentWorkflow()
|
||||
|
||||
const deploymentStatus = useWorkflowRegistry((state) =>
|
||||
state.getWorkflowDeploymentStatus(activeWorkflowId)
|
||||
)
|
||||
const isDeployed = deploymentStatus?.isDeployed || false
|
||||
const { data: deploymentInfo } = useDeploymentInfo(activeWorkflowId, {
|
||||
enabled: !isRegistryLoading,
|
||||
})
|
||||
const isDeployed = deploymentInfo?.isDeployed ?? false
|
||||
|
||||
const isDeployedStateEnabled = Boolean(activeWorkflowId) && isDeployed && !isRegistryLoading
|
||||
const {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useMemo } from 'react'
|
||||
import type { Edge } from 'reactflow'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
|
||||
import type { DeploymentStatus } from '@/stores/workflows/registry/types'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import type { BlockState, Loop, Parallel, WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
|
||||
@@ -16,8 +15,6 @@ export interface CurrentWorkflow {
|
||||
loops: Record<string, Loop>
|
||||
parallels: Record<string, Parallel>
|
||||
lastSaved?: number
|
||||
deploymentStatuses?: Record<string, DeploymentStatus>
|
||||
needsRedeployment?: boolean
|
||||
|
||||
// Mode information
|
||||
isDiffMode: boolean
|
||||
@@ -48,8 +45,6 @@ export function useCurrentWorkflow(): CurrentWorkflow {
|
||||
loops: state.loops,
|
||||
parallels: state.parallels,
|
||||
lastSaved: state.lastSaved,
|
||||
deploymentStatuses: state.deploymentStatuses,
|
||||
needsRedeployment: state.needsRedeployment,
|
||||
}))
|
||||
)
|
||||
|
||||
@@ -78,8 +73,6 @@ export function useCurrentWorkflow(): CurrentWorkflow {
|
||||
loops: activeWorkflow.loops || {},
|
||||
parallels: activeWorkflow.parallels || {},
|
||||
lastSaved: activeWorkflow.lastSaved,
|
||||
deploymentStatuses: activeWorkflow.deploymentStatuses,
|
||||
needsRedeployment: activeWorkflow.needsRedeployment,
|
||||
|
||||
// Mode information - update to reflect ready state
|
||||
isDiffMode: hasActiveDiff && isShowingDiff,
|
||||
|
||||
@@ -124,8 +124,7 @@ export async function applyAutoLayoutAndUpdateStore(
|
||||
try {
|
||||
useWorkflowStore.getState().updateLastSaved()
|
||||
|
||||
const { deploymentStatuses, needsRedeployment, dragStartPosition, ...stateToSave } =
|
||||
newWorkflowState
|
||||
const { dragStartPosition, ...stateToSave } = newWorkflowState
|
||||
|
||||
const cleanedWorkflowState = {
|
||||
...stateToSave,
|
||||
|
||||
@@ -424,7 +424,6 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
|
||||
loops: workflowState.loops || {},
|
||||
parallels: workflowState.parallels || {},
|
||||
lastSaved: workflowState.lastSaved || Date.now(),
|
||||
deploymentStatuses: workflowState.deploymentStatuses || {},
|
||||
})
|
||||
|
||||
useSubBlockStore.setState((state: any) => ({
|
||||
|
||||
@@ -5,7 +5,6 @@ import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tansta
|
||||
import type { WorkflowDeploymentVersionResponse } from '@/lib/workflows/persistence/utils'
|
||||
import { fetchDeploymentVersionState } from '@/hooks/queries/utils/fetch-deployment-version-state'
|
||||
import { workflowKeys } from '@/hooks/queries/utils/workflow-keys'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import type { WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
|
||||
const logger = createLogger('DeploymentQueries')
|
||||
@@ -321,7 +320,6 @@ interface DeployWorkflowResult {
|
||||
*/
|
||||
export function useDeployWorkflow() {
|
||||
const queryClient = useQueryClient()
|
||||
const setDeploymentStatus = useWorkflowRegistry((state) => state.setDeploymentStatus)
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
@@ -351,18 +349,12 @@ export function useDeployWorkflow() {
|
||||
warnings: data.warnings,
|
||||
}
|
||||
},
|
||||
onSuccess: (data, variables) => {
|
||||
logger.info('Workflow deployed successfully', { workflowId: variables.workflowId })
|
||||
|
||||
setDeploymentStatus(
|
||||
variables.workflowId,
|
||||
data.isDeployed,
|
||||
data.deployedAt ? new Date(data.deployedAt) : undefined,
|
||||
data.apiKey
|
||||
)
|
||||
|
||||
useWorkflowRegistry.getState().setWorkflowNeedsRedeployment(variables.workflowId, false)
|
||||
|
||||
onSettled: (_data, error, variables) => {
|
||||
if (error) {
|
||||
logger.error('Failed to deploy workflow', { error })
|
||||
} else {
|
||||
logger.info('Workflow deployed successfully', { workflowId: variables.workflowId })
|
||||
}
|
||||
return Promise.all([
|
||||
invalidateDeploymentQueries(queryClient, variables.workflowId),
|
||||
queryClient.invalidateQueries({
|
||||
@@ -370,9 +362,6 @@ export function useDeployWorkflow() {
|
||||
}),
|
||||
])
|
||||
},
|
||||
onError: (error) => {
|
||||
logger.error('Failed to deploy workflow', { error })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -389,7 +378,6 @@ interface UndeployWorkflowVariables {
|
||||
*/
|
||||
export function useUndeployWorkflow() {
|
||||
const queryClient = useQueryClient()
|
||||
const setDeploymentStatus = useWorkflowRegistry((state) => state.setDeploymentStatus)
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ workflowId }: UndeployWorkflowVariables): Promise<void> => {
|
||||
@@ -402,11 +390,12 @@ export function useUndeployWorkflow() {
|
||||
throw new Error(errorData.error || 'Failed to undeploy workflow')
|
||||
}
|
||||
},
|
||||
onSuccess: (_, variables) => {
|
||||
logger.info('Workflow undeployed successfully', { workflowId: variables.workflowId })
|
||||
|
||||
setDeploymentStatus(variables.workflowId, false)
|
||||
|
||||
onSettled: (_data, error, variables) => {
|
||||
if (error) {
|
||||
logger.error('Failed to undeploy workflow', { error })
|
||||
} else {
|
||||
logger.info('Workflow undeployed successfully', { workflowId: variables.workflowId })
|
||||
}
|
||||
return Promise.all([
|
||||
invalidateDeploymentQueries(queryClient, variables.workflowId),
|
||||
queryClient.invalidateQueries({
|
||||
@@ -414,9 +403,6 @@ export function useUndeployWorkflow() {
|
||||
}),
|
||||
])
|
||||
},
|
||||
onError: (error) => {
|
||||
logger.error('Failed to undeploy workflow', { error })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -613,7 +599,6 @@ interface ActivateVersionResult {
|
||||
*/
|
||||
export function useActivateDeploymentVersion() {
|
||||
const queryClient = useQueryClient()
|
||||
const setDeploymentStatus = useWorkflowRegistry((state) => state.setDeploymentStatus)
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
@@ -663,20 +648,13 @@ export function useActivateDeploymentVersion() {
|
||||
)
|
||||
}
|
||||
},
|
||||
onSuccess: (data, variables) => {
|
||||
logger.info('Deployment version activated', {
|
||||
workflowId: variables.workflowId,
|
||||
version: variables.version,
|
||||
})
|
||||
|
||||
setDeploymentStatus(
|
||||
variables.workflowId,
|
||||
true,
|
||||
data.deployedAt ? new Date(data.deployedAt) : undefined,
|
||||
data.apiKey
|
||||
)
|
||||
},
|
||||
onSettled: (_, __, variables) => {
|
||||
onSettled: (_data, error, variables) => {
|
||||
if (!error) {
|
||||
logger.info('Deployment version activated', {
|
||||
workflowId: variables.workflowId,
|
||||
version: variables.version,
|
||||
})
|
||||
}
|
||||
return invalidateDeploymentQueries(queryClient, variables.workflowId)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { getQueryDataMock } = vi.hoisted(() => ({
|
||||
getQueryDataMock: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/_shell/providers/get-query-client', () => ({
|
||||
getQueryClient: vi.fn(() => ({
|
||||
getQueryData: getQueryDataMock,
|
||||
})),
|
||||
}))
|
||||
|
||||
import { getCustomTool, getCustomTools } from '@/hooks/queries/utils/custom-tool-cache'
|
||||
|
||||
describe('custom tool cache helpers', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('reads workspace-scoped custom tools from the cache', () => {
|
||||
const tools = [{ id: 'tool-1', title: 'Weather', schema: {}, code: '', workspaceId: 'ws-1' }]
|
||||
getQueryDataMock.mockReturnValue(tools)
|
||||
|
||||
expect(getCustomTools('ws-1')).toBe(tools)
|
||||
expect(getQueryDataMock).toHaveBeenCalledWith(['customTools', 'list', 'ws-1'])
|
||||
})
|
||||
|
||||
it('resolves custom tools by id or title', () => {
|
||||
getQueryDataMock.mockReturnValue([
|
||||
{ id: 'tool-1', title: 'Weather', schema: {}, code: '', workspaceId: 'ws-1' },
|
||||
])
|
||||
|
||||
expect(getCustomTool('tool-1', 'ws-1')?.title).toBe('Weather')
|
||||
expect(getCustomTool('Weather', 'ws-1')?.id).toBe('tool-1')
|
||||
})
|
||||
})
|
||||
@@ -1,23 +0,0 @@
|
||||
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
|
||||
import type { CustomToolDefinition } from '@/hooks/queries/custom-tools'
|
||||
import { customToolsKeys } from '@/hooks/queries/utils/custom-tool-keys'
|
||||
|
||||
/**
|
||||
* Reads custom tools for a workspace directly from the React Query cache.
|
||||
*/
|
||||
export function getCustomTools(workspaceId: string): CustomToolDefinition[] {
|
||||
return (
|
||||
getQueryClient().getQueryData<CustomToolDefinition[]>(customToolsKeys.list(workspaceId)) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a custom tool from the cache by id or title.
|
||||
*/
|
||||
export function getCustomTool(
|
||||
identifier: string,
|
||||
workspaceId: string
|
||||
): CustomToolDefinition | undefined {
|
||||
const tools = getCustomTools(workspaceId)
|
||||
return tools.find((tool) => tool.id === identifier || tool.title === identifier)
|
||||
}
|
||||
@@ -562,7 +562,6 @@ export function useCollaborativeWorkflow() {
|
||||
loops: workflowData.state.loops || {},
|
||||
parallels: workflowData.state.parallels || {},
|
||||
lastSaved: workflowData.state.lastSaved || Date.now(),
|
||||
deploymentStatuses: workflowData.state.deploymentStatuses || {},
|
||||
})
|
||||
|
||||
// Update subblock store with reverted values
|
||||
|
||||
@@ -40,7 +40,11 @@ import {
|
||||
XCircle,
|
||||
Zap,
|
||||
} from 'lucide-react'
|
||||
import { getCustomTool } from '@/hooks/queries/utils/custom-tool-cache'
|
||||
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
|
||||
import type { CustomToolDefinition } from '@/hooks/queries/custom-tools'
|
||||
import type { WorkflowDeploymentInfo } from '@/hooks/queries/deployments'
|
||||
import { deploymentKeys } from '@/hooks/queries/deployments'
|
||||
import { customToolsKeys } from '@/hooks/queries/utils/custom-tool-keys'
|
||||
import { getWorkflowById } from '@/hooks/queries/utils/workflow-cache'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
@@ -445,7 +449,8 @@ const META_deploy_api: ToolMetadata = {
|
||||
|
||||
const workflowId = params?.workflowId || useWorkflowRegistry.getState().activeWorkflowId
|
||||
const isAlreadyDeployed = workflowId
|
||||
? useWorkflowRegistry.getState().getWorkflowDeploymentStatus(workflowId)?.isDeployed
|
||||
? (getQueryClient().getQueryData<WorkflowDeploymentInfo>(deploymentKeys.info(workflowId))
|
||||
?.isDeployed ?? false)
|
||||
: false
|
||||
|
||||
let actionText = action
|
||||
@@ -1053,7 +1058,11 @@ const META_manage_custom_tool: ToolMetadata = {
|
||||
let toolName = params?.schema?.function?.name
|
||||
if (!toolName && params?.toolId && workspaceId) {
|
||||
try {
|
||||
const tool = getCustomTool(params.toolId, workspaceId)
|
||||
const tools =
|
||||
getQueryClient().getQueryData<CustomToolDefinition[]>(
|
||||
customToolsKeys.list(workspaceId)
|
||||
) ?? []
|
||||
const tool = tools.find((t) => t.id === params.toolId || t.title === params.toolId)
|
||||
toolName = tool?.schema?.function?.name
|
||||
} catch {
|
||||
// Ignore errors accessing cache
|
||||
|
||||
@@ -2,10 +2,9 @@ import type { Edge } from 'reactflow'
|
||||
import type { AsyncExecutionCorrelation } from '@/lib/core/async-jobs/types'
|
||||
import type { ParentIteration, SerializableExecutionState } from '@/executor/execution/types'
|
||||
import type { BlockLog, NormalizedBlockOutput } from '@/executor/types'
|
||||
import type { DeploymentStatus } from '@/stores/workflows/registry/types'
|
||||
import type { Loop, Parallel, WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
|
||||
export type { WorkflowState, Loop, Parallel, DeploymentStatus }
|
||||
export type { WorkflowState, Loop, Parallel }
|
||||
export type WorkflowEdge = Edge
|
||||
export type { NormalizedBlockOutput, BlockLog }
|
||||
|
||||
|
||||
@@ -123,8 +123,6 @@ export function buildDefaultWorkflowArtifacts(): DefaultWorkflowArtifacts {
|
||||
loops: {},
|
||||
parallels: {},
|
||||
lastSaved: Date.now(),
|
||||
deploymentStatuses: {},
|
||||
needsRedeployment: false,
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -985,7 +985,6 @@ describe('Database Helpers', () => {
|
||||
edges: loadedState!.edges,
|
||||
loops: {},
|
||||
parallels: {},
|
||||
deploymentStatuses: {},
|
||||
}
|
||||
|
||||
const mockTransaction = vi.fn().mockImplementation(async (callback) => {
|
||||
|
||||
@@ -186,7 +186,6 @@ export async function getWorkflowState(workflowId: string) {
|
||||
|
||||
if (normalizedData) {
|
||||
const finalState = {
|
||||
deploymentStatuses: {},
|
||||
hasActiveWebhook: false,
|
||||
blocks: normalizedData.blocks,
|
||||
edges: normalizedData.edges,
|
||||
|
||||
@@ -203,7 +203,6 @@ export const resetAllStores = () => {
|
||||
useWorkflowRegistry.setState({
|
||||
activeWorkflowId: null,
|
||||
error: null,
|
||||
deploymentStatuses: {},
|
||||
hydration: {
|
||||
phase: 'idle',
|
||||
workspaceId: null,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getWorkflows } from '@/hooks/queries/utils/workflow-cache'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { mergeSubblockState } from '@/stores/workflows/utils'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import type { BlockState, WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
import type { WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
|
||||
const logger = createLogger('Workflows')
|
||||
|
||||
@@ -30,9 +30,6 @@ export function getWorkflowWithValues(workflowId: string, workspaceId: string) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Get deployment status from registry
|
||||
const deploymentStatus = useWorkflowRegistry.getState().getWorkflowDeploymentStatus(workflowId)
|
||||
|
||||
// Use the current state from the store (only available for active workflow)
|
||||
const workflowState: WorkflowState = useWorkflowStore.getState().getWorkflowState()
|
||||
|
||||
@@ -52,110 +49,10 @@ export function getWorkflowWithValues(workflowId: string, workspaceId: string) {
|
||||
loops: workflowState.loops,
|
||||
parallels: workflowState.parallels,
|
||||
lastSaved: workflowState.lastSaved,
|
||||
// Get deployment fields from registry for API compatibility
|
||||
isDeployed: deploymentStatus?.isDeployed || false,
|
||||
deployedAt: deploymentStatus?.deployedAt,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific block with its subblock values merged in
|
||||
* @param blockId ID of the block to retrieve
|
||||
* @returns The block with merged subblock values or null if not found
|
||||
*/
|
||||
export function getBlockWithValues(blockId: string): BlockState | null {
|
||||
const workflowState = useWorkflowStore.getState()
|
||||
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
|
||||
|
||||
if (!activeWorkflowId || !workflowState.blocks[blockId]) return null
|
||||
|
||||
const mergedBlocks = mergeSubblockState(workflowState.blocks, activeWorkflowId, blockId)
|
||||
return mergedBlocks[blockId] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all workflows with their values merged
|
||||
* Note: Since localStorage has been removed, this only includes the active workflow state
|
||||
* @param workspaceId Workspace containing the workflow metadata
|
||||
* @returns An object containing workflows, with state only for the active workflow
|
||||
*/
|
||||
export function getAllWorkflowsWithValues(workspaceId: string) {
|
||||
const workflows = getWorkflows(workspaceId)
|
||||
const result: Record<
|
||||
string,
|
||||
{
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
color: string
|
||||
folderId?: string | null
|
||||
workspaceId?: string
|
||||
apiKey?: string
|
||||
state: WorkflowState & { isDeployed: boolean; deployedAt?: Date }
|
||||
}
|
||||
> = {}
|
||||
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
|
||||
const currentState = useWorkflowStore.getState()
|
||||
|
||||
// Only sync the active workflow to ensure we always send valid state data
|
||||
const activeMetadata = activeWorkflowId
|
||||
? workflows.find((w) => w.id === activeWorkflowId)
|
||||
: undefined
|
||||
if (activeWorkflowId && activeMetadata) {
|
||||
const metadata = activeMetadata
|
||||
|
||||
// Get deployment status from registry
|
||||
const deploymentStatus = useWorkflowRegistry
|
||||
.getState()
|
||||
.getWorkflowDeploymentStatus(activeWorkflowId)
|
||||
|
||||
// Ensure state has all required fields for Zod validation
|
||||
const workflowState: WorkflowState = {
|
||||
...useWorkflowStore.getState().getWorkflowState(),
|
||||
// Ensure fallback values for safer handling
|
||||
blocks: currentState.blocks || {},
|
||||
edges: currentState.edges || [],
|
||||
loops: currentState.loops || {},
|
||||
parallels: currentState.parallels || {},
|
||||
lastSaved: currentState.lastSaved || Date.now(),
|
||||
}
|
||||
|
||||
// Merge the subblock values for this specific workflow
|
||||
const mergedBlocks = mergeSubblockState(workflowState.blocks, activeWorkflowId)
|
||||
|
||||
// Include the API key in the state if it exists in the deployment status
|
||||
const apiKey = deploymentStatus?.apiKey
|
||||
|
||||
result[activeWorkflowId] = {
|
||||
id: activeWorkflowId,
|
||||
name: metadata.name,
|
||||
description: metadata.description,
|
||||
color: metadata.color || '#3972F6',
|
||||
folderId: metadata.folderId,
|
||||
state: {
|
||||
blocks: mergedBlocks,
|
||||
edges: workflowState.edges,
|
||||
loops: workflowState.loops,
|
||||
parallels: workflowState.parallels,
|
||||
lastSaved: workflowState.lastSaved,
|
||||
// Get deployment fields from registry for API compatibility
|
||||
isDeployed: deploymentStatus?.isDeployed || false,
|
||||
deployedAt: deploymentStatus?.deployedAt,
|
||||
},
|
||||
// Include API key if available
|
||||
apiKey,
|
||||
}
|
||||
|
||||
// Only include workspaceId if it's not null/undefined
|
||||
if (metadata.workspaceId) {
|
||||
result[activeWorkflowId].workspaceId = metadata.workspaceId
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
export type { WorkflowMetadata } from '@/stores/workflows/registry/types'
|
||||
export { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
|
||||
@@ -3,14 +3,12 @@ import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
import { DEFAULT_DUPLICATE_OFFSET } from '@/lib/workflows/autolayout/constants'
|
||||
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
|
||||
import type { WorkflowDeploymentInfo } from '@/hooks/queries/deployments'
|
||||
import { deploymentKeys } from '@/hooks/queries/deployments'
|
||||
import { invalidateWorkflowLists } from '@/hooks/queries/utils/invalidate-workflow-lists'
|
||||
import { useVariablesStore } from '@/stores/panel/variables/store'
|
||||
import type { Variable } from '@/stores/panel/variables/types'
|
||||
import type {
|
||||
DeploymentStatus,
|
||||
HydrationState,
|
||||
WorkflowRegistry,
|
||||
} from '@/stores/workflows/registry/types'
|
||||
import type { HydrationState, WorkflowRegistry } from '@/stores/workflows/registry/types'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
import { getUniqueBlockName, regenerateBlockIds } from '@/stores/workflows/utils'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
@@ -34,7 +32,6 @@ function resetWorkflowStores() {
|
||||
edges: [],
|
||||
loops: {},
|
||||
parallels: {},
|
||||
deploymentStatuses: {},
|
||||
lastSaved: Date.now(),
|
||||
})
|
||||
|
||||
@@ -48,7 +45,6 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
|
||||
(set, get) => ({
|
||||
activeWorkflowId: null,
|
||||
error: null,
|
||||
deploymentStatuses: {},
|
||||
hydration: initialHydration,
|
||||
clipboard: null,
|
||||
pendingSelection: null,
|
||||
@@ -61,7 +57,6 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
|
||||
|
||||
set({
|
||||
activeWorkflowId: null,
|
||||
deploymentStatuses: {},
|
||||
error: null,
|
||||
hydration: {
|
||||
phase: 'idle',
|
||||
@@ -73,74 +68,6 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
|
||||
})
|
||||
},
|
||||
|
||||
getWorkflowDeploymentStatus: (workflowId: string | null): DeploymentStatus | null => {
|
||||
if (!workflowId) {
|
||||
workflowId = get().activeWorkflowId
|
||||
if (!workflowId) return null
|
||||
}
|
||||
|
||||
const { deploymentStatuses = {} } = get()
|
||||
|
||||
if (deploymentStatuses[workflowId]) {
|
||||
return deploymentStatuses[workflowId]
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
setDeploymentStatus: (
|
||||
workflowId: string | null,
|
||||
isDeployed: boolean,
|
||||
deployedAt?: Date,
|
||||
apiKey?: string
|
||||
) => {
|
||||
if (!workflowId) {
|
||||
workflowId = get().activeWorkflowId
|
||||
if (!workflowId) return
|
||||
}
|
||||
|
||||
set((state) => ({
|
||||
deploymentStatuses: {
|
||||
...state.deploymentStatuses,
|
||||
[workflowId as string]: {
|
||||
isDeployed,
|
||||
deployedAt: deployedAt || (isDeployed ? new Date() : undefined),
|
||||
apiKey,
|
||||
needsRedeployment: isDeployed
|
||||
? false
|
||||
: (state.deploymentStatuses?.[workflowId as string]?.needsRedeployment ?? false),
|
||||
},
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
setWorkflowNeedsRedeployment: (workflowId: string | null, needsRedeployment: boolean) => {
|
||||
if (!workflowId) {
|
||||
workflowId = get().activeWorkflowId
|
||||
if (!workflowId) return
|
||||
}
|
||||
|
||||
set((state) => {
|
||||
const deploymentStatuses = state.deploymentStatuses || {}
|
||||
const currentStatus = deploymentStatuses[workflowId as string] || { isDeployed: false }
|
||||
|
||||
return {
|
||||
deploymentStatuses: {
|
||||
...deploymentStatuses,
|
||||
[workflowId as string]: {
|
||||
...currentStatus,
|
||||
needsRedeployment,
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const { activeWorkflowId } = get()
|
||||
if (workflowId === activeWorkflowId) {
|
||||
useWorkflowStore.getState().setNeedsRedeploymentFlag(needsRedeployment)
|
||||
}
|
||||
},
|
||||
|
||||
loadWorkflowState: async (workflowId: string) => {
|
||||
const workspaceId = get().hydration.workspaceId
|
||||
if (!workspaceId) {
|
||||
@@ -170,20 +97,19 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
|
||||
}
|
||||
|
||||
const workflowData = (await response.json()).data
|
||||
const nextDeploymentStatuses =
|
||||
workflowData?.isDeployed || workflowData?.deployedAt
|
||||
? {
|
||||
...get().deploymentStatuses,
|
||||
[workflowId]: {
|
||||
isDeployed: workflowData.isDeployed || false,
|
||||
deployedAt: workflowData.deployedAt
|
||||
? new Date(workflowData.deployedAt)
|
||||
: undefined,
|
||||
apiKey: workflowData.apiKey || undefined,
|
||||
needsRedeployment: false,
|
||||
},
|
||||
}
|
||||
: get().deploymentStatuses
|
||||
|
||||
if (workflowData?.isDeployed !== undefined) {
|
||||
getQueryClient().setQueryData<WorkflowDeploymentInfo>(
|
||||
deploymentKeys.info(workflowId),
|
||||
(prev) => ({
|
||||
isDeployed: workflowData.isDeployed ?? false,
|
||||
deployedAt: workflowData.deployedAt ?? null,
|
||||
apiKey: workflowData.apiKey ?? prev?.apiKey ?? null,
|
||||
needsRedeployment: prev?.needsRedeployment ?? false,
|
||||
isPublicApi: prev?.isPublicApi ?? false,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
let workflowState: WorkflowState
|
||||
|
||||
@@ -195,7 +121,6 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
|
||||
loops: workflowData.state.loops || {},
|
||||
parallels: workflowData.state.parallels || {},
|
||||
lastSaved: Date.now(),
|
||||
deploymentStatuses: nextDeploymentStatuses,
|
||||
}
|
||||
} else {
|
||||
workflowState = {
|
||||
@@ -204,7 +129,6 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
|
||||
edges: [],
|
||||
loops: {},
|
||||
parallels: {},
|
||||
deploymentStatuses: nextDeploymentStatuses,
|
||||
lastSaved: Date.now(),
|
||||
}
|
||||
|
||||
@@ -250,7 +174,6 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
|
||||
set((state) => ({
|
||||
activeWorkflowId: workflowId,
|
||||
error: null,
|
||||
deploymentStatuses: nextDeploymentStatuses,
|
||||
hydration: {
|
||||
phase: 'ready',
|
||||
workspaceId: state.hydration.workspaceId,
|
||||
@@ -367,7 +290,6 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
|
||||
|
||||
set({
|
||||
activeWorkflowId: null,
|
||||
deploymentStatuses: {},
|
||||
error: null,
|
||||
hydration: initialHydration,
|
||||
clipboard: null,
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import type { Edge } from 'reactflow'
|
||||
import type { BlockState, Loop, Parallel } from '@/stores/workflows/workflow/types'
|
||||
|
||||
export interface DeploymentStatus {
|
||||
isDeployed: boolean
|
||||
deployedAt?: Date
|
||||
apiKey?: string
|
||||
needsRedeployment?: boolean
|
||||
}
|
||||
|
||||
export interface ClipboardData {
|
||||
blocks: Record<string, BlockState>
|
||||
edges: Edge[]
|
||||
@@ -45,7 +38,6 @@ export interface HydrationState {
|
||||
export interface WorkflowRegistryState {
|
||||
activeWorkflowId: string | null
|
||||
error: string | null
|
||||
deploymentStatuses: Record<string, DeploymentStatus>
|
||||
hydration: HydrationState
|
||||
clipboard: ClipboardData | null
|
||||
pendingSelection: string[] | null
|
||||
@@ -57,14 +49,6 @@ export interface WorkflowRegistryActions {
|
||||
switchToWorkspace: (id: string) => void
|
||||
markWorkflowCreating: (workflowId: string) => void
|
||||
markWorkflowCreated: (workflowId: string | null) => void
|
||||
getWorkflowDeploymentStatus: (workflowId: string | null) => DeploymentStatus | null
|
||||
setDeploymentStatus: (
|
||||
workflowId: string | null,
|
||||
isDeployed: boolean,
|
||||
deployedAt?: Date,
|
||||
apiKey?: string
|
||||
) => void
|
||||
setWorkflowNeedsRedeployment: (workflowId: string | null, needsRedeployment: boolean) => void
|
||||
copyBlocks: (blockIds: string[]) => void
|
||||
preparePasteData: (positionOffset?: { x: number; y: number }) => {
|
||||
blocks: Record<string, BlockState>
|
||||
|
||||
@@ -107,8 +107,6 @@ const initialState = {
|
||||
loops: {},
|
||||
parallels: {},
|
||||
lastSaved: undefined,
|
||||
deploymentStatuses: {},
|
||||
needsRedeployment: false,
|
||||
}
|
||||
|
||||
export const useWorkflowStore = create<WorkflowStore>()(
|
||||
@@ -116,10 +114,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
(set, get) => ({
|
||||
...initialState,
|
||||
|
||||
setNeedsRedeploymentFlag: (needsRedeployment: boolean) => {
|
||||
set({ needsRedeployment })
|
||||
},
|
||||
|
||||
setCurrentWorkflowId: (currentWorkflowId) => {
|
||||
set({ currentWorkflowId })
|
||||
},
|
||||
@@ -540,8 +534,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
loops: state.loops,
|
||||
parallels: state.parallels,
|
||||
lastSaved: state.lastSaved,
|
||||
deploymentStatuses: state.deploymentStatuses,
|
||||
needsRedeployment: state.needsRedeployment,
|
||||
}
|
||||
},
|
||||
replaceWorkflowState: (
|
||||
@@ -580,11 +572,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
edges: nextEdges,
|
||||
loops: nextLoops,
|
||||
parallels: nextParallels,
|
||||
deploymentStatuses: nextState.deploymentStatuses || state.deploymentStatuses,
|
||||
needsRedeployment:
|
||||
nextState.needsRedeployment !== undefined
|
||||
? nextState.needsRedeployment
|
||||
: state.needsRedeployment,
|
||||
lastSaved:
|
||||
options?.updateLastSaved === true
|
||||
? Date.now()
|
||||
@@ -1141,67 +1128,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
}))
|
||||
},
|
||||
|
||||
revertToDeployedState: async (deployedState: WorkflowState) => {
|
||||
const activeWorkflowId = get().currentWorkflowId
|
||||
|
||||
if (!activeWorkflowId) {
|
||||
logger.error('Cannot revert: no active workflow ID')
|
||||
return
|
||||
}
|
||||
|
||||
const deploymentStatus = get().deploymentStatuses?.[activeWorkflowId]
|
||||
|
||||
get().replaceWorkflowState({
|
||||
...deployedState,
|
||||
needsRedeployment: false,
|
||||
deploymentStatuses: {
|
||||
...get().deploymentStatuses,
|
||||
...(deploymentStatus ? { [activeWorkflowId]: deploymentStatus } : {}),
|
||||
},
|
||||
})
|
||||
|
||||
const values: Record<string, Record<string, any>> = {}
|
||||
Object.entries(deployedState.blocks).forEach(([blockId, block]) => {
|
||||
values[blockId] = {}
|
||||
Object.entries(block.subBlocks || {}).forEach(([subBlockId, subBlock]) => {
|
||||
values[blockId][subBlockId] = subBlock.value
|
||||
})
|
||||
})
|
||||
|
||||
useSubBlockStore.setState({
|
||||
workflowValues: {
|
||||
...useSubBlockStore.getState().workflowValues,
|
||||
[activeWorkflowId]: values,
|
||||
},
|
||||
})
|
||||
|
||||
get().updateLastSaved()
|
||||
|
||||
// Call API to persist the revert to normalized tables
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/workflows/${activeWorkflowId}/deployments/active/revert`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
logger.error('Failed to persist revert to deployed state:', errorData.error)
|
||||
// Don't throw error to avoid breaking the UI, but log it
|
||||
} else {
|
||||
logger.info('Successfully persisted revert to deployed state')
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error calling revert to deployed API:', error)
|
||||
// Don't throw error to avoid breaking the UI
|
||||
}
|
||||
},
|
||||
|
||||
toggleBlockAdvancedMode: (id: string) => {
|
||||
const block = get().blocks[id]
|
||||
if (!block) return
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Edge } from 'reactflow'
|
||||
import type { OutputFieldDefinition, SubBlockType } from '@/blocks/types'
|
||||
import type { DeploymentStatus } from '@/stores/workflows/registry/types'
|
||||
|
||||
export const SUBFLOW_TYPES = {
|
||||
LOOP: 'loop',
|
||||
@@ -173,8 +172,6 @@ export interface WorkflowState {
|
||||
exportedAt?: string
|
||||
}
|
||||
variables?: Record<string, Variable>
|
||||
deploymentStatuses?: Record<string, DeploymentStatus>
|
||||
needsRedeployment?: boolean
|
||||
dragStartPosition?: DragStartPosition | null
|
||||
}
|
||||
|
||||
@@ -228,8 +225,6 @@ export interface WorkflowActions {
|
||||
updateParallelType: (parallelId: string, parallelType: 'count' | 'collection') => void
|
||||
generateLoopBlocks: () => Record<string, Loop>
|
||||
generateParallelBlocks: () => Record<string, Parallel>
|
||||
setNeedsRedeploymentFlag: (needsRedeployment: boolean) => void
|
||||
revertToDeployedState: (deployedState: WorkflowState) => void
|
||||
toggleBlockAdvancedMode: (id: string) => void
|
||||
setDragStartPosition: (position: DragStartPosition | null) => void
|
||||
getDragStartPosition: () => DragStartPosition | null
|
||||
|
||||
@@ -193,8 +193,8 @@ vi.mock('@/tools/registry', () => {
|
||||
return { tools: mockTools }
|
||||
})
|
||||
|
||||
// Mock custom tools - define mock data inside factory function
|
||||
vi.mock('@/hooks/queries/utils/custom-tool-cache', () => {
|
||||
// Mock query client for custom tool cache reads
|
||||
vi.mock('@/app/_shell/providers/get-query-client', () => {
|
||||
const mockCustomTool = {
|
||||
id: 'custom-tool-123',
|
||||
title: 'Custom Weather Tool',
|
||||
@@ -214,13 +214,12 @@ vi.mock('@/hooks/queries/utils/custom-tool-cache', () => {
|
||||
},
|
||||
}
|
||||
return {
|
||||
getCustomTool: (toolId: string) => {
|
||||
if (toolId === 'custom-tool-123') {
|
||||
return mockCustomTool
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
getCustomTools: () => [mockCustomTool],
|
||||
getQueryClient: () => ({
|
||||
getQueryData: (key: string[]) => {
|
||||
if (key[0] === 'customTools') return [mockCustomTool]
|
||||
return undefined
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user