From bc7cd3b21743ff2032bceed458771e0f58b4a5bb Mon Sep 17 00:00:00 2001 From: Ainur <59531286+yagudin10@users.noreply.github.com> Date: Mon, 22 Jan 2024 16:30:36 +0100 Subject: [PATCH] CB-4289 add file name param for data export (#2308) * CB-4289 add file name param for data export * CB-4289 pass file name based on context * CB-4289 use existing export action --------- Co-authored-by: naumov Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com> --- .../schema/service.data.transfer.graphqls | 1 + .../WebDataTransferDefaultExportSettings.java | 3 +- .../impl/WebDataTransferOutputSettings.java | 9 +- .../transfer/impl/WebServiceDataTransfer.java | 9 +- .../core-localization/src/locales/en.ts | 1 + .../core-localization/src/locales/it.ts | 1 + .../core-localization/src/locales/ru.ts | 1 + .../core-localization/src/locales/zh.ts | 1 + .../core-utils/src/generateFileName.test.ts | 24 ---- webapp/packages/core-utils/src/index.ts | 2 +- .../core-utils/src/withTimestamp.test.ts | 22 +++ .../{generateFileName.ts => withTimestamp.ts} | 6 +- .../src/Action/Actions/ACTION_EXPORT.ts | 3 +- .../src/DataExportMenuService.ts | 130 +++++++++++------- .../src/Dialog/useDataExportDialog.ts | 5 +- .../plugin-data-export/src/IExportContext.ts | 1 + .../src/DdlViewer/DDLViewerFooterService.ts | 4 +- .../src/SqlEditor/useTools.tsx | 4 +- 18 files changed, 140 insertions(+), 87 deletions(-) delete mode 100644 webapp/packages/core-utils/src/generateFileName.test.ts create mode 100644 webapp/packages/core-utils/src/withTimestamp.test.ts rename webapp/packages/core-utils/src/{generateFileName.ts => withTimestamp.ts} (53%) diff --git a/server/bundles/io.cloudbeaver.service.data.transfer/schema/service.data.transfer.graphqls b/server/bundles/io.cloudbeaver.service.data.transfer/schema/service.data.transfer.graphqls index 608c5ad8c1..f2d0b80b61 100644 --- a/server/bundles/io.cloudbeaver.service.data.transfer/schema/service.data.transfer.graphqls +++ b/server/bundles/io.cloudbeaver.service.data.transfer/schema/service.data.transfer.graphqls @@ -19,6 +19,7 @@ input DataTransferOutputSettingsInput { encoding: String timestampPattern: String compress: Boolean + fileName: String } type DataTransferOutputSettings { diff --git a/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferDefaultExportSettings.java b/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferDefaultExportSettings.java index 17f2d16e51..decb1a96fe 100644 --- a/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferDefaultExportSettings.java +++ b/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferDefaultExportSettings.java @@ -31,7 +31,8 @@ public class WebDataTransferDefaultExportSettings { false, defConsumerSettings.getOutputEncoding(), defConsumerSettings.getOutputTimestampPattern(), - defConsumerSettings.isCompressResults() + defConsumerSettings.isCompressResults(), + null ); this.supportedEncodings = Charset.availableCharsets().keySet(); } diff --git a/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferOutputSettings.java b/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferOutputSettings.java index 1633cd17e0..7c288644b6 100644 --- a/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferOutputSettings.java +++ b/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebDataTransferOutputSettings.java @@ -25,19 +25,22 @@ public class WebDataTransferOutputSettings { private final String encoding; private final String timestampPattern; private final boolean compress; + private final String fileName; public WebDataTransferOutputSettings(Map outputSettings) { this.insertBom = JSONUtils.getBoolean(outputSettings, "insertBom", false); this.encoding = JSONUtils.getString(outputSettings, "encoding"); this.timestampPattern = JSONUtils.getString(outputSettings, "timestampPattern"); this.compress = JSONUtils.getBoolean(outputSettings, "compress", false); + this.fileName = JSONUtils.getString(outputSettings, "fileName"); } - public WebDataTransferOutputSettings(boolean insertBom, String encoding, String timestampPattern, boolean compress) { + public WebDataTransferOutputSettings(boolean insertBom, String encoding, String timestampPattern, boolean compress, String fileName) { this.insertBom = insertBom; this.encoding = encoding; this.timestampPattern = timestampPattern; this.compress = compress; + this.fileName = fileName; } public boolean isInsertBom() { @@ -55,4 +58,8 @@ public class WebDataTransferOutputSettings { public boolean isCompress() { return compress; } + + public String getFileName() { + return fileName; + } } diff --git a/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebServiceDataTransfer.java b/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebServiceDataTransfer.java index 6581b19d54..f503fc3dfb 100644 --- a/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebServiceDataTransfer.java +++ b/server/bundles/io.cloudbeaver.service.data.transfer/src/io/cloudbeaver/service/data/transfer/impl/WebServiceDataTransfer.java @@ -173,12 +173,15 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer { } throw new DBException("Error exporting data", e); } - Path finallyExportFile = parameters.getOutputSettings().isCompress() + var outputSettings = parameters.getOutputSettings(); + Path finallyExportFile = outputSettings.isCompress() ? exportFile.resolveSibling(WebDataTransferUtils.normalizeFileName( - exportFile.getFileName().toString(), parameters.getOutputSettings())) + exportFile.getFileName().toString(), outputSettings)) : exportFile; WebDataTransferTaskConfig taskConfig = new WebDataTransferTaskConfig(finallyExportFile, parameters); - String exportFileName = CommonUtils.escapeFileName(CommonUtils.truncateString(dataContainer.getName(), 32)); + String exportFileName = CommonUtils.isEmpty(outputSettings.getFileName()) ? + CommonUtils.escapeFileName(CommonUtils.truncateString(dataContainer.getName(), 32)) : + outputSettings.getFileName(); taskConfig.setExportFileName(exportFileName); WebDataTransferUtils.getSessionDataTransferConfig(sqlProcessor.getWebSession()).addTask(taskConfig); diff --git a/webapp/packages/core-localization/src/locales/en.ts b/webapp/packages/core-localization/src/locales/en.ts index 3cf6bbcd39..a2327a3d65 100644 --- a/webapp/packages/core-localization/src/locales/en.ts +++ b/webapp/packages/core-localization/src/locales/en.ts @@ -110,6 +110,7 @@ export default [ ['ui_disable', 'Disable'], ['ui_readonly', 'Read-only'], ['ui_test', 'Test'], + ['ui_export', 'Export'], ['root_permission_denied', "You don't have permissions"], ['root_permission_no_permission', "You don't have permission for this action"], diff --git a/webapp/packages/core-localization/src/locales/it.ts b/webapp/packages/core-localization/src/locales/it.ts index aa883679e9..c39bd5d2d1 100644 --- a/webapp/packages/core-localization/src/locales/it.ts +++ b/webapp/packages/core-localization/src/locales/it.ts @@ -94,6 +94,7 @@ export default [ ['ui_disable', 'Disable'], ['ui_readonly', 'In sola lettura'], ['ui_test', 'Test'], + ['ui_export', 'Export'], ['root_permission_denied', 'Non hai i permessi'], ['app_root_session_expire_warning_title', 'La sessione sta per scadere'], diff --git a/webapp/packages/core-localization/src/locales/ru.ts b/webapp/packages/core-localization/src/locales/ru.ts index cacfd3d9fb..97dc376b02 100644 --- a/webapp/packages/core-localization/src/locales/ru.ts +++ b/webapp/packages/core-localization/src/locales/ru.ts @@ -106,6 +106,7 @@ export default [ ['ui_disable', 'Отключить'], ['ui_readonly', 'Доступно только для чтения'], ['ui_test', 'Проверить'], + ['ui_export', 'Экспорт'], ['root_permission_denied', 'Отказано в доступе'], ['root_permission_no_permission', 'У вас нет разрешения на это действие'], diff --git a/webapp/packages/core-localization/src/locales/zh.ts b/webapp/packages/core-localization/src/locales/zh.ts index b54e69b60a..70a1b2dfc4 100644 --- a/webapp/packages/core-localization/src/locales/zh.ts +++ b/webapp/packages/core-localization/src/locales/zh.ts @@ -107,6 +107,7 @@ export default [ ['ui_disable', 'Disable'], ['ui_readonly', '只读'], ['ui_test', 'Test'], + ['ui_export', 'Export'], ['root_permission_denied', '您没有权限'], ['root_permission_no_permission', '您没有权限执行此操作'], diff --git a/webapp/packages/core-utils/src/generateFileName.test.ts b/webapp/packages/core-utils/src/generateFileName.test.ts deleted file mode 100644 index 8b10836ec6..0000000000 --- a/webapp/packages/core-utils/src/generateFileName.test.ts +++ /dev/null @@ -1,24 +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 { generateFileName } from './generateFileName'; - -describe('generateFileName', () => { - it('should generate a file name in the expected format', () => { - const mockDate = new Date('2020-09-09T14:13:20'); - const spy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate); - - const fileName = 'my-file'; - const fileFormat = '.txt'; - const expectedFileName = `${fileName} 2020-09-09 14-13-20${fileFormat}`; - - // Test the generateFileName function - expect(generateFileName(fileName, fileFormat)).toEqual(expectedFileName); - - spy.mockRestore(); - }); -}); diff --git a/webapp/packages/core-utils/src/index.ts b/webapp/packages/core-utils/src/index.ts index b471a596a2..449c232bc6 100644 --- a/webapp/packages/core-utils/src/index.ts +++ b/webapp/packages/core-utils/src/index.ts @@ -20,7 +20,6 @@ export * from './copyToClipboard'; export * from './declensionOfNumber'; export * from './isNotNullDefined'; export * from './flat'; -export * from './generateFileName'; export * from './getMIME'; export * from './getOS'; export * from './getPathName'; @@ -72,3 +71,4 @@ export * from './renamePathName'; export * from './removeLineBreak'; export * from './replaceSubstring'; export * from './formatNumber'; +export * from './withTimestamp'; diff --git a/webapp/packages/core-utils/src/withTimestamp.test.ts b/webapp/packages/core-utils/src/withTimestamp.test.ts new file mode 100644 index 0000000000..84fa123294 --- /dev/null +++ b/webapp/packages/core-utils/src/withTimestamp.test.ts @@ -0,0 +1,22 @@ +/* + * 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 { withTimestamp } from './withTimestamp'; + +describe('withTimestamp', () => { + it('should generate a value with timestamp at the end', () => { + const mockDate = new Date('2020-09-09T14:13:20'); + const spy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate); + + const value = 'value'; + const expectedValue = `${value} 2020-09-09 14-13-20`; + + expect(withTimestamp(value)).toEqual(expectedValue); + + spy.mockRestore(); + }); +}); diff --git a/webapp/packages/core-utils/src/generateFileName.ts b/webapp/packages/core-utils/src/withTimestamp.ts similarity index 53% rename from webapp/packages/core-utils/src/generateFileName.ts rename to webapp/packages/core-utils/src/withTimestamp.ts index 53bd44f71c..4858da1e1e 100644 --- a/webapp/packages/core-utils/src/generateFileName.ts +++ b/webapp/packages/core-utils/src/withTimestamp.ts @@ -6,9 +6,9 @@ * you may not use this file except in compliance with the License. */ -export function generateFileName(fileName: string, fileFormat: string) { +export function withTimestamp(value: string) { const now = new Date(); - return `${fileName} ${now.toISOString().slice(0, 10)} ${('0' + now.getHours()).slice(-2)}-${('0' + now.getMinutes()).slice(-2)}-${( + return `${value} ${now.toISOString().slice(0, 10)} ${('0' + now.getHours()).slice(-2)}-${('0' + now.getMinutes()).slice(-2)}-${( '0' + now.getSeconds() - ).slice(-2)}${fileFormat}`; + ).slice(-2)}`; } diff --git a/webapp/packages/core-view/src/Action/Actions/ACTION_EXPORT.ts b/webapp/packages/core-view/src/Action/Actions/ACTION_EXPORT.ts index d645a9ab45..f5c31aeea4 100644 --- a/webapp/packages/core-view/src/Action/Actions/ACTION_EXPORT.ts +++ b/webapp/packages/core-view/src/Action/Actions/ACTION_EXPORT.ts @@ -8,5 +8,6 @@ import { createAction } from '../createAction'; export const ACTION_EXPORT = createAction('export', { - label: 'data_transfer_dialog_export', + label: 'ui_export', + tooltip: 'ui_export', }); diff --git a/webapp/packages/plugin-data-export/src/DataExportMenuService.ts b/webapp/packages/plugin-data-export/src/DataExportMenuService.ts index e7845520a3..738cc20264 100644 --- a/webapp/packages/plugin-data-export/src/DataExportMenuService.ts +++ b/webapp/packages/plugin-data-export/src/DataExportMenuService.ts @@ -8,11 +8,19 @@ import { EAdminPermission } from '@cloudbeaver/core-authentication'; import { createConnectionParam, DATA_CONTEXT_CONNECTION } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; -import { CommonDialogService, IMenuContext } from '@cloudbeaver/core-dialogs'; +import { CommonDialogService } from '@cloudbeaver/core-dialogs'; +import { LocalizationService } from '@cloudbeaver/core-localization'; import { DATA_CONTEXT_NAV_NODE, EObjectFeature } from '@cloudbeaver/core-navigation-tree'; import { SessionPermissionsResource } from '@cloudbeaver/core-root'; -import { ACTION_EXPORT, ActionService, DATA_CONTEXT_MENU_NESTED, MenuService } from '@cloudbeaver/core-view'; -import { IDatabaseDataSource, IDataContainerOptions, ITableFooterMenuContext, TableFooterMenuService } from '@cloudbeaver/plugin-data-viewer'; +import { withTimestamp } from '@cloudbeaver/core-utils'; +import { ACTION_EXPORT, ActionService, DATA_CONTEXT_MENU, DATA_CONTEXT_MENU_NESTED, menuExtractItems, MenuService } from '@cloudbeaver/core-view'; +import { + DATA_CONTEXT_DV_DDM, + DATA_CONTEXT_DV_DDM_RESULT_INDEX, + DATA_VIEWER_DATA_MODEL_ACTIONS_MENU, + IDatabaseDataSource, + IDataContainerOptions, +} from '@cloudbeaver/plugin-data-viewer'; import type { IDataQueryOptions } from '@cloudbeaver/plugin-sql-editor'; import { DataExportSettingsService } from './DataExportSettingsService'; @@ -22,32 +30,83 @@ import { DataExportDialog } from './Dialog/DataExportDialog'; export class DataExportMenuService { constructor( private readonly commonDialogService: CommonDialogService, - private readonly tableFooterMenuService: TableFooterMenuService, private readonly dataExportSettingsService: DataExportSettingsService, private readonly actionService: ActionService, private readonly menuService: MenuService, private readonly sessionPermissionsResource: SessionPermissionsResource, + private readonly localizationService: LocalizationService, ) {} register(): void { - this.tableFooterMenuService.registerMenuItem({ - id: 'export ', - order: 5, - title: 'data_transfer_dialog_export', - tooltip: 'data_transfer_dialog_export_tooltip', - icon: 'table-export', - isPresent(context) { - return context.contextType === TableFooterMenuService.nodeContextType; + this.actionService.addHandler({ + id: 'data-export-base-handler', + isActionApplicable(context, action) { + const menu = context.hasValue(DATA_CONTEXT_MENU, DATA_VIEWER_DATA_MODEL_ACTIONS_MENU); + const model = context.tryGet(DATA_CONTEXT_DV_DDM); + const resultIndex = context.tryGet(DATA_CONTEXT_DV_DDM_RESULT_INDEX); + + if (!menu || !model || resultIndex === undefined) { + return false; + } + + return [ACTION_EXPORT].includes(action); }, - isHidden: () => this.isDisabled(), isDisabled(context) { - return ( - context.data.model.isLoading() || - context.data.model.isDisabled(context.data.resultIndex) || - !context.data.model.getResult(context.data.resultIndex) - ); + const model = context.get(DATA_CONTEXT_DV_DDM); + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX); + + return model.isLoading() || model.isDisabled(resultIndex) || !model.getResult(resultIndex); + }, + getActionInfo(context, action) { + if (action === ACTION_EXPORT) { + return { ...action.info, icon: 'table-export' }; + } + + return action.info; + }, + handler: (context, action) => { + const model = context.get(DATA_CONTEXT_DV_DDM); + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX); + + if (action === ACTION_EXPORT) { + const result = model.getResult(resultIndex); + + if (!result) { + throw new Error('Result must be provided'); + } + + const source = model.source as IDatabaseDataSource; + + if (!source.options) { + throw new Error('Source options must be provided'); + } + + this.commonDialogService.open(DataExportDialog, { + connectionKey: source.options.connectionKey, + contextId: source.executionContext?.context?.id, + containerNodePath: source.options.containerNodePath, + resultId: result.id, + name: model.name ?? undefined, + fileName: withTimestamp(model.name ?? this.localizationService.translate('data_transfer_dialog_title')), + query: source.options.query, + filter: { + constraints: source.options.constraints, + where: source.options.whereFilter, + }, + }); + } + }, + }); + this.menuService.addCreator({ + menus: [DATA_VIEWER_DATA_MODEL_ACTIONS_MENU], + isApplicable: () => !this.isExportDisabled(), + getItems(context, items) { + return [...items, ACTION_EXPORT]; + }, + orderItems(context, items) { + const extracted = menuExtractItems(items, [ACTION_EXPORT]); + return [...items, ...extracted]; }, - onClick: this.exportData.bind(this), }); this.menuService.addCreator({ @@ -58,7 +117,7 @@ export class DataExportMenuService { return false; } - return !this.isDisabled() && context.has(DATA_CONTEXT_CONNECTION) && !context.has(DATA_CONTEXT_MENU_NESTED); + return !this.isExportDisabled() && context.has(DATA_CONTEXT_CONNECTION) && !context.has(DATA_CONTEXT_MENU_NESTED); }, getItems: (context, items) => [...items, ACTION_EXPORT], }); @@ -69,44 +128,19 @@ export class DataExportMenuService { handler: async (context, action) => { const node = context.get(DATA_CONTEXT_NAV_NODE); const connection = context.get(DATA_CONTEXT_CONNECTION); + const fileName = withTimestamp(`${connection.name}${node?.name ? ` - ${node.name}` : ''}`); this.commonDialogService.open(DataExportDialog, { connectionKey: createConnectionParam(connection), name: node?.name, + fileName, containerNodePath: node?.id, }); }, }); } - private exportData(context: IMenuContext) { - const result = context.data.model.getResult(context.data.resultIndex); - - if (!result) { - throw new Error('Result must be provided'); - } - - const source = context.data.model.source as IDatabaseDataSource; - - if (!source.options) { - throw new Error('Source options must be provided'); - } - - this.commonDialogService.open(DataExportDialog, { - connectionKey: source.options.connectionKey, - contextId: context.data.model.source.executionContext?.context?.id, - containerNodePath: source.options.containerNodePath, - resultId: result.id, - name: context.data.model.name ?? undefined, - query: source.options.query, - filter: { - constraints: source.options.constraints, - where: source.options.whereFilter, - }, - }); - } - - private isDisabled() { + private isExportDisabled() { if (this.sessionPermissionsResource.has(EAdminPermission.admin)) { return false; } diff --git a/webapp/packages/plugin-data-export/src/Dialog/useDataExportDialog.ts b/webapp/packages/plugin-data-export/src/Dialog/useDataExportDialog.ts index c0aa0888bf..57daa2fa6b 100644 --- a/webapp/packages/plugin-data-export/src/Dialog/useDataExportDialog.ts +++ b/webapp/packages/plugin-data-export/src/Dialog/useDataExportDialog.ts @@ -94,7 +94,10 @@ export function useDataExportDialog(context: IExportContext, onExport?: () => vo processorId: this.processor.id, processorProperties: this.processorProperties, filter: this.context.filter, - outputSettings: this.outputSettings, + outputSettings: { + ...this.outputSettings, + fileName: this.context.fileName, + }, }); this.onExport?.(); diff --git a/webapp/packages/plugin-data-export/src/IExportContext.ts b/webapp/packages/plugin-data-export/src/IExportContext.ts index 538da64e0a..0f56f4b0bb 100644 --- a/webapp/packages/plugin-data-export/src/IExportContext.ts +++ b/webapp/packages/plugin-data-export/src/IExportContext.ts @@ -14,6 +14,7 @@ export interface IExportContext { resultId?: string | null; containerNodePath?: string; name?: string; + fileName?: string; query?: string; filter?: SqlDataFilter; } diff --git a/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DDLViewerFooterService.ts b/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DDLViewerFooterService.ts index 1aa2d6bca3..d926b3ceca 100644 --- a/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DDLViewerFooterService.ts +++ b/webapp/packages/plugin-ddl-viewer/src/DdlViewer/DDLViewerFooterService.ts @@ -8,7 +8,7 @@ import { ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; import { NavNodeManagerService } from '@cloudbeaver/core-navigation-tree'; -import { download, generateFileName } from '@cloudbeaver/core-utils'; +import { download, withTimestamp } from '@cloudbeaver/core-utils'; import { ACTION_SAVE, ActionService, DATA_CONTEXT_MENU, MenuService } from '@cloudbeaver/core-view'; import { LocalStorageSqlDataSource } from '@cloudbeaver/plugin-sql-editor'; import { ACTION_SQL_EDITOR_OPEN, SqlEditorNavigatorService } from '@cloudbeaver/plugin-sql-editor-navigation-tab'; @@ -58,7 +58,7 @@ export class DDLViewerFooterService { const node = nodeId ? this.navNodeManagerService.getNode(nodeId) : undefined; const name = node?.name ? `DDL_${node.nodeType ? node.nodeType + '_' : ''}${node.name}` : 'DDL'; - download(blob, generateFileName(name, '.sql')); + download(blob, `${withTimestamp(name)}.sql`); break; } case ACTION_SQL_EDITOR_OPEN: { diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditor/useTools.tsx b/webapp/packages/plugin-sql-editor/src/SqlEditor/useTools.tsx index aff8dccc6b..96eb9140dc 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditor/useTools.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlEditor/useTools.tsx @@ -12,7 +12,7 @@ import { Connection, ConnectionInfoResource, createConnectionParam } from '@clou import { useService } from '@cloudbeaver/core-di'; import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; -import { download, generateFileName, getTextFileReadingProcess } from '@cloudbeaver/core-utils'; +import { download, getTextFileReadingProcess, withTimestamp } from '@cloudbeaver/core-utils'; import { getSqlEditorName } from '../getSqlEditorName'; import type { ISqlEditorTabState } from '../ISqlEditorTabState'; @@ -110,7 +110,7 @@ export function useTools(state: ISqlEditorTabState): Readonly { const name = getSqlEditorName(this.state, dataSource, connection); - download(blob, generateFileName(name, '.sql')); + download(blob, `${withTimestamp(name)}.sql`); }, }), {