mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
* dbeaver/pro#7562 feat: persist data editor filter state after reconnect
* dbeaver/pro#7562 feat: update data viewer to persist filter state
* dbeaver/pro#7562 feat: enhance constraint resolution in WebSQLDataFilter
allow using attrName without attrPosition
* dbeaver/pro#7562 refactor: don't use options
replaced private restorePendingState() with public restoreViewState(pinnedColumnNames, columnOrderNames?) that takes data directly as arguments
* dbeaver/pro#7562 fix: stale constraints in UI
bug with skipping constraints if any without a position.
if (!prevColumn) {
return;
}
* dbeaver/pro#7562 refactor: add method to retrieve pinned column names in GridViewAction
* dbeaver/pro#7562 refactor: simplify state validation using schema
* dbeaver/pro#7562 refactor: simplify state management in useDataViewerPanel
* dbeaver/pro#7562 refactor: move persisted state to IDataViewerPageState
* dbeaver/pro#7562 fix: initial pageState
* dbeaver/pro#7562 refactor: simplify getPinnedColumnNames method in GridViewAction
* dbeaver/pro#7562 refactor: shadowed column renamed to c for clarity
* dbeaver/pro#7562 refactor: change type of value in IPersistedConstraint to unknown
* dbeaver/pro#7562 refactor: remove manual state operations
* dbeaver/pro#7562 refactor: introduce DatabasePersistedStateAction for centralized state persistence
* dbeaver/pro#7562 fix
* dbeaver/pro#7562 refactor: replace reaction with when
* Revert "dbeaver/pro#7562 feat: enhance constraint resolution in WebSQLDataFilter"
This reverts commit 3e0e86f4b2.
Last test showed that we don't need any additional logic on BE. It handles all constraints properly.
* dbeaver/pro#7562 fix: make pinColumns optional
* dbeaver/pro#7562 refactor: simplify pinned column handling
* dbeaver/pro#7562 refactor: initialize store in DatabasePersistedStateAction constructor as observable
* dbeaver/pro#7562 refactor: rename getColumnNameByPosition to getColumnNameAt for clarity
* dbeaver/pro#7562 refactor: move DatabasePersistedStateAction to source level, simplify persisted state sync
- Make DatabasePersistedStateAction a standalone class on DatabaseDataSource instead of per-result action
- Convert options to getter
- Auto-sync constraints to persisted state via autorun + action batching
- Restore view state (pinned columns, column order) in GridViewAction constructor
* dbeaver/pro#7562 refactor: replace DatabasePersistedStateAction with DatabasePersistedStateStore and update related references
* dbeaver/pro#7562 refactor: simplify view-state persistence
- DatabasePersistedStateStore: pure KV (observable.ref + actions)
- DatabaseDataSource: add loadPersistedState() + onPersistedStateLoaded() hook
- DatabaseDataConstraintAction: own the constraint persist
- ResultSetDataSource: thin dispatcher
- GridViewAction: columnsOrder/pinnedColumns become computed getters
* dbeaver/pro#7562 refactor: create universal ColumnReference
* dbeaver/pro#7562 refactor: simplify persistence flow, remove extra guards, simplify persisted data filter state handling
* dbeaver/pro#7562 refactor: simplify GridViewAction column-ref handling and layout resolution
* dbeaver/pro#7562 refactor: per-key reactive persisted state store, drop constraint persistence hacks
* dbeaver/pro#7562 refactor: simplify setOptions method to use superclass implementation
* dbeaver/pro#7562 add missing fields
---------
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: alex <48489896+devnaumov@users.noreply.github.com>
157 lines
5.5 KiB
TypeScript
157 lines
5.5 KiB
TypeScript
/*
|
|
* CloudBeaver - Cloud Database Manager
|
|
* Copyright (C) 2020-2026 DBeaver Corp and others
|
|
*
|
|
* Licensed under the Apache License, Version 2.0.
|
|
* you may not use this file except in compliance with the License.
|
|
*/
|
|
import { importLazyComponent } from '@cloudbeaver/core-blocks';
|
|
import { ConnectionInfoResource, ConnectionsManagerService, type IConnectionExecutorData } from '@cloudbeaver/core-connections';
|
|
import { injectable } from '@cloudbeaver/core-di';
|
|
import { NotificationService } from '@cloudbeaver/core-events';
|
|
import { ExecutorInterrupter, type IExecutionContextProvider } from '@cloudbeaver/core-executor';
|
|
import { type INodeNavigationData, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree';
|
|
import { resourceKeyList } from '@cloudbeaver/core-resource';
|
|
import { type ITab, NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs';
|
|
import {
|
|
DBObjectPageService,
|
|
type IObjectViewerTabState,
|
|
isObjectViewerTab,
|
|
ObjectPage,
|
|
ObjectViewerTabService,
|
|
} from '@cloudbeaver/plugin-object-viewer';
|
|
|
|
import type { IDataViewerPageState } from './IDataViewerPageState.js';
|
|
import { TableViewerStorageService } from './TableViewer/TableViewerStorageService.js';
|
|
|
|
const DataViewerTab = importLazyComponent(() => import('./DataViewerPage/DataViewerTab.js').then(module => module.DataViewerTab));
|
|
const DataViewerPanel = importLazyComponent(() => import('./DataViewerPage/DataViewerPanel.js').then(module => module.DataViewerPanel));
|
|
|
|
@injectable(() => [
|
|
NavNodeManagerService,
|
|
ObjectViewerTabService,
|
|
DBObjectPageService,
|
|
NotificationService,
|
|
ConnectionsManagerService,
|
|
NavigationTabsService,
|
|
ConnectionInfoResource,
|
|
TableViewerStorageService,
|
|
])
|
|
export class DataViewerTabService {
|
|
readonly page: ObjectPage<IDataViewerPageState>;
|
|
|
|
constructor(
|
|
private readonly navNodeManagerService: NavNodeManagerService,
|
|
private readonly objectViewerTabService: ObjectViewerTabService,
|
|
private readonly dbObjectPageService: DBObjectPageService,
|
|
private readonly notificationService: NotificationService,
|
|
private readonly connectionsManagerService: ConnectionsManagerService,
|
|
private readonly navigationTabsService: NavigationTabsService,
|
|
private readonly connectionInfoResource: ConnectionInfoResource,
|
|
private readonly tableViewerStorageService: TableViewerStorageService,
|
|
) {
|
|
this.page = this.dbObjectPageService.register({
|
|
key: 'data_viewer_data',
|
|
priority: 2,
|
|
order: 2,
|
|
getTabComponent: () => DataViewerTab,
|
|
getPanelComponent: () => DataViewerPanel,
|
|
onRestore: this.handleTabRestore.bind(this),
|
|
onUnload: this.handleTabClose.bind(this),
|
|
canClose: this.handleTabCanClose.bind(this),
|
|
onClose: this.handleTabClose.bind(this),
|
|
});
|
|
}
|
|
|
|
register() {
|
|
this.connectionsManagerService.onDisconnect.addHandler(this.disconnectHandler.bind(this));
|
|
}
|
|
|
|
registerTabHandler(): void {
|
|
this.navNodeManagerService.navigator.addHandler(this.navigationHandler.bind(this));
|
|
}
|
|
|
|
private async disconnectHandler(data: IConnectionExecutorData, contexts: IExecutionContextProvider<IConnectionExecutorData>) {
|
|
const connectionsKey = resourceKeyList(data.connections);
|
|
const tabs = Array.from(
|
|
this.navigationTabsService.findTabs(
|
|
isObjectViewerTab(tab => {
|
|
if (!tab.handlerState.connectionKey) {
|
|
return false;
|
|
}
|
|
return this.connectionInfoResource.isIntersect(connectionsKey, tab.handlerState.connectionKey);
|
|
}),
|
|
),
|
|
);
|
|
|
|
for (const tab of tabs) {
|
|
if (data.state === 'before') {
|
|
const canDisconnect = await this.handleTabCanClose(tab);
|
|
|
|
if (!canDisconnect) {
|
|
ExecutorInterrupter.interrupt(contexts);
|
|
return;
|
|
}
|
|
} else if (isObjectViewerTab(tab) && tab.handlerState.tableId) {
|
|
await this.disposeTableModel(tab.handlerState.tableId);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async navigationHandler(data: INodeNavigationData, contexts: IExecutionContextProvider<INodeNavigationData>) {
|
|
try {
|
|
const { nodeInfo, tabInfo, initTab, trySwitchPage } = contexts.getContext(this.objectViewerTabService.objectViewerTabContext);
|
|
|
|
const node = await this.navNodeManagerService.loadNode(nodeInfo);
|
|
|
|
if (!this.navNodeManagerService.isNodeHasData(node)) {
|
|
return;
|
|
}
|
|
|
|
await initTab();
|
|
|
|
if (tabInfo.isNewlyCreated) {
|
|
trySwitchPage(this.page, {
|
|
resultIndex: 0,
|
|
presentationId: '',
|
|
valuePresentationId: null,
|
|
persistedState: {},
|
|
});
|
|
}
|
|
} catch (exception: any) {
|
|
this.notificationService.logException(exception, 'Data Editor Error', 'Error in Data Editor while processing action with database node');
|
|
}
|
|
}
|
|
|
|
private handleTabRestore(tab: ITab<IObjectViewerTabState>) {
|
|
return true;
|
|
}
|
|
|
|
private async handleTabCanClose(tab: ITab<IObjectViewerTabState>): Promise<boolean> {
|
|
const model = this.tableViewerStorageService.get(tab.handlerState.tableId || '');
|
|
|
|
if (model) {
|
|
return await model.source.canSafelyDispose();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private async handleTabClose(tab: ITab<IObjectViewerTabState>) {
|
|
if (tab.handlerState.tableId) {
|
|
await this.disposeTableModel(tab.handlerState.tableId);
|
|
}
|
|
}
|
|
|
|
private async disposeTableModel(tableId: string) {
|
|
if (tableId) {
|
|
const model = this.tableViewerStorageService.get(tableId);
|
|
|
|
if (model) {
|
|
await model.dispose();
|
|
this.tableViewerStorageService.remove(tableId);
|
|
}
|
|
}
|
|
}
|
|
}
|