diff --git a/web/app/components/apps/__tests__/list.spec.tsx b/web/app/components/apps/__tests__/list.spec.tsx index 5c93952aba..59c04f8101 100644 --- a/web/app/components/apps/__tests__/list.spec.tsx +++ b/web/app/components/apps/__tests__/list.spec.tsx @@ -47,10 +47,18 @@ vi.mock('../hooks/use-apps-query-state', () => ({ })) let mockOnDSLFileDropped: ((file: File) => void) | null = null +let mockOnBundleFileDropped: ((file: File) => void) | null = null let mockDragging = false vi.mock('../hooks/use-dsl-drag-drop', () => ({ - useDSLDragDrop: ({ onDSLFileDropped }: { onDSLFileDropped: (file: File) => void }) => { + useDSLDragDrop: ({ + onDSLFileDropped, + onBundleFileDropped, + }: { + onDSLFileDropped: (file: File) => void + onBundleFileDropped?: (file: File) => void + }) => { mockOnDSLFileDropped = onDSLFileDropped + mockOnBundleFileDropped = onBundleFileDropped ?? null return { dragging: mockDragging } }, })) @@ -223,6 +231,7 @@ describe('List', () => { mockIsCurrentWorkspaceDatasetOperator.mockReturnValue(false) mockDragging = false mockOnDSLFileDropped = null + mockOnBundleFileDropped = null mockServiceState.error = null mockServiceState.hasNextPage = false mockServiceState.isLoading = false @@ -510,6 +519,18 @@ describe('List', () => { expect(screen.getByTestId('create-dsl-modal')).toBeInTheDocument() }) + it('should handle zip file drop and show modal', () => { + renderList() + + const mockFile = new File(['zip content'], 'bundle.zip', { type: 'application/zip' }) + act(() => { + if (mockOnBundleFileDropped) + mockOnBundleFileDropped(mockFile) + }) + + expect(screen.getByTestId('create-dsl-modal')).toBeInTheDocument() + }) + it('should close DSL modal when onClose is called', () => { renderList() diff --git a/web/app/components/apps/hooks/__tests__/use-dsl-drag-drop.spec.ts b/web/app/components/apps/hooks/__tests__/use-dsl-drag-drop.spec.ts index 58fed4caa8..e01095745e 100644 --- a/web/app/components/apps/hooks/__tests__/use-dsl-drag-drop.spec.ts +++ b/web/app/components/apps/hooks/__tests__/use-dsl-drag-drop.spec.ts @@ -5,12 +5,14 @@ import { useDSLDragDrop } from '../use-dsl-drag-drop' describe('useDSLDragDrop', () => { let container: HTMLDivElement let mockOnDSLFileDropped: Mock + let mockOnBundleFileDropped: Mock beforeEach(() => { vi.clearAllMocks() container = document.createElement('div') document.body.appendChild(container) mockOnDSLFileDropped = vi.fn() + mockOnBundleFileDropped = vi.fn() }) afterEach(() => { @@ -195,6 +197,7 @@ describe('useDSLDragDrop', () => { renderHook(() => useDSLDragDrop({ onDSLFileDropped: mockOnDSLFileDropped, + onBundleFileDropped: mockOnBundleFileDropped, containerRef, }), ) @@ -214,6 +217,7 @@ describe('useDSLDragDrop', () => { renderHook(() => useDSLDragDrop({ onDSLFileDropped: mockOnDSLFileDropped, + onBundleFileDropped: mockOnBundleFileDropped, containerRef, }), ) @@ -233,6 +237,7 @@ describe('useDSLDragDrop', () => { renderHook(() => useDSLDragDrop({ onDSLFileDropped: mockOnDSLFileDropped, + onBundleFileDropped: mockOnBundleFileDropped, containerRef, }), ) @@ -247,11 +252,33 @@ describe('useDSLDragDrop', () => { expect(mockOnDSLFileDropped).toHaveBeenCalledWith(file) }) + it('should call onBundleFileDropped for .zip file', () => { + const containerRef = { current: container } + renderHook(() => + useDSLDragDrop({ + onDSLFileDropped: mockOnDSLFileDropped, + onBundleFileDropped: mockOnBundleFileDropped, + containerRef, + }), + ) + + const file = createMockFile('test.zip') + const dropEvent = createDragEvent('drop', [file]) + + act(() => { + container.dispatchEvent(dropEvent) + }) + + expect(mockOnBundleFileDropped).toHaveBeenCalledWith(file) + expect(mockOnDSLFileDropped).not.toHaveBeenCalled() + }) + it('should not call onDSLFileDropped for non-yaml file', () => { const containerRef = { current: container } renderHook(() => useDSLDragDrop({ onDSLFileDropped: mockOnDSLFileDropped, + onBundleFileDropped: mockOnBundleFileDropped, containerRef, }), ) @@ -264,6 +291,7 @@ describe('useDSLDragDrop', () => { }) expect(mockOnDSLFileDropped).not.toHaveBeenCalled() + expect(mockOnBundleFileDropped).not.toHaveBeenCalled() }) it('should set dragging to false on drop', () => { @@ -271,6 +299,7 @@ describe('useDSLDragDrop', () => { const { result } = renderHook(() => useDSLDragDrop({ onDSLFileDropped: mockOnDSLFileDropped, + onBundleFileDropped: mockOnBundleFileDropped, containerRef, }), ) @@ -294,6 +323,7 @@ describe('useDSLDragDrop', () => { renderHook(() => useDSLDragDrop({ onDSLFileDropped: mockOnDSLFileDropped, + onBundleFileDropped: mockOnBundleFileDropped, containerRef, }), ) @@ -324,6 +354,7 @@ describe('useDSLDragDrop', () => { renderHook(() => useDSLDragDrop({ onDSLFileDropped: mockOnDSLFileDropped, + onBundleFileDropped: mockOnBundleFileDropped, containerRef, }), ) @@ -386,6 +417,7 @@ describe('useDSLDragDrop', () => { ({ enabled }) => useDSLDragDrop({ onDSLFileDropped: mockOnDSLFileDropped, + onBundleFileDropped: mockOnBundleFileDropped, containerRef, enabled, }), @@ -407,6 +439,7 @@ describe('useDSLDragDrop', () => { const { result } = renderHook(() => useDSLDragDrop({ onDSLFileDropped: mockOnDSLFileDropped, + onBundleFileDropped: mockOnBundleFileDropped, containerRef, }), ) @@ -429,6 +462,7 @@ describe('useDSLDragDrop', () => { const { unmount } = renderHook(() => useDSLDragDrop({ onDSLFileDropped: mockOnDSLFileDropped, + onBundleFileDropped: mockOnBundleFileDropped, containerRef, }), ) @@ -450,6 +484,7 @@ describe('useDSLDragDrop', () => { const { result } = renderHook(() => useDSLDragDrop({ onDSLFileDropped: mockOnDSLFileDropped, + onBundleFileDropped: mockOnBundleFileDropped, containerRef, }), ) diff --git a/web/app/components/apps/list.tsx b/web/app/components/apps/list.tsx index d3fc916c10..db02cbc723 100644 --- a/web/app/components/apps/list.tsx +++ b/web/app/components/apps/list.tsx @@ -18,22 +18,18 @@ import { import { parseAsString, useQueryState } from 'nuqs' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import { useContext } from 'use-context-selector' import Input from '@/app/components/base/input' import TabSliderNew from '@/app/components/base/tab-slider-new' import TagFilter from '@/app/components/base/tag-management/filter' import { useStore as useTagStore } from '@/app/components/base/tag-management/store' -import { ToastContext } from '@/app/components/base/toast' import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { useAppContext } from '@/context/app-context' import { useGlobalPublicStore } from '@/context/global-public-context' import { CheckModal } from '@/hooks/use-pay' -import { DSLImportStatus } from '@/models/app' -import { fetchWorkflowOnlineUsers, importAppBundle } from '@/service/apps' +import { fetchWorkflowOnlineUsers } from '@/service/apps' import { useInfiniteAppList } from '@/service/use-apps' import { AppModeEnum } from '@/types/app' -import { getRedirection } from '@/utils/app-redirection' import { cn } from '@/utils/classnames' import AppCard from './app-card' import { AppCardSkeleton } from './app-card-skeleton' @@ -67,10 +63,8 @@ const List: FC = ({ controlRefreshList = 0, }) => { const { t } = useTranslation() - const { notify } = useContext(ToastContext) const { systemFeatures } = useGlobalPublicStore() const router = useRouter() - const { push } = useRouter() const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator, isLoadingCurrentWorkspace } = useAppContext() const showTagManagementModal = useTagStore(s => s.showTagManagementModal) const [activeTab, setActiveTab] = useQueryState( @@ -98,37 +92,10 @@ const List: FC = ({ setShowCreateFromDSLModal(true) }, []) - const [importingBundle, setImportingBundle] = useState(false) - - const handleBundleFileDropped = useCallback(async (file: File) => { - if (importingBundle) - return - setImportingBundle(true) - try { - const response = await importAppBundle({ file }) - const { status, app_id, app_mode } = response - - if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) { - notify({ - type: status === DSLImportStatus.COMPLETED ? 'success' : 'warning', - message: t(status === DSLImportStatus.COMPLETED ? 'newApp.appCreated' : 'newApp.caution', { ns: 'app' }), - }) - localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') - if (app_id) - getRedirection(isCurrentWorkspaceEditor, { id: app_id, mode: app_mode }, push) - } - else { - notify({ type: 'error', message: t('importBundleFailed', { ns: 'app' }) }) - } - } - catch (e) { - const error = e as Error - notify({ type: 'error', message: error.message || t('importBundleFailed', { ns: 'app' }) }) - } - finally { - setImportingBundle(false) - } - }, [importingBundle, isCurrentWorkspaceEditor, notify, push, t]) + const handleBundleFileDropped = useCallback((file: File) => { + setDroppedDSLFile(file) + setShowCreateFromDSLModal(true) + }, []) const { dragging } = useDSLDragDrop({ onDSLFileDropped: handleDSLFileDropped,