mirror of
https://github.com/langgenius/dify.git
synced 2026-04-15 18:06:36 +08:00
stabilize MCP service card collaboration subscription
This commit is contained in:
parent
f0cd2e8465
commit
5e0e0982bc
@ -26,6 +26,23 @@ const mockHandleGenCode = vi.fn()
|
||||
const mockOpenConfirmDelete = vi.fn()
|
||||
const mockCloseConfirmDelete = vi.fn()
|
||||
const mockOpenServerModal = vi.fn()
|
||||
const mockOnMcpServerUpdate = vi.hoisted(() => vi.fn())
|
||||
const mockUnsubscribeMcpServerUpdate = vi.hoisted(() => vi.fn())
|
||||
const invalidateMCPServerDetailFns = vi.hoisted(() => [] as Array<ReturnType<typeof vi.fn>>)
|
||||
|
||||
vi.mock('@/app/components/workflow/collaboration/core/collaboration-manager', () => ({
|
||||
collaborationManager: {
|
||||
onMcpServerUpdate: mockOnMcpServerUpdate,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-tools', () => ({
|
||||
useInvalidateMCPServerDetail: () => {
|
||||
const invalidateFn = vi.fn()
|
||||
invalidateMCPServerDetailFns.push(invalidateFn)
|
||||
return invalidateFn
|
||||
},
|
||||
}))
|
||||
|
||||
type MockHookState = {
|
||||
genLoading: boolean
|
||||
@ -106,12 +123,15 @@ describe('MCPServiceCard', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mockHookState = createDefaultHookState()
|
||||
invalidateMCPServerDetailFns.length = 0
|
||||
mockHandleStatusChange.mockClear().mockResolvedValue({ activated: true })
|
||||
mockHandleServerModalHide.mockClear().mockReturnValue({ shouldDeactivate: false })
|
||||
mockHandleGenCode.mockClear()
|
||||
mockOpenConfirmDelete.mockClear()
|
||||
mockCloseConfirmDelete.mockClear()
|
||||
mockOpenServerModal.mockClear()
|
||||
mockUnsubscribeMcpServerUpdate.mockClear()
|
||||
mockOnMcpServerUpdate.mockReset().mockReturnValue(mockUnsubscribeMcpServerUpdate)
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@ -431,4 +451,26 @@ describe('MCPServiceCard', () => {
|
||||
expect(screen.getByRole('switch')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Collaboration Sync', () => {
|
||||
it('should keep a stable MCP update subscription across rerenders and invalidate with the latest callback', async () => {
|
||||
let mcpUpdateHandler: ((payload: unknown) => void) | undefined
|
||||
mockOnMcpServerUpdate.mockImplementation((handler: (payload: unknown) => void) => {
|
||||
mcpUpdateHandler = handler
|
||||
return mockUnsubscribeMcpServerUpdate
|
||||
})
|
||||
|
||||
const { rerender } = render(<MCPServiceCard appInfo={createMockAppInfo()} />, { wrapper: createWrapper() })
|
||||
|
||||
rerender(<MCPServiceCard appInfo={createMockAppInfo()} />, { wrapper: createWrapper() })
|
||||
|
||||
expect(mockOnMcpServerUpdate).toHaveBeenCalledTimes(1)
|
||||
expect(invalidateMCPServerDetailFns).toHaveLength(2)
|
||||
|
||||
mcpUpdateHandler?.({ type: 'mcp_server_update' })
|
||||
|
||||
expect(invalidateMCPServerDetailFns[0]).not.toHaveBeenCalled()
|
||||
expect(invalidateMCPServerDetailFns[1]).toHaveBeenCalledWith('app-123')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -5,7 +5,7 @@ import type { CollaborationUpdate } from '@/app/components/workflow/collaboratio
|
||||
import type { AppDetailResponse } from '@/models/app'
|
||||
import type { AppSSO } from '@/types/app'
|
||||
import { RiEditLine, RiLoopLeftLine } from '@remixicon/react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
@ -16,6 +16,7 @@ import Switch from '@/app/components/base/switch'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import MCPServerModal from '@/app/components/tools/mcp/mcp-server-modal'
|
||||
import { collaborationManager } from '@/app/components/workflow/collaboration/core/collaboration-manager'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { useInvalidateMCPServerDetail } from '@/service/use-tools'
|
||||
import { cn } from '@/utils/classnames'
|
||||
@ -166,6 +167,11 @@ const MCPServiceCard: FC<IAppCardProps> = ({
|
||||
const docLink = useDocLink()
|
||||
const appId = appInfo.id
|
||||
const invalidateMCPServerDetail = useInvalidateMCPServerDetail()
|
||||
const invalidateMCPServerDetailRef = useRef(invalidateMCPServerDetail)
|
||||
|
||||
useEffect(() => {
|
||||
invalidateMCPServerDetailRef.current = invalidateMCPServerDetail
|
||||
}, [invalidateMCPServerDetail])
|
||||
|
||||
const {
|
||||
genLoading,
|
||||
@ -258,34 +264,17 @@ const MCPServiceCard: FC<IAppCardProps> = ({
|
||||
if (!appId)
|
||||
return
|
||||
|
||||
let unsubscribe: (() => void) | undefined
|
||||
let cancelled = false
|
||||
|
||||
void (async () => {
|
||||
const unsubscribe = collaborationManager.onMcpServerUpdate((_update: CollaborationUpdate) => {
|
||||
try {
|
||||
const { collaborationManager } = await import('@/app/components/workflow/collaboration/core/collaboration-manager')
|
||||
if (cancelled)
|
||||
return
|
||||
|
||||
unsubscribe = collaborationManager.onMcpServerUpdate((_update: CollaborationUpdate) => {
|
||||
try {
|
||||
invalidateMCPServerDetail(appId)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('MCP server update failed:', error)
|
||||
}
|
||||
})
|
||||
invalidateMCPServerDetailRef.current(appId)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('MCP collaboration subscription failed:', error)
|
||||
console.error('MCP server update failed:', error)
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
unsubscribe?.()
|
||||
}
|
||||
}, [appId, invalidateMCPServerDetail])
|
||||
return unsubscribe
|
||||
}, [appId])
|
||||
|
||||
if (isLoading)
|
||||
return null
|
||||
|
||||
Loading…
Reference in New Issue
Block a user