Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion webapp/common-typescript/@dbeaver/js-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
"vitest": "^3"
},
"dependencies": {
"async-mutex": "^0"
"async-mutex": "^0",
"p-debounce": "^4.0.0",
"p-memoize": "^8.0.0",
"quick-lru": "^7.1.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/

export { default as debouncePromise } from 'p-debounce';
10 changes: 10 additions & 0 deletions webapp/common-typescript/@dbeaver/js-helpers/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/

export * from './debouncePromise.js';
export * from './isDefined.js';
export * from './isNotNullDefined.js';
export * from './memoizeLast.js';
export * from './mutex.js';
19 changes: 19 additions & 0 deletions webapp/common-typescript/@dbeaver/js-helpers/src/memoizeLast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 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 pMemoize from 'p-memoize';
import QuickLRU from 'quick-lru';

const cacheKey = (args: unknown[]) => JSON.stringify(args);

export function memoizeLast<T extends (...a: any[]) => Promise<any>>(fn: T) {

Check warning on line 14 in webapp/common-typescript/@dbeaver/js-helpers/src/memoizeLast.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Missing return type on function
return pMemoize(fn, {
cache: new QuickLRU({ maxSize: 5 }),
cacheKey,
});
}
1 change: 1 addition & 0 deletions webapp/packages/core-blocks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@

export * from './AuthenticationProviderLoader.js';
export * from './useAuthenticationAction.js';
export * from './CommonDialog/CommonDialog/CommonDialogBody.js';

Check failure on line 13 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/CommonDialog/CommonDialogFooter.js';

Check failure on line 14 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/CommonDialog/CommonDialogHeader.js';

Check failure on line 15 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/CommonDialog/CommonDialogWrapper.js';

Check failure on line 16 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/ConfirmationDialog.js';

Check failure on line 17 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export { default as ConfirmationDialogStyles } from './CommonDialog/ConfirmationDialog.module.css';
export * from './CommonDialog/ConfirmationDialogDelete.js';

Check failure on line 19 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/RenameDialog.js';

Check failure on line 20 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/DialogsPortal.js';

Check failure on line 21 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()

export * from './ErrorDetailsDialog/ErrorDetailsDialog.js';

Check failure on line 23 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()

export * from './ComponentsRegistry/CRegistryLoader.js';
export * from './ComponentsRegistry/registry.js';

Check failure on line 26 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './ComponentsRegistry/CRegistryList.js';
export * from './ComponentsRegistry/IComponentsTreeNodeValidator.js';
export * from './ComponentsRegistry/useParentProps.js';
Expand All @@ -34,6 +34,7 @@
export * from './ErrorBoundary.js';
export * from './Icon.js';
export * from './useHotkeys.js';
export * from './useSync.js';

export * from './ItemList/ItemList.js';
export * from './ItemList/ItemListSearch.js';
Expand Down
59 changes: 59 additions & 0 deletions webapp/packages/core-blocks/src/useSync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 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 { mutex } from '@dbeaver/js-helpers';
import { useEffect, useState } from 'react';
import { useObjectRef } from './useObjectRef.js';

interface ISyncHook {
markOutdated(): void;
}

export function useSync(callback: () => void | Promise<void>, canSync = true): ISyncHook {
const [syncMutex] = useState(() => new mutex.Mutex());
const [outdated, setOutdated] = useState(false);
const [delayedOutdated, setDelayedOutdated] = useState(false);

function markOutdated() {
if (syncMutex.isLocked()) {
setDelayedOutdated(true);
} else {
setOutdated(true);
}
}

function markUpdated() {
if (delayedOutdated) {
setDelayedOutdated(false);
setOutdated(true);
} else {
setOutdated(false);
}
}

const data = useObjectRef(() => ({
markOutdated,
markUpdated,
}));

useEffect(() => {
if (outdated && !syncMutex.isLocked() && canSync) {
syncMutex
.runExclusive(async () => {
try {
await callback();
} finally {
data.markUpdated();
}
})
.catch(error => console.error(error));
}
});

return data;
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
type ISqlEditorTabState,
SQL_EDITOR_TAB_STATE_SCHEMA,
SqlDataSourceService,
SqlEditorModelService,
SqlEditorService,
SqlResultTabsService,
} from '@cloudbeaver/plugin-sql-editor';
Expand All @@ -67,6 +68,7 @@
ConnectionsManagerService,
ContainerResource,
CommonDialogService,
SqlEditorModelService,
])
export class SqlEditorTabService extends Bootstrap {
get sqlEditorTabs(): ITab<ISqlEditorTabState>[] {
Expand All @@ -89,6 +91,7 @@
private readonly connectionsManagerService: ConnectionsManagerService,
private readonly containerResource: ContainerResource,
private readonly commonDialogService: CommonDialogService,
private readonly sqlEditorModelService: SqlEditorModelService,
) {
super();

Expand Down Expand Up @@ -305,6 +308,7 @@

private async handleTabRestore(tab: ITab<ISqlEditorTabState>): Promise<boolean> {
if (!SQL_EDITOR_TAB_STATE_SCHEMA.safeParse(tab.handlerState).success) {
await this.sqlEditorModelService.destroy(tab.handlerState.editorId);
await this.sqlDataSourceService.destroy(tab.handlerState.editorId);
return false;
}
Expand Down Expand Up @@ -400,7 +404,7 @@
return true;
}

async setConnectionId(tab: ITab<ISqlEditorTabState>, connectionKey: IConnectionInfoParams, catalogId?: string, schemaId?: string) {

Check warning on line 407 in webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Missing return type on function
const state = await this.sqlEditorService.setConnection(tab.handlerState, connectionKey, catalogId, schemaId);

if (state) {
Expand Down Expand Up @@ -531,12 +535,14 @@
}

private async handleTabUnload(editorTab: ITab<ISqlEditorTabState>) {
await this.sqlEditorModelService.unload(editorTab.handlerState.editorId);
await this.sqlDataSourceService.unload(editorTab.handlerState.editorId);

this.sqlResultTabsService.removeResultTabs(editorTab.handlerState);
}

private async handleTabCloseSilent(editorTab: ITab<ISqlEditorTabState>) {
await this.sqlEditorModelService.destroySilent(editorTab.handlerState.editorId);
const dataSource = this.sqlDataSourceService.get(editorTab.handlerState.editorId);

if (dataSource?.executionContext) {
Expand All @@ -548,6 +554,8 @@
}

private async handleTabClose(editorTab: ITab<ISqlEditorTabState>) {
await this.sqlEditorModelService.destroy(editorTab.handlerState.editorId);

const dataSource = this.sqlDataSourceService.get(editorTab.handlerState.editorId);

if (dataSource?.executionContext) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2025 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 { observer } from 'mobx-react-lite';
import { forwardRef } from 'react';
import { importLazyComponent } from '@cloudbeaver/core-blocks';

import { ComplexLoader, createComplexLoader } from '@cloudbeaver/core-blocks';
import type { IDefaultExtensions, IEditorProps, IEditorRef } from '@cloudbeaver/plugin-codemirror6';

const loader = createComplexLoader(async function loader() {
const { SQLCodeEditor } = await import('./SQLCodeEditor.js');
return { SQLCodeEditor };
});

export const SQLCodeEditorLoader = observer<IEditorProps & IDefaultExtensions, IEditorRef>(
forwardRef(function SQLCodeEditorLoader(props, ref) {
return <ComplexLoader loader={loader}>{({ SQLCodeEditor }) => <SQLCodeEditor {...props} ref={ref} />}</ComplexLoader>;
}),
);
export const SQLCodeEditorLoader = importLazyComponent(() => import('./SQLCodeEditor.js').then(m => m.SQLCodeEditor));
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ export const SQLCodeEditorPanel: TabContainerPanelComponent<ISqlEditorModeProps>
});

function applyIncoming() {
data.dataSource?.applyIncoming();
data.model.dataSource?.applyIncoming();
}

function keepCurrent() {
data.dataSource?.keepCurrent();
data.model.dataSource?.keepCurrent();
}

return (
Expand All @@ -99,8 +99,8 @@ export const SQLCodeEditorPanel: TabContainerPanelComponent<ISqlEditorModeProps>
ref={setEditorRef}
getValue={() => data.value}
cursor={{
anchor: data.cursor.anchor,
head: data.cursor.head,
anchor: data.model.cursor.anchor,
head: data.model.cursor.head,
}}
incomingValue={data.incomingValue}
extensions={extensions}
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-2025 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 SQLCodeEditorPanelService {
key: 'sql-editor',
icon: '/icons/sql_script_sm.svg',
name: 'sql_editor_script_editor',
isHidden: (_, props) => props?.data.dataSource?.hasFeature(ESqlDataSourceFeatures.script) !== true,
isHidden: (_, props) => props?.data.model.dataSource?.hasFeature(ESqlDataSourceFeatures.script) !== true,
panel: () => SQLCodeEditorPanel,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@

export const ON_QUERY_CHANGE_SOURCE = 'QueryChange';

export function useSQLCodeEditorPanel(data: ISQLEditorData, editor: IEditor) {

Check warning on line 25 in webapp/packages/plugin-sql-editor-new/src/SQLEditor/SQLCodeEditorPanel/useSQLCodeEditorPanel.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Missing return type on function
const state: State = useObservableRef(
() => ({
highlightActiveQuery() {
this.editor.clearActiveQueryHighlight();

const segment = this.data.activeSegment;
const segment = this.data.model.cursorSegment;

if (segment) {
this.editor.highlightActiveQuery(segment.begin, segment.end);
Expand All @@ -46,13 +46,13 @@
{ editor, data },
);

const updateHighlight = useCallback(

Check warning on line 49 in webapp/packages/plugin-sql-editor-new/src/SQLEditor/SQLCodeEditorPanel/useSQLCodeEditorPanel.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead
throttle(() => state.highlightActiveQuery(), 1000),
[state],
);

useExecutor({
executor: data.onUpdate,
executor: data.model.onUpdate,
handlers: [updateHighlight],
});

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-2025 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 @@ -28,6 +28,7 @@
const SQL_EDITOR_COMPARTMENT = new Compartment();

export function useSqlDialectExtension(dialectInfo: SqlDialectInfo | undefined): [Compartment, Extension] {
//TODO: probably we need to refactor it to lazy approach without triggering suspense
const { SQLDialect, SQL_EDITOR } = useComplexLoader(codemirrorComplexLoader);
const loader = getDialectLoader(dialectInfo?.name);
const dialect = useComplexLoader(loader);
Expand All @@ -54,7 +55,7 @@
dialect: dialectInner,
}),
];
}, [dialect, dialectInfo]);

Check warning on line 58 in webapp/packages/plugin-sql-editor-new/src/SQLEditor/useSqlDialectExtension.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

React Hook useMemo has missing dependencies: 'SQLDialect' and 'SQL_EDITOR'. Either include them or remove the dependency array
}

function getDialectLoader(name?: string): IComplexLoaderData<SQLDialect> {
Expand Down
1 change: 1 addition & 0 deletions webapp/packages/plugin-sql-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@cloudbeaver/plugin-codemirror6": "workspace:*",
"@cloudbeaver/plugin-data-viewer": "workspace:*",
"@cloudbeaver/plugin-navigation-tabs": "workspace:*",
"@dbeaver/js-helpers": "workspace:^",
"mobx": "^6",
"mobx-react-lite": "^4",
"react": "^19",
Expand Down
41 changes: 8 additions & 33 deletions webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,6 @@ export class MenuBootstrap extends Bootstrap {
id: 'sql-editor-actions-more',
actions: [ACTION_DOWNLOAD, ACTION_UPLOAD],
contexts: [DATA_CONTEXT_SQL_EDITOR_DATA, DATA_CONTEXT_SQL_EDITOR_STATE],
isHidden: context => {
const data = context.get(DATA_CONTEXT_SQL_EDITOR_DATA)!;

return data.activeSegmentMode.activeSegmentMode;
},
isDisabled: (context, action) => {
const state = context.get(DATA_CONTEXT_SQL_EDITOR_STATE)!;

Expand Down Expand Up @@ -217,10 +212,7 @@ export class MenuBootstrap extends Bootstrap {
this.menuService.addCreator({
menus: [SQL_EDITOR_ACTIONS_MENU],
contexts: [DATA_CONTEXT_SQL_EDITOR_DATA, DATA_CONTEXT_SQL_EDITOR_STATE],
getItems: (context, items) => [
...items,
...EXECUTIONS_ACTIONS,
],
getItems: (context, items) => [...items, ...EXECUTIONS_ACTIONS],
});

this.keyBindingService.addKeyBindingHandler({
Expand All @@ -242,12 +234,7 @@ export class MenuBootstrap extends Bootstrap {

this.actionService.addHandler({
id: 'sql-editor-actions',
actions: [
...EXECUTIONS_ACTIONS,
ACTION_SQL_EDITOR_FORMAT,
ACTION_REDO,
ACTION_UNDO,
],
actions: [...EXECUTIONS_ACTIONS, ACTION_SQL_EDITOR_FORMAT, ACTION_REDO, ACTION_UNDO],
contexts: [DATA_CONTEXT_SQL_EDITOR_DATA],
isActionApplicable: (contexts, action): boolean => {
const sqlEditorData = contexts.get(DATA_CONTEXT_SQL_EDITOR_DATA)!;
Expand All @@ -256,25 +243,21 @@ export class MenuBootstrap extends Bootstrap {
return false;
}

if (
!sqlEditorData.isExecutionAllowed &&
EXECUTIONS_ACTIONS.includes(action)
) {
if (!sqlEditorData.isExecutionAllowed && EXECUTIONS_ACTIONS.includes(action)) {
return false;
}

if (action === ACTION_SQL_EDITOR_FORMAT) {
return !!sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.script) && !sqlEditorData.activeSegmentMode.activeSegmentMode;
return !!sqlEditorData.model.dataSource?.hasFeature(ESqlDataSourceFeatures.script);
}

if (action === ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN) {
return !!sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.query) &&
!!sqlEditorData.dialect?.supportsExplainExecutionPlan;
return !!sqlEditorData.model.dataSource?.hasFeature(ESqlDataSourceFeatures.query) && !!sqlEditorData.dialect?.supportsExplainExecutionPlan;
}

// TODO we have to add check for output action ?
if (
!sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.query) &&
!sqlEditorData.model.dataSource?.hasFeature(ESqlDataSourceFeatures.query) &&
[ACTION_SQL_EDITOR_EXECUTE, ACTION_SQL_EDITOR_EXECUTE_NEW, ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN].includes(action)
) {
return false;
Expand Down Expand Up @@ -402,24 +385,16 @@ export class MenuBootstrap extends Bootstrap {
data.executeQueryNewTab();
break;
case ACTION_SQL_EDITOR_EXECUTE_SCRIPT:
if (data.activeSegmentMode.activeSegmentMode) {
return;
}

data.executeScript();
break;
case ACTION_SQL_EDITOR_FORMAT:
if (data.activeSegmentMode.activeSegmentMode) {
return;
}

data.formatScript();
break;
case ACTION_UNDO:
data.dataSource?.history.undo();
data.model.dataSource?.history.undo();
break;
case ACTION_REDO:
data.dataSource?.history.redo();
data.model.dataSource?.history.redo();
break;
case ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN:
data.showExecutionPlan();
Expand Down
Loading
Loading