mirror of
https://github.com/langgenius/dify.git
synced 2026-05-09 12:59:18 +08:00
Merge 6acafc3f52 into 271019006e
This commit is contained in:
commit
98ab55745d
@ -4,6 +4,7 @@ import { TaskStatus } from '@/app/components/plugins/types'
|
||||
import { usePluginTaskStatus } from '../hooks'
|
||||
|
||||
const mockClearTask = vi.fn().mockResolvedValue({})
|
||||
const mockStopAllTask = vi.fn().mockResolvedValue({})
|
||||
const mockRefetch = vi.fn()
|
||||
|
||||
vi.mock('@/service/use-plugins', () => ({
|
||||
@ -28,6 +29,9 @@ vi.mock('@/service/use-plugins', () => ({
|
||||
useMutationClearTaskPlugin: () => ({
|
||||
mutateAsync: mockClearTask,
|
||||
}),
|
||||
useMutationStopAllTaskPlugins: () => ({
|
||||
mutateAsync: mockStopAllTask,
|
||||
}),
|
||||
}))
|
||||
|
||||
describe('usePluginTaskStatus', () => {
|
||||
@ -74,4 +78,13 @@ describe('usePluginTaskStatus', () => {
|
||||
})
|
||||
expect(mockRefetch).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle stop all plugins', async () => {
|
||||
const { result } = renderHook(() => usePluginTaskStatus())
|
||||
|
||||
await result.current.handleStopAllPlugins()
|
||||
|
||||
expect(mockStopAllTask).toHaveBeenCalled()
|
||||
expect(mockRefetch).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -3,7 +3,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { PluginSource, TaskStatus } from '@/app/components/plugins/types'
|
||||
// Import mocked modules
|
||||
import { useMutationClearTaskPlugin, usePluginTaskList } from '@/service/use-plugins'
|
||||
import { useMutationClearTaskPlugin, useMutationStopAllTaskPlugins, usePluginTaskList } from '@/service/use-plugins'
|
||||
import PluginTaskList from '../components/plugin-task-list'
|
||||
import TaskStatusIndicator from '../components/task-status-indicator'
|
||||
import { usePluginTaskStatus } from '../hooks'
|
||||
@ -14,6 +14,7 @@ import PluginTasks from '../index'
|
||||
vi.mock('@/service/use-plugins', () => ({
|
||||
usePluginTaskList: vi.fn(),
|
||||
useMutationClearTaskPlugin: vi.fn(),
|
||||
useMutationStopAllTaskPlugins: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/plugins/install-plugin/base/use-get-icon', () => ({
|
||||
@ -45,6 +46,7 @@ const createMockPlugin = (overrides: Partial<PluginStatus> = {}): PluginStatus =
|
||||
// Helper to setup mock hook returns
|
||||
const setupMocks = (plugins: PluginStatus[] = []) => {
|
||||
const mockMutateAsync = vi.fn().mockResolvedValue({})
|
||||
const mockStopAllMutateAsync = vi.fn().mockResolvedValue({})
|
||||
const mockHandleRefetch = vi.fn()
|
||||
|
||||
vi.mocked(usePluginTaskList).mockReturnValue({
|
||||
@ -58,7 +60,11 @@ const setupMocks = (plugins: PluginStatus[] = []) => {
|
||||
mutateAsync: mockMutateAsync,
|
||||
} as unknown as ReturnType<typeof useMutationClearTaskPlugin>)
|
||||
|
||||
return { mockMutateAsync, mockHandleRefetch }
|
||||
vi.mocked(useMutationStopAllTaskPlugins).mockReturnValue({
|
||||
mutateAsync: mockStopAllMutateAsync,
|
||||
} as unknown as ReturnType<typeof useMutationStopAllTaskPlugins>)
|
||||
|
||||
return { mockMutateAsync, mockStopAllMutateAsync, mockHandleRefetch }
|
||||
}
|
||||
|
||||
const getTaskMenuTrigger = () =>
|
||||
@ -273,6 +279,31 @@ describe('usePluginTaskStatus Hook', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('handleStopAllPlugins', () => {
|
||||
it('should call stop all mutateAsync and handleRefetch', async () => {
|
||||
const { mockStopAllMutateAsync, mockHandleRefetch } = setupMocks([
|
||||
createMockPlugin({ status: TaskStatus.running }),
|
||||
])
|
||||
|
||||
const TestComponent = () => {
|
||||
const { handleStopAllPlugins } = usePluginTaskStatus()
|
||||
return (
|
||||
<button onClick={handleStopAllPlugins}>
|
||||
Stop all
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
render(<TestComponent />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockStopAllMutateAsync).toHaveBeenCalledWith()
|
||||
expect(mockHandleRefetch).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
@ -424,6 +455,7 @@ describe('PluginTaskList Component', () => {
|
||||
onClearAll: vi.fn(),
|
||||
onClearErrors: vi.fn(),
|
||||
onClearSingle: vi.fn(),
|
||||
onStopAll: vi.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -495,6 +527,23 @@ describe('PluginTaskList Component', () => {
|
||||
expect(handleClearAll).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should call onStopAll when stop all button is clicked in running section', () => {
|
||||
const handleStopAll = vi.fn()
|
||||
const runningPlugins = [createMockPlugin({ status: TaskStatus.running })]
|
||||
|
||||
render(
|
||||
<PluginTaskList
|
||||
{...defaultProps}
|
||||
runningPlugins={runningPlugins}
|
||||
onStopAll={handleStopAll}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /task\.stopAll/i }))
|
||||
|
||||
expect(handleStopAll).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should call onClearErrors when clear all button is clicked in error section', () => {
|
||||
const handleClearErrors = vi.fn()
|
||||
const errorPlugins = [createMockPlugin({ status: TaskStatus.failed })]
|
||||
|
||||
@ -57,6 +57,7 @@ describe('PluginTaskList', () => {
|
||||
onClearAll: vi.fn(),
|
||||
onClearErrors: vi.fn(),
|
||||
onClearSingle: vi.fn(),
|
||||
onStopAll: vi.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -180,44 +181,28 @@ describe('PluginTaskList', () => {
|
||||
})
|
||||
|
||||
describe('Running section', () => {
|
||||
it('should not render clear buttons for running plugins', () => {
|
||||
it('should render stop all button for running plugins', () => {
|
||||
render(<PluginTaskList {...defaultProps} runningPlugins={runningPlugins} />)
|
||||
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
// Running section has no headerAction and no onClearSingle
|
||||
expect(screen.getByRole('button', { name: /plugin\.task\.stopAll/ })).toBeInTheDocument()
|
||||
expect(screen.queryByText(/plugin\.task\.clearAll/)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call onStopAll when stop all button is clicked', () => {
|
||||
const onStopAll = vi.fn()
|
||||
render(
|
||||
<PluginTaskList
|
||||
{...defaultProps}
|
||||
runningPlugins={runningPlugins}
|
||||
onStopAll={onStopAll}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /plugin\.task\.stopAll/ }))
|
||||
|
||||
expect(onStopAll).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should show installing hint as status text', () => {
|
||||
render(<PluginTaskList {...defaultProps} runningPlugins={runningPlugins} />)
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ type PluginTaskListProps = {
|
||||
onClearAll: () => void
|
||||
onClearErrors: () => void
|
||||
onClearSingle: (taskId: string, pluginId: string) => void
|
||||
onStopAll: () => void
|
||||
}
|
||||
|
||||
const PluginTaskList: FC<PluginTaskListProps> = ({
|
||||
@ -25,6 +26,7 @@ const PluginTaskList: FC<PluginTaskListProps> = ({
|
||||
onClearAll,
|
||||
onClearErrors,
|
||||
onClearSingle,
|
||||
onStopAll,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useGetLanguage()
|
||||
@ -43,6 +45,16 @@ const PluginTaskList: FC<PluginTaskListProps> = ({
|
||||
<span className="i-ri-loader-2-line h-3.5 w-3.5 animate-spin text-text-accent" />
|
||||
}
|
||||
defaultStatusText={t('task.installingHint', { ns: 'plugin' })}
|
||||
headerAction={(
|
||||
<Button
|
||||
className="shrink-0"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
onClick={onStopAll}
|
||||
>
|
||||
{t('task.stopAll', { ns: 'plugin' })}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
import { TaskStatus } from '@/app/components/plugins/types'
|
||||
import {
|
||||
useMutationClearTaskPlugin,
|
||||
useMutationStopAllTaskPlugins,
|
||||
usePluginTaskList,
|
||||
} from '@/service/use-plugins'
|
||||
|
||||
@ -14,6 +15,7 @@ export const usePluginTaskStatus = () => {
|
||||
handleRefetch,
|
||||
} = usePluginTaskList()
|
||||
const { mutateAsync } = useMutationClearTaskPlugin()
|
||||
const { mutateAsync: mutateStopAllAsync } = useMutationStopAllTaskPlugins()
|
||||
const allPlugins = pluginTasks.map(task => task.plugins.map((plugin) => {
|
||||
return {
|
||||
...plugin,
|
||||
@ -40,6 +42,11 @@ export const usePluginTaskStatus = () => {
|
||||
})
|
||||
handleRefetch()
|
||||
}, [mutateAsync, handleRefetch])
|
||||
|
||||
const handleStopAllPlugins = useCallback(async () => {
|
||||
await mutateStopAllAsync()
|
||||
handleRefetch()
|
||||
}, [mutateStopAllAsync, handleRefetch])
|
||||
const totalPluginsLength = allPlugins.length
|
||||
const runningPluginsLength = runningPlugins.length
|
||||
const errorPluginsLength = errorPlugins.length
|
||||
@ -65,5 +72,6 @@ export const usePluginTaskStatus = () => {
|
||||
isSuccess,
|
||||
isFailed,
|
||||
handleClearErrorPlugin,
|
||||
handleStopAllPlugins,
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +31,7 @@ const PluginTasks = () => {
|
||||
isSuccess,
|
||||
isFailed,
|
||||
handleClearErrorPlugin,
|
||||
handleStopAllPlugins,
|
||||
} = usePluginTaskStatus()
|
||||
const { getIconUrl } = useGetIcon()
|
||||
const canOpenMenu = isFailed || isInstalling || isInstallingWithSuccess || isInstallingWithError || isSuccess
|
||||
@ -81,6 +82,11 @@ const PluginTasks = () => {
|
||||
[clearPluginsAndClose, errorPlugins],
|
||||
)
|
||||
|
||||
const handleStopAll = useCallback(async () => {
|
||||
await handleStopAllPlugins()
|
||||
setOpen(false)
|
||||
}, [handleStopAllPlugins])
|
||||
|
||||
const handleClearSingle = useCallback(
|
||||
(taskId: string, pluginId: string) => clearPluginsAndClose([{ taskId, plugin_unique_identifier: pluginId }]),
|
||||
[clearPluginsAndClose],
|
||||
@ -127,6 +133,7 @@ const PluginTasks = () => {
|
||||
onClearAll={handleClearAll}
|
||||
onClearErrors={handleClearErrors}
|
||||
onClearSingle={handleClearSingle}
|
||||
onStopAll={handleStopAll}
|
||||
/>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@ -248,6 +248,7 @@
|
||||
"task.installingWithError": "Installing {{installingLength}} plugins, {{successLength}} success, {{errorLength}} failed",
|
||||
"task.installingWithSuccess": "Installing {{installingLength}} plugins, {{successLength}} success.",
|
||||
"task.runningPlugins": "Installing Plugins",
|
||||
"task.stopAll": "Stop all",
|
||||
"task.successPlugins": "Successfully Installed Plugins",
|
||||
"upgrade.close": "Close",
|
||||
"upgrade.description": "About to install the following plugin",
|
||||
|
||||
@ -248,6 +248,7 @@
|
||||
"task.installingWithError": "{{installingLength}} 个插件安装中,{{successLength}} 安装成功,{{errorLength}} 安装失败",
|
||||
"task.installingWithSuccess": "{{installingLength}} 个插件安装中,{{successLength}} 安装成功",
|
||||
"task.runningPlugins": "正在安装的插件",
|
||||
"task.stopAll": "停止所有",
|
||||
"task.successPlugins": "安装成功的插件",
|
||||
"upgrade.close": "关闭",
|
||||
"upgrade.description": "即将安装以下插件",
|
||||
|
||||
@ -248,6 +248,7 @@
|
||||
"task.installingWithError": "安裝 {{installingLength}} 個插件,{{successLength}} 成功,{{errorLength}} 失敗",
|
||||
"task.installingWithSuccess": "安裝 {{installingLength}} 個插件,{{successLength}} 成功。",
|
||||
"task.runningPlugins": "Installing Plugins",
|
||||
"task.stopAll": "停止所有",
|
||||
"task.successPlugins": "Successfully Installed Plugins",
|
||||
"upgrade.close": "關閉",
|
||||
"upgrade.description": "即將安裝以下插件",
|
||||
|
||||
@ -560,6 +560,14 @@ export const useMutationClearTaskPlugin = () => {
|
||||
})
|
||||
}
|
||||
|
||||
export const useMutationStopAllTaskPlugins = () => {
|
||||
return useMutation({
|
||||
mutationFn: () => {
|
||||
return post<{ success: boolean }>('/workspaces/current/plugin/tasks/delete_all')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const usePluginManifestInfo = (pluginUID: string) => {
|
||||
return useQuery({
|
||||
enabled: !!pluginUID,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user