diff --git a/webapp/packages/core-navigation-tree/src/NodesManager/EObjectFeature.ts b/webapp/packages/core-navigation-tree/src/NodesManager/EObjectFeature.ts index 5208f2b4a0c..7538c59897d 100644 --- a/webapp/packages/core-navigation-tree/src/NodesManager/EObjectFeature.ts +++ b/webapp/packages/core-navigation-tree/src/NodesManager/EObjectFeature.ts @@ -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. @@ -20,4 +20,5 @@ export enum EObjectFeature { 'dataSourceConnected' = 'dataSourceConnected', 'entityContainer' = 'entityContainer', 'supportsDataFilter' = 'supportsDataFilter', + 'supportsFullDdl' = 'supportsFullDdl', // some connections may not have the full DDL feature } diff --git a/webapp/packages/core-sdk/src/queries/asyncSqlGenerateEntityQuery.gql b/webapp/packages/core-sdk/src/queries/asyncSqlGenerateEntityQuery.gql new file mode 100644 index 00000000000..3cc9c98b026 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/asyncSqlGenerateEntityQuery.gql @@ -0,0 +1,5 @@ +mutation asyncSqlGenerateEntityQuery($generatorId: String!, $nodePathList: [String!]!, $generatorOptions: SQLQueryGeneratorOptions) { + taskInfo: asyncSqlGenerateEntityQuery(generatorId: $generatorId, nodePathList: $nodePathList, generatorOptions: $generatorOptions) { + ...AsyncTaskInfo + } +} diff --git a/webapp/packages/core-sdk/src/queries/metadataGetNodeDDL.gql b/webapp/packages/core-sdk/src/queries/metadataGetNodeDDL.gql deleted file mode 100644 index eb2944a809b..00000000000 --- a/webapp/packages/core-sdk/src/queries/metadataGetNodeDDL.gql +++ /dev/null @@ -1,3 +0,0 @@ -query metadataGetNodeDDL( $nodeId: ID! ) { - metadataGetNodeDDL( nodeId: $nodeId ) -} diff --git a/webapp/packages/core-sdk/src/queries/sqlGenerateEntityQuery.gql b/webapp/packages/core-sdk/src/queries/sqlGenerateEntityQuery.gql deleted file mode 100644 index aa437c90138..00000000000 --- a/webapp/packages/core-sdk/src/queries/sqlGenerateEntityQuery.gql +++ /dev/null @@ -1,3 +0,0 @@ -query sqlGenerateEntityQuery($generatorId: String!, $nodePathList: [String!]!, $generatorOptions: SQLQueryGeneratorOptions) { - sqlGenerateEntityQuery(generatorId: $generatorId, nodePathList: $nodePathList, generatorOptions: $generatorOptions) -} 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 7e80a0cc1e5..be59e15d0f6 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 @@ -179,6 +179,7 @@ export class DataGridContextMenuGenerateSqlService { query, nodeId: nodePath ?? connectionId, nodeName: this.getEntityNameFromNodePath(containerNodePath, model.name), + generatorId, generatorName: this.localizationService.translate(generatorLabel), options: getDefaultQueryGeneratorOptions(), regenerateQuery: options => @@ -234,6 +235,7 @@ export class DataGridContextMenuGenerateSqlService { query, nodeId: nodePathList, nodeName: this.getEntityNameFromNodePath(nodePathList, model.name), + generatorId: createGenerator.id, generatorName: createGenerator.label, options: getDefaultQueryGeneratorOptions(), regenerateQuery: genOptions => this.sqlGenerationResource.generateEntityQuery(createGenerator.id, nodePathList, genOptions), diff --git a/webapp/packages/plugin-ddl-viewer/package.json b/webapp/packages/plugin-ddl-viewer/package.json index 58aa5d1277a..1925cc09b2f 100644 --- a/webapp/packages/plugin-ddl-viewer/package.json +++ b/webapp/packages/plugin-ddl-viewer/package.json @@ -25,6 +25,8 @@ "@cloudbeaver/core-connections": "workspace:*", "@cloudbeaver/core-data-context": "workspace:*", "@cloudbeaver/core-di": "workspace:*", + "@cloudbeaver/core-events": "workspace:*", + "@cloudbeaver/core-localization": "workspace:*", "@cloudbeaver/core-navigation-tree": "workspace:*", "@cloudbeaver/core-resource": "workspace:*", "@cloudbeaver/core-sdk": "workspace:*", @@ -36,6 +38,7 @@ "@cloudbeaver/plugin-sql-editor": "workspace:*", "@cloudbeaver/plugin-sql-editor-codemirror": "workspace:*", "@cloudbeaver/plugin-sql-editor-navigation-tab": "workspace:*", + "@cloudbeaver/plugin-sql-generator": "workspace:*", "@dbeaver/js-helpers": "workspace:*", "mobx": "^6", "mobx-react-lite": "^4", diff --git a/webapp/packages/plugin-ddl-viewer/src/DdlViewer/ACTION_DDL_VIEWER_FULL_DDL.ts b/webapp/packages/plugin-ddl-viewer/src/DdlViewer/ACTION_DDL_VIEWER_FULL_DDL.ts new file mode 100644 index 00000000000..40fb969bcf4 --- /dev/null +++ b/webapp/packages/plugin-ddl-viewer/src/DdlViewer/ACTION_DDL_VIEWER_FULL_DDL.ts @@ -0,0 +1,14 @@ +/* + * 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 { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_DDL_VIEWER_FULL_DDL = createAction('ddl-viewer-full-ddl', { + label: 'plugin_ddl_viewer_full_ddl', + tooltip: 'plugin_ddl_viewer_full_ddl', + type: 'checkbox', +}); diff --git a/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DATA_CONTEXT_DDL_VIEWER_VALUE.ts b/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DATA_CONTEXT_DDL_VIEWER_QUERY.ts similarity index 63% rename from webapp/packages/plugin-ddl-viewer/src/DdlViewer/DATA_CONTEXT_DDL_VIEWER_VALUE.ts rename to webapp/packages/plugin-ddl-viewer/src/DdlViewer/DATA_CONTEXT_DDL_VIEWER_QUERY.ts index ac2e51d7669..caa900ab7a8 100644 --- a/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DATA_CONTEXT_DDL_VIEWER_VALUE.ts +++ b/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DATA_CONTEXT_DDL_VIEWER_QUERY.ts @@ -1,10 +1,10 @@ /* * 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. */ import { createDataContext } from '@cloudbeaver/core-data-context'; -export const DATA_CONTEXT_DDL_VIEWER_VALUE = createDataContext('ddl-value'); +export const DATA_CONTEXT_DDL_VIEWER_QUERY = createDataContext('ddl-query'); diff --git a/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DDLViewerFooterService.ts b/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DDLViewerFooterService.ts index 30a2a12f42a..6dcf8cb2c51 100644 --- a/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DDLViewerFooterService.ts +++ b/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DDLViewerFooterService.ts @@ -7,36 +7,63 @@ */ import { ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; -import { NavNodeManagerService } from '@cloudbeaver/core-navigation-tree'; +import { EObjectFeature, NavNodeInfoResource, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree'; +import { MetadataMap } from '@cloudbeaver/core-utils'; import { download, withTimestamp } from '@dbeaver/js-helpers'; import { ACTION_SAVE, ActionService, MenuService } from '@cloudbeaver/core-view'; +import { DDL_GENERATOR_ID, SqlGeneratorsResource } from '@cloudbeaver/plugin-sql-generator'; import { LocalStorageSqlDataSource } from '@cloudbeaver/plugin-sql-editor'; import { ACTION_SQL_EDITOR_OPEN, SqlEditorNavigatorService } from '@cloudbeaver/plugin-sql-editor-navigation-tab'; +import { ACTION_DDL_VIEWER_FULL_DDL } from './ACTION_DDL_VIEWER_FULL_DDL.js'; import { DATA_CONTEXT_DDL_VIEWER_NODE } from './DATA_CONTEXT_DDL_VIEWER_NODE.js'; -import { DATA_CONTEXT_DDL_VIEWER_VALUE } from './DATA_CONTEXT_DDL_VIEWER_VALUE.js'; +import { DATA_CONTEXT_DDL_VIEWER_QUERY } from './DATA_CONTEXT_DDL_VIEWER_QUERY.js'; import { MENU_DDL_VIEWER_FOOTER } from './MENU_DDL_VIEWER_FOOTER.js'; -@injectable(() => [NavNodeManagerService, ActionService, MenuService, SqlEditorNavigatorService, ConnectionInfoResource]) +@injectable(() => [ + NavNodeManagerService, + ActionService, + MenuService, + SqlEditorNavigatorService, + ConnectionInfoResource, + NavNodeInfoResource, + SqlGeneratorsResource, +]) export class DDLViewerFooterService { + private readonly fullDdlState = new MetadataMap(() => false); + constructor( private readonly navNodeManagerService: NavNodeManagerService, private readonly actionsService: ActionService, private readonly menuService: MenuService, private readonly sqlEditorNavigatorService: SqlEditorNavigatorService, private readonly connectionInfoResource: ConnectionInfoResource, + private readonly navNodeInfoResource: NavNodeInfoResource, + private readonly sqlGeneratorsResource: SqlGeneratorsResource, ) {} + isFullDdlEnabled(nodeId: string): boolean { + return this.fullDdlState.get(nodeId); + } + + setFullDdlEnabled(nodeId: string, value: boolean): void { + this.fullDdlState.set(nodeId, value); + } + + toggleFullDdl(nodeId: string): void { + this.setFullDdlEnabled(nodeId, !this.isFullDdlEnabled(nodeId)); + } + register(): void { this.actionsService.addHandler({ id: 'ddl-viewer-footer-base-handler', menus: [MENU_DDL_VIEWER_FOOTER], - contexts: [DATA_CONTEXT_DDL_VIEWER_NODE, DATA_CONTEXT_DDL_VIEWER_VALUE], + contexts: [DATA_CONTEXT_DDL_VIEWER_NODE, DATA_CONTEXT_DDL_VIEWER_QUERY], actions: [ACTION_SAVE, ACTION_SQL_EDITOR_OPEN], handler: async (context, action) => { switch (action) { case ACTION_SAVE: { - const ddl = context.get(DATA_CONTEXT_DDL_VIEWER_VALUE)!; + const ddl = context.get(DATA_CONTEXT_DDL_VIEWER_QUERY)!; const nodeId = context.get(DATA_CONTEXT_DDL_VIEWER_NODE); const blob = new Blob([ddl], { @@ -50,7 +77,7 @@ export class DDLViewerFooterService { break; } case ACTION_SQL_EDITOR_OPEN: { - const ddl = context.get(DATA_CONTEXT_DDL_VIEWER_VALUE)!; + const ddl = context.get(DATA_CONTEXT_DDL_VIEWER_QUERY)!; const nodeId = context.get(DATA_CONTEXT_DDL_VIEWER_NODE)!; const connection = this.connectionInfoResource.getConnectionForNode(nodeId); @@ -92,5 +119,34 @@ export class DDLViewerFooterService { menus: [MENU_DDL_VIEWER_FOOTER], getItems: (context, items) => [...items, ACTION_SAVE, ACTION_SQL_EDITOR_OPEN], }); + + this.menuService.addCreator({ + menus: [MENU_DDL_VIEWER_FOOTER], + contexts: [DATA_CONTEXT_DDL_VIEWER_NODE], + isApplicable: context => this.isFullDDLSupported(context.get(DATA_CONTEXT_DDL_VIEWER_NODE)!), + getItems: (context, items) => [...items, ACTION_DDL_VIEWER_FULL_DDL], + }); + + this.actionsService.addHandler({ + id: 'ddl-viewer-footer-full-ddl-handler', + menus: [MENU_DDL_VIEWER_FOOTER], + actions: [ACTION_DDL_VIEWER_FULL_DDL], + contexts: [DATA_CONTEXT_DDL_VIEWER_NODE], + isChecked: context => this.isFullDdlEnabled(context.get(DATA_CONTEXT_DDL_VIEWER_NODE)!), + handler: (context, action) => { + if (action === ACTION_DDL_VIEWER_FULL_DDL) { + this.toggleFullDdl(context.get(DATA_CONTEXT_DDL_VIEWER_NODE)!); + } + }, + }); + } + + private isFullDDLSupported(nodeId: string): boolean { + const supportsFullDdl = this.navNodeInfoResource.get(nodeId)?.objectFeatures.includes(EObjectFeature.supportsFullDdl) ?? false; + const ddlGenerator = this.sqlGeneratorsResource + .get(nodeId) + ?.find(generator => generator.id.toLowerCase().includes(DDL_GENERATOR_ID.toLowerCase())); + + return supportsFullDdl && !!ddlGenerator; } } diff --git a/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DDLViewerTabPanel.tsx b/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DDLViewerTabPanel.tsx index 716f93861c6..7eedf48bdf5 100644 --- a/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DDLViewerTabPanel.tsx +++ b/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DDLViewerTabPanel.tsx @@ -7,7 +7,7 @@ */ import { observer } from 'mobx-react-lite'; -import { s, useResource, useS } from '@cloudbeaver/core-blocks'; +import { s, useAutoLoad, useResource, useS } from '@cloudbeaver/core-blocks'; import { ConnectionDialectResource, ConnectionInfoActiveProjectKey, @@ -22,17 +22,15 @@ import type { NavNodeTransformViewComponent } from '@cloudbeaver/plugin-navigati import { SQLCodeEditor, useSqlDialectExtension } from '@cloudbeaver/plugin-sql-editor-codemirror'; import { DATA_CONTEXT_DDL_VIEWER_NODE } from './DATA_CONTEXT_DDL_VIEWER_NODE.js'; -import { DATA_CONTEXT_DDL_VIEWER_VALUE } from './DATA_CONTEXT_DDL_VIEWER_VALUE.js'; -import { DdlResource } from './DdlResource.js'; +import { DATA_CONTEXT_DDL_VIEWER_QUERY } from './DATA_CONTEXT_DDL_VIEWER_QUERY.js'; import style from './DDLViewerTabPanel.module.css'; import { MENU_DDL_VIEWER_FOOTER } from './MENU_DDL_VIEWER_FOOTER.js'; +import { useDdlEntityQuery } from './useDdlEntityQuery.js'; export const DDLViewerTabPanel: NavNodeTransformViewComponent = observer(function DDLViewerTabPanel({ nodeId, folderId }) { const styles = useS(style, MenuBarStyles, MenuBarItemStyles, MenuBarGroupStyles); const menu = useMenu({ menu: MENU_DDL_VIEWER_FOOTER }); - const ddlResource = useResource(DDLViewerTabPanel, DdlResource, nodeId); - const connectionInfoResource = useResource(DDLViewerTabPanel, ConnectionInfoResource, ConnectionInfoActiveProjectKey); const connection = connectionInfoResource.resource.getConnectionForNode(nodeId); const connectionParam = connection ? createConnectionParam(connection) : null; @@ -42,16 +40,21 @@ export const DDLViewerTabPanel: NavNodeTransformViewComponent = observer(functio if (sqlDialect) { extensions.set(...sqlDialect); } - const ddlData = ddlResource.data; + + const ddlQueryState = useDdlEntityQuery(nodeId); + + useAutoLoad(DDLViewerTabPanel, ddlQueryState); + + const query = ddlQueryState.query ?? ''; useDataContextLink(menu.context, (context, id) => { context.set(DATA_CONTEXT_DDL_VIEWER_NODE, nodeId, id); - context.set(DATA_CONTEXT_DDL_VIEWER_VALUE, ddlData, id); + context.set(DATA_CONTEXT_DDL_VIEWER_QUERY, query, id); }); return (
- +
); diff --git a/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DdlResource.ts b/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DdlResource.ts deleted file mode 100644 index c6a8ecb43ae..00000000000 --- a/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DdlResource.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 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 { injectable } from '@cloudbeaver/core-di'; -import { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree'; -import { CachedMapResource, isResourceAlias, type ResourceKey, ResourceKeyUtils } from '@cloudbeaver/core-resource'; -import { GraphQLService } from '@cloudbeaver/core-sdk'; - -@injectable(() => [GraphQLService, NavNodeInfoResource]) -export class DdlResource extends CachedMapResource { - constructor( - private readonly graphQLService: GraphQLService, - private readonly navNodeInfoResource: NavNodeInfoResource, - ) { - super(); - - this.navNodeInfoResource.outdateResource(this); - this.navNodeInfoResource.deleteInResource(this); - } - - protected async loader(key: ResourceKey): Promise> { - if (isResourceAlias(key)) { - throw new Error('Aliases not supported by this resource.'); - } - - const values: string[] = []; - - await ResourceKeyUtils.forEachAsync(key, async nodeId => { - const { metadataGetNodeDDL } = await this.graphQLService.sdk.metadataGetNodeDDL({ nodeId }); - if (metadataGetNodeDDL) { - values.push(metadataGetNodeDDL); - } - }); - - this.set(ResourceKeyUtils.toList(key), values); - - return this.data; - } - - protected validateKey(key: string): boolean { - return typeof key === 'string'; - } -} diff --git a/webapp/packages/plugin-ddl-viewer/src/DdlViewer/useDdlEntityQuery.ts b/webapp/packages/plugin-ddl-viewer/src/DdlViewer/useDdlEntityQuery.ts new file mode 100644 index 00000000000..9c154a63109 --- /dev/null +++ b/webapp/packages/plugin-ddl-viewer/src/DdlViewer/useDdlEntityQuery.ts @@ -0,0 +1,99 @@ +/* + * 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 { observable } from 'mobx'; + +import { useObservableRef, useResource } from '@cloudbeaver/core-blocks'; +import type { ILoadableState } from '@cloudbeaver/core-utils'; +import { DDL_GENERATOR_ID, getDefaultQueryGeneratorOptions, SqlGeneratorsResource } from '@cloudbeaver/plugin-sql-generator'; +import { useService } from '@cloudbeaver/core-di'; +import { DDLViewerFooterService } from './DDLViewerFooterService.js'; + +interface IState extends ILoadableState { + query: string | null; + exception: Error | null; + promise: Promise | null; + key: string | null; + nodeId: string; + sqlGeneratorsResource: SqlGeneratorsResource; + ddlGeneratorId: string | null; + ddlViewerFooterService: DDLViewerFooterService; + getCurrentKey: () => string | null; +} + +export function useDdlEntityQuery(nodeId: string): IState { + const ddlViewerFooterService = useService(DDLViewerFooterService); + const sqlGeneratorsResource = useResource(useDdlEntityQuery, SqlGeneratorsResource, nodeId); + const ddlGenerator = sqlGeneratorsResource.data?.find(generator => generator.id.toLowerCase().includes(DDL_GENERATOR_ID.toLowerCase())); + const ddlGeneratorId = ddlGenerator?.id ?? null; + + const state = useObservableRef( + () => ({ + query: null, + exception: null, + promise: null, + key: null, + isLoadable() { + return this.ddlGeneratorId !== null; + }, + isLoaded() { + return this.query !== null; + }, + isError() { + return this.exception !== null; + }, + isLoading() { + return this.promise !== null; + }, + isOutdated() { + return this.getCurrentKey() !== this.key; + }, + getCurrentKey() { + if (this.ddlGeneratorId === null) { + return null; + } + + const isFullDdl = this.ddlViewerFooterService.isFullDdlEnabled(this.nodeId); + + return `${this.nodeId}::${this.ddlGeneratorId}::${isFullDdl}`; + }, + async load() { + if (this.ddlGeneratorId === null) { + return; + } + + const key = this.getCurrentKey(); + const isFullDdl = this.ddlViewerFooterService.isFullDdlEnabled(this.nodeId); + + try { + this.exception = null; + this.promise = this.sqlGeneratorsResource.generateEntityQuery(this.ddlGeneratorId, this.nodeId, { + ...getDefaultQueryGeneratorOptions(), + showFullDdl: isFullDdl, + }); + this.query = await this.promise; + this.key = key; + } catch (exception: any) { + this.exception = exception; + } finally { + this.promise = null; + } + }, + }), + { + query: observable.ref, + exception: observable.ref, + promise: observable.ref, + key: observable.ref, + nodeId: observable.ref, + ddlGeneratorId: observable.ref, + }, + { nodeId, ddlGeneratorId, sqlGeneratorsResource: sqlGeneratorsResource.resource, ddlViewerFooterService }, + ); + + return state; +} diff --git a/webapp/packages/plugin-ddl-viewer/src/ExtendedDDLViewer/ExtendedDDLViewerTabPanel.tsx b/webapp/packages/plugin-ddl-viewer/src/ExtendedDDLViewer/ExtendedDDLViewerTabPanel.tsx index 466ece51a33..08fdde94288 100644 --- a/webapp/packages/plugin-ddl-viewer/src/ExtendedDDLViewer/ExtendedDDLViewerTabPanel.tsx +++ b/webapp/packages/plugin-ddl-viewer/src/ExtendedDDLViewer/ExtendedDDLViewerTabPanel.tsx @@ -22,7 +22,7 @@ import type { NavNodeTransformViewComponent } from '@cloudbeaver/plugin-navigati import { SQLCodeEditor, useSqlDialectExtension } from '@cloudbeaver/plugin-sql-editor-codemirror'; import { DATA_CONTEXT_DDL_VIEWER_NODE } from '../DdlViewer/DATA_CONTEXT_DDL_VIEWER_NODE.js'; -import { DATA_CONTEXT_DDL_VIEWER_VALUE } from '../DdlViewer/DATA_CONTEXT_DDL_VIEWER_VALUE.js'; +import { DATA_CONTEXT_DDL_VIEWER_QUERY } from '../DdlViewer/DATA_CONTEXT_DDL_VIEWER_QUERY.js'; import style from '../DdlViewer/DDLViewerTabPanel.module.css'; import { MENU_DDL_VIEWER_FOOTER } from '../DdlViewer/MENU_DDL_VIEWER_FOOTER.js'; import { ExtendedDDLResource } from './ExtendedDDLResource.js'; @@ -46,7 +46,7 @@ export const ExtendedDDLViewerTabPanel: NavNodeTransformViewComponent = observer useDataContextLink(menu.context, (context, id) => { context.set(DATA_CONTEXT_DDL_VIEWER_NODE, nodeId, id); - context.set(DATA_CONTEXT_DDL_VIEWER_VALUE, extendedDDlData, id); + context.set(DATA_CONTEXT_DDL_VIEWER_QUERY, extendedDDlData, id); }); return ( diff --git a/webapp/packages/plugin-ddl-viewer/src/LocaleService.ts b/webapp/packages/plugin-ddl-viewer/src/LocaleService.ts new file mode 100644 index 00000000000..fae7357b770 --- /dev/null +++ b/webapp/packages/plugin-ddl-viewer/src/LocaleService.ts @@ -0,0 +1,29 @@ +/* + * 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 { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { LocalizationService } from '@cloudbeaver/core-localization'; + +@injectable(() => [LocalizationService]) +export class LocaleService extends Bootstrap { + constructor(private readonly localizationService: LocalizationService) { + super(); + } + + override register(): void { + this.localizationService.addProvider(this.provider.bind(this)); + } + + private async provider(locale: string) { + switch (locale) { + case 'ru': + return (await import('./locales/ru.js')).default; + default: + return (await import('./locales/en.js')).default; + } + } +} diff --git a/webapp/packages/plugin-ddl-viewer/src/locales/en.ts b/webapp/packages/plugin-ddl-viewer/src/locales/en.ts new file mode 100644 index 00000000000..a0d8e3ed2e0 --- /dev/null +++ b/webapp/packages/plugin-ddl-viewer/src/locales/en.ts @@ -0,0 +1,4 @@ +export default [ + ['plugin_ddl_viewer_full_ddl', 'Full DDL'], + ['plugin_ddl_viewer_full_ddl_error_title', 'Error generating full DDL'], +]; diff --git a/webapp/packages/plugin-ddl-viewer/src/locales/ru.ts b/webapp/packages/plugin-ddl-viewer/src/locales/ru.ts new file mode 100644 index 00000000000..3277e1a5d34 --- /dev/null +++ b/webapp/packages/plugin-ddl-viewer/src/locales/ru.ts @@ -0,0 +1,4 @@ +export default [ + ['plugin_ddl_viewer_full_ddl', 'Полный DDL'], + ['plugin_ddl_viewer_full_ddl_error_title', 'Ошибка генерации полного DDL'], +]; diff --git a/webapp/packages/plugin-ddl-viewer/src/module.ts b/webapp/packages/plugin-ddl-viewer/src/module.ts index 31511d19d02..c25c2b4e27c 100644 --- a/webapp/packages/plugin-ddl-viewer/src/module.ts +++ b/webapp/packages/plugin-ddl-viewer/src/module.ts @@ -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. @@ -9,8 +9,8 @@ import { Bootstrap, Dependency, ModuleRegistry, proxy } from '@cloudbeaver/core-di'; import { ExtendedDDLResource } from './ExtendedDDLViewer/ExtendedDDLResource.js'; import { DdlViewerBootstrap } from './DdlViewerBootstrap.js'; -import { DdlResource } from './DdlViewer/DdlResource.js'; import { DDLViewerFooterService } from './DdlViewer/DDLViewerFooterService.js'; +import { LocaleService } from './LocaleService.js'; export default ModuleRegistry.add({ name: '@cloudbeaver/plugin-ddl-viewer', @@ -18,10 +18,9 @@ export default ModuleRegistry.add({ configure: serviceCollection => { serviceCollection .addSingleton(Bootstrap, DdlViewerBootstrap) + .addSingleton(Bootstrap, LocaleService) .addSingleton(Dependency, proxy(ExtendedDDLResource)) - .addSingleton(Dependency, proxy(DdlResource)) .addSingleton(DDLViewerFooterService) - .addSingleton(ExtendedDDLResource) - .addSingleton(DdlResource); + .addSingleton(ExtendedDDLResource); }, }); diff --git a/webapp/packages/plugin-ddl-viewer/tsconfig.json b/webapp/packages/plugin-ddl-viewer/tsconfig.json index cbfa4f5193d..0574b48ad75 100644 --- a/webapp/packages/plugin-ddl-viewer/tsconfig.json +++ b/webapp/packages/plugin-ddl-viewer/tsconfig.json @@ -25,6 +25,12 @@ { "path": "../core-di" }, + { + "path": "../core-events" + }, + { + "path": "../core-localization" + }, { "path": "../core-navigation-tree" }, @@ -57,6 +63,9 @@ }, { "path": "../plugin-sql-editor-navigation-tab" + }, + { + "path": "../plugin-sql-generator" } ], "include": [ diff --git a/webapp/packages/plugin-sql-generator/package.json b/webapp/packages/plugin-sql-generator/package.json index 9d0e2a0127f..57270f6b136 100644 --- a/webapp/packages/plugin-sql-generator/package.json +++ b/webapp/packages/plugin-sql-generator/package.json @@ -30,6 +30,7 @@ "@cloudbeaver/core-localization": "workspace:*", "@cloudbeaver/core-navigation-tree": "workspace:*", "@cloudbeaver/core-resource": "workspace:*", + "@cloudbeaver/core-root": "workspace:*", "@cloudbeaver/core-sdk": "workspace:*", "@cloudbeaver/core-utils": "workspace:*", "@cloudbeaver/core-view": "workspace:*", diff --git a/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx b/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx index 75faaafa144..349bf317d28 100644 --- a/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx +++ b/webapp/packages/plugin-sql-generator/src/SqlGenerators/GeneratedSqlDialog.tsx @@ -23,8 +23,8 @@ import { import { ConnectionDialectResource, ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections'; import { useService } from '@cloudbeaver/core-di'; import type { DialogComponentProps } from '@cloudbeaver/core-dialogs'; +import { EObjectFeature, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree'; import { download, withTimestamp } from '@dbeaver/js-helpers'; -import { NavNodeManagerService } from '@cloudbeaver/core-navigation-tree'; 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,9 +33,12 @@ import { action, observable } from 'mobx'; import { NotificationService } from '@cloudbeaver/core-events'; import type { SqlQueryGeneratorOptions } from '@cloudbeaver/core-sdk'; +import { DDL_GENERATOR_ID } from './SqlGeneratorsResource.js'; + interface Payload { nodeId: string; nodeName?: string; + generatorId: string; generatorName?: string; query: string; options?: SqlQueryGeneratorOptions; @@ -51,6 +54,7 @@ export const GeneratedSqlDialog = observer>(functi () => ({ useFullyQualifiedNames: payload.options?.useFullyQualifiedNames ?? true, compactSql: payload.options?.compactSql ?? false, + showFullDdl: payload.options?.showFullDdl, query: payload.query, loading: false, handleOptionChange: async function handleOptionChange(key: T, value: (typeof state)[T]) { @@ -61,6 +65,7 @@ export const GeneratedSqlDialog = observer>(functi this.query = await this.payload.regenerateQuery({ compactSql: this.compactSql, useFullyQualifiedNames: this.useFullyQualifiedNames, + showFullDdl: this.showFullDdl, }); } catch (error: any) { this.notificationService.logException(error, 'app_shared_sql_generators_error_title'); @@ -73,6 +78,7 @@ export const GeneratedSqlDialog = observer>(functi handleOptionChange: action.bound, useFullyQualifiedNames: observable.ref, compactSql: observable.ref, + showFullDdl: observable.ref, query: observable.ref, loading: observable.ref, }, @@ -96,6 +102,10 @@ export const GeneratedSqlDialog = observer>(functi } const visibleLoading = useStateDelay(state.loading, 300); + const isDdlGenerator = payload.generatorId.toLowerCase().includes(DDL_GENERATOR_ID.toLowerCase()); + const node = navNodeManagerService.getNode(payload.nodeId); + const supportsFullDdl = node?.objectFeatures.includes(EObjectFeature.supportsFullDdl) ?? false; + const hasFullDdl = isDdlGenerator && supportsFullDdl; async function handleOpenInEditor() { try { @@ -134,21 +144,24 @@ export const GeneratedSqlDialog = observer>(functi
state.handleOptionChange('useFullyQualifiedNames', value)} /> - state.handleOptionChange('compactSql', value)} /> + {hasFullDdl && ( + state.handleOptionChange('showFullDdl', value)} + /> + )}
diff --git a/webapp/packages/plugin-sql-generator/src/SqlGenerators/SqlGeneratorsBootstrap.ts b/webapp/packages/plugin-sql-generator/src/SqlGenerators/SqlGeneratorsBootstrap.ts index ee4d5385de7..c24631afcb5 100644 --- a/webapp/packages/plugin-sql-generator/src/SqlGenerators/SqlGeneratorsBootstrap.ts +++ b/webapp/packages/plugin-sql-generator/src/SqlGenerators/SqlGeneratorsBootstrap.ts @@ -12,8 +12,8 @@ import { DATA_CONTEXT_NAV_NODE, EObjectFeature } from '@cloudbeaver/core-navigat import { getCachedMapResourceLoaderState } from '@cloudbeaver/core-resource'; import { MenuBaseItem, MenuService } from '@cloudbeaver/core-view'; -import { MENU_SQL_GENERATORS } from './MENU_SQL_GENERATORS.js'; import { getDefaultQueryGeneratorOptions, SqlGeneratorsResource } from './SqlGeneratorsResource.js'; +import { MENU_SQL_GENERATORS } from './MENU_SQL_GENERATORS.js'; import { NotificationService } from '@cloudbeaver/core-events'; const GeneratedSqlDialog = importLazyComponent(() => import('./GeneratedSqlDialog.js').then(m => m.GeneratedSqlDialog)); @@ -86,6 +86,7 @@ export class SqlGeneratorsBootstrap extends Bootstrap { query, nodeId: node.uri, nodeName: node.name, + generatorId: action.id, generatorName: action.label, options: getDefaultQueryGeneratorOptions(), regenerateQuery: options => this.sqlGeneratorsResource.generateEntityQuery(action.id, node.uri, options), diff --git a/webapp/packages/plugin-sql-generator/src/SqlGenerators/SqlGeneratorsResource.ts b/webapp/packages/plugin-sql-generator/src/SqlGenerators/SqlGeneratorsResource.ts index 95eaf63a122..a712535ef0d 100644 --- a/webapp/packages/plugin-sql-generator/src/SqlGenerators/SqlGeneratorsResource.ts +++ b/webapp/packages/plugin-sql-generator/src/SqlGenerators/SqlGeneratorsResource.ts @@ -8,6 +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 { AsyncTaskInfoService } from '@cloudbeaver/core-root'; import { GraphQLService, type SqlGenerateResultSetQueryQueryVariables, @@ -19,13 +20,15 @@ export const MAX_GENERATORS_LENGTH = 15; export const getDefaultQueryGeneratorOptions = (): SqlQueryGeneratorOptions => ({ useFullyQualifiedNames: true, compactSql: false, + showFullDdl: false, }); export const DDL_GENERATOR_ID = 'tableDDL'; -@injectable(() => [GraphQLService, NavNodeInfoResource]) +@injectable(() => [GraphQLService, AsyncTaskInfoService, NavNodeInfoResource]) export class SqlGeneratorsResource extends CachedMapResource { constructor( private readonly graphQLService: GraphQLService, + private readonly asyncTaskInfoService: AsyncTaskInfoService, private readonly navNodeInfoResource: NavNodeInfoResource, ) { super(); @@ -35,13 +38,20 @@ export class SqlGeneratorsResource extends CachedMapResource { - const result = await this.graphQLService.sdk.sqlGenerateEntityQuery({ - generatorId, - nodePathList, - generatorOptions: options, + const task = this.asyncTaskInfoService.create(async () => { + const { taskInfo } = await this.graphQLService.sdk.asyncSqlGenerateEntityQuery({ + generatorId, + nodePathList, + generatorOptions: options, + }); + + return taskInfo; }); - return result.sqlGenerateEntityQuery; + const info = await this.asyncTaskInfoService.run(task); + await this.asyncTaskInfoService.remove(task.id); + + return info.taskResult; } async generateResultSetSql(params: SqlGenerateResultSetQueryQueryVariables): Promise { diff --git a/webapp/packages/plugin-sql-generator/src/locales/en.ts b/webapp/packages/plugin-sql-generator/src/locales/en.ts index ee34546328a..48c468ac3ee 100644 --- a/webapp/packages/plugin-sql-generator/src/locales/en.ts +++ b/webapp/packages/plugin-sql-generator/src/locales/en.ts @@ -2,6 +2,7 @@ export default [ ['app_shared_sql_generators_error_title', 'SQL Generation Error'], ['app_shared_sql_generators_use_fully_qualified_names', 'Use fully qualified names'], ['app_shared_sql_generators_compact_sql', 'Compact SQL'], + ['app_shared_sql_generators_full_ddl', 'Full DDL'], ['app_shared_sql_generators_open_in_editor', 'Open in SQL Editor'], ['app_shared_sql_generators_error_open_editor', 'Error opening SQL Editor'], ]; diff --git a/webapp/packages/plugin-sql-generator/src/locales/fr.ts b/webapp/packages/plugin-sql-generator/src/locales/fr.ts index b1877ca2c77..326376d22d6 100644 --- a/webapp/packages/plugin-sql-generator/src/locales/fr.ts +++ b/webapp/packages/plugin-sql-generator/src/locales/fr.ts @@ -2,6 +2,7 @@ export default [ ['app_shared_sql_generators_error_title', 'Erreur de génération SQL'], ['app_shared_sql_generators_use_fully_qualified_names', 'Utiliser les noms complets'], ['app_shared_sql_generators_compact_sql', 'SQL compact'], + ['app_shared_sql_generators_full_ddl', 'Full DDL'], ['app_shared_sql_generators_open_in_editor', "Ouvrir dans l'éditeur SQL"], ['app_shared_sql_generators_error_open_editor', "Erreur lors de l'ouverture de l'éditeur SQL"], ]; diff --git a/webapp/packages/plugin-sql-generator/src/locales/it.ts b/webapp/packages/plugin-sql-generator/src/locales/it.ts index b980aadd5a5..50176cbdfcd 100644 --- a/webapp/packages/plugin-sql-generator/src/locales/it.ts +++ b/webapp/packages/plugin-sql-generator/src/locales/it.ts @@ -2,6 +2,7 @@ export default [ ['app_shared_sql_generators_error_title', 'Errore di generazione SQL'], ['app_shared_sql_generators_use_fully_qualified_names', 'Usa nomi completi'], ['app_shared_sql_generators_compact_sql', 'SQL compatto'], + ['app_shared_sql_generators_full_ddl', 'Full DDL'], ['app_shared_sql_generators_open_in_editor', "Apri nell'editor SQL"], ['app_shared_sql_generators_error_open_editor', "Errore durante l'apertura dell'editor SQL"], ]; diff --git a/webapp/packages/plugin-sql-generator/src/locales/ru.ts b/webapp/packages/plugin-sql-generator/src/locales/ru.ts index 8a44f1cefbf..643f6c438e7 100644 --- a/webapp/packages/plugin-sql-generator/src/locales/ru.ts +++ b/webapp/packages/plugin-sql-generator/src/locales/ru.ts @@ -2,6 +2,7 @@ export default [ ['app_shared_sql_generators_error_title', 'Ошибка генерации SQL'], ['app_shared_sql_generators_use_fully_qualified_names', 'Использовать полные имена'], ['app_shared_sql_generators_compact_sql', 'Компактный SQL'], + ['app_shared_sql_generators_full_ddl', 'Полный DDL'], ['app_shared_sql_generators_open_in_editor', 'Открыть в SQL редакторе'], ['app_shared_sql_generators_error_open_editor', 'Ошибка при открытии SQL редактора'], ]; diff --git a/webapp/packages/plugin-sql-generator/src/locales/vi.ts b/webapp/packages/plugin-sql-generator/src/locales/vi.ts index 41cbd4a09ce..454f3896784 100644 --- a/webapp/packages/plugin-sql-generator/src/locales/vi.ts +++ b/webapp/packages/plugin-sql-generator/src/locales/vi.ts @@ -2,6 +2,7 @@ export default [ ['app_shared_sql_generators_error_title', 'Lỗi tạo SQL'], ['app_shared_sql_generators_use_fully_qualified_names', 'Sử dụng tên đầy đủ'], ['app_shared_sql_generators_compact_sql', 'SQL gọn'], + ['app_shared_sql_generators_full_ddl', 'Full DDL'], ['app_shared_sql_generators_open_in_editor', 'Mở trong trình soạn thảo SQL'], ['app_shared_sql_generators_error_open_editor', 'Lỗi khi mở trình soạn thảo SQL'], ]; diff --git a/webapp/packages/plugin-sql-generator/src/locales/zh.ts b/webapp/packages/plugin-sql-generator/src/locales/zh.ts index e40f345c8c9..0eee5fc34ec 100644 --- a/webapp/packages/plugin-sql-generator/src/locales/zh.ts +++ b/webapp/packages/plugin-sql-generator/src/locales/zh.ts @@ -2,6 +2,7 @@ export default [ ['app_shared_sql_generators_error_title', 'SQL生成错误'], ['app_shared_sql_generators_use_fully_qualified_names', '使用完全限定名'], ['app_shared_sql_generators_compact_sql', '紧凑SQL'], + ['app_shared_sql_generators_full_ddl', 'Full DDL'], ['app_shared_sql_generators_open_in_editor', '在SQL编辑器中打开'], ['app_shared_sql_generators_error_open_editor', '打开SQL编辑器时出错'], ]; diff --git a/webapp/packages/plugin-sql-generator/tsconfig.json b/webapp/packages/plugin-sql-generator/tsconfig.json index 95eb404164a..c7229863bd5 100644 --- a/webapp/packages/plugin-sql-generator/tsconfig.json +++ b/webapp/packages/plugin-sql-generator/tsconfig.json @@ -40,6 +40,9 @@ { "path": "../core-resource" }, + { + "path": "../core-root" + }, { "path": "../core-sdk" }, diff --git a/webapp/yarn.lock b/webapp/yarn.lock index c0159b38add..fc9976dce3d 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -3504,6 +3504,8 @@ __metadata: "@cloudbeaver/core-connections": "workspace:*" "@cloudbeaver/core-data-context": "workspace:*" "@cloudbeaver/core-di": "workspace:*" + "@cloudbeaver/core-events": "workspace:*" + "@cloudbeaver/core-localization": "workspace:*" "@cloudbeaver/core-navigation-tree": "workspace:*" "@cloudbeaver/core-resource": "workspace:*" "@cloudbeaver/core-sdk": "workspace:*" @@ -3515,6 +3517,7 @@ __metadata: "@cloudbeaver/plugin-sql-editor": "workspace:*" "@cloudbeaver/plugin-sql-editor-codemirror": "workspace:*" "@cloudbeaver/plugin-sql-editor-navigation-tab": "workspace:*" + "@cloudbeaver/plugin-sql-generator": "workspace:*" "@cloudbeaver/tsconfig": "workspace:*" "@dbeaver/js-helpers": "workspace:*" "@types/react": "npm:^19" @@ -4719,6 +4722,7 @@ __metadata: "@cloudbeaver/core-localization": "workspace:*" "@cloudbeaver/core-navigation-tree": "workspace:*" "@cloudbeaver/core-resource": "workspace:*" + "@cloudbeaver/core-root": "workspace:*" "@cloudbeaver/core-sdk": "workspace:*" "@cloudbeaver/core-utils": "workspace:*" "@cloudbeaver/core-view": "workspace:*"