From 06639abf5e1c18b0fb3ea4127cd25ff4e4ffa260 Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Mon, 20 Jul 2026 18:29:28 +0200 Subject: [PATCH 1/5] dbeaver/pro#9170 adds download button for the script --- .../DataGridContextMenuGenerateSqlService.ts | 18 +++++++++++++--- .../src/SqlGenerators/GeneratedSqlDialog.tsx | 21 +++++++++++++++++++ .../SqlGenerators/SqlGeneratorsBootstrap.ts | 2 ++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuGenerateSqlService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuGenerateSqlService.ts index 26a6c731d1f..e1d772b3605 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuGenerateSqlService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuGenerateSqlService.ts @@ -8,6 +8,7 @@ import { injectable } from '@cloudbeaver/core-di'; import { CommonDialogService } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; +import { LocalizationService } from '@cloudbeaver/core-localization'; import { SqlResultSetGeneratorId, type SqlQueryGeneratorOptions, type SqlResultRow } from '@cloudbeaver/core-sdk'; import { ActionService, MenuService, type IAction } from '@cloudbeaver/core-view'; import { @@ -42,7 +43,7 @@ import type { IDataContextProvider } from '@cloudbeaver/core-data-context'; import { getDefaultQueryGeneratorOptions, GeneratedSqlDialog, SqlGeneratorsResource, DDL_GENERATOR_ID } from '@cloudbeaver/plugin-sql-generator'; import { isNotNullDefined } from '@dbeaver/js-helpers'; -@injectable(() => [ActionService, MenuService, CommonDialogService, NotificationService, SqlGeneratorsResource]) +@injectable(() => [ActionService, MenuService, CommonDialogService, NotificationService, SqlGeneratorsResource, LocalizationService]) export class DataGridContextMenuGenerateSqlService { constructor( private readonly actionService: ActionService, @@ -50,6 +51,7 @@ export class DataGridContextMenuGenerateSqlService { private readonly commonDialogService: CommonDialogService, private readonly notificationService: NotificationService, private readonly sqlGenerationResource: SqlGeneratorsResource, + private readonly localizationService: LocalizationService, ) {} register(): void { @@ -107,15 +109,17 @@ export class DataGridContextMenuGenerateSqlService { return; } - await this.openSqlDialog(context, mapGeneratorIdFromAction(action)); + await this.openSqlDialog(context, mapGeneratorIdFromAction(action), action.info.label); }, }); } - private async openSqlDialog(context: IDataContextProvider, generatorId: SqlResultSetGeneratorId): Promise { + private async openSqlDialog(context: IDataContextProvider, generatorId: SqlResultSetGeneratorId, generatorLabel: string): Promise { const model = context.get(DATA_CONTEXT_DV_DDM)!; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; const key = context.get(DATA_CONTEXT_DV_RESULT_KEY); + const options = model.source.options as IDataContainerOptions | undefined; + const containerNodePath = options?.containerNodePath; if (!isResultSetDataModel(model)) { return; @@ -163,6 +167,8 @@ export class DataGridContextMenuGenerateSqlService { await this.commonDialogService.open(GeneratedSqlDialog, { query, nodeId: connectionId, + nodeName: getEntityNameFromNodePath(containerNodePath), + generatorName: this.localizationService.translate(generatorLabel), options: getDefaultQueryGeneratorOptions(), regenerateQuery: options => this.generateQuery({ @@ -216,6 +222,8 @@ export class DataGridContextMenuGenerateSqlService { await this.commonDialogService.open(GeneratedSqlDialog, { query, nodeId: connectionId, + nodeName: getEntityNameFromNodePath(nodePathList), + generatorName: createGenerator.label, options: getDefaultQueryGeneratorOptions(), regenerateQuery: genOptions => this.sqlGenerationResource.generateEntityQuery(createGenerator.id, nodePathList, genOptions), }); @@ -259,6 +267,10 @@ export class DataGridContextMenuGenerateSqlService { } } +function getEntityNameFromNodePath(nodePath: string | undefined): string | undefined { + return nodePath?.split('/').pop(); +} + function mapGeneratorIdFromAction(action: IAction): SqlResultSetGeneratorId { switch (action) { case ACTION_DATA_GRID_GENERATE_SQL_INSERT: diff --git a/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx b/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx index 607bb7b3c14..0f7fb4903e7 100644 --- a/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx +++ b/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx @@ -23,6 +23,7 @@ import { import { ConnectionDialectResource, ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections'; import { useService } from '@cloudbeaver/core-di'; import type { DialogComponentProps } from '@cloudbeaver/core-dialogs'; +import { download } from '@cloudbeaver/core-utils'; import { useCodemirrorExtensions } from '@cloudbeaver/plugin-codemirror6'; import { SqlEditorNavigatorService } from '@cloudbeaver/plugin-sql-editor-navigation-tab'; import { SQLCodeEditor, useSqlDialectExtension } from '@cloudbeaver/plugin-sql-editor-codemirror'; @@ -33,11 +34,23 @@ import type { SqlQueryGeneratorOptions } from '@cloudbeaver/core-sdk'; interface Payload { nodeId: string; + nodeName?: string; + generatorName?: string; query: string; options?: SqlQueryGeneratorOptions; regenerateQuery: (options: SqlQueryGeneratorOptions) => Promise; } +function getFileName(parts: Array): string { + const date = new Date().toISOString().slice(0, 10); + const name = [...parts, date] + .filter(Boolean) + .join('-') + .replace(/[^\w-]+/g, '_'); + + return `${name}.sql`; +} + export const GeneratedSqlDialog = observer>(function GeneratedSqlDialog({ rejectDialog, payload }) { const translate = useTranslate(); const copy = useClipboard(); @@ -104,6 +117,11 @@ export const GeneratedSqlDialog = observer>(functi } } + function handleSaveToFile() { + const blob = new Blob([state.query], { type: 'text/plain' }); + download(blob, getFileName([payload.nodeName, payload.generatorName])); + } + return ( @@ -136,6 +154,9 @@ export const GeneratedSqlDialog = observer>(functi
+ diff --git a/webapp/packages/plugin-sql-generator/src/SqlGenerators/SqlGeneratorsBootstrap.ts b/webapp/packages/plugin-sql-generator/src/SqlGenerators/SqlGeneratorsBootstrap.ts index 70bcfea020e..ee4d5385de7 100644 --- a/webapp/packages/plugin-sql-generator/src/SqlGenerators/SqlGeneratorsBootstrap.ts +++ b/webapp/packages/plugin-sql-generator/src/SqlGenerators/SqlGeneratorsBootstrap.ts @@ -85,6 +85,8 @@ export class SqlGeneratorsBootstrap extends Bootstrap { await this.commonDialogService.open(GeneratedSqlDialog, { query, nodeId: node.uri, + nodeName: node.name, + generatorName: action.label, options: getDefaultQueryGeneratorOptions(), regenerateQuery: options => this.sqlGeneratorsResource.generateEntityQuery(action.id, node.uri, options), }); From 1b54ce071f935ec2cc791f09776b805a012d467d Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Tue, 21 Jul 2026 17:54:29 +0200 Subject: [PATCH 2/5] dbeaver/pro#9170 gets sql filename from node or model --- .../plugin-data-spreadsheet-new/package.json | 1 + .../DataGridContextMenuGenerateSqlService.ts | 30 +++++++++++++++---- .../plugin-data-spreadsheet-new/tsconfig.json | 3 ++ .../src/SqlGenerators/GeneratedSqlDialog.tsx | 18 ++++------- webapp/yarn.lock | 1 + 5 files changed, 34 insertions(+), 19 deletions(-) diff --git a/webapp/packages/plugin-data-spreadsheet-new/package.json b/webapp/packages/plugin-data-spreadsheet-new/package.json index a5e89776c87..80b9ae3cf6e 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/package.json +++ b/webapp/packages/plugin-data-spreadsheet-new/package.json @@ -30,6 +30,7 @@ "@cloudbeaver/core-dialogs": "workspace:*", "@cloudbeaver/core-events": "workspace:*", "@cloudbeaver/core-localization": "workspace:*", + "@cloudbeaver/core-navigation-tree": "workspace:*", "@cloudbeaver/core-sdk": "workspace:*", "@cloudbeaver/core-settings": "workspace:*", "@cloudbeaver/core-ui": "workspace:*", diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuGenerateSqlService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuGenerateSqlService.ts index e1d772b3605..d40bb07b2e6 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuGenerateSqlService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuGenerateSqlService.ts @@ -10,6 +10,7 @@ import { CommonDialogService } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; import { LocalizationService } from '@cloudbeaver/core-localization'; import { SqlResultSetGeneratorId, type SqlQueryGeneratorOptions, type SqlResultRow } from '@cloudbeaver/core-sdk'; +import { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree'; import { ActionService, MenuService, type IAction } from '@cloudbeaver/core-view'; import { DATA_CONTEXT_DV_DDM, @@ -43,7 +44,15 @@ import type { IDataContextProvider } from '@cloudbeaver/core-data-context'; import { getDefaultQueryGeneratorOptions, GeneratedSqlDialog, SqlGeneratorsResource, DDL_GENERATOR_ID } from '@cloudbeaver/plugin-sql-generator'; import { isNotNullDefined } from '@dbeaver/js-helpers'; -@injectable(() => [ActionService, MenuService, CommonDialogService, NotificationService, SqlGeneratorsResource, LocalizationService]) +@injectable(() => [ + ActionService, + MenuService, + CommonDialogService, + NotificationService, + SqlGeneratorsResource, + LocalizationService, + NavNodeInfoResource, +]) export class DataGridContextMenuGenerateSqlService { constructor( private readonly actionService: ActionService, @@ -52,6 +61,7 @@ export class DataGridContextMenuGenerateSqlService { private readonly notificationService: NotificationService, private readonly sqlGenerationResource: SqlGeneratorsResource, private readonly localizationService: LocalizationService, + private readonly navNodeInfoResource: NavNodeInfoResource, ) {} register(): void { @@ -167,7 +177,7 @@ export class DataGridContextMenuGenerateSqlService { await this.commonDialogService.open(GeneratedSqlDialog, { query, nodeId: connectionId, - nodeName: getEntityNameFromNodePath(containerNodePath), + nodeName: this.getEntityNameFromNodePath(containerNodePath, model.name), generatorName: this.localizationService.translate(generatorLabel), options: getDefaultQueryGeneratorOptions(), regenerateQuery: options => @@ -222,7 +232,7 @@ export class DataGridContextMenuGenerateSqlService { await this.commonDialogService.open(GeneratedSqlDialog, { query, nodeId: connectionId, - nodeName: getEntityNameFromNodePath(nodePathList), + nodeName: this.getEntityNameFromNodePath(nodePathList, model.name), generatorName: createGenerator.label, options: getDefaultQueryGeneratorOptions(), regenerateQuery: genOptions => this.sqlGenerationResource.generateEntityQuery(createGenerator.id, nodePathList, genOptions), @@ -265,10 +275,18 @@ export class DataGridContextMenuGenerateSqlService { return query; } -} -function getEntityNameFromNodePath(nodePath: string | undefined): string | undefined { - return nodePath?.split('/').pop(); + private getEntityNameFromNodePath(nodePath: string | undefined, fallbackName?: string | null): string | undefined { + if (nodePath) { + const name = this.navNodeInfoResource.get(nodePath)?.name; + + if (name) { + return name; + } + } + + return fallbackName ?? undefined; + } } function mapGeneratorIdFromAction(action: IAction): SqlResultSetGeneratorId { diff --git a/webapp/packages/plugin-data-spreadsheet-new/tsconfig.json b/webapp/packages/plugin-data-spreadsheet-new/tsconfig.json index 19ff9f0ee4d..70bdb722c7a 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/tsconfig.json +++ b/webapp/packages/plugin-data-spreadsheet-new/tsconfig.json @@ -49,6 +49,9 @@ { "path": "../core-localization" }, + { + "path": "../core-navigation-tree" + }, { "path": "../core-sdk" }, diff --git a/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx b/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx index 0f7fb4903e7..bff26ccb9d4 100644 --- a/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx +++ b/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx @@ -23,7 +23,7 @@ import { import { ConnectionDialectResource, ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections'; import { useService } from '@cloudbeaver/core-di'; import type { DialogComponentProps } from '@cloudbeaver/core-dialogs'; -import { download } from '@cloudbeaver/core-utils'; +import { download, withTimestamp } from '@cloudbeaver/core-utils'; import { useCodemirrorExtensions } from '@cloudbeaver/plugin-codemirror6'; import { SqlEditorNavigatorService } from '@cloudbeaver/plugin-sql-editor-navigation-tab'; import { SQLCodeEditor, useSqlDialectExtension } from '@cloudbeaver/plugin-sql-editor-codemirror'; @@ -41,16 +41,6 @@ interface Payload { regenerateQuery: (options: SqlQueryGeneratorOptions) => Promise; } -function getFileName(parts: Array): string { - const date = new Date().toISOString().slice(0, 10); - const name = [...parts, date] - .filter(Boolean) - .join('-') - .replace(/[^\w-]+/g, '_'); - - return `${name}.sql`; -} - export const GeneratedSqlDialog = observer>(function GeneratedSqlDialog({ rejectDialog, payload }) { const translate = useTranslate(); const copy = useClipboard(); @@ -118,8 +108,10 @@ export const GeneratedSqlDialog = observer>(functi } function handleSaveToFile() { - const blob = new Blob([state.query], { type: 'text/plain' }); - download(blob, getFileName([payload.nodeName, payload.generatorName])); + const blob = new Blob([state.query], { type: 'application/sql' }); + const name = [payload.nodeName, payload.generatorName].join('-') || 'Generated'; + + download(blob, `${withTimestamp(name)}.sql`); } return ( diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 0e583545f97..3fe8360e0c0 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -3049,6 +3049,7 @@ __metadata: "@cloudbeaver/core-dialogs": "workspace:*" "@cloudbeaver/core-events": "workspace:*" "@cloudbeaver/core-localization": "workspace:*" + "@cloudbeaver/core-navigation-tree": "workspace:*" "@cloudbeaver/core-sdk": "workspace:*" "@cloudbeaver/core-settings": "workspace:*" "@cloudbeaver/core-ui": "workspace:*" From 8e3ddd26db81f2aa5cea9602938cd0f358799e5c Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Tue, 21 Jul 2026 18:57:43 +0200 Subject: [PATCH 3/5] dbeaver/pro#9170 adds action buttons --- .../src/SqlGenerators/GeneratedSqlDialog.tsx | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx b/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx index bff26ccb9d4..8fd62a5374c 100644 --- a/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx +++ b/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx @@ -8,6 +8,7 @@ import { observer } from 'mobx-react-lite'; import { + ActionIconButton, Button, Checkbox, CommonDialogBody, @@ -145,17 +146,29 @@ export const GeneratedSqlDialog = observer>(functi />
-
- - - - +
+
+ + copy(state.query, true)} + /> +
+
+ + +
From 7e2b2e0a20b7536e86d66d4f8853ed7ccb07f8b4 Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Wed, 22 Jul 2026 19:57:45 +0200 Subject: [PATCH 4/5] dbeaver/pro#9170 adds borders to the buttons --- .../src/SqlGenerators/GeneratedSqlDialog.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx b/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx index 8fd62a5374c..858fd37b955 100644 --- a/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx +++ b/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx @@ -8,7 +8,6 @@ import { observer } from 'mobx-react-lite'; import { - ActionIconButton, Button, Checkbox, CommonDialogBody, @@ -148,18 +147,20 @@ export const GeneratedSqlDialog = observer>(functi
- - copy(state.query, true)} />
From 95b5a6ac015919f8c40a6ced120addcdb77390bd Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Mon, 27 Jul 2026 14:26:00 +0200 Subject: [PATCH 5/5] dbeaver/pro#9170 fixes build --- .../DataGridContextMenu/DataGridContextMenuGenerateSqlService.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuGenerateSqlService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuGenerateSqlService.ts index 20260d419bd..7e80a0cc1e5 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuGenerateSqlService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuGenerateSqlService.ts @@ -135,7 +135,6 @@ export class DataGridContextMenuGenerateSqlService { return; } - const options = model.source.options as IDataContainerOptions | undefined; const nodePath = options?.containerNodePath; const select = model.source.tryGetAction(resultIndex, IDatabaseDataSelectAction, GridSelectAction); const projectId = model.source.executionContext?.context?.projectId;