Skip to content

dbeaver/pro#4425 [CB] Confirmation for unsaved changes in administration#4439

Open
SychevAndrey wants to merge 10 commits into
develfrom
4425-cb-923-confirmation-for-unsaved-changes-in-administration
Open

dbeaver/pro#4425 [CB] Confirmation for unsaved changes in administration#4439
SychevAndrey wants to merge 10 commits into
develfrom
4425-cb-923-confirmation-for-unsaved-changes-in-administration

Conversation

@SychevAndrey

Copy link
Copy Markdown
Contributor

Shows a unified Save / Don't save / Cancel dialog before you leave any admin page or close a create/edit form, so changes are never lost silently. It's built on a shared confirmUnsavedChanges helper and a useUnsavedChanges hook

To stop the admin drawer from getting out of sync when you press Cancel, TabsState gets an optional controlledSelection mode, where the selected tab follows the actual route instead of moving on click.

controlledSelection is a temporary, scoped flag. Ideally currentTabId should always work this way (a proper controlled component), so the next step is to make that the default and remove the flag

image
Screen.Recording.2026-06-30.at.16.00.01.mov

@codacy-production

codacy-production Bot commented Jun 30, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 13 complexity

Metric Results
Complexity 13

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a unified unsaved-changes confirmation flow across administration screens/forms to prevent silently losing edits, and introduces a temporary “controlled tab selection” mode to keep the admin drawer tabs in sync with route transitions that can be cancelled.

Changes:

  • Introduced shared confirmUnsavedChanges helper and a global UnsavedChangesService + useUnsavedChanges hook to guard route transitions and admin form closures.
  • Updated multiple admin pages/forms to register unsaved state and to use the unified Save / Don’t save / Cancel dialog.
  • Added controlledSelection to TabsState and enabled it for the administration drawer so tab selection follows the actual route.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
webapp/packages/plugin-settings-administration/src/SettingsAdministration.tsx Registers settings editor state with the global unsaved-changes guard and aligns save to return a boolean result.
webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserEdit.tsx Replaces ad-hoc confirmation with shared confirmUnsavedChanges on close.
webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUser.tsx Registers the create-user form with useUnsavedChanges.
webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsTable/TeamEdit.tsx Replaces ad-hoc confirmation with shared confirmUnsavedChanges on close.
webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsTable/CreateTeam.tsx Registers team creation flow with useUnsavedChanges using service-backed state.
webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts Uses confirmUnsavedChanges in wizard route guard instead of custom dialog logic.
webapp/packages/plugin-administration/src/Administration/Administration.tsx Enables controlledSelection for admin drawer tabs to keep selection synced with route.
webapp/packages/core-ui/src/Tabs/TabsState.tsx Adds controlledSelection behavior and route-synced selection updates.
webapp/packages/core-ui/src/Screens/AppScreen/useUnsavedChanges.ts New hook to register/unregister a provider with the global unsaved-changes guard.
webapp/packages/core-ui/src/Screens/AppScreen/UnsavedChangesService.ts New singleton that blocks route transitions and prompts via confirmUnsavedChanges.
webapp/packages/core-ui/src/module.ts Registers UnsavedChangesService as a singleton.
webapp/packages/core-ui/src/index.ts Exports the new service and hook.
webapp/packages/core-ui/src/Form/IFormState.ts Extends form state contract with isSaving to support unsaved-changes UX.
webapp/packages/core-localization/src/locales/en.ts Adds “Don’t save” (and related) localization tokens.
webapp/packages/core-localization/src/locales/fr.ts Adds “Don’t save” (and related) localization tokens.
webapp/packages/core-localization/src/locales/it.ts Adds “Don’t save” and “Changes might be lost” localization tokens.
webapp/packages/core-localization/src/locales/ru.ts Adds “Don’t save” (and related) localization tokens.
webapp/packages/core-localization/src/locales/vi.ts Adds “Don’t save” (and related) localization tokens.
webapp/packages/core-localization/src/locales/zh.ts Adds “Don’t save” (and related) localization tokens.
webapp/packages/core-blocks/src/index.ts Exposes confirmUnsavedChanges from core-blocks.
webapp/packages/core-blocks/src/CommonDialog/confirmUnsavedChanges.ts New shared helper implementing Save / Don’t save / Cancel flow.
webapp/packages/core-blocks/src/CommonDialog/ConfirmationDialog.tsx Adds async onConfirm support so confirm can execute and gate resolution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +40 to +43
/**
* Async action run when the confirm button is pressed. While it runs the confirm button shows a loader and the
* other actions are disabled. The dialog resolves only if it returns `true`; otherwise it is rejected (no confirm).
*/
Comment on lines +131 to +135
if (controlledSelection && isNotNullDefined(currentTabId) && selected !== currentTabId) {
dynamic.store.setSelectedId(currentTabId);
dynamic.selectedId = currentTabId;
}
}, [controlledSelection, currentTabId, selected]);
* Registers the page form with the global unsaved-changes guard for as long as the component is mounted.
* Leaving the page (route transition) prompts a Save / Don't save / Cancel dialog when the provider is changed.
*/
export function useUnsavedChanges(provider: IUnsavedChangesProvider): void {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not introduce new hooks and services that are responsible for showing notification about unsaved changes.

For cases such as driver creation, Identity Providers, Secret Management and and other places where we use options panel, use the same approach we use for options panel in connection and other dialogs. Add handler to the closeTask and check everything there. As general rule, use only handlers for resolving unsaved changes issues, prevent closing by interrupting context.

All other cases should be resolved by using canDeActivate handler. Seems like we have regression in terms of unsaved changes notification when we are leaving server configuration page, you can find and fix this problem first

@SychevAndrey

Copy link
Copy Markdown
Contributor Author

Why I kept confirmUnsavedChanges
It is not a new pattern. It still calls commonDialogService.open(ConfirmationDialog, …) and is invoked from the same canDeActivate / closeTask handlers, interrupting the context on cancel. It only removes the boilerplate inside those handlers.

  1. It removes real duplication. The same code was copy-pasted across many places (Server Config, Domain Manager, edit panels etc.). Now it becomes like that:
if (!(await confirmUnsavedChanges(commonDialogService, formState))) {
  ExecutorInterrupter.interrupt(contexts);
}
  1. It fixes inconsistencies in translation between dialogs and improves UX
  • titles/messages/buttons vary: _ui_save_reminder+ui_are_you_sure_ vs core_blocks_confirmation_dialog_title+administration_drivers_driver_unsaved_changes, confirm button ui_yes vs ui_close vs ui_processing_ok
  • most are two-way (OK/Cancel) where OK silently discards changes — there's no explicit Save; and the message is ambiguous
  • the isSaving guard exists in some copies (Server Config, Domain Manager) and is missing in the edit panels. The helper encodes one correct behavior in one place
  1. Hides the implementation. DialogueStateResult.Resolved, extraAction and other things that might be tricky for a developer to think about every time they want to add a confirmation dialog. It returns a plain boolean, so the caller keeps full control of the interrupt. Enables save-loader, navigate only after.

  2. One shape for three kinds of state. IUnsavedChangesProvider lets FormState, IEditableSettingsSource (Settings/Permissions), and other services be handled identically.

  3. Reasonable defaults, still customizable. Defaults for title/message/buttons live in one place; any call can override title/subTitle/message.

Trade-offs
One extra abstraction.
Risk of becoming a really complex wrapper with many props if we keep adding options. (we can control it)
onConfirm prop added to ConfirmationDialog, so affects all those dialogs.

So, overall, I believe it's a good helper for our case and a nice feature for our users.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants