mirror of
https://github.com/reconurge/flowsint.git
synced 2026-05-07 20:28:48 -05:00
feat(app): refetch on new nodes
This commit is contained in:
@@ -19,6 +19,7 @@ import GraphMain from './graph-main'
|
||||
import Settings, { KeyboardShortcuts } from './settings'
|
||||
import { type GraphNode, type GraphEdge } from '@/types'
|
||||
import { MergeDialog } from './merge-nodes'
|
||||
import { useGraphRefresh } from '@/hooks/use-graph-refresh'
|
||||
const RelationshipsTable = lazy(() => import('@/components/table/relationships-view'))
|
||||
|
||||
// Separate component for the drag overlay
|
||||
@@ -50,11 +51,14 @@ const GraphPanel = ({ graphData, isLoading }: GraphPanelProps) => {
|
||||
const filters = useGraphStore((s) => s.filters)
|
||||
const { actionItems, isLoading: isLoadingActionItems } = useActionItems()
|
||||
|
||||
const { sketch } = useLoaderData({
|
||||
const { params, sketch } = useLoaderData({
|
||||
from: '/_auth/dashboard/investigations/$investigationId/$type/$id'
|
||||
})
|
||||
const [isDraggingOver, setIsDraggingOver] = useState(false)
|
||||
|
||||
// Dedicated hook for graph refresh on transform completion
|
||||
useGraphRefresh(params.id)
|
||||
|
||||
useEffect(() => {
|
||||
if (graphData?.nds && graphData?.rls) {
|
||||
updateGraphData(graphData.nds, graphData.rls)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useEffect, useState, useMemo } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { logService } from '@/api/log-service'
|
||||
import { EventLevel } from '@/types'
|
||||
import { useGraphControls } from '@/stores/graph-controls-store'
|
||||
import { queryKeys } from '@/api/query-keys'
|
||||
|
||||
const API_URL = import.meta.env.VITE_API_URL
|
||||
@@ -10,8 +8,6 @@ const API_URL = import.meta.env.VITE_API_URL
|
||||
|
||||
export function useEvents(sketch_id: string | undefined) {
|
||||
const [liveLogs, setLiveLogs] = useState<Event[]>([])
|
||||
const refetchGraph = useGraphControls((s) => s.refetchGraph)
|
||||
const setShouldRegenerateLayoutOnNextRefetch = useGraphControls((s) => s.setShouldRegenerateLayoutOnNextRefetch)
|
||||
|
||||
const { data: previousLogs = [], refetch } = useQuery({
|
||||
queryKey: queryKeys.logs.bySketch(sketch_id as string),
|
||||
@@ -40,11 +36,6 @@ export function useEvents(sketch_id: string | undefined) {
|
||||
try {
|
||||
const raw = JSON.parse(e.data) as any
|
||||
const event = JSON.parse(raw.data) as Event
|
||||
if (event.type === EventLevel.COMPLETED) {
|
||||
// Set flag to regenerate layout after refetch completes
|
||||
setShouldRegenerateLayoutOnNextRefetch(true)
|
||||
refetchGraph()
|
||||
}
|
||||
setLiveLogs((prev) => [...prev.slice(-99), event])
|
||||
} catch (error) {
|
||||
console.error('[useSketchEvents] Failed to parse SSE event:', error)
|
||||
@@ -59,7 +50,7 @@ export function useEvents(sketch_id: string | undefined) {
|
||||
return () => {
|
||||
eventSource.close()
|
||||
}
|
||||
}, [sketch_id, refetchGraph])
|
||||
}, [sketch_id])
|
||||
|
||||
const logs = useMemo(
|
||||
() => [...previousLogs, ...liveLogs].slice(-100),
|
||||
|
||||
72
flowsint-app/src/hooks/use-graph-refresh.ts
Normal file
72
flowsint-app/src/hooks/use-graph-refresh.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useGraphControls } from '@/stores/graph-controls-store'
|
||||
import { EventLevel } from '@/types'
|
||||
|
||||
const API_URL = import.meta.env.VITE_API_URL
|
||||
|
||||
/**
|
||||
* Dedicated hook for graph refresh on transform completion.
|
||||
*
|
||||
* This hook listens ONLY to COMPLETED events via SSE and triggers
|
||||
* a graph refetch followed by layout regeneration. It's completely
|
||||
* separate from the logging system (use-events.ts).
|
||||
*
|
||||
* Architecture:
|
||||
* - Transform completes → Logger.completed() → SSE event
|
||||
* - This hook receives COMPLETED event
|
||||
* - Calls refetchGraph() with callback
|
||||
* - Callback triggers regenerateLayout() with fresh data
|
||||
*/
|
||||
export function useGraphRefresh(sketch_id: string | undefined) {
|
||||
const refetchGraph = useGraphControls((s) => s.refetchGraph)
|
||||
const regenerateLayout = useGraphControls((s) => s.regenerateLayout)
|
||||
const currentLayoutType = useGraphControls((s) => s.currentLayoutType)
|
||||
|
||||
useEffect(() => {
|
||||
if (!sketch_id) return
|
||||
|
||||
console.log('[useGraphRefresh] Connecting to SSE for sketch:', sketch_id)
|
||||
const eventSource = new EventSource(
|
||||
`${API_URL}/api/events/sketch/${sketch_id}/stream`
|
||||
)
|
||||
|
||||
eventSource.onopen = () => {
|
||||
console.log('[useGraphRefresh] SSE connection opened')
|
||||
}
|
||||
|
||||
eventSource.onmessage = (e) => {
|
||||
try {
|
||||
const raw = JSON.parse(e.data) as any
|
||||
const event = JSON.parse(raw.data) as any
|
||||
console.log('[useGraphRefresh] Received event:', event.type, event)
|
||||
|
||||
// Only handle COMPLETED events
|
||||
if (event.type === EventLevel.COMPLETED) {
|
||||
console.log('[useGraphRefresh] COMPLETED event detected, triggering refetch')
|
||||
console.log('[useGraphRefresh] refetchGraph function:', refetchGraph)
|
||||
console.log('[useGraphRefresh] currentLayoutType:', currentLayoutType)
|
||||
|
||||
// Refetch graph data, then regenerate layout with fresh data
|
||||
refetchGraph(() => {
|
||||
console.log('[useGraphRefresh] Refetch callback executing, regenerating layout')
|
||||
if (currentLayoutType) {
|
||||
regenerateLayout(currentLayoutType)
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[useGraphRefresh] Failed to parse SSE event:', error)
|
||||
}
|
||||
}
|
||||
|
||||
eventSource.onerror = (error) => {
|
||||
console.error('[useGraphRefresh] EventSource error:', error)
|
||||
eventSource.close()
|
||||
}
|
||||
|
||||
return () => {
|
||||
console.log('[useGraphRefresh] Disconnecting SSE')
|
||||
eventSource.close()
|
||||
}
|
||||
}, [sketch_id, refetchGraph, regenerateLayout, currentLayoutType])
|
||||
}
|
||||
@@ -45,7 +45,19 @@ const GraphPageContent = () => {
|
||||
}, [id, reset])
|
||||
|
||||
useEffect(() => {
|
||||
setActions({ refetchGraph: refetch })
|
||||
const refetchWithCallback = async (onSuccess?: () => void) => {
|
||||
console.log('[Route] refetchWithCallback called with callback:', !!onSuccess)
|
||||
await refetch()
|
||||
console.log('[Route] refetch completed')
|
||||
// Execute callback after refetch completes and data is updated
|
||||
if (onSuccess) {
|
||||
console.log('[Route] executing callback after refetch')
|
||||
// Small delay to ensure React Query has updated the data in the store
|
||||
setTimeout(onSuccess, 0)
|
||||
}
|
||||
}
|
||||
console.log('[Route] Setting refetchGraph in store')
|
||||
setActions({ refetchGraph: refetchWithCallback })
|
||||
}, [refetch, setActions, id])
|
||||
|
||||
if (type === 'graph') {
|
||||
|
||||
@@ -16,7 +16,7 @@ type GraphControlsStore = {
|
||||
zoomOut: () => void
|
||||
onLayout: (layout: any) => void
|
||||
setActions: (actions: Partial<GraphControlsStore>) => void
|
||||
refetchGraph: () => void
|
||||
refetchGraph: (onSuccess?: () => void) => void
|
||||
regenerateLayout: (layoutType: LayoutType) => void
|
||||
setCurrentLayoutType: (layoutType: LayoutType) => void
|
||||
setShouldRegenerateLayoutOnNextRefetch: (should: boolean) => void
|
||||
@@ -38,7 +38,7 @@ export const useGraphControls = create<GraphControlsStore>()(
|
||||
zoomOut: () => { },
|
||||
onLayout: () => { },
|
||||
setActions: (actions) => set(actions),
|
||||
refetchGraph: () => { },
|
||||
refetchGraph: (onSuccess) => { },
|
||||
regenerateLayout: () => { },
|
||||
setCurrentLayoutType: (layoutType) => set({ currentLayoutType: layoutType }),
|
||||
setShouldRegenerateLayoutOnNextRefetch: (should) => set({ shouldRegenerateLayoutOnNextRefetch: should }),
|
||||
|
||||
Reference in New Issue
Block a user