From b70241ad362c19a4e88753c0510f7ebd53e9b713 Mon Sep 17 00:00:00 2001 From: Joel Date: Mon, 18 May 2026 16:01:35 +0800 Subject: [PATCH] fix: app list not refresh --- .../create-app-dialog/app-list/__tests__/index.spec.tsx | 5 +++++ .../components/app/create-app-dialog/app-list/index.tsx | 3 +++ .../app/create-app-modal/__tests__/index.spec.tsx | 5 +++++ web/app/components/app/create-app-modal/index.tsx | 5 ++++- .../app/create-from-dsl-modal/__tests__/index.spec.tsx | 6 ++++++ web/app/components/app/create-from-dsl-modal/index.tsx | 4 ++++ web/hooks/use-import-dsl.ts | 8 ++++++-- 7 files changed, 33 insertions(+), 3 deletions(-) diff --git a/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx b/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx index cd6c6b57eb..221d6eb30c 100644 --- a/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx +++ b/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx @@ -12,6 +12,7 @@ const mockPush = vi.fn() const mockToastSuccess = vi.fn() const mockToastError = vi.fn() const mockTrackCreateApp = vi.fn() +const mockInvalidateAppList = vi.hoisted(() => vi.fn()) let latestDebounceFn = () => {} vi.mock('ahooks', () => ({ @@ -98,6 +99,9 @@ vi.mock('@/utils/create-app-tracking', () => ({ vi.mock('@/service/apps', () => ({ importDSL: (...args: unknown[]) => mockImportDSL(...args), })) +vi.mock('@/service/use-apps', () => ({ + useInvalidateAppList: () => mockInvalidateAppList, +})) vi.mock('@/service/explore', () => ({ fetchAppDetail: (...args: unknown[]) => mockFetchAppDetail(...args), })) @@ -253,6 +257,7 @@ describe('Apps', () => { expect(onSuccess).toHaveBeenCalled() expect(mockHandleCheckPluginDependencies).toHaveBeenCalledWith('created-app-id') expect(localStorage.getItem(NEED_REFRESH_APP_LIST_KEY)).toBe('1') + expect(mockInvalidateAppList).toHaveBeenCalledTimes(1) expect(mockGetRedirection).toHaveBeenCalledWith(true, { id: 'created-app-id', mode: AppModeEnum.CHAT, diff --git a/web/app/components/app/create-app-dialog/app-list/index.tsx b/web/app/components/app/create-app-dialog/app-list/index.tsx index b0f0b8ca59..a1c9124564 100644 --- a/web/app/components/app/create-app-dialog/app-list/index.tsx +++ b/web/app/components/app/create-app-dialog/app-list/index.tsx @@ -21,6 +21,7 @@ import { DSLImportMode } from '@/models/app' import { useRouter } from '@/next/navigation' import { importDSL } from '@/service/apps' import { fetchAppDetail } from '@/service/explore' +import { useInvalidateAppList } from '@/service/use-apps' import { useExploreAppList } from '@/service/use-explore' import { AppModeEnum } from '@/types/app' import { getRedirection } from '@/utils/app-redirection' @@ -45,6 +46,7 @@ const Apps = ({ const { t } = useTranslation() const { isCurrentWorkspaceEditor } = useAppContext() const { push } = useRouter() + const invalidateAppList = useInvalidateAppList() const allCategoriesEn = AppCategories.RECOMMENDED const [keywords, setKeywords] = useState('') @@ -136,6 +138,7 @@ const Apps = ({ if (app.app_id) await handleCheckPluginDependencies(app.app_id) localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') + invalidateAppList() getRedirection(isCurrentWorkspaceEditor, { id: app.app_id!, mode }, push) } catch { diff --git a/web/app/components/app/create-app-modal/__tests__/index.spec.tsx b/web/app/components/app/create-app-modal/__tests__/index.spec.tsx index 24fb21747f..4c1649661c 100644 --- a/web/app/components/app/create-app-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/create-app-modal/__tests__/index.spec.tsx @@ -15,6 +15,7 @@ import CreateAppModal from '../index' const ahooksMocks = vi.hoisted(() => ({ keyPressHandlers: [] as Array<() => void>, })) +const mockInvalidateAppList = vi.hoisted(() => vi.fn()) vi.mock('ahooks', () => ({ useDebounceFn: unknown>(fn: T) => { @@ -37,6 +38,9 @@ vi.mock('@/utils/create-app-tracking', () => ({ vi.mock('@/service/apps', () => ({ createApp: vi.fn(), })) +vi.mock('@/service/use-apps', () => ({ + useInvalidateAppList: () => mockInvalidateAppList, +})) const toastMocks = vi.hoisted(() => ({ mockToastSuccess: vi.fn(), mockToastError: vi.fn(), @@ -175,6 +179,7 @@ describe('CreateAppModal', () => { expect(onSuccess).toHaveBeenCalled() expect(onClose).toHaveBeenCalled() await waitFor(() => expect(mockSetItem).toHaveBeenCalledWith(NEED_REFRESH_APP_LIST_KEY, '1')) + expect(mockInvalidateAppList).toHaveBeenCalledTimes(1) await waitFor(() => expect(mockGetRedirection).toHaveBeenCalledWith(true, mockApp, mockPush)) }) diff --git a/web/app/components/app/create-app-modal/index.tsx b/web/app/components/app/create-app-modal/index.tsx index c27231bc87..94c787a1dd 100644 --- a/web/app/components/app/create-app-modal/index.tsx +++ b/web/app/components/app/create-app-modal/index.tsx @@ -21,6 +21,7 @@ import { useProviderContext } from '@/context/provider-context' import useTheme from '@/hooks/use-theme' import { useRouter } from '@/next/navigation' import { createApp } from '@/service/apps' +import { useInvalidateAppList } from '@/service/use-apps' import { AppModeEnum } from '@/types/app' import { getRedirection } from '@/utils/app-redirection' import { trackCreateApp } from '@/utils/create-app-tracking' @@ -54,6 +55,7 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }: const { plan, enableBilling } = useProviderContext() const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps) const { isCurrentWorkspaceEditor } = useAppContext() + const invalidateAppList = useInvalidateAppList() const isCreatingRef = useRef(false) @@ -85,13 +87,14 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }: onSuccess() onClose() localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') + invalidateAppList() getRedirection(isCurrentWorkspaceEditor, app, push) } catch (error) { toast.error(error instanceof Error ? error.message : t('newApp.appCreateFailed', { ns: 'app' })) } isCreatingRef.current = false - }, [name, t, appMode, appIcon, description, onSuccess, onClose, push, isCurrentWorkspaceEditor]) + }, [name, t, appMode, appIcon, description, onSuccess, onClose, push, isCurrentWorkspaceEditor, invalidateAppList]) const { run: handleCreateApp } = useDebounceFn(onCreate, { wait: 300 }) useKeyPress(['meta.enter', 'ctrl.enter'], () => { diff --git a/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx b/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx index dac5b22ee6..263fc8fcca 100644 --- a/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx @@ -11,6 +11,7 @@ const mockImportDSLConfirm = vi.fn() const mockTrackCreateApp = vi.fn() const mockHandleCheckPluginDependencies = vi.fn() const mockGetRedirection = vi.fn() +const mockInvalidateAppList = vi.hoisted(() => vi.fn()) const toastMocks = vi.hoisted(() => ({ call: vi.fn(), success: vi.fn(), @@ -52,6 +53,9 @@ vi.mock('@/service/apps', () => ({ importDSL: (...args: unknown[]) => mockImportDSL(...args), importDSLConfirm: (...args: unknown[]) => mockImportDSLConfirm(...args), })) +vi.mock('@/service/use-apps', () => ({ + useInvalidateAppList: () => mockInvalidateAppList, +})) vi.mock('@/app/components/workflow/plugin-dependency/hooks', () => ({ usePluginDependencies: () => ({ @@ -201,6 +205,7 @@ describe('CreateFromDSLModal', () => { expect(handleSuccess).toHaveBeenCalledTimes(1) expect(handleClose).toHaveBeenCalledTimes(1) expect(localStorage.getItem(NEED_REFRESH_APP_LIST_KEY)).toBe('1') + expect(mockInvalidateAppList).toHaveBeenCalledTimes(1) expect(mockHandleCheckPluginDependencies).toHaveBeenCalledWith('app-1') expect(mockGetRedirection).toHaveBeenCalledWith(true, { id: 'app-1', mode: 'chat' }, mockPush) }) @@ -305,6 +310,7 @@ describe('CreateFromDSLModal', () => { import_id: 'import-3', }) expect(mockTrackCreateApp).toHaveBeenCalledWith({ appMode: AppModeEnum.WORKFLOW }) + expect(mockInvalidateAppList).toHaveBeenCalledTimes(1) }) it('should close the DSL mismatch modal when dialog requests close', async () => { diff --git a/web/app/components/app/create-from-dsl-modal/index.tsx b/web/app/components/app/create-from-dsl-modal/index.tsx index 5b8a4e7469..31ea98c5b0 100644 --- a/web/app/components/app/create-from-dsl-modal/index.tsx +++ b/web/app/components/app/create-from-dsl-modal/index.tsx @@ -23,6 +23,7 @@ import { importDSL, importDSLConfirm, } from '@/service/apps' +import { useInvalidateAppList } from '@/service/use-apps' import { getRedirection } from '@/utils/app-redirection' import { trackCreateApp } from '@/utils/create-app-tracking' import ShortcutsName from '../../workflow/shortcuts-name' @@ -74,6 +75,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS const { isCurrentWorkspaceEditor } = useAppContext() const { plan, enableBilling } = useProviderContext() const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps) + const invalidateAppList = useInvalidateAppList() const isCreatingRef = useRef(false) @@ -124,6 +126,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS : undefined, }) localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') + invalidateAppList() if (app_id) await handleCheckPluginDependencies(app_id) getRedirection(isCurrentWorkspaceEditor, { id: app_id!, mode: app_mode }, push) @@ -181,6 +184,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS if (app_id) await handleCheckPluginDependencies(app_id) localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') + invalidateAppList() getRedirection(isCurrentWorkspaceEditor, { id: app_id!, mode: app_mode }, push) } else if (status === DSLImportStatus.FAILED) { diff --git a/web/hooks/use-import-dsl.ts b/web/hooks/use-import-dsl.ts index 1ece2a27f0..e4a3e6e3ad 100644 --- a/web/hooks/use-import-dsl.ts +++ b/web/hooks/use-import-dsl.ts @@ -19,6 +19,7 @@ import { importDSL, importDSLConfirm, } from '@/service/apps' +import { useInvalidateAppList } from '@/service/use-apps' import { getRedirection } from '@/utils/app-redirection' type DSLPayload = { @@ -42,6 +43,7 @@ export const useImportDSL = () => { const { handleCheckPluginDependencies } = usePluginDependencies() const isCurrentWorkspaceEditor = useSelector(s => s.isCurrentWorkspaceEditor) const { push } = useRouter() + const invalidateAppList = useInvalidateAppList() const [versions, setVersions] = useState<{ importedVersion: string, systemVersion: string }>() const importIdRef = useRef('') @@ -87,6 +89,7 @@ export const useImportDSL = () => { toast.warning(message, { description }) onSuccess?.() localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') + invalidateAppList() await handleCheckPluginDependencies(app_id) getRedirection(isCurrentWorkspaceEditor, { id: app_id, mode: app_mode }, push) } @@ -110,7 +113,7 @@ export const useImportDSL = () => { finally { setIsFetching(false) } - }, [t, handleCheckPluginDependencies, isCurrentWorkspaceEditor, push, isFetching]) + }, [t, handleCheckPluginDependencies, isCurrentWorkspaceEditor, push, isFetching, invalidateAppList]) const handleImportDSLConfirm = useCallback(async ( { @@ -138,6 +141,7 @@ export const useImportDSL = () => { toast.success(t('newApp.appCreated', { ns: 'app' })) await handleCheckPluginDependencies(app_id) localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') + invalidateAppList() getRedirection(isCurrentWorkspaceEditor, { id: app_id!, mode: app_mode }, push) } else if (status === DSLImportStatus.FAILED) { @@ -152,7 +156,7 @@ export const useImportDSL = () => { finally { setIsFetching(false) } - }, [t, handleCheckPluginDependencies, isCurrentWorkspaceEditor, push, isFetching]) + }, [t, handleCheckPluginDependencies, isCurrentWorkspaceEditor, push, isFetching, invalidateAppList]) return { handleImportDSL,