mirror of
https://github.com/langgenius/dify.git
synced 2026-11-13 06:41:07 +08:00
fix(web): keep plugin settings open while saving (#44838)
This commit is contained in:
1 parent
b1d2e2983b
commit
caf04b0d7e
3 files changed
+39
-18
No files matched your search
+24
-14
@@ -152,24 +152,27 @@ it('keeps an earlier category rollback separate from the current category sessio
|
||||
)
|
||||
changeCategory(PluginCategoryEnum.tool)
|
||||
await waitFor(() =>
|
||||
expect(settingsTrigger()).toHaveTextContent('plugin.autoUpdate.strategy.fixOnly.name'),
|
||||
expect(getSettings).toHaveBeenCalledWith('/workspaces/current/plugin/auto-upgrade/fetch', {
|
||||
params: { category: PluginCategoryEnum.tool },
|
||||
}),
|
||||
)
|
||||
await user.click(settingsTrigger())
|
||||
expect(strategy('fixOnly')).toBeChecked()
|
||||
await act(async () => {
|
||||
pendingSave.reject(new Error('Save failed'))
|
||||
})
|
||||
expect(strategy('fixOnly')).toBeChecked()
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument())
|
||||
expect(settingsTrigger()).toHaveTextContent('plugin.autoUpdate.strategy.fixOnly.name')
|
||||
await user.click(settingsTrigger())
|
||||
expect(strategy('fixOnly')).toBeChecked()
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument())
|
||||
changeCategory(PluginCategoryEnum.model)
|
||||
await waitFor(() =>
|
||||
expect(settingsTrigger()).toHaveTextContent('plugin.autoUpdate.strategy.disabled.name'),
|
||||
)
|
||||
})
|
||||
|
||||
it('blocks another save while one is pending and rolls back a failed save without losing the open draft', async () => {
|
||||
it('keeps a failed save open for retry and blocks dismissal while pending', async () => {
|
||||
const pendingSave = deferred<void>()
|
||||
saveSettings.mockReturnValue(pendingSave.promise)
|
||||
const user = userEvent.setup()
|
||||
@@ -180,26 +183,32 @@ it('blocks another save while one is pending and rolls back a failed save withou
|
||||
await user.click(settingsTrigger())
|
||||
await user.click(strategy('latest'))
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument())
|
||||
expect(settingsTrigger()).toHaveTextContent('plugin.autoUpdate.strategy.latest.name')
|
||||
const trigger = settingsTrigger()
|
||||
await user.click(trigger)
|
||||
const dialog = screen.getByRole('dialog')
|
||||
const save = screen.getByRole('button', { name: 'common.operation.save' })
|
||||
const cancel = screen.getByRole('button', { name: 'common.operation.cancel' })
|
||||
expect(dialog).toBeVisible()
|
||||
expect(save).toBeDisabled()
|
||||
expect(cancel).toBeDisabled()
|
||||
await user.click(save)
|
||||
await user.keyboard('{Escape}')
|
||||
expect(dialog).toBeVisible()
|
||||
expect(saveSettings).toHaveBeenCalledOnce()
|
||||
await act(async () => {
|
||||
pendingSave.reject(new Error('Save failed'))
|
||||
})
|
||||
await waitFor(() => expect(trigger).toHaveTextContent('plugin.autoUpdate.strategy.disabled.name'))
|
||||
expect(save).toBeEnabled()
|
||||
expect(dialog).toBeVisible()
|
||||
await waitFor(() => expect(save).toBeEnabled())
|
||||
expect(cancel).toBeEnabled()
|
||||
expect(strategy('latest')).toBeChecked()
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
await user.click(cancel)
|
||||
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument())
|
||||
expect(settingsTrigger()).toHaveTextContent('plugin.autoUpdate.strategy.disabled.name')
|
||||
await user.click(settingsTrigger())
|
||||
expect(strategy('disabled')).toBeChecked()
|
||||
expect(saveSettings).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('reports success after the submitted form has closed', async () => {
|
||||
it('closes and reports success only after the save succeeds', async () => {
|
||||
const pendingSave = deferred<void>()
|
||||
saveSettings.mockReturnValue(pendingSave.promise)
|
||||
const success = vi.spyOn(toast, 'success')
|
||||
@@ -209,10 +218,11 @@ it('reports success after the submitted form has closed', async () => {
|
||||
await user.click(settingsTrigger())
|
||||
await user.click(strategy('latest'))
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument())
|
||||
expect(screen.getByRole('dialog')).toBeVisible()
|
||||
expect(success).not.toHaveBeenCalled()
|
||||
await act(async () => {
|
||||
pendingSave.resolve()
|
||||
})
|
||||
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument())
|
||||
await waitFor(() => expect(success).toHaveBeenCalledWith('common.api.actionSuccess'))
|
||||
})
|
||||
@@ -254,7 +254,10 @@ export function UpdateSettingDialogForm({
|
||||
)}
|
||||
</div>
|
||||
<div className="flex h-19 items-center justify-end gap-2 px-6 pt-5 pb-6">
|
||||
<DialogClose render={<Button variant="secondary" className="min-w-18" />}>
|
||||
<DialogClose
|
||||
disabled={isSavePending}
|
||||
render={<Button variant="secondary" className="min-w-18" />}
|
||||
>
|
||||
{t(($) => $['operation.cancel'], { ns: 'common' })}
|
||||
</DialogClose>
|
||||
<Button type="submit" variant="primary" className="min-w-18" disabled={isSavePending}>
|
||||
|
||||
@@ -40,6 +40,7 @@ export function UpdateSettingDialog({ category }: Props) {
|
||||
category,
|
||||
onSuccess: () => {
|
||||
toast.success(t(($) => $['api.actionSuccess'], { ns: 'common' }))
|
||||
actionsRef.current?.close()
|
||||
},
|
||||
})
|
||||
const savedAutoUpgrade = autoUpgradeSetting?.auto_upgrade
|
||||
@@ -62,11 +63,15 @@ export function UpdateSettingDialog({ category }: Props) {
|
||||
: ''
|
||||
const handleSave = (autoUpgrade: AutoUpdateConfig) => {
|
||||
saveAutoUpgrade(autoUpgrade)
|
||||
actionsRef.current?.close()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog actionsRef={actionsRef}>
|
||||
<Dialog
|
||||
actionsRef={actionsRef}
|
||||
onOpenChange={(open, details) => {
|
||||
if (!open && isSavePending && details.reason !== 'imperative-action') details.cancel()
|
||||
}}
|
||||
>
|
||||
<DialogTrigger
|
||||
render={
|
||||
<Button variant="secondary" className="h-8 system-sm-medium">
|
||||
@@ -85,7 +90,10 @@ export function UpdateSettingDialog({ category }: Props) {
|
||||
<DialogTitle>
|
||||
{t(($) => $['autoUpdate.autoUpdateSettings'], { ns: 'plugin' })}
|
||||
</DialogTitle>
|
||||
<DialogHeaderCloseButton aria-label={t(($) => $['operation.close'], { ns: 'common' })} />
|
||||
<DialogHeaderCloseButton
|
||||
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
|
||||
disabled={isSavePending}
|
||||
/>
|
||||
</DialogHeader>
|
||||
{isSettingsLoading && (
|
||||
<DialogBody>
|
||||
|
||||
Reference in new issue
Block a user