mirror of
https://github.com/langgenius/dify.git
synced 2026-04-15 09:57:03 +08:00
chore: try to track why nodes missing
This commit is contained in:
parent
692bf7ced1
commit
a68482da77
@ -42,10 +42,17 @@ const Header = () => {
|
||||
else
|
||||
setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.BILLING })
|
||||
}, [isFreePlan, setShowAccountSettingModal, setShowPricingModal])
|
||||
const handleDownloadGraphImportLog = useCallback(() => {
|
||||
void import('@/app/components/workflow/collaboration/core/collaboration-manager')
|
||||
.then(({ collaborationManager }) => {
|
||||
collaborationManager.downloadGraphImportLog()
|
||||
})
|
||||
.catch(() => {})
|
||||
}, [])
|
||||
|
||||
const renderLogo = () => (
|
||||
<h1>
|
||||
<Link href="/apps" className="flex h-8 shrink-0 items-center justify-center overflow-hidden whitespace-nowrap px-0.5 indent-[-9999px]">
|
||||
<Link href="/apps" className="flex h-8 shrink-0 items-center justify-center overflow-hidden px-0.5 indent-[-9999px] whitespace-nowrap">
|
||||
{isBrandingEnabled && systemFeatures.branding.application_title ? systemFeatures.branding.application_title : 'Dify'}
|
||||
{systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo
|
||||
? (
|
||||
@ -91,7 +98,7 @@ const Header = () => {
|
||||
|
||||
return (
|
||||
<div className="flex h-[56px] items-center">
|
||||
<div className="flex min-w-0 flex-1 items-center pl-3 pr-2 min-[1280px]:pr-3">
|
||||
<div className="flex min-w-0 flex-1 items-center pr-2 pl-3 min-[1280px]:pr-3">
|
||||
{renderLogo()}
|
||||
<div className="mx-1.5 shrink-0 font-light text-divider-deep">/</div>
|
||||
<WorkspaceProvider>
|
||||
@ -105,7 +112,15 @@ const Header = () => {
|
||||
{(isCurrentWorkspaceEditor || isCurrentWorkspaceDatasetOperator) && <DatasetNav />}
|
||||
{!isCurrentWorkspaceDatasetOperator && <ToolsNav className={navClassName} />}
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 items-center justify-end pl-2 pr-3 min-[1280px]:pl-3">
|
||||
<div className="flex min-w-0 flex-1 items-center justify-end pr-3 pl-2 min-[1280px]:pl-3">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="workflow-import-log-download"
|
||||
className="top-1/2 left-full ml-1 h-3 w-3 -translate-y-1/2 opacity-0"
|
||||
aria-hidden="true"
|
||||
tabIndex={-1}
|
||||
onClick={handleDownloadGraphImportLog}
|
||||
/>
|
||||
<EnvNav />
|
||||
<div className="mr-2">
|
||||
<PluginsNav />
|
||||
|
||||
@ -78,7 +78,32 @@ type GraphImportLogEntry = {
|
||||
}
|
||||
}
|
||||
|
||||
type SetNodesAnomalyReason = 'node_count_decrease' | 'start_removed'
|
||||
|
||||
type SetNodesAnomalyLogEntry = {
|
||||
timestamp: number
|
||||
appId: string | null
|
||||
source: string
|
||||
reasons: SetNodesAnomalyReason[]
|
||||
oldCount: number
|
||||
newCount: number
|
||||
removedNodeIds: string[]
|
||||
oldStartNodeIds: string[]
|
||||
newStartNodeIds: string[]
|
||||
oldNodeIds: string[]
|
||||
newNodeIds: string[]
|
||||
visibilityState: DocumentVisibilityState | 'unknown'
|
||||
meta: {
|
||||
leaderId: string | null
|
||||
isLeader: boolean
|
||||
graphViewActive: boolean | null
|
||||
pendingInitialSync: boolean
|
||||
isConnected: boolean
|
||||
}
|
||||
}
|
||||
|
||||
const GRAPH_IMPORT_LOG_LIMIT = 20
|
||||
const SET_NODES_ANOMALY_LOG_LIMIT = 100
|
||||
|
||||
const toLoroValue = (value: unknown): Value => cloneDeep(value) as Value
|
||||
const toLoroRecord = (value: unknown): Record<string, Value> => cloneDeep(value) as Record<string, Value>
|
||||
@ -102,6 +127,7 @@ export class CollaborationManager {
|
||||
private pendingGraphImportEmit = false
|
||||
private graphViewActive: boolean | null = null
|
||||
private graphImportLogs: GraphImportLogEntry[] = []
|
||||
private setNodesAnomalyLogs: SetNodesAnomalyLogEntry[] = []
|
||||
private pendingImportLog: {
|
||||
timestamp: number
|
||||
sources: Set<'nodes' | 'edges'>
|
||||
@ -391,7 +417,7 @@ export class CollaborationManager {
|
||||
this.connect(appId, reactFlowStore)
|
||||
}
|
||||
|
||||
setNodes = (oldNodes: Node[], newNodes: Node[]): void => {
|
||||
setNodes = (oldNodes: Node[], newNodes: Node[], source = 'collaboration-manager:setNodes'): void => {
|
||||
if (!this.doc)
|
||||
return
|
||||
|
||||
@ -399,6 +425,7 @@ export class CollaborationManager {
|
||||
if (this.isUndoRedoInProgress)
|
||||
return
|
||||
|
||||
this.captureSetNodesAnomaly(oldNodes, newNodes, source)
|
||||
this.syncNodes(oldNodes, newNodes)
|
||||
this.doc.commit()
|
||||
}
|
||||
@ -480,8 +507,9 @@ export class CollaborationManager {
|
||||
if (value?.value && typeof value.value === 'object' && 'selectedNodeId' in value.value && this.reactFlowStore) {
|
||||
const selectedNodeId = (value.value as { selectedNodeId?: string | null }).selectedNodeId
|
||||
if (selectedNodeId) {
|
||||
const { setNodes } = this.reactFlowStore.getState()
|
||||
const nodes = this.reactFlowStore.getState().getNodes()
|
||||
const state = this.reactFlowStore.getState()
|
||||
const { setNodes } = state
|
||||
const nodes = state.getNodes()
|
||||
const newNodes = nodes.map((n: Node) => ({
|
||||
...n,
|
||||
data: {
|
||||
@ -489,6 +517,7 @@ export class CollaborationManager {
|
||||
selected: n.id === selectedNodeId,
|
||||
},
|
||||
}))
|
||||
this.captureSetNodesAnomaly(nodes, newNodes, 'reactflow-native:undo-redo-selection-restore')
|
||||
setNodes(newNodes)
|
||||
}
|
||||
}
|
||||
@ -791,9 +820,11 @@ export class CollaborationManager {
|
||||
requestAnimationFrame(() => {
|
||||
// Get ReactFlow's native setters, not the collaborative ones
|
||||
const state = reactFlowStore.getState()
|
||||
const previousNodes = state.getNodes()
|
||||
const updatedNodes = Array.from(this.nodesMap?.values() || []) as Node[]
|
||||
const updatedEdges = Array.from(this.edgesMap?.values() || []) as Edge[]
|
||||
// Call ReactFlow's native setters directly to avoid triggering collaboration
|
||||
this.captureSetNodesAnomaly(previousNodes, updatedNodes, 'reactflow-native:undo-apply')
|
||||
state.setNodes(updatedNodes)
|
||||
state.setEdges(updatedEdges)
|
||||
|
||||
@ -831,9 +862,11 @@ export class CollaborationManager {
|
||||
requestAnimationFrame(() => {
|
||||
// Get ReactFlow's native setters, not the collaborative ones
|
||||
const state = reactFlowStore.getState()
|
||||
const previousNodes = state.getNodes()
|
||||
const updatedNodes = Array.from(this.nodesMap?.values() || []) as Node[]
|
||||
const updatedEdges = Array.from(this.edgesMap?.values() || []) as Edge[]
|
||||
// Call ReactFlow's native setters directly to avoid triggering collaboration
|
||||
this.captureSetNodesAnomaly(previousNodes, updatedNodes, 'reactflow-native:redo-apply')
|
||||
state.setNodes(updatedNodes)
|
||||
state.setEdges(updatedEdges)
|
||||
|
||||
@ -972,6 +1005,7 @@ export class CollaborationManager {
|
||||
})
|
||||
|
||||
// Call ReactFlow's native setter directly to avoid triggering collaboration
|
||||
this.captureSetNodesAnomaly(previousNodes, updatedNodes, 'reactflow-native:import-nodes-map-subscribe')
|
||||
state.setNodes(updatedNodes)
|
||||
|
||||
this.scheduleGraphImportEmit()
|
||||
@ -1067,17 +1101,37 @@ export class CollaborationManager {
|
||||
|
||||
clearGraphImportLog(): void {
|
||||
this.graphImportLogs = []
|
||||
this.setNodesAnomalyLogs = []
|
||||
this.pendingImportLog = null
|
||||
}
|
||||
|
||||
downloadGraphImportLog(): void {
|
||||
if (this.graphImportLogs.length === 0)
|
||||
return
|
||||
|
||||
const reactFlowState = this.reactFlowStore?.getState()
|
||||
const payload = {
|
||||
appId: this.currentAppId,
|
||||
generatedAt: new Date().toISOString(),
|
||||
entries: this.graphImportLogs,
|
||||
setNodesAnomalies: this.setNodesAnomalyLogs,
|
||||
summary: {
|
||||
logCount: this.graphImportLogs.length,
|
||||
setNodesAnomalyCount: this.setNodesAnomalyLogs.length,
|
||||
leaderId: this.leaderId,
|
||||
isLeader: this.isLeader,
|
||||
graphViewActive: this.graphViewActive,
|
||||
pendingInitialSync: this.pendingInitialSync,
|
||||
isConnected: this.isConnected(),
|
||||
hasDoc: Boolean(this.doc),
|
||||
hasReactFlowStore: Boolean(this.reactFlowStore),
|
||||
onlineUsersCount: this.onlineUsers.length,
|
||||
crdtCounts: {
|
||||
nodes: this.getNodes().length,
|
||||
edges: this.getEdges().length,
|
||||
},
|
||||
reactFlowCounts: {
|
||||
nodes: reactFlowState?.getNodes().length ?? 0,
|
||||
edges: reactFlowState?.getEdges().length ?? 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
const stamp = new Date().toISOString().replace(/[:.]/g, '-')
|
||||
const appSuffix = this.currentAppId ?? 'unknown'
|
||||
@ -1091,6 +1145,54 @@ export class CollaborationManager {
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
private captureSetNodesAnomaly(oldNodes: Node[], newNodes: Node[], source: string): void {
|
||||
const oldNodeIds = oldNodes.map(node => node.id)
|
||||
const newNodeIds = newNodes.map(node => node.id)
|
||||
const newNodeIdSet = new Set(newNodeIds)
|
||||
const removedNodeIds = oldNodeIds.filter(nodeId => !newNodeIdSet.has(nodeId))
|
||||
|
||||
const oldStartNodeIds = oldNodes
|
||||
.filter(node => (node.data as CommonNodeType | undefined)?.type === 'start')
|
||||
.map(node => node.id)
|
||||
const newStartNodeIds = newNodes
|
||||
.filter(node => (node.data as CommonNodeType | undefined)?.type === 'start')
|
||||
.map(node => node.id)
|
||||
|
||||
const reasons: SetNodesAnomalyReason[] = []
|
||||
if (newNodes.length < oldNodes.length)
|
||||
reasons.push('node_count_decrease')
|
||||
if (oldStartNodeIds.length > 0 && newStartNodeIds.length === 0)
|
||||
reasons.push('start_removed')
|
||||
|
||||
if (!reasons.length)
|
||||
return
|
||||
|
||||
const entry: SetNodesAnomalyLogEntry = {
|
||||
timestamp: Date.now(),
|
||||
appId: this.currentAppId,
|
||||
source,
|
||||
reasons,
|
||||
oldCount: oldNodes.length,
|
||||
newCount: newNodes.length,
|
||||
removedNodeIds,
|
||||
oldStartNodeIds,
|
||||
newStartNodeIds,
|
||||
oldNodeIds,
|
||||
newNodeIds,
|
||||
visibilityState: typeof document === 'undefined' ? 'unknown' : document.visibilityState,
|
||||
meta: {
|
||||
leaderId: this.leaderId,
|
||||
isLeader: this.isLeader,
|
||||
graphViewActive: this.graphViewActive,
|
||||
pendingInitialSync: this.pendingInitialSync,
|
||||
isConnected: this.isConnected(),
|
||||
},
|
||||
}
|
||||
this.setNodesAnomalyLogs.push(entry)
|
||||
if (this.setNodesAnomalyLogs.length > SET_NODES_ANOMALY_LOG_LIMIT)
|
||||
this.setNodesAnomalyLogs.splice(0, this.setNodesAnomalyLogs.length - SET_NODES_ANOMALY_LOG_LIMIT)
|
||||
}
|
||||
|
||||
private snapshotReactFlowGraph(): { nodes: Node[], edges: Edge[] } {
|
||||
if (!this.reactFlowStore) {
|
||||
return {
|
||||
|
||||
@ -39,13 +39,14 @@ export const useCollaborativeWorkflow = () => {
|
||||
const store = useStoreApi()
|
||||
const { setNodes: collabSetNodes, setEdges: collabSetEdges } = collaborationManager
|
||||
|
||||
const setNodes = useCallback((newNodes: Node[], shouldBroadcast: boolean = true) => {
|
||||
const setNodes = useCallback((newNodes: Node[], shouldBroadcast: boolean = true, source = 'use-collaborative-workflow:setNodes') => {
|
||||
const { getNodes, setNodes: reactFlowSetNodes } = store.getState()
|
||||
if (shouldBroadcast) {
|
||||
const oldNodes = getNodes()
|
||||
collabSetNodes(
|
||||
oldNodes.map(sanitizeNodeForBroadcast),
|
||||
newNodes.map(sanitizeNodeForBroadcast),
|
||||
source,
|
||||
)
|
||||
}
|
||||
reactFlowSetNodes(newNodes)
|
||||
|
||||
@ -45,7 +45,7 @@ export const usePerformRestore = () => {
|
||||
const currentNodes = collaborationManager.getNodes()
|
||||
const currentEdges = collaborationManager.getEdges()
|
||||
|
||||
collaborationManager.setNodes(currentNodes, nodes)
|
||||
collaborationManager.setNodes(currentNodes, nodes, 'leader-restore:apply-graph')
|
||||
collaborationManager.setEdges(currentEdges, edges)
|
||||
collaborationManager.refreshGraphSynchronously()
|
||||
|
||||
|
||||
@ -627,7 +627,6 @@ export const useNodesInteractions = () => {
|
||||
)
|
||||
|
||||
const { deleteNodeInspectorVars } = useInspectVarsCrud()
|
||||
|
||||
const handleNodeDelete = useCallback(
|
||||
(nodeId: string) => {
|
||||
if (getNodesReadOnly())
|
||||
@ -766,7 +765,7 @@ export const useNodesInteractions = () => {
|
||||
})
|
||||
draft.splice(currentNodeIndex, 1)
|
||||
})
|
||||
setNodes(newNodes)
|
||||
setNodes(newNodes, true, 'nodes:perform-batch-cascade-delete')
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
return draft.filter(
|
||||
edge =>
|
||||
@ -2116,7 +2115,7 @@ export const useNodesInteractions = () => {
|
||||
|
||||
const shouldBroadcast = collaborationManager.isConnected()
|
||||
setEdges(edges, shouldBroadcast)
|
||||
setNodes(nodes, shouldBroadcast)
|
||||
setNodes(nodes, shouldBroadcast, 'nodes:history-back')
|
||||
if (shouldBroadcast)
|
||||
collaborationManager.emitHistoryAction('undo')
|
||||
workflowStore.setState({ edgeMenu: undefined })
|
||||
@ -2141,7 +2140,7 @@ export const useNodesInteractions = () => {
|
||||
|
||||
const shouldBroadcast = collaborationManager.isConnected()
|
||||
setEdges(edges, shouldBroadcast)
|
||||
setNodes(nodes, shouldBroadcast)
|
||||
setNodes(nodes, shouldBroadcast, 'nodes:history-forward')
|
||||
if (shouldBroadcast)
|
||||
collaborationManager.emitHistoryAction('redo')
|
||||
workflowStore.setState({ edgeMenu: undefined })
|
||||
|
||||
Loading…
Reference in New Issue
Block a user