fix(web): preserve marketplace plugin caches

This commit is contained in:
CodingOnStar 2026-08-04 16:41:59 +08:00
parent 6f35232013
commit 02f41e145b
7 changed files with 98 additions and 21 deletions

View File

@ -25,7 +25,7 @@ vi.mock('@/app/components/plugins/marketplace/hooks', () => ({
describe('useMarketplaceAllPlugins', () => {
const mockQueryPlugins = vi.fn()
const mockQueryPluginsWithDebounced = vi.fn()
const mockResetPlugins = vi.fn()
const mockResetQueryParams = vi.fn()
const mockCancelQueryPluginsWithDebounced = vi.fn()
const mockFetchNextPage = vi.fn()
@ -35,7 +35,7 @@ describe('useMarketplaceAllPlugins', () => {
({
plugins: [],
total: 0,
resetPlugins: mockResetPlugins,
resetQueryParams: mockResetQueryParams,
queryPlugins: mockQueryPlugins,
queryPluginsWithDebounced: mockQueryPluginsWithDebounced,
cancelQueryPluginsWithDebounced: mockCancelQueryPluginsWithDebounced,

View File

@ -987,7 +987,7 @@ describe('hooks', () => {
const queryPlugins = vi.fn()
const queryPluginsWithDebounced = vi.fn()
const cancelQueryPluginsWithDebounced = vi.fn()
const resetPlugins = vi.fn()
const resetQueryParams = vi.fn()
;(useMarketplacePluginsByCollectionId as Mock).mockReturnValue({
plugins: [{ plugin_id: 'collection-only', type: 'plugin' }],
isLoading: true,
@ -997,7 +997,7 @@ describe('hooks', () => {
queryPlugins,
queryPluginsWithDebounced,
cancelQueryPluginsWithDebounced,
resetPlugins,
resetQueryParams,
isLoading: true,
})
@ -1007,8 +1007,8 @@ describe('hooks', () => {
expect(queryPlugins).not.toHaveBeenCalled()
expect(queryPluginsWithDebounced).not.toHaveBeenCalled()
expect(cancelQueryPluginsWithDebounced).toHaveBeenCalled()
expect(resetPlugins).not.toHaveBeenCalled()
expect(useMarketplacePlugins).toHaveBeenCalledWith(false)
expect(resetQueryParams).toHaveBeenCalled()
expect(result.current.plugins).toEqual([])
expect(result.current.isLoading).toBe(false)
})

View File

@ -241,12 +241,14 @@ export const useMarketplaceAllPlugins = (
queryPlugins,
queryPluginsWithDebounced,
cancelQueryPluginsWithDebounced = () => {},
resetQueryParams = () => {},
isLoading: isPluginsLoading,
} = useMarketplacePlugins(enabled)
useEffect(() => {
if (!enabled) {
cancelQueryPluginsWithDebounced()
resetQueryParams()
return
}
@ -275,6 +277,7 @@ export const useMarketplaceAllPlugins = (
enabled,
queryPlugins,
queryPluginsWithDebounced,
resetQueryParams,
searchText,
exclude,
])

View File

@ -1,6 +1,8 @@
import type { ReactNode } from 'react'
import type { Plugin } from '@/app/components/plugins/types'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { act, renderHook, waitFor } from '@testing-library/react'
import { PluginCategoryEnum } from '@/app/components/plugins/types'
const getMarketplacePluginsByCollectionId = vi.hoisted(() => vi.fn())
const getMarketplaceCollectionsAndPlugins = vi.hoisted(() => vi.fn())
@ -149,3 +151,79 @@ describe('useMarketplaceCollectionsAndPlugins', () => {
})
})
})
const createPlugin = (pluginID: string, category: PluginCategoryEnum) =>
({
plugin_id: pluginID,
type: 'plugin',
category,
}) as Plugin
const createInfiniteData = (plugin: Plugin, pageSize: number) => ({
pages: [
{
plugins: [plugin],
total: 1,
page: 1,
page_size: pageSize,
},
],
pageParams: [1],
})
const createWrapperWithQueryClient = (queryClient: QueryClient) =>
function Wrapper({ children }: { children: ReactNode }) {
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
}
describe('useMarketplacePlugins', () => {
it('should reset local query params without removing marketplace plugin caches', async () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false, gcTime: Infinity },
},
})
const toolPlugin = createPlugin('tool-plugin', PluginCategoryEnum.tool)
const modelPlugin = createPlugin('model-plugin', PluginCategoryEnum.model)
const toolParams = {
query: 'search',
category: PluginCategoryEnum.tool,
type: 'plugin' as const,
page_size: 40,
}
const modelParams = {
query: '',
category: PluginCategoryEnum.model,
type: 'plugin' as const,
page_size: 1000,
}
const toolQueryKey = ['marketplacePlugins', toolParams]
const modelQueryKey = ['marketplacePlugins', modelParams]
const toolQueryData = createInfiniteData(toolPlugin, toolParams.page_size)
const modelQueryData = createInfiniteData(modelPlugin, modelParams.page_size)
queryClient.setQueryData(toolQueryKey, toolQueryData)
queryClient.setQueryData(modelQueryKey, modelQueryData)
const { useMarketplacePlugins } = await import('../hooks')
const { result } = renderHook(() => useMarketplacePlugins(), {
wrapper: createWrapperWithQueryClient(queryClient),
})
act(() => {
result.current.queryPlugins(toolParams)
})
await waitFor(() => {
expect(result.current.plugins).toEqual([toolPlugin])
})
act(() => {
result.current.resetQueryParams()
})
expect(result.current.plugins).toBeUndefined()
expect(queryClient.getQueryData(toolQueryKey)).toEqual(toolQueryData)
expect(queryClient.getQueryData(modelQueryKey)).toEqual(modelQueryData)
})
})

View File

@ -5,7 +5,7 @@ import type {
PluginsSearchParams,
} from '@dify/contracts/marketplace'
import type { Plugin } from '../types'
import { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'
import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
import { useDebounceFn } from 'ahooks'
import { useCallback, useEffect, useState } from 'react'
import { postMarketplace } from '@/service/base'
@ -81,7 +81,6 @@ export const useMarketplacePluginsByCollectionId = (
* @deprecated Use useMarketplacePlugins from query.ts instead
*/
export const useMarketplacePlugins = (enabled = true) => {
const queryClient = useQueryClient()
const [queryParams, setQueryParams] = useState<PluginsSearchParams>()
const normalizeParams = useCallback((pluginsSearchParams: PluginsSearchParams) => {
@ -156,12 +155,9 @@ export const useMarketplacePlugins = (enabled = true) => {
retry: false,
})
const resetPlugins = useCallback(() => {
const resetQueryParams = useCallback(() => {
setQueryParams(undefined)
queryClient.removeQueries({
queryKey: ['marketplacePlugins'],
})
}, [queryClient])
}, [])
const handleUpdatePlugins = useCallback(
(pluginsSearchParams: PluginsSearchParams) => {
@ -195,7 +191,7 @@ export const useMarketplacePlugins = (enabled = true) => {
return {
plugins,
total,
resetPlugins,
resetQueryParams,
queryPlugins: handleUpdatePlugins,
queryPluginsWithDebounced,
cancelQueryPluginsWithDebounced,

View File

@ -14,7 +14,7 @@ import { useMarketplace } from '../hooks'
const mockQueryMarketplaceCollectionsAndPlugins = vi.fn()
const mockQueryPlugins = vi.fn()
const mockQueryPluginsWithDebounced = vi.fn()
const mockResetPlugins = vi.fn()
const mockResetQueryParams = vi.fn()
const mockFetchNextPage = vi.fn()
const mockUseMarketplaceCollectionsAndPlugins = vi.fn()
@ -70,7 +70,7 @@ const setupHookMocks = (overrides?: {
})
mockUseMarketplacePlugins.mockReturnValue({
plugins: overrides?.plugins,
resetPlugins: mockResetPlugins,
resetQueryParams: mockResetQueryParams,
queryPlugins: mockQueryPlugins,
queryPluginsWithDebounced: mockQueryPluginsWithDebounced,
isLoading: overrides?.isPluginsLoading ?? false,
@ -125,7 +125,7 @@ describe('useMarketplace', () => {
})
expect(mockQueryPluginsWithDebounced).not.toHaveBeenCalled()
expect(mockQueryMarketplaceCollectionsAndPlugins).not.toHaveBeenCalled()
expect(mockResetPlugins).not.toHaveBeenCalled()
expect(mockResetQueryParams).not.toHaveBeenCalled()
})
it('should query plugins immediately when only tags are provided', async () => {
@ -163,7 +163,7 @@ describe('useMarketplace', () => {
type: 'plugin',
})
})
expect(mockResetPlugins).toHaveBeenCalledTimes(1)
expect(mockResetQueryParams).toHaveBeenCalledTimes(1)
})
})

View File

@ -29,13 +29,13 @@ export const useMarketplace = (
} = useMarketplaceCollectionsAndPlugins()
const {
plugins,
resetPlugins,
resetQueryParams,
queryPlugins,
isLoading: isPluginsLoading,
fetchNextPage,
hasNextPage,
page: pluginsPage,
} = useMarketplacePlugins()
} = useMarketplacePlugins(enabled)
const searchPluginTextRef = useRef(searchPluginText)
const filterPluginTagsRef = useRef(filterPluginTags)
@ -72,7 +72,7 @@ export const useMarketplace = (
exclude,
type: 'plugin',
})
resetPlugins()
resetQueryParams()
}
}
}, [
@ -80,7 +80,7 @@ export const useMarketplace = (
filterPluginTags,
queryPlugins,
queryMarketplaceCollectionsAndPlugins,
resetPlugins,
resetQueryParams,
exclude,
enabled,
isSuccess,