fix(web): invalidate plugin checkInstalled cache after version updates

This commit is contained in:
yyh 2026-03-04 22:33:17 +08:00
parent 0f7ed6f67e
commit 22a4100dd7
No known key found for this signature in database
3 changed files with 59 additions and 7 deletions

View File

@ -15,6 +15,7 @@ type VersionPickerMock = {
const { const {
mockSetShowUpdatePluginModal, mockSetShowUpdatePluginModal,
mockRefreshModelProviders, mockRefreshModelProviders,
mockInvalidateCheckInstalled,
mockInvalidateAllToolProviders, mockInvalidateAllToolProviders,
mockUninstallPlugin, mockUninstallPlugin,
mockFetchReleases, mockFetchReleases,
@ -23,6 +24,7 @@ const {
return { return {
mockSetShowUpdatePluginModal: vi.fn(), mockSetShowUpdatePluginModal: vi.fn(),
mockRefreshModelProviders: vi.fn(), mockRefreshModelProviders: vi.fn(),
mockInvalidateCheckInstalled: vi.fn(),
mockInvalidateAllToolProviders: vi.fn(), mockInvalidateAllToolProviders: vi.fn(),
mockUninstallPlugin: vi.fn(() => Promise.resolve({ success: true })), mockUninstallPlugin: vi.fn(() => Promise.resolve({ success: true })),
mockFetchReleases: vi.fn(() => Promise.resolve([{ tag_name: 'v2.0.0' }])), mockFetchReleases: vi.fn(() => Promise.resolve([{ tag_name: 'v2.0.0' }])),
@ -46,6 +48,10 @@ vi.mock('@/service/plugins', () => ({
uninstallPlugin: mockUninstallPlugin, uninstallPlugin: mockUninstallPlugin,
})) }))
vi.mock('@/service/use-plugins', () => ({
useInvalidateCheckInstalled: () => mockInvalidateCheckInstalled,
}))
vi.mock('@/service/use-tools', () => ({ vi.mock('@/service/use-tools', () => ({
useInvalidateAllToolProviders: () => mockInvalidateAllToolProviders, useInvalidateAllToolProviders: () => mockInvalidateAllToolProviders,
})) }))
@ -178,6 +184,7 @@ describe('usePluginOperations', () => {
result.current.handleUpdatedFromMarketplace() result.current.handleUpdatedFromMarketplace()
}) })
expect(mockInvalidateCheckInstalled).toHaveBeenCalled()
expect(mockOnUpdate).toHaveBeenCalled() expect(mockOnUpdate).toHaveBeenCalled()
expect(modalStates.hideUpdateModal).toHaveBeenCalled() expect(modalStates.hideUpdateModal).toHaveBeenCalled()
}) })
@ -251,6 +258,32 @@ describe('usePluginOperations', () => {
expect(mockSetShowUpdatePluginModal).toHaveBeenCalled() expect(mockSetShowUpdatePluginModal).toHaveBeenCalled()
}) })
it('should invalidate checkInstalled when GitHub update save callback fires', async () => {
const detail = createPluginDetail({
source: PluginSource.github,
meta: { repo: 'owner/repo', version: 'v1.0.0', package: 'pkg' },
})
const { result } = renderHook(() =>
usePluginOperations({
detail,
modalStates,
versionPicker,
isFromMarketplace: false,
onUpdate: mockOnUpdate,
}),
)
await act(async () => {
await result.current.handleUpdate()
})
const firstCall = mockSetShowUpdatePluginModal.mock.calls.at(0)?.[0]
firstCall?.onSaveCallback()
expect(mockInvalidateCheckInstalled).toHaveBeenCalled()
expect(mockOnUpdate).toHaveBeenCalled()
})
it('should not show modal when no releases found', async () => { it('should not show modal when no releases found', async () => {
mockFetchReleases.mockResolvedValueOnce([]) mockFetchReleases.mockResolvedValueOnce([])
const detail = createPluginDetail({ const detail = createPluginDetail({
@ -388,6 +421,7 @@ describe('usePluginOperations', () => {
await result.current.handleDelete() await result.current.handleDelete()
}) })
expect(mockInvalidateCheckInstalled).toHaveBeenCalled()
expect(mockOnUpdate).toHaveBeenCalledWith(true) expect(mockOnUpdate).toHaveBeenCalledWith(true)
}) })

View File

@ -9,6 +9,7 @@ import Toast from '@/app/components/base/toast'
import { useModalContext } from '@/context/modal-context' import { useModalContext } from '@/context/modal-context'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { uninstallPlugin } from '@/service/plugins' import { uninstallPlugin } from '@/service/plugins'
import { useInvalidateCheckInstalled } from '@/service/use-plugins'
import { useInvalidateAllToolProviders } from '@/service/use-tools' import { useInvalidateAllToolProviders } from '@/service/use-tools'
import { useGitHubReleases } from '../../../install-plugin/hooks' import { useGitHubReleases } from '../../../install-plugin/hooks'
import { PluginCategoryEnum, PluginSource } from '../../../types' import { PluginCategoryEnum, PluginSource } from '../../../types'
@ -41,10 +42,15 @@ export const usePluginOperations = ({
const { checkForUpdates, fetchReleases } = useGitHubReleases() const { checkForUpdates, fetchReleases } = useGitHubReleases()
const { setShowUpdatePluginModal } = useModalContext() const { setShowUpdatePluginModal } = useModalContext()
const { refreshModelProviders } = useProviderContext() const { refreshModelProviders } = useProviderContext()
const invalidateCheckInstalled = useInvalidateCheckInstalled()
const invalidateAllToolProviders = useInvalidateAllToolProviders() const invalidateAllToolProviders = useInvalidateAllToolProviders()
const { id, meta, plugin_id } = detail const { id, meta, plugin_id } = detail
const { author, category, name } = detail.declaration || detail const { author, category, name } = detail.declaration || detail
const handlePluginUpdated = useCallback((isDelete?: boolean) => {
invalidateCheckInstalled()
onUpdate?.(isDelete)
}, [invalidateCheckInstalled, onUpdate])
const handleUpdate = useCallback(async (isDowngrade?: boolean) => { const handleUpdate = useCallback(async (isDowngrade?: boolean) => {
if (isFromMarketplace) { if (isFromMarketplace) {
@ -73,7 +79,7 @@ export const usePluginOperations = ({
if (needUpdate) { if (needUpdate) {
setShowUpdatePluginModal({ setShowUpdatePluginModal({
onSaveCallback: () => { onSaveCallback: () => {
onUpdate?.() handlePluginUpdated()
}, },
payload: { payload: {
type: PluginSource.github, type: PluginSource.github,
@ -99,15 +105,15 @@ export const usePluginOperations = ({
checkForUpdates, checkForUpdates,
setShowUpdatePluginModal, setShowUpdatePluginModal,
detail, detail,
onUpdate, handlePluginUpdated,
modalStates, modalStates,
versionPicker, versionPicker,
]) ])
const handleUpdatedFromMarketplace = useCallback(() => { const handleUpdatedFromMarketplace = useCallback(() => {
onUpdate?.() handlePluginUpdated()
modalStates.hideUpdateModal() modalStates.hideUpdateModal()
}, [onUpdate, modalStates]) }, [handlePluginUpdated, modalStates])
const handleDelete = useCallback(async () => { const handleDelete = useCallback(async () => {
modalStates.showDeleting() modalStates.showDeleting()
@ -120,7 +126,7 @@ export const usePluginOperations = ({
type: 'success', type: 'success',
message: t('action.deleteSuccess', { ns: 'plugin' }), message: t('action.deleteSuccess', { ns: 'plugin' }),
}) })
onUpdate?.(true) handlePluginUpdated(true)
if (PluginCategoryEnum.model.includes(category)) if (PluginCategoryEnum.model.includes(category))
refreshModelProviders() refreshModelProviders()
@ -136,7 +142,7 @@ export const usePluginOperations = ({
plugin_id, plugin_id,
name, name,
modalStates, modalStates,
onUpdate, handlePluginUpdated,
refreshModelProviders, refreshModelProviders,
invalidateAllToolProviders, invalidateAllToolProviders,
]) ])

View File

@ -47,6 +47,7 @@ import { useInvalidateAllBuiltInTools } from './use-tools'
const NAME_SPACE = 'plugins' const NAME_SPACE = 'plugins'
const useInstalledPluginListKey = [NAME_SPACE, 'installedPluginList'] const useInstalledPluginListKey = [NAME_SPACE, 'installedPluginList']
const useCheckInstalledKey = [NAME_SPACE, 'checkInstalled'] as const
export const useCheckInstalled = ({ export const useCheckInstalled = ({
pluginIds, pluginIds,
enabled, enabled,
@ -55,7 +56,7 @@ export const useCheckInstalled = ({
enabled: boolean enabled: boolean
}) => { }) => {
return useQuery<{ plugins: PluginDetail[] }>({ return useQuery<{ plugins: PluginDetail[] }>({
queryKey: [NAME_SPACE, 'checkInstalled', pluginIds], queryKey: [...useCheckInstalledKey, pluginIds],
queryFn: () => post<{ plugins: PluginDetail[] }>('/workspaces/current/plugin/list/installations/ids', { queryFn: () => post<{ plugins: PluginDetail[] }>('/workspaces/current/plugin/list/installations/ids', {
body: { body: {
plugin_ids: pluginIds, plugin_ids: pluginIds,
@ -66,6 +67,17 @@ export const useCheckInstalled = ({
}) })
} }
export const useInvalidateCheckInstalled = () => {
const queryClient = useQueryClient()
return () => {
queryClient.invalidateQueries(
{
queryKey: useCheckInstalledKey,
},
)
}
}
const useRecommendedMarketplacePluginsKey = [NAME_SPACE, 'recommendedMarketplacePlugins'] const useRecommendedMarketplacePluginsKey = [NAME_SPACE, 'recommendedMarketplacePlugins']
export const useRecommendedMarketplacePlugins = ({ export const useRecommendedMarketplacePlugins = ({
collection = '__recommended-plugins-tools', collection = '__recommended-plugins-tools',