Merge pull request #3172 from simstudioai/fix/notifs

fix(notifications): throw notification on runtime errors, move predeploy checks to update in deploy modal
This commit is contained in:
Waleed
2026-02-09 11:49:58 -08:00
committed by GitHub
parent c74922997c
commit 622d0cad22
3 changed files with 75 additions and 64 deletions

View File

@@ -19,6 +19,7 @@ import {
import { getBaseUrl } from '@/lib/core/utils/urls'
import { getInputFormatExample as getInputFormatExampleUtil } from '@/lib/workflows/operations/deployment-utils'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
import { runPreDeployChecks } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/hooks/use-predeploy-checks'
import { CreateApiKeyModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/api-keys/components'
import { startsWithUuid } from '@/executor/constants'
import { useA2AAgentByWorkflow } from '@/hooks/queries/a2a/agents'
@@ -38,6 +39,7 @@ import { useWorkspaceSettings } from '@/hooks/queries/workspace'
import { usePermissionConfig } from '@/hooks/use-permission-config'
import { useSettingsModalStore } from '@/stores/modals/settings/store'
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'
import { A2aDeploy } from './components/a2a/a2a'
@@ -335,6 +337,20 @@ export function DeployModal({
setDeployError(null)
setDeployWarnings([])
const { blocks, edges, loops, parallels } = useWorkflowStore.getState()
const liveBlocks = mergeSubblockState(blocks, workflowId)
const checkResult = runPreDeployChecks({
blocks: liveBlocks,
edges,
loops,
parallels,
workflowId,
})
if (!checkResult.passed) {
setDeployError(checkResult.error || 'Pre-deploy validation failed')
return
}
try {
const result = await deployMutation.mutateAsync({ workflowId, deployChatEnabled: false })
if (result.warnings && result.warnings.length > 0) {

View File

@@ -2,9 +2,6 @@ import { useCallback, useState } from 'react'
import { createLogger } from '@sim/logger'
import { useNotificationStore } from '@/stores/notifications'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { mergeSubblockState } from '@/stores/workflows/utils'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import { runPreDeployChecks } from './use-predeploy-checks'
const logger = createLogger('useDeployment')
@@ -25,36 +22,16 @@ export function useDeployment({
const [isDeploying, setIsDeploying] = useState(false)
const setDeploymentStatus = useWorkflowRegistry((state) => state.setDeploymentStatus)
const addNotification = useNotificationStore((state) => state.addNotification)
const blocks = useWorkflowStore((state) => state.blocks)
const edges = useWorkflowStore((state) => state.edges)
const loops = useWorkflowStore((state) => state.loops)
const parallels = useWorkflowStore((state) => state.parallels)
/**
* Handle deploy button click
* First deploy: calls API to deploy, then opens modal on success
* Redeploy: validates client-side, then opens modal if valid
* Already deployed: opens modal directly (validation happens on Update in modal)
*/
const handleDeployClick = useCallback(async () => {
if (!workflowId) return { success: false, shouldOpenModal: false }
if (isDeployed) {
const liveBlocks = mergeSubblockState(blocks, workflowId)
const checkResult = runPreDeployChecks({
blocks: liveBlocks,
edges,
loops,
parallels,
workflowId,
})
if (!checkResult.passed) {
addNotification({
level: 'error',
message: checkResult.error || 'Pre-deploy validation failed',
workflowId,
})
return { success: false, shouldOpenModal: false }
}
return { success: true, shouldOpenModal: true }
}
@@ -101,17 +78,7 @@ export function useDeployment({
} finally {
setIsDeploying(false)
}
}, [
workflowId,
isDeployed,
blocks,
edges,
loops,
parallels,
refetchDeployedState,
setDeploymentStatus,
addNotification,
])
}, [workflowId, isDeployed, refetchDeployedState, setDeploymentStatus, addNotification])
return {
isDeploying,

View File

@@ -62,6 +62,45 @@ const shouldSkipEntry = (output: any): boolean => {
return false
}
interface NotifyBlockErrorParams {
error: unknown
blockName: string
workflowId?: string
logContext: Record<string, unknown>
}
/**
* Sends an error notification for a block failure if error notifications are enabled.
*/
const notifyBlockError = ({ error, blockName, workflowId, logContext }: NotifyBlockErrorParams) => {
const settings = getQueryClient().getQueryData<GeneralSettings>(generalSettingsKeys.settings())
const isErrorNotificationsEnabled = settings?.errorNotificationsEnabled ?? true
if (!isErrorNotificationsEnabled) return
try {
const errorMessage = String(error)
const displayName = blockName || 'Unknown Block'
const displayMessage = `${displayName}: ${errorMessage}`
const copilotMessage = `${errorMessage}\n\nError in ${displayName}.\n\nPlease fix this.`
useNotificationStore.getState().addNotification({
level: 'error',
message: displayMessage,
workflowId,
action: {
type: 'copilot',
message: copilotMessage,
},
})
} catch (notificationError) {
logger.error('Failed to create block error notification', {
...logContext,
error: notificationError,
})
}
}
export const useTerminalConsoleStore = create<ConsoleStore>()(
devtools(
persist(
@@ -154,35 +193,12 @@ export const useTerminalConsoleStore = create<ConsoleStore>()(
const newEntry = get().entries[0]
if (newEntry?.error) {
const settings = getQueryClient().getQueryData<GeneralSettings>(
generalSettingsKeys.settings()
)
const isErrorNotificationsEnabled = settings?.errorNotificationsEnabled ?? true
if (isErrorNotificationsEnabled) {
try {
const errorMessage = String(newEntry.error)
const blockName = newEntry.blockName || 'Unknown Block'
const displayMessage = `${blockName}: ${errorMessage}`
const copilotMessage = `${errorMessage}\n\nError in ${blockName}.\n\nPlease fix this.`
useNotificationStore.getState().addNotification({
level: 'error',
message: displayMessage,
workflowId: entry.workflowId,
action: {
type: 'copilot',
message: copilotMessage,
},
})
} catch (notificationError) {
logger.error('Failed to create block error notification', {
entryId: newEntry.id,
error: notificationError,
})
}
}
notifyBlockError({
error: newEntry.error,
blockName: newEntry.blockName || 'Unknown Block',
workflowId: entry.workflowId,
logContext: { entryId: newEntry.id },
})
}
return newEntry
@@ -376,6 +392,18 @@ export const useTerminalConsoleStore = create<ConsoleStore>()(
return { entries: updatedEntries }
})
if (typeof update === 'object' && update.error) {
const matchingEntry = get().entries.find(
(e) => e.blockId === blockId && e.executionId === executionId
)
notifyBlockError({
error: update.error,
blockName: matchingEntry?.blockName || 'Unknown Block',
workflowId: matchingEntry?.workflowId,
logContext: { blockId },
})
}
},
cancelRunningEntries: (workflowId: string) => {