Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
1458076
dbeaver/pro#7562 feat: persist data editor filter state after reconnect
SychevAndrey Mar 23, 2026
98de428
dbeaver/pro#7562 feat: update data viewer to persist filter state
SychevAndrey Mar 24, 2026
3e0e86f
dbeaver/pro#7562 feat: enhance constraint resolution in WebSQLDataFilter
SychevAndrey Mar 24, 2026
472815d
dbeaver/pro#7562 refactor: don't use options
SychevAndrey Mar 25, 2026
6b2f4f9
dbeaver/pro#7562 fix: stale constraints in UI
SychevAndrey Mar 25, 2026
52dd2b8
dbeaver/pro#7562 refactor: add method to retrieve pinned column names…
SychevAndrey Mar 25, 2026
0517067
dbeaver/pro#7562 refactor: simplify state validation using schema
SychevAndrey Mar 26, 2026
8038250
dbeaver/pro#7562 refactor: simplify state management in useDataViewer…
SychevAndrey Mar 26, 2026
b897cc2
Merge branch 'devel' into 7562-cb-keep-data-editor-filter-state-after…
SychevAndrey Mar 27, 2026
e3563d4
dbeaver/pro#7562 refactor: move persisted state to IDataViewerPageState
SychevAndrey Mar 27, 2026
1c0eea7
Merge branch '7562-cb-keep-data-editor-filter-state-after-reconnect' …
SychevAndrey Mar 27, 2026
9d26ed9
dbeaver/pro#7562 fix: initial pageState
SychevAndrey Mar 27, 2026
17e733e
dbeaver/pro#7562 refactor: simplify getPinnedColumnNames method in Gr…
SychevAndrey Mar 27, 2026
3ba78e6
dbeaver/pro#7562 refactor: shadowed column renamed to c for clarity
SychevAndrey Mar 27, 2026
a7bd511
dbeaver/pro#7562 refactor: change type of value in IPersistedConstrai…
SychevAndrey Mar 27, 2026
576cfeb
dbeaver/pro#7562 refactor: remove manual state operations
SychevAndrey Mar 27, 2026
f1f5a6c
Merge branch 'devel' into 7562-cb-keep-data-editor-filter-state-after…
SychevAndrey Apr 2, 2026
281c3dd
dbeaver/pro#7562 refactor: introduce DatabasePersistedStateAction for…
SychevAndrey Apr 2, 2026
7cdb1a9
Merge branch 'devel' into 7562-cb-keep-data-editor-filter-state-after…
SychevAndrey Apr 2, 2026
64fe0ae
Merge branch 'devel' into 7562-cb-keep-data-editor-filter-state-after…
SychevAndrey Apr 2, 2026
32cb99d
dbeaver/pro#7562 fix
SychevAndrey Apr 2, 2026
04bec45
dbeaver/pro#7562 refactor: replace reaction with when
SychevAndrey Apr 3, 2026
98d7c92
Revert "dbeaver/pro#7562 feat: enhance constraint resolution in WebSQ…
SychevAndrey Apr 3, 2026
b228b7b
dbeaver/pro#7562 fix: make pinColumns optional
SychevAndrey Apr 3, 2026
094c231
dbeaver/pro#7562 refactor: simplify pinned column handling
SychevAndrey Apr 3, 2026
4229081
dbeaver/pro#7562 refactor: initialize store in DatabasePersistedState…
SychevAndrey Apr 6, 2026
f07eb1e
dbeaver/pro#7562 refactor: rename getColumnNameByPosition to getColum…
SychevAndrey Apr 6, 2026
ccd6c7b
dbeaver/pro#7562 refactor: move DatabasePersistedStateAction to sourc…
SychevAndrey Apr 9, 2026
1c50c22
dbeaver/pro#7562 refactor: replace DatabasePersistedStateAction with …
SychevAndrey Apr 14, 2026
177722d
Merge branch 'devel' into 7562-cb-keep-data-editor-filter-state-durin…
SychevAndrey Apr 14, 2026
993b4a2
Merge branch '7562-cb-keep-data-editor-filter-state-during-session' o…
SychevAndrey Apr 14, 2026
0e0eb79
dbeaver/pro#7562 refactor: simplify view-state persistence
SychevAndrey Apr 15, 2026
154a4eb
dbeaver/pro#7562 refactor: create universal ColumnReference
SychevAndrey Apr 16, 2026
1a87cf9
dbeaver/pro#7562 refactor: simplify persistence flow, remove extra gu…
SychevAndrey Apr 16, 2026
46a093b
dbeaver/pro#7562 refactor: simplify GridViewAction column-ref handlin…
SychevAndrey Apr 16, 2026
da326d7
dbeaver/pro#7562 refactor: per-key reactive persisted state store, dr…
SychevAndrey Apr 17, 2026
d52796b
dbeaver/pro#7562 refactor: simplify setOptions method to use supercla…
SychevAndrey Apr 17, 2026
c314506
Merge branch 'devel' into 7562-cb-keep-data-editor-filter-state-durin…
SychevAndrey Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
* 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.
Expand All @@ -20,7 +20,7 @@ export class DVResultTraceDetailsBootstrap extends Bootstrap {
super();
}

override register() {
override register(): void {
this.dataPresentationService.add({
id: 'result-trace-details-presentation',
type: DataPresentationType.toolsPanel,
Expand All @@ -32,7 +32,7 @@ export class DVResultTraceDetailsBootstrap extends Bootstrap {
if (!isResultSetDataSource(source)) {
return true;
}
const result = (source as ResultSetDataSource<unknown>).getResult(resultIndex);
const result = (source as ResultSetDataSource).getResult(resultIndex);
return !result?.data?.hasDynamicTrace;
},
getPresentationComponent: () => DVResultTraceDetailsPresentation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const DataViewerPanel: ObjectPagePanelComponent<IDataViewerPageState> = o
presentationId,
resultIndex: 0,
valuePresentationId: null,
persistedState: {},
});
} else {
pageState.presentationId = presentationId;
Expand All @@ -47,6 +48,7 @@ export const DataViewerPanel: ObjectPagePanelComponent<IDataViewerPageState> = o
presentationId: '',
resultIndex: 0,
valuePresentationId,
persistedState: {},
});
} else {
pageState.valuePresentationId = valuePresentationId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* 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.
Expand All @@ -20,7 +20,7 @@
import { TableViewerStorageService } from '../TableViewer/TableViewerStorageService.js';
import { useDataViewerModel } from '../useDataViewerModel.js';

export function useDataViewerPanel(tab: ITab<IObjectViewerTabState>) {

Check warning on line 23 in webapp/packages/plugin-data-viewer/src/DataViewerPage/useDataViewerPanel.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Missing return type on function
const dataViewerTableService = useService(DataViewerTableService);
const tableViewerStorageService = useService(TableViewerStorageService);
const navNodeManagerService = useService(NavNodeManagerService);
Expand Down Expand Up @@ -57,18 +57,29 @@

model = dataViewerTableService.create(connectionInfo, node);
tab.handlerState.tableId = model.id;
model.source.setOutdated();
dataViewerDataChangeConfirmationService.trackTableDataUpdate(model.id);

const pageState = dataViewerTabService.page.getState(tab);
let pageState = dataViewerTabService.page.getState(tab);

if (!pageState) {
dataViewerTabService.page.setState(tab, {
resultIndex: 0,
presentationId: '',
valuePresentationId: null,
persistedState: {},
});
pageState = dataViewerTabService.page.getState(tab)!;
}

model.source.loadPersistedState(pageState.persistedState);

if (pageState) {
const presentation = dataPresentationService.get(pageState.presentationId);
const presentation = dataPresentationService.get(pageState.presentationId);

if (presentation?.dataFormat !== undefined) {
model.setDataFormat(presentation.dataFormat);
}
if (presentation?.dataFormat !== undefined) {
model.setDataFormat(presentation.dataFormat);
}

model.source.setOutdated();
dataViewerDataChangeConfirmationService.trackTableDataUpdate(model.id);
}

if (node?.name) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
* 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.
Expand Down Expand Up @@ -63,7 +63,7 @@
});
}

register() {

Check warning on line 66 in webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Missing return type on function
this.connectionsManagerService.onDisconnect.addHandler(this.disconnectHandler.bind(this));
}

Expand Down Expand Up @@ -111,14 +111,19 @@
await initTab();

if (tabInfo.isNewlyCreated) {
trySwitchPage(this.page);
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>) {

Check warning on line 126 in webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

'tab' is defined but never used
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { computed, makeObservable } from 'mobx';
import { action, computed, makeObservable, runInAction } from 'mobx';

import { type DataTypeLogicalOperation, ResultDataFormat, type SqlDataFilterConstraint } from '@cloudbeaver/core-sdk';
import { type DataTypeLogicalOperation, ResultDataFormat, type SqlDataFilterConstraint, type SqlResultColumn } from '@cloudbeaver/core-sdk';

import { DatabaseDataAction } from '../DatabaseDataAction.js';
import type { IDatabaseDataOptions } from '../IDatabaseDataOptions.js';
Expand All @@ -21,10 +21,47 @@ import { IDatabaseDataResult } from '../IDatabaseDataResult.js';
export const IS_NULL_ID = 'IS_NULL';
export const IS_NOT_NULL_ID = 'IS_NOT_NULL';

const CONSTRAINTS_KEY = 'constraints';
const WHERE_FILTER_KEY = 'whereFilter';

export function persistDataFilterConstraints<TOptions extends IDatabaseDataOptions>(
source: IDatabaseDataSource<TOptions, IDatabaseResultSet>,
): void {
const options = source.options;
if (!options) {
return;
}

source.persistedState.set(CONSTRAINTS_KEY, options.constraints.filter(hasConstraintIdentity));
source.persistedState.set(WHERE_FILTER_KEY, options.whereFilter || '');
}

export function applyPersistedDataFilterConstraints<TOptions extends IDatabaseDataOptions>(
source: IDatabaseDataSource<TOptions, IDatabaseResultSet>,
): void {
const options = source.options;
if (!options) {
return;
}

const constraints = source.persistedState.get<SqlDataFilterConstraint[]>(CONSTRAINTS_KEY);
const whereFilter = source.persistedState.get<string>(WHERE_FILTER_KEY);

if (!Array.isArray(constraints) || typeof whereFilter !== 'string') {
return;
}

runInAction(() => {
options.constraints = constraints.map(constraint => ({ ...constraint }));
options.whereFilter = whereFilter;
});
}

@injectable(() => [IDatabaseDataSource, IDatabaseDataResult])
export class DatabaseDataConstraintAction
extends DatabaseDataAction<IDatabaseDataOptions, IDatabaseResultSet>
implements IDatabaseDataConstraintAction<IDatabaseResultSet> {
implements IDatabaseDataConstraintAction<IDatabaseResultSet>
{
static dataFormat = [ResultDataFormat.Resultset, ResultDataFormat.Document];

get supported(): boolean {
Expand Down Expand Up @@ -54,6 +91,16 @@ export class DatabaseDataConstraintAction
makeObservable(this, {
orderConstraints: computed,
filterConstraints: computed,
deleteAll: action,
deleteFilter: action,
deleteFilters: action,
deleteOrders: action,
deleteOrder: action,
deleteDataFilters: action,
deleteData: action,
setWhereFilter: action,
setFilter: action,
setOrder: action,
});
}

Expand Down Expand Up @@ -161,19 +208,19 @@ export class DatabaseDataConstraintAction
this.resetWhereFilter();
}

setWhereFilter(value: string) {
setWhereFilter(value: string): void {
if (!this.source.options) {
throw new Error('Options must be provided');
}

this.source.options.whereFilter = value;
}

resetWhereFilter() {
resetWhereFilter(): void {
this.setWhereFilter('');
}

setFilter(attributePosition: number, operator: string, value?: any): void {
setFilter(attributePosition: number, operator: string, value?: unknown): void {
if (!this.source.options) {
throw new Error('Options must be provided');
}
Expand All @@ -182,6 +229,7 @@ export class DatabaseDataConstraintAction

if (currentConstraint) {
currentConstraint.operator = operator;
currentConstraint.attributeName = this.getColumnNameAt(attributePosition);
if (value !== undefined) {
currentConstraint.value = value;
} else if (currentConstraint.value !== undefined) {
Expand All @@ -192,6 +240,7 @@ export class DatabaseDataConstraintAction

const constraint: SqlDataFilterConstraint = {
attributePosition,
attributeName: this.getColumnNameAt(attributePosition),
operator,
};

Expand Down Expand Up @@ -219,6 +268,7 @@ export class DatabaseDataConstraintAction
if (!resetOrder) {
this.source.options.constraints.push({
attributePosition,
attributeName: this.getColumnNameAt(attributePosition),
orderPosition: this.getMaxOrderPosition(),
orderAsc: order === EOrder.asc,
});
Expand Down Expand Up @@ -257,38 +307,77 @@ export class DatabaseDataConstraintAction
override updateResult(result: IDatabaseResultSet): void {
updateConstraintsForResult(this.source, result);
}

private getColumnNameAt(colIdx: number): string | undefined {
return this.result.data?.columns?.find(c => c.position === colIdx)?.name;
}
}

function updateConstraintsForResult(source: IDatabaseDataSource<IDatabaseDataOptions, IDatabaseResultSet>, result: IDatabaseResultSet) {
if (!source.options) {
return;
}

for (const constraint of source.options.constraints) {
const prevColumn = result.data?.columns?.find(column => column.position === constraint.attributePosition);
const columns = result.data?.columns ?? [];

if (!prevColumn) {
return;
}
if (columns.length === 0) {
return;
}

let column = result.data?.columns?.find(column => column.position === prevColumn.position);
runInAction(() => {
for (const constraint of source.options!.constraints) {
if (!hasConstraintIdentity(constraint)) {
resetDataFilterState(source);
return;
}

if (!column || column.label !== prevColumn.label) {
column = result.data?.columns?.find(column => column.label === prevColumn.label);
}
const initialPosition = constraint.attributePosition;
const initialName = constraint.attributeName;

const resolvedColumn = resolveConstraintColumn(columns, initialName, initialPosition);

if (!resolvedColumn) {
resetDataFilterState(source);
return;
}

constraint.attributeName = resolvedColumn.name;
constraint.attributePosition = resolvedColumn.position;

if (column && prevColumn.position !== column.position) {
const prevConstraint = source.prevOptions?.constraints.find(
prevConstraint => prevConstraint.attributePosition === constraint.attributePosition,
prevConstraint => prevConstraint.attributePosition === initialPosition && prevConstraint.attributeName === initialName,
);

constraint.attributePosition = column.position;

if (prevConstraint) {
prevConstraint.attributeName = constraint.attributeName;
prevConstraint.attributePosition = constraint.attributePosition;
}
}
}
});
}

function resetDataFilterState(source: IDatabaseDataSource<IDatabaseDataOptions, IDatabaseResultSet>): void {
source.options!.constraints = [];
source.options!.whereFilter = '';
source.persistedState.delete(CONSTRAINTS_KEY);
source.persistedState.delete(WHERE_FILTER_KEY);
}

function resolveConstraintColumn(
columns: SqlResultColumn[],
attributeName: string,
attributePosition: number,
): (SqlResultColumn & { name: string; position: number }) | undefined {
return columns.find(
(column): column is SqlResultColumn & { name: string; position: number } =>
typeof column.name === 'string' && typeof column.position === 'number' && column.position === attributePosition && column.name === attributeName,
);
}

function hasConstraintIdentity(
constraint: SqlDataFilterConstraint,
): constraint is SqlDataFilterConstraint & { attributeName: string; attributePosition: number } {
return typeof constraint.attributeName === 'string' && constraint.attributeName.length > 0 && typeof constraint.attributePosition === 'number';
}

export function nullOperationsFilter(operation: DataTypeLogicalOperation): boolean {
Expand All @@ -306,12 +395,12 @@ export function getNextOrder(order: Order): Order {
}
}

export function wrapOperationArgument(operationId: string, argument: any): string {
export function wrapOperationArgument(operationId: string, argument: unknown): string {
if (operationId === 'LIKE') {
return `%${argument}%`;
}

return argument;
return String(argument);
}

export function isFilterConstraint(constraint: SqlDataFilterConstraint): boolean {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
* 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.
Expand Down
Loading
Loading