[CB] SQL Query generation: front-end part (#4132)

* dbeaver/pro#8048 pass commit message validation

* fixes json + translations

* fixes gis generation

* uses format action to get text value for sql generators
This commit is contained in:
sergeyteleshev
2026-02-11 18:16:38 +01:00
committed by GitHub
parent a2d336166b
commit 2719f53655
47 changed files with 588 additions and 82 deletions
@@ -32,6 +32,7 @@
"@cloudbeaver/core-navigation-tree": "workspace:*",
"@cloudbeaver/core-resource": "workspace:*",
"@cloudbeaver/core-sdk": "workspace:*",
"@cloudbeaver/core-utils": "workspace:*",
"@cloudbeaver/core-view": "workspace:*",
"@cloudbeaver/plugin-codemirror6": "workspace:*",
"@cloudbeaver/plugin-data-viewer": "workspace:*",
@@ -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.
@@ -42,6 +42,7 @@ export class GeneratorMenuBootstrap extends Bootstrap {
const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!;
const presentation = context.get(DATA_CONTEXT_DV_PRESENTATION);
return (
// TODO add more proper way to define to what features it should be added https://github.com/dbeaver/pro/issues/8299
!model.isReadonly(resultIndex) &&
model.source.getResult(resultIndex)?.dataFormat === ResultDataFormat.Resultset &&
!presentation?.readonly &&
@@ -5,9 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observable } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useEffect } from 'react';
import {
Button,
@@ -15,12 +13,8 @@ import {
CommonDialogFooter,
CommonDialogHeader,
CommonDialogWrapper,
ErrorMessage,
Loader,
s,
useClipboard,
useErrorDetails,
useObservableRef,
useResource,
useS,
useTranslate,
@@ -28,16 +22,14 @@ import {
import { ConnectionDialectResource, ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
import type { DialogComponentProps } from '@cloudbeaver/core-dialogs';
import { GQLErrorCatcher } from '@cloudbeaver/core-sdk';
import { useCodemirrorExtensions } from '@cloudbeaver/plugin-codemirror6';
import { SQLCodeEditor, useSqlDialectExtension } from '@cloudbeaver/plugin-sql-editor-codemirror';
import style from './GeneratedSqlDialog.module.css';
import { SqlGeneratorsResource } from './SqlGeneratorsResource.js';
interface Payload {
generatorId: string;
pathId: string;
nodeId: string;
query: string;
}
export const GeneratedSqlDialog = observer<DialogComponentProps<Payload>>(function GeneratedSqlDialog({ rejectDialog, payload }) {
@@ -45,34 +37,9 @@ export const GeneratedSqlDialog = observer<DialogComponentProps<Payload>>(functi
const copy = useClipboard();
const styles = useS(style);
const sqlGeneratorsResource = useService(SqlGeneratorsResource);
const connectionInfoResource = useService(ConnectionInfoResource);
const connection = connectionInfoResource.getConnectionForNode(payload.pathId);
const connection = connectionInfoResource.getConnectionForNode(payload.nodeId);
const state = useObservableRef(
() => ({
query: '',
loading: true,
error: new GQLErrorCatcher(),
async load() {
this.error.clear();
try {
this.query = await sqlGeneratorsResource.generateEntityQuery(payload.generatorId, payload.pathId);
} catch (exception: any) {
this.error.catch(exception);
} finally {
this.loading = false;
}
},
}),
{
query: observable.ref,
loading: observable.ref,
connection: observable.ref,
},
{ connection },
);
const connectionDialectResource = useResource(GeneratedSqlDialog, ConnectionDialectResource, connection ? createConnectionParam(connection) : null);
const sqlDialect = useSqlDialectExtension(connectionDialectResource.data);
@@ -80,34 +47,19 @@ export const GeneratedSqlDialog = observer<DialogComponentProps<Payload>>(functi
if (sqlDialect) {
extensions.set(...sqlDialect);
}
const error = useErrorDetails(state.error.exception);
useEffect(() => {
state.load();
}, []);
return (
<CommonDialogWrapper size="large">
<CommonDialogHeader title="app_shared_sql_generators_dialog_title" icon="sql-script" onReject={rejectDialog} />
<CommonDialogBody noOverflow noBodyPadding>
<div className={s(styles, { wrapper: true })}>
<Loader loading={state.loading}>
{() => <SQLCodeEditor className={s(styles, { sqlCodeEditorLoader: true })} value={state.query} extensions={extensions} readonly />}
</Loader>
<SQLCodeEditor className={s(styles, { sqlCodeEditorLoader: true })} value={payload.query} extensions={extensions} readonly />
</div>
</CommonDialogBody>
<CommonDialogFooter>
<div className={s(styles, { footerContainer: true })}>
{state.error.responseMessage && (
<ErrorMessage
className={s(styles, { errorMessage: true })}
text={state.error.responseMessage}
hasDetails={error.hasDetails}
onShowDetails={error.open}
/>
)}
<div className={s(styles, { buttons: true })}>
<Button variant="secondary" onClick={() => copy(state.query, true)}>
<Button variant="secondary" disabled={!payload.query} onClick={() => copy(payload.query, true)}>
{translate('ui_copy_to_clipboard')}
</Button>
<Button onClick={() => rejectDialog()}>{translate('ui_close')}</Button>
@@ -14,15 +14,17 @@ import { MenuBaseItem, MenuService } from '@cloudbeaver/core-view';
import { MENU_SQL_GENERATORS } from './MENU_SQL_GENERATORS.js';
import { SqlGeneratorsResource } from './SqlGeneratorsResource.js';
import { NotificationService } from '@cloudbeaver/core-events';
const GeneratedSqlDialog = importLazyComponent(() => import('./GeneratedSqlDialog.js').then(m => m.GeneratedSqlDialog));
@injectable(() => [SqlGeneratorsResource, CommonDialogService, MenuService])
@injectable(() => [SqlGeneratorsResource, CommonDialogService, MenuService, NotificationService])
export class SqlGeneratorsBootstrap extends Bootstrap {
constructor(
private readonly sqlGeneratorsResource: SqlGeneratorsResource,
private readonly commonDialogService: CommonDialogService,
private readonly menuService: MenuService,
private readonly notificationService: NotificationService,
) {
super();
}
@@ -77,11 +79,16 @@ export class SqlGeneratorsBootstrap extends Bootstrap {
tooltip: action.description,
},
{
onSelect: () => {
this.commonDialogService.open(GeneratedSqlDialog, {
generatorId: action.id,
pathId: node.id,
});
onSelect: async () => {
try {
const query = await this.sqlGeneratorsResource.generateEntityQuery(action.id, node.id);
await this.commonDialogService.open(GeneratedSqlDialog, {
query,
nodeId: node.id,
});
} catch (e: any) {
this.notificationService.logException(e, 'sql_generator_error_title');
}
},
},
),
@@ -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.
@@ -8,7 +8,7 @@
import { injectable } from '@cloudbeaver/core-di';
import { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
import { CachedMapResource, isResourceAlias, type ResourceKey, resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-resource';
import { GraphQLService, type SqlQueryGenerator } from '@cloudbeaver/core-sdk';
import { GraphQLService, type SqlGenerateResultSetQueryQueryVariables, type SqlQueryGenerator } from '@cloudbeaver/core-sdk';
export const MAX_GENERATORS_LENGTH = 15;
@@ -34,6 +34,12 @@ export class SqlGeneratorsResource extends CachedMapResource<string, SqlQueryGen
return result.sqlGenerateEntityQuery;
}
async generateResultSetSql(params: SqlGenerateResultSetQueryQueryVariables): Promise<string | null> {
const response = await this.graphQLService.sdk.sqlGenerateResultSetQuery(params);
return response.sqlGenerateResultSetQuery;
}
protected async loader(key: ResourceKey<string>): Promise<Map<string, SqlQueryGenerator[]>> {
if (isResourceAlias(key)) {
throw new Error('Aliases not supported by this resource.');
@@ -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.
@@ -11,3 +11,4 @@ import './module.js';
export * from './SqlGenerators/SqlGeneratorsResource.js';
export * from './SqlGenerators/SqlGeneratorsBootstrap.js';
export * from './actions/ACTION_SQL_GENERATE.js';
export * from './SqlGenerators/GeneratedSqlDialog.js';
@@ -1 +1 @@
export default [];
export default [['sql_generator_error_title', 'SQL Generation Error']];
@@ -1 +1 @@
export default [];
export default [['sql_generator_error_title', 'Erreur de génération SQL']];
@@ -1 +1 @@
export default [];
export default [['sql_generator_error_title', 'Errore di generazione SQL']];
@@ -1 +1 @@
export default [];
export default [['sql_generator_error_title', 'Ошибка генерации SQL']];
@@ -1 +1 @@
export default [];
export default [['sql_generator_error_title', 'Lỗi tạo SQL']];
@@ -1 +1 @@
export default [];
export default [['sql_generator_error_title', 'SQL生成错误']];
@@ -40,6 +40,9 @@
{
"path": "../core-sdk"
},
{
"path": "../core-utils"
},
{
"path": "../core-view"
},