From c5190b2567ff6c94f6455e0c357716a4c73abacd Mon Sep 17 00:00:00 2001 From: Ainur <59531286+yagudin10@users.noreply.github.com> Date: Wed, 10 Sep 2025 15:33:34 +0300 Subject: [PATCH 1/6] dbeaver/pro#6249 fix events for changing extensions (#3667) * dbeaver/pro#6249 fix events for changing extensions * dbeaver/pro#6249 send connection updated info event on rm side --------- Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com> --- .../WebGlobalProjectRegistryProxy.java | 3 +- .../io/cloudbeaver/WebSessionProjectImpl.java | 36 ++- .../rm/local/BaseLocalResourceController.java | 21 +- .../rm/local/LocalResourceController.java | 241 ++++++++++++++++++ .../utils/WebConnectionFolderUtils.java | 6 - .../src/io/cloudbeaver/WebServiceUtils.java | 6 +- .../WSDataSourceUpdatedEventHandlerImpl.java | 5 +- .../service/ConnectionControllerCE.java | 47 ---- .../service/core/impl/WebServiceCore.java | 92 ++----- .../navigator/impl/WebServiceNavigator.java | 43 +--- 10 files changed, 304 insertions(+), 196 deletions(-) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebGlobalProjectRegistryProxy.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebGlobalProjectRegistryProxy.java index 1c7fcf5cc5..6eb54b4f78 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebGlobalProjectRegistryProxy.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebGlobalProjectRegistryProxy.java @@ -35,6 +35,7 @@ import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.secret.DBSSecretController; import org.jkiss.dbeaver.model.struct.DBSObjectFilter; import org.jkiss.dbeaver.registry.DataSourceConfigurationManager; +import org.jkiss.dbeaver.registry.DataSourceParseResults; import org.jkiss.dbeaver.registry.DataSourcePersistentRegistry; import org.jkiss.dbeaver.registry.DataSourceRegistry; @@ -377,7 +378,7 @@ public class WebGlobalProjectRegistryProxy implements DBPDataSourceRegistry, Dat } @Override - public boolean loadDataSources( + public DataSourceParseResults loadDataSources( @NotNull List storages, @NotNull DataSourceConfigurationManager manager, @Nullable Collection dataSourceIds, boolean refresh, diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebSessionProjectImpl.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebSessionProjectImpl.java index acd11ef022..7e5ec7a754 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebSessionProjectImpl.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebSessionProjectImpl.java @@ -29,13 +29,16 @@ import org.jkiss.dbeaver.model.navigator.DBNModel; import org.jkiss.dbeaver.model.rm.RMProject; import org.jkiss.dbeaver.model.rm.RMUtils; import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent; +import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty; import org.jkiss.dbeaver.registry.DataSourceDescriptor; import org.jkiss.dbeaver.registry.DataSourceRegistry; import org.jkiss.dbeaver.runtime.jobs.DisconnectJob; import java.nio.file.Path; -import java.util.*; -import java.util.stream.Collectors; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class WebSessionProjectImpl extends WebProjectImpl { private static final Log log = Log.getLog(WebSessionProjectImpl.class); @@ -194,40 +197,31 @@ public class WebSessionProjectImpl extends WebProjectImpl { /** * updates data sources based on event in web session * - * @param dataSourceIds list of updated connections - * @param eventId id of event + * @param event data source updated event */ - public synchronized boolean updateProjectDataSources(@NotNull List dataSourceIds, @NotNull String eventId) { + public synchronized boolean updateProjectDataSources(@NotNull WSDataSourceEvent event) { var sendDataSourceUpdatedEvent = false; DBPDataSourceRegistry registry = getDataSourceRegistry(); - // save old connections - var oldDataSources = dataSourceIds.stream() - .map(registry::getDataSource) - .filter(Objects::nonNull) - .collect(Collectors.toMap( - DBPDataSourceContainer::getId, - registry::createDataSource) - ); - if (WSDataSourceEvent.CREATED.equals(eventId) || WSDataSourceEvent.UPDATED.equals(eventId)) { - registry.refreshConfig(dataSourceIds); + if (WSDataSourceEvent.CREATED.equals(event.getId()) || WSDataSourceEvent.UPDATED.equals(event.getId())) { + registry.refreshConfig(event.getDataSourceIds()); } - for (String dsId : dataSourceIds) { + for (String dsId : event.getDataSourceIds()) { DataSourceDescriptor ds = (DataSourceDescriptor) registry.getDataSource(dsId); if (ds == null) { continue; } - switch (eventId) { + switch (event.getId()) { case WSDataSourceEvent.CREATED -> { addConnection(ds); sendDataSourceUpdatedEvent = true; } case WSDataSourceEvent.UPDATED -> { - boolean connectionUpdated = !ds.equalSettings(oldDataSources.get(dsId)); - if (connectionUpdated) { - sendDataSourceUpdatedEvent = true; + if (event.getProperty() == WSDataSourceProperty.CONFIGURATION) { WebDataSourceUtils.disconnectDataSource(webSession, ds); } - // if settings were changed we need to send event + if (event.getProperty() != WSDataSourceProperty.INTERNAL) { + sendDataSourceUpdatedEvent = true; + } } case WSDataSourceEvent.DELETED -> { WebDataSourceUtils.disconnectDataSource(webSession, ds); diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/BaseLocalResourceController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/BaseLocalResourceController.java index 847ed829a2..0e18ed7f0c 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/BaseLocalResourceController.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/BaseLocalResourceController.java @@ -149,27 +149,37 @@ public abstract class BaseLocalResourceController implements RMController { @NotNull String projectId, @NotNull String configuration, @Nullable List dataSourceIds + ) throws DBException { + return updateProjectDataSourcesConfig(projectId, configuration, dataSourceIds) != null; + } + + @Nullable + protected DataSourceParseResults updateProjectDataSourcesConfig( + @NotNull String projectId, + @NotNull String configuration, + @Nullable List dataSourceIds ) throws DBException { try (var lock = lockController.lock(projectId, "updateProjectDataSources")) { DBPProject project = getWebProject(projectId, false); - return doFileWriteOperation(projectId, project.getMetadataFolder(false), + return doFileWriteOperation( + projectId, project.getMetadataFolder(false), () -> { DBPDataSourceRegistry registry = project.getDataSourceRegistry(); DBPDataSourceConfigurationStorage storage = new DataSourceMemoryStorage(configuration.getBytes( StandardCharsets.UTF_8)); DataSourceConfigurationManager manager = new DataSourceConfigurationManagerBuffer(); - var configChanged = ((DataSourcePersistentRegistry) registry).loadDataSources( + final DataSourceParseResults parseResults = ((DataSourcePersistentRegistry) registry).loadDataSources( List.of(storage), manager, dataSourceIds, true, - false + dataSourceIds == null ); registry.checkForErrors(); log.debug("Save data sources configuration in project '" + projectId + "'"); ((DataSourcePersistentRegistry) registry).saveDataSources(); registry.checkForErrors(); - return configChanged; + return parseResults; } ); } @@ -217,6 +227,7 @@ public abstract class BaseLocalResourceController implements RMController { var parent = result.getParent(); var parentFolder = parent == null ? null : registry.getFolder(parent.toString().replace("\\", "/")); DBPDataSourceFolder newFolder = registry.addFolder(parentFolder, newName); + ((DataSourcePersistentRegistry) registry).saveDataSources(); registry.checkForErrors(); return null; } @@ -244,6 +255,7 @@ public abstract class BaseLocalResourceController implements RMController { log.warn("Can not find folder by path [" + folderPath + "] for deletion"); } } + ((DataSourcePersistentRegistry) registry).saveDataSources(); registry.checkForErrors(); return null; } @@ -265,6 +277,7 @@ public abstract class BaseLocalResourceController implements RMController { DBPDataSourceRegistry registry = project.getDataSourceRegistry(); registry.moveFolder(oldPath, newPath); registry.checkForErrors(); + ((DataSourcePersistentRegistry) registry).saveDataSources(); return null; } ); diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java index 67f2aac53e..8138830b8f 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java @@ -28,18 +28,26 @@ import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.DBPDataSourceContainer; +import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry; import org.jkiss.dbeaver.model.app.DBPWorkspace; import org.jkiss.dbeaver.model.auth.SMCredentials; import org.jkiss.dbeaver.model.auth.SMCredentialsProvider; import org.jkiss.dbeaver.model.fs.lock.FileLockController; import org.jkiss.dbeaver.model.impl.app.BaseProjectImpl; import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl; +import org.jkiss.dbeaver.model.navigator.DBNLocalFolder; import org.jkiss.dbeaver.model.rm.*; import org.jkiss.dbeaver.model.security.SMController; import org.jkiss.dbeaver.model.security.SMObjectType; import org.jkiss.dbeaver.model.sql.DBQuotaException; import org.jkiss.dbeaver.model.websocket.event.MessageType; import org.jkiss.dbeaver.model.websocket.event.WSSessionLogUpdatedEvent; +import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent; +import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty; +import org.jkiss.dbeaver.model.websocket.event.datasource.WSDatasourceFolderEvent; +import org.jkiss.dbeaver.registry.DataSourceDescriptor; +import org.jkiss.dbeaver.registry.DataSourceParseResults; import org.jkiss.dbeaver.registry.ResourceTypeDescriptor; import org.jkiss.dbeaver.registry.ResourceTypeRegistry; import org.jkiss.dbeaver.runtime.DBWorkbench; @@ -334,6 +342,239 @@ public class LocalResourceController extends BaseLocalResourceController { return project; } + @Override + public void createProjectDataSources( + @NotNull String projectId, + @NotNull String configuration, + @Nullable List dataSourceIds + ) throws DBException { + super.createProjectDataSources(projectId, configuration, dataSourceIds); + if (credentialsProvider.getActiveUserCredentials() != null && dataSourceIds != null) { + ServletAppUtils.getServletApplication().getEventController().addEvent( + WSDataSourceEvent.create( + credentialsProvider.getActiveUserCredentials().getSmSessionId(), + credentialsProvider.getActiveUserCredentials().getUserId(), + projectId, + dataSourceIds, + WSDataSourceProperty.CONFIGURATION + ) + ); + } + } + + @Override + public boolean updateProjectDataSources( + @NotNull String projectId, + @NotNull String configuration, + @Nullable List dataSourceIds + ) throws DBException { + DBPDataSourceRegistry registry = getWebProject(projectId, false).getDataSourceRegistry(); + Map oldDataSources = registry.getDataSources().stream() + .filter(ds -> dataSourceIds == null || dataSourceIds.contains(ds.getId())) + .collect(Collectors.toMap( + DBPDataSourceContainer::getId, + registry::createDataSource + ) + ); + DataSourceParseResults parseResults = super.updateProjectDataSourcesConfig(projectId, configuration, dataSourceIds); + sendDataSourcesConfigUpdatedEvent(registry, oldDataSources, parseResults); + return parseResults != null; + } + + @Override + public void deleteProjectDataSources(@NotNull String projectId, @NotNull String[] dataSourceIds) throws DBException { + super.deleteProjectDataSources(projectId, dataSourceIds); + if (credentialsProvider.getActiveUserCredentials() != null && dataSourceIds.length > 0) { + ServletAppUtils.getServletApplication().getEventController().addEvent( + WSDataSourceEvent.delete( + credentialsProvider.getActiveUserCredentials().getSmSessionId(), + credentialsProvider.getActiveUserCredentials().getUserId(), + projectId, + Arrays.asList(dataSourceIds), + WSDataSourceProperty.CONFIGURATION + ) + ); + } + } + + @Override + public void createProjectDataSourceFolder(@NotNull String projectId, @NotNull String folderPath) throws DBException { + super.createProjectDataSourceFolder(projectId, folderPath); + if (credentialsProvider.getActiveUserCredentials() != null) { + ServletAppUtils.getServletApplication().getEventController().addEvent( + WSDatasourceFolderEvent.create( + credentialsProvider.getActiveUserCredentials().getSmSessionId(), + credentialsProvider.getActiveUserCredentials().getUserId(), + projectId, + List.of(createNodePathFromFolderPath(projectId, folderPath)) + ) + ); + } + } + + @Override + public void moveProjectDataSourceFolder( + @NotNull String projectId, + @NotNull String oldPath, + @NotNull String newPath + ) throws DBException { + super.moveProjectDataSourceFolder(projectId, oldPath, newPath); + if (credentialsProvider.getActiveUserCredentials() != null) { + ServletAppUtils.getServletApplication().getEventController().addEvent( + WSDatasourceFolderEvent.delete( + credentialsProvider.getActiveUserCredentials().getSmSessionId(), + credentialsProvider.getActiveUserCredentials().getUserId(), + projectId, + List.of(createNodePathFromFolderPath(projectId, oldPath)) + ) + ); + ServletAppUtils.getServletApplication().getEventController().addEvent( + WSDatasourceFolderEvent.create( + credentialsProvider.getActiveUserCredentials().getSmSessionId(), + credentialsProvider.getActiveUserCredentials().getUserId(), + projectId, + List.of(createNodePathFromFolderPath(projectId, newPath)) + ) + ); + } + } + + @Override + public void deleteProjectDataSourceFolders(@NotNull String projectId, @NotNull String[] folderPaths, boolean dropContents) + throws DBException { + super.deleteProjectDataSourceFolders(projectId, folderPaths, dropContents); + if (credentialsProvider.getActiveUserCredentials() != null) { + ServletAppUtils.getServletApplication().getEventController().addEvent( + WSDatasourceFolderEvent.create( + credentialsProvider.getActiveUserCredentials().getSmSessionId(), + credentialsProvider.getActiveUserCredentials().getUserId(), + projectId, + Arrays.stream(folderPaths).map( + p -> createNodePathFromFolderPath(projectId, p) + ).collect(Collectors.toList()) + ) + ); + } + } + + private String createNodePathFromFolderPath(String projectId, String folderPath) { + return DBNLocalFolder.makeLocalFolderItemPath(projectId, folderPath); + } + + private void sendDataSourcesConfigUpdatedEvent( + @NotNull DBPDataSourceRegistry registry, + @NotNull Map oldDataSources, + @Nullable DataSourceParseResults parseResults + ) { + if (parseResults == null || credentialsProvider.getActiveUserCredentials() == null || oldDataSources.isEmpty()) { + return; + } + List updatedConfigurationDataSourceIds = new ArrayList<>(); + List updatedNameDataSourceIds = new ArrayList<>(); + List updatedInternalConfigurationDataSourceIds = new ArrayList<>(); + + for (Map.Entry entry : oldDataSources.entrySet()) { + String dsId = entry.getKey(); + DataSourceDescriptor oldDs = entry.getValue(); + DataSourceDescriptor newDs = (DataSourceDescriptor) registry.getDataSource(dsId); + if (newDs == null) { + continue; + } + if (!oldDs.equalConfiguration(newDs)) { + updatedConfigurationDataSourceIds.add(dsId); + } else if (!oldDs.isLooselyEqualTo(newDs)) { + updatedNameDataSourceIds.add(dsId); + } else if (!oldDs.equalInternalConfiguration(newDs)) { + updatedInternalConfigurationDataSourceIds.add(dsId); + } + } + + if (!updatedConfigurationDataSourceIds.isEmpty()) { + ServletAppUtils.getServletApplication().getEventController().addEvent( + WSDataSourceEvent.update( + credentialsProvider.getActiveUserCredentials().getSmSessionId(), + credentialsProvider.getActiveUserCredentials().getUserId(), + registry.getProject().getId(), + updatedConfigurationDataSourceIds, + WSDataSourceProperty.CONFIGURATION + ) + ); + } + if (!updatedNameDataSourceIds.isEmpty()) { + ServletAppUtils.getServletApplication().getEventController().addEvent( + WSDataSourceEvent.update( + credentialsProvider.getActiveUserCredentials().getSmSessionId(), + credentialsProvider.getActiveUserCredentials().getUserId(), + registry.getProject().getId(), + updatedNameDataSourceIds, + WSDataSourceProperty.NAME + ) + ); + } + if (!updatedInternalConfigurationDataSourceIds.isEmpty()) { + ServletAppUtils.getServletApplication().getEventController().addEvent( + WSDataSourceEvent.update( + credentialsProvider.getActiveUserCredentials().getSmSessionId(), + credentialsProvider.getActiveUserCredentials().getUserId(), + registry.getProject().getId(), + updatedInternalConfigurationDataSourceIds, + WSDataSourceProperty.INTERNAL + ) + ); + } + + if (!parseResults.addedDataSources.isEmpty()) { + ServletAppUtils.getServletApplication().getEventController().addEvent( + WSDataSourceEvent.create( + credentialsProvider.getActiveUserCredentials().getSmSessionId(), + credentialsProvider.getActiveUserCredentials().getUserId(), + registry.getProject().getId(), + updatedNameDataSourceIds, + WSDataSourceProperty.CONFIGURATION + ) + ); + } + + if (!parseResults.removedDataSources.isEmpty()) { + ServletAppUtils.getServletApplication().getEventController().addEvent( + WSDataSourceEvent.delete( + credentialsProvider.getActiveUserCredentials().getSmSessionId(), + credentialsProvider.getActiveUserCredentials().getUserId(), + registry.getProject().getId(), + updatedNameDataSourceIds, + WSDataSourceProperty.CONFIGURATION + ) + ); + } + + if (!parseResults.addedFolders.isEmpty()) { + ServletAppUtils.getServletApplication().getEventController().addEvent( + WSDatasourceFolderEvent.create( + credentialsProvider.getActiveUserCredentials().getSmSessionId(), + credentialsProvider.getActiveUserCredentials().getUserId(), + registry.getProject().getId(), + parseResults.addedFolders.stream().map( + f -> createNodePathFromFolderPath(registry.getProject().getId(), f.getFolderPath()) + ).toList() + ) + ); + } + + if (!parseResults.removedFolders.isEmpty()) { + ServletAppUtils.getServletApplication().getEventController().addEvent( + WSDatasourceFolderEvent.delete( + credentialsProvider.getActiveUserCredentials().getSmSessionId(), + credentialsProvider.getActiveUserCredentials().getUserId(), + registry.getProject().getId(), + parseResults.removedFolders.stream().map( + f -> createNodePathFromFolderPath(registry.getProject().getId(), f.getFolderPath()) + ).toList() + ) + ); + } + + } + @NotNull @Override public RMResource[] listResources( diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebConnectionFolderUtils.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebConnectionFolderUtils.java index dfeb3996da..bcd6ff7ce8 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebConnectionFolderUtils.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebConnectionFolderUtils.java @@ -21,7 +21,6 @@ import io.cloudbeaver.model.WebConnectionFolderInfo; import io.cloudbeaver.model.session.WebSession; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.model.DBPDataSourceFolder; -import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry; import org.jkiss.utils.CommonUtils; public class WebConnectionFolderUtils { @@ -45,9 +44,4 @@ public class WebConnectionFolderUtils { throw new DBWebException("Folder name '" + folderName + "' contains illegal characters: /"); } } - - public static DBPDataSourceFolder createFolder(WebConnectionFolderInfo parentFolder, String newName, DBPDataSourceRegistry registry) throws DBWebException { - DBPDataSourceFolder folder = registry.addFolder(parentFolder == null ? null : parentFolder.getDataSourceFolder(), newName); - return folder; - } } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java index e9f60f7e03..ae50682a82 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java @@ -318,9 +318,11 @@ public class WebServiceUtils extends WebCommonUtils { return container.getName() + " [" + container.getId() + "]"; } - public static void updateConfigAndRefreshDatabases(WebSession session, String projectId) throws DBWebException { + public static void refreshDatabases(WebSession session, String projectId) throws DBWebException { DBNProject projectNode = session.getNavigatorModelOrThrow().getRoot().getProjectNode(session.getProjectById(projectId)); - DBNModel.updateConfigAndRefreshDatabases(projectNode.getDatabases()); + if (projectNode != null) { + projectNode.getDatabases().refreshChildren(); + } } public static boolean isGlobalProject(DBPProject project) { diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/events/WSDataSourceUpdatedEventHandlerImpl.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/events/WSDataSourceUpdatedEventHandlerImpl.java index 8887d2d984..6bde4ea246 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/events/WSDataSourceUpdatedEventHandlerImpl.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/events/WSDataSourceUpdatedEventHandlerImpl.java @@ -39,10 +39,7 @@ public class WSDataSourceUpdatedEventHandlerImpl extends WSAbstractProjectEventH log.debug("Project " + event.getProjectId() + " is not found in session " + webSession.getSessionId()); return; } - sendEvent = project.updateProjectDataSources( - event.getDataSourceIds(), - event.getId() - ); + sendEvent = project.updateProjectDataSources(event); } if (sendEvent) { activeUserSession.addSessionEvent(event); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/ConnectionControllerCE.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/ConnectionControllerCE.java index 6e21e85bf8..be2fa613cc 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/ConnectionControllerCE.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/ConnectionControllerCE.java @@ -24,7 +24,6 @@ import io.cloudbeaver.model.WebPropertyInfo; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.utils.ServletAppUtils; import io.cloudbeaver.utils.WebDataSourceUtils; -import io.cloudbeaver.utils.WebEventUtils; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; @@ -38,8 +37,6 @@ import org.jkiss.dbeaver.model.rm.RMProjectType; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.secret.DBSSecretController; import org.jkiss.dbeaver.model.secret.DBSSecretValue; -import org.jkiss.dbeaver.model.websocket.WSConstants; -import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty; import org.jkiss.dbeaver.registry.DataSourceDescriptor; import org.jkiss.dbeaver.runtime.DBWorkbench; import org.jkiss.dbeaver.runtime.jobs.ConnectionTestJob; @@ -100,13 +97,6 @@ public class ConnectionControllerCE implements ConnectionController { WebConnectionInfo connectionInfo = project.addConnection(newDataSource); webSession.addInfoMessage("New connection was created - " + WebServiceUtils.getConnectionContainerInfo( newDataSource)); - WebEventUtils.addDataSourceUpdatedEvent( - webSession.getProjectById(projectId), - webSession, - connectionInfo.getId(), - WSConstants.EventAction.CREATE, - WSDataSourceProperty.CONFIGURATION - ); log.info(String.format( "New connection was created: [info=%s, user=%s]", WebServiceUtils.getConnectionContainerInfo(newDataSource), @@ -190,7 +180,6 @@ public class ConnectionControllerCE implements ConnectionController { DBPDataSourceRegistry sessionRegistry ) throws DBWebException { WebConnectionInfo connectionInfo = WebDataSourceUtils.getWebConnectionInfo(webSession, projectId, config.getConnectionId()); - boolean sendEvent = !((DataSourceDescriptor) dataSource).equalSettings(getOldDataSource(dataSource)); if (!isSharedCredentials(dataSource)) { // secret controller is responsible for notification, password changes applied after checks WebServiceUtils.saveAuthProperties( @@ -201,24 +190,12 @@ public class ConnectionControllerCE implements ConnectionController { config.isSharedCredentials() ); } - - WSDataSourceProperty property = getDatasourceEventProperty(getOldDataSource(dataSource), dataSource); - try { sessionRegistry.updateDataSource(dataSource); sessionRegistry.checkForErrors(); } catch (DBException e) { throw new DBWebException("Failed to update connection", e); } - if (sendEvent) { - WebEventUtils.addDataSourceUpdatedEvent( - webSession.getProjectById(projectId), - webSession, - connectionInfo.getId(), - WSConstants.EventAction.UPDATE, - property - ); - } log.info(String.format( "Connection updated: [info=%s, userId=%s]", WebServiceUtils.getConnectionContainerInfo(dataSource), @@ -238,13 +215,6 @@ public class ConnectionControllerCE implements ConnectionController { webSession.addInfoMessage("Delete connection - " + WebServiceUtils.getConnectionContainerInfo(connectionInfo.getDataSourceContainer())); closeAndDeleteConnection(webSession, projectId, connectionId, true); - WebEventUtils.addDataSourceUpdatedEvent( - webSession.getProjectById(projectId), - webSession, - connectionId, - WSConstants.EventAction.DELETE, - WSDataSourceProperty.CONFIGURATION - ); log.info(String.format( "Connection deleted: [info=%s, userId=%s]", @@ -361,23 +331,6 @@ public class ConnectionControllerCE implements ConnectionController { return project; } - private WSDataSourceProperty getDatasourceEventProperty( - DataSourceDescriptor oldDataSource, - DBPDataSourceContainer dataSource - ) { - if (!oldDataSource.equalConfiguration((DataSourceDescriptor) dataSource)) { - return WSDataSourceProperty.CONFIGURATION; - } - - var nameChanged = !CommonUtils.equalObjects(oldDataSource.getName(), dataSource.getName()); - var descriptionChanged = !CommonUtils.equalObjects(oldDataSource.getDescription(), dataSource.getDescription()); - if (nameChanged && descriptionChanged) { - return WSDataSourceProperty.CONFIGURATION; - } - - return nameChanged ? WSDataSourceProperty.NAME : WSDataSourceProperty.CONFIGURATION; - } - @NotNull private WebConnectionInfo closeAndDeleteConnection( @NotNull WebSession webSession, diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java index 2710ec36b2..e094affe4e 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java @@ -27,7 +27,10 @@ import io.cloudbeaver.server.WebAppUtils; import io.cloudbeaver.server.WebApplication; import io.cloudbeaver.service.core.DBWServiceCore; import io.cloudbeaver.service.security.SMUtils; -import io.cloudbeaver.utils.*; +import io.cloudbeaver.utils.ServletAppUtils; +import io.cloudbeaver.utils.WebCommonUtils; +import io.cloudbeaver.utils.WebConnectionFolderUtils; +import io.cloudbeaver.utils.WebDataSourceUtils; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.jkiss.code.NotNull; @@ -41,14 +44,16 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry; import org.jkiss.dbeaver.model.app.DBPProject; import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration; import org.jkiss.dbeaver.model.connection.DBPDriver; -import org.jkiss.dbeaver.model.navigator.*; +import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings; +import org.jkiss.dbeaver.model.navigator.DBNDataSource; +import org.jkiss.dbeaver.model.navigator.DBNModel; +import org.jkiss.dbeaver.model.navigator.DBNNode; import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration; import org.jkiss.dbeaver.model.net.DBWNetworkHandler; import org.jkiss.dbeaver.model.net.DBWTunnel; import org.jkiss.dbeaver.model.net.ssh.SSHSession; import org.jkiss.dbeaver.model.rm.RMProjectType; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; -import org.jkiss.dbeaver.model.websocket.WSConstants; import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty; import org.jkiss.dbeaver.registry.DataSourceDescriptor; import org.jkiss.dbeaver.registry.DataSourceProviderRegistry; @@ -56,7 +61,6 @@ import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor; import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry; import org.jkiss.dbeaver.registry.settings.ProductSettingDescriptor; import org.jkiss.dbeaver.registry.settings.ProductSettingsRegistry; -import org.jkiss.dbeaver.runtime.DBWorkbench; import org.jkiss.utils.CommonUtils; import java.util.*; @@ -393,15 +397,7 @@ public class WebServiceCore implements DBWServiceCore { dataSourceRegistry.addDataSource(newDataSource); dataSourceRegistry.checkForErrors(); - WebConnectionInfo connectionInfo = project.addConnection(newDataSource); - WebEventUtils.addDataSourceUpdatedEvent( - webSession.getProjectById(projectId), - webSession, - connectionInfo.getId(), - WSConstants.EventAction.CREATE, - WSDataSourceProperty.CONFIGURATION - ); - return connectionInfo; + return project.addConnection(newDataSource); } catch (DBException e) { throw new DBWebException("Error copying connection", e); } @@ -536,25 +532,18 @@ public class WebServiceCore implements DBWServiceCore { ) throws DBWebException { WebConnectionFolderUtils.validateConnectionFolder(folderName); session.addInfoMessage("Create new folder"); - WebConnectionFolderInfo parentNode = null; + WebConnectionFolderInfo parentFolder = null; try { if (parentPath != null) { - parentNode = WebConnectionFolderUtils.getFolderInfo(session, projectId, parentPath); + parentFolder = WebConnectionFolderUtils.getFolderInfo(session, projectId, parentPath); } WebProjectImpl project = getProjectById(session, projectId); - DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry(); - DBPDataSourceFolder newFolder = WebConnectionFolderUtils.createFolder(parentNode, - folderName, - sessionRegistry); - WebConnectionFolderInfo folderInfo = new WebConnectionFolderInfo(session, newFolder); - WebServiceUtils.updateConfigAndRefreshDatabases(session, projectId); - WebEventUtils.addNavigatorNodeUpdatedEvent( - session.getProjectById(projectId), - session, - DBNLocalFolder.makeLocalFolderItemPath(newFolder), - WSConstants.EventAction.CREATE + DBPDataSourceRegistry registry = project.getDataSourceRegistry(); + DBPDataSourceFolder newFolder = registry.addFolder( + parentFolder == null ? null : parentFolder.getDataSourceFolder(), folderName ); - return folderInfo; + WebServiceUtils.refreshDatabases(session, projectId); + return new WebConnectionFolderInfo(session, newFolder); } catch (DBException e) { throw new DBWebException(e.getMessage(), e); } @@ -565,26 +554,12 @@ public class WebServiceCore implements DBWServiceCore { @NotNull WebSession session, @Nullable String projectId, @NotNull String folderPath, - @NotNull String newName + @NotNull String newPath ) throws DBWebException { - WebConnectionFolderUtils.validateConnectionFolder(newName); + WebConnectionFolderUtils.validateConnectionFolder(newPath); WebConnectionFolderInfo folderInfo = WebConnectionFolderUtils.getFolderInfo(session, projectId, folderPath); - var oldFolderNode = DBNLocalFolder.makeLocalFolderItemPath(folderInfo.getDataSourceFolder()); - folderInfo.getDataSourceFolder().setName(newName); - var newFolderNode = DBNLocalFolder.makeLocalFolderItemPath(folderInfo.getDataSourceFolder()); - WebServiceUtils.updateConfigAndRefreshDatabases(session, projectId); - WebEventUtils.addNavigatorNodeUpdatedEvent( - session.getProjectById(projectId), - session, - oldFolderNode, - WSConstants.EventAction.DELETE - ); - WebEventUtils.addNavigatorNodeUpdatedEvent( - session.getProjectById(projectId), - session, - newFolderNode, - WSConstants.EventAction.CREATE - ); + folderInfo.getDataSourceFolder().setName(newPath); + WebServiceUtils.refreshDatabases(session, projectId); return folderInfo; } @@ -599,17 +574,10 @@ public class WebServiceCore implements DBWServiceCore { if (folder.getDataSourceRegistry().getProject() != project) { throw new DBWebException("Global folder '" + folderInfo.getId() + "' cannot be deleted"); } - var folderNode = DBNLocalFolder.makeLocalFolderItemPath(folderInfo.getDataSourceFolder()); session.addInfoMessage("Delete folder"); DBPDataSourceRegistry sessionRegistry = project.getDataSourceRegistry(); sessionRegistry.removeFolder(folderInfo.getDataSourceFolder(), false); - WebServiceUtils.updateConfigAndRefreshDatabases(session, projectId); - WebEventUtils.addNavigatorNodeUpdatedEvent( - session.getProjectById(projectId), - session, - folderNode, - WSConstants.EventAction.DELETE - ); + WebServiceUtils.refreshDatabases(session, projectId); } catch (DBException e) { throw new DBWebException(e.getMessage(), e); } @@ -624,12 +592,6 @@ public class WebServiceCore implements DBWServiceCore { DataSourceDescriptor dataSourceDescriptor = ((DataSourceDescriptor) connectionInfo.getDataSourceContainer()); dataSourceDescriptor.setNavigatorSettings(settings); dataSourceDescriptor.persistConfiguration(); - WebEventUtils.addDataSourceUpdatedEvent( - webSession.getProjectById(projectId), - webSession, - id, - WSConstants.EventAction.UPDATE, - WSDataSourceProperty.CONFIGURATION); return connectionInfo; } @@ -656,16 +618,4 @@ public class WebServiceCore implements DBWServiceCore { } return project; } - - private void validateDriverLibrariesPresence(@NotNull DBPDataSourceContainer container) throws DBWebException { - if (!DBWorkbench.isDistributed() && container.getDriver().getDriverLoader(container).needsExternalDependencies()) { - throwDriverNotFoundException(container); - } - } - - @NotNull - private static String throwDriverNotFoundException(@NotNull DBPDataSourceContainer container) throws DBWebException { - throw new DBWebException("Driver files for %s are not found. Please ask the administrator to download it." - .formatted(container.getDriver().getName())); - } } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java index b817174230..3c0c6888b8 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java @@ -55,7 +55,6 @@ import org.jkiss.dbeaver.model.struct.DBSObjectFilter; import org.jkiss.dbeaver.model.struct.rdb.DBSCatalog; import org.jkiss.dbeaver.model.struct.rdb.DBSSchema; import org.jkiss.dbeaver.model.websocket.WSConstants; -import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty; import org.jkiss.dbeaver.model.websocket.event.resource.WSResourceProperty; import org.jkiss.dbeaver.runtime.DBWorkbench; import org.jkiss.utils.CommonUtils; @@ -461,28 +460,10 @@ public class WebServiceNavigator implements DBWServiceNavigator { if (siblings.contains(newName)) { throw new DBWebException("Name " + newName + " is unavailable or invalid"); } - var oldNodePath = node.getNodeItemPath(); node.rename(session.getProgressMonitor(), newName); - var newNodePath = node.getNodeItemPath(); - addNavigatorNodeMoveEvent(session, node, oldNodePath, newNodePath); return node.getName(); } - private void addNavigatorNodeMoveEvent(@NotNull WebSession session, DBNNode node, String oldNodePath, String newNodePath) { - WebEventUtils.addNavigatorNodeUpdatedEvent( - node.getOwnerProject(), - session, - oldNodePath, - WSConstants.EventAction.DELETE - ); - WebEventUtils.addNavigatorNodeUpdatedEvent( - node.getOwnerProject(), - session, - newNodePath, - WSConstants.EventAction.CREATE - ); - } - @NotNull private String renameRmResourceNode(@NotNull WebSession session, DBNNode node, @NotNull String newName) throws DBException { if (newName.contains("/") || newName.contains("\\")) { @@ -570,14 +551,7 @@ public class WebServiceNavigator implements DBWServiceNavigator { throw e; } } else if (node instanceof DBNLocalFolder) { - var nodePath = node.getNodeItemPath(); node.getOwnerProject().getDataSourceRegistry().removeFolder(((DBNLocalFolder) node).getFolder(), false); - WebEventUtils.addNavigatorNodeUpdatedEvent( - session.getProjectById(projectId), - session, - nodePath, - WSConstants.EventAction.DELETE - ); } else if (node instanceof DBNResourceManagerResource) { DBNResourceManagerResource rmResource = ((DBNResourceManagerResource) node); String resourceProjectId = rmResource.getResourceProject().getId(); @@ -592,7 +566,7 @@ public class WebServiceNavigator implements DBWServiceNavigator { } } if (containsFolderNodes) { - WebServiceUtils.updateConfigAndRefreshDatabases(session, projectId); + WebServiceUtils.refreshDatabases(session, projectId); } return nodes.size(); @@ -645,15 +619,7 @@ public class WebServiceNavigator implements DBWServiceNavigator { folder = localFolderNode.getFolder(); } dataSourceNode.moveToFolder(folderNode.getOwnerProject(), folder); - node.getOwnerProject().getDataSourceRegistry().updateDataSource( - dataSourceNode.getDataSourceContainer()); - WebEventUtils.addDataSourceUpdatedEvent( - node.getOwnerProject(), - session, - dataSourceNode.getDataSourceContainer().getId(), - WSConstants.EventAction.UPDATE, - WSDataSourceProperty.CONFIGURATION - ); + node.getOwnerProject().getDataSourceRegistry().updateDataSource(dataSourceNode.getDataSourceContainer()); } else if (node instanceof DBNLocalFolder dbnLocalFolder) { DBPDataSourceFolder parentFolder = null; if (folderNode instanceof DBNLocalFolder parentFolderNode) { @@ -667,15 +633,12 @@ public class WebServiceNavigator implements DBWServiceNavigator { throw new DBWebException("Node " + folderNodePath + " contains folder with name '" + node.getName() + "'"); } } - var oldNodePath = node.getNodeItemPath(); node.getOwnerProject().getDataSourceRegistry().moveFolder( dbnLocalFolder.getFolder().getFolderPath(), dbnLocalFolder.generateNewFolderPath(parentFolder, dbnLocalFolder.getNodeDisplayName()) ); node.getOwnerProject().getDataSourceRegistry().checkForErrors(); - var newNodePath = node.getNodeItemPath(); - WebServiceUtils.updateConfigAndRefreshDatabases(session, node.getOwnerProject().getId()); - addNavigatorNodeMoveEvent(session, node, oldNodePath, newNodePath); + WebServiceUtils.refreshDatabases(session, node.getOwnerProject().getId()); } else if (node instanceof DBNResourceManagerResource) { boolean rmNewNode = folderNode instanceof DBNAbstractResourceManagerNode; DBNResourceManagerResource rmOldNode = (DBNResourceManagerResource) node; From ab813e21aba794ea1e3a5f01b87d6fc5a6ad86ea Mon Sep 17 00:00:00 2001 From: Sychev Andrey <44414066+SychevAndrey@users.noreply.github.com> Date: Wed, 10 Sep 2025 15:04:03 +0200 Subject: [PATCH 2/6] dbeaver/pro#5129 cb 6095 sql editor buttons shortcut labels are hardcoded (#3696) * dbeaver/pro#5129 feat: use action item bindings API to get binding label removes hardcoded shortcuts adds formatting for shortcuts, uses OS-specific shortcuts * dbeaver/pro#5129 feat: add example of manual using getBindingLabel function * dbeaver/pro#5129 feat: use menu api for sql editor actions * dbeaver/pro#5129 fix: lint async * dbeaver/pro#5129 fix: remove wrong action icons move private methods to the end of files * dbeaver/pro#5129 refactor: use context via useDataContextLink hook * dbeaver/pro#5129 refactor: DRY ACTIONS * dbeaver/pro#5129 fix: DRY ACTIONS * dbeaver/pro#5129 fix: action titles * dbeaver/pro#5129 fix: DI after devel merge * dbeaver/pro#5129 bonus: fix text content in dropdowns --------- Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com> Co-authored-by: Alexey Potsetsuev --- .../core-blocks/src/FormControls/Combobox.css | 2 +- .../src/ContextMenu/MenuActionElement.tsx | 15 +- .../src/ContextMenu/MenuBar/MenuBar.tsx | 7 +- .../src/Action/Actions/ACTION_COLLAPSE_ALL.ts | 4 +- .../KeyBinding/Bindings/KEY_BINDING_ADD.ts | 13 ++ .../Bindings/KEY_BINDING_DUPLICATE.ts | 13 ++ .../src/Action/KeyBinding/getBindingLabel.ts | 65 +++++++-- webapp/packages/core-view/src/index.ts | 2 + .../DataGridContextMenuCellEditingService.ts | 33 +++-- .../TableFooterMenu/TableFooterMenuService.ts | 129 ++++++++++-------- .../ElementsTreeToolsMenuService.ts | 23 +--- .../src/ACTION_LINK_OBJECT.ts | 2 +- .../src/ObjectViewerNavTreeLinkMenuService.ts | 21 +-- .../src/locales/en.ts | 2 +- .../src/locales/fr.ts | 2 +- .../src/locales/it.ts | 2 +- .../src/locales/ru.ts | 2 +- .../src/locales/vi.ts | 2 +- .../src/locales/zh.ts | 2 +- .../plugin-sql-editor/src/MenuBootstrap.ts | 47 +++++-- .../src/SqlEditor/SQLEditorActions.tsx | 67 +++------ .../src/actions/ACTION_SQL_EDITOR_EXECUTE.ts | 3 +- .../actions/ACTION_SQL_EDITOR_EXECUTE_NEW.ts | 3 +- .../ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN.ts | 3 +- .../plugin-sql-editor/src/locales/en.ts | 12 +- .../plugin-sql-editor/src/locales/fr.ts | 14 +- .../plugin-sql-editor/src/locales/it.ts | 12 +- .../plugin-sql-editor/src/locales/ru.ts | 12 +- .../plugin-sql-editor/src/locales/vi.ts | 12 +- .../plugin-sql-editor/src/locales/zh.ts | 12 +- 30 files changed, 306 insertions(+), 232 deletions(-) create mode 100644 webapp/packages/core-view/src/Action/KeyBinding/Bindings/KEY_BINDING_ADD.ts create mode 100644 webapp/packages/core-view/src/Action/KeyBinding/Bindings/KEY_BINDING_DUPLICATE.ts diff --git a/webapp/packages/core-blocks/src/FormControls/Combobox.css b/webapp/packages/core-blocks/src/FormControls/Combobox.css index 77b1457ba6..b8228597df 100644 --- a/webapp/packages/core-blocks/src/FormControls/Combobox.css +++ b/webapp/packages/core-blocks/src/FormControls/Combobox.css @@ -29,6 +29,6 @@ padding-inline: calc(var(--tw-spacing) * 3); padding-block: calc(var(--tw-spacing) * 2); gap: calc(var(--tw-spacing) * 2); - line-height: 1; + line-height: 1.25; } } diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuActionElement.tsx b/webapp/packages/core-ui/src/ContextMenu/MenuActionElement.tsx index 21c1b260f7..ddb44067db 100644 --- a/webapp/packages/core-ui/src/ContextMenu/MenuActionElement.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/MenuActionElement.tsx @@ -47,12 +47,7 @@ export const MenuActionElement = observer(function Menu focusable onClick={handleClick} > - } - tooltip={actionInfo.tooltip} - loading={loading} - /> + } tooltip={actionInfo.tooltip} loading={loading} /> ); } @@ -72,7 +67,13 @@ export const MenuActionElement = observer(function Menu focusable onClick={handleClick} > - } tooltip={actionInfo.tooltip} loading={loading} /> + } + tooltip={actionInfo.tooltip} + loading={loading} + /> ); } diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx index c067e7161e..7fa8691458 100644 --- a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx @@ -21,11 +21,13 @@ import { useListKeyboardNavigation, useMergeRefs, useS, + useTranslate, } from '@cloudbeaver/core-blocks'; import { type IDataContext, useDataContextLink } from '@cloudbeaver/core-data-context'; import { DATA_CONTEXT_MENU_NESTED, DATA_CONTEXT_SUBMENU_ITEM, + getBindingLabel, type IMenuActionItem, type IMenuData, type IMenuInfo, @@ -146,11 +148,14 @@ export interface IMenuBarActionProps { export const MenuBarAction = registry( observer(function MenuBarAction({ item, parentMenuInfo, submenu, className }) { + const translate = useTranslate(); const actionInfo = item.action.actionInfo; const loading = item.action.isLoading(); /** @deprecated must be refactored (#1)*/ const displayLabel = item.action.isLabelVisible(); + const binding = item.action.binding ? getBindingLabel(item.action.binding.binding) : null; + const title = translate(actionInfo.tooltip ?? actionInfo.label); function handleClick() { item.events?.onSelect?.(); @@ -165,7 +170,7 @@ export const MenuBarAction = registry( label={actionInfo.label} displayLabel={displayLabel} icon={actionInfo.icon ?? parentMenuInfo?.icon} - title={actionInfo.tooltip ?? parentMenuInfo?.tooltip} + title={binding ? `${title} (${binding})` : title} disabled={item.disabled} loading={loading} submenu={submenu} diff --git a/webapp/packages/core-view/src/Action/Actions/ACTION_COLLAPSE_ALL.ts b/webapp/packages/core-view/src/Action/Actions/ACTION_COLLAPSE_ALL.ts index d8b1e5f4e0..58dd8d1610 100644 --- a/webapp/packages/core-view/src/Action/Actions/ACTION_COLLAPSE_ALL.ts +++ b/webapp/packages/core-view/src/Action/Actions/ACTION_COLLAPSE_ALL.ts @@ -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. @@ -8,6 +8,6 @@ import { createAction } from '../createAction.js'; export const ACTION_COLLAPSE_ALL = createAction('collapse-all', { - label: 'collapse-all', + label: 'app_navigationTree_action_collapse_all', icon: '/icons/collapse_sm.svg', }); diff --git a/webapp/packages/core-view/src/Action/KeyBinding/Bindings/KEY_BINDING_ADD.ts b/webapp/packages/core-view/src/Action/KeyBinding/Bindings/KEY_BINDING_ADD.ts new file mode 100644 index 0000000000..0a5f062ef9 --- /dev/null +++ b/webapp/packages/core-view/src/Action/KeyBinding/Bindings/KEY_BINDING_ADD.ts @@ -0,0 +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. + */ +import { createKeyBinding } from '../createKeyBinding.js'; + +export const KEY_BINDING_ADD = createKeyBinding({ + id: 'add', + keys: ['alt+r'], +}); \ No newline at end of file diff --git a/webapp/packages/core-view/src/Action/KeyBinding/Bindings/KEY_BINDING_DUPLICATE.ts b/webapp/packages/core-view/src/Action/KeyBinding/Bindings/KEY_BINDING_DUPLICATE.ts new file mode 100644 index 0000000000..fc42bae435 --- /dev/null +++ b/webapp/packages/core-view/src/Action/KeyBinding/Bindings/KEY_BINDING_DUPLICATE.ts @@ -0,0 +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. + */ +import { createKeyBinding } from '../createKeyBinding.js'; + +export const KEY_BINDING_DUPLICATE = createKeyBinding({ + id: 'duplicate', + keys: ['alt+shift+r'], +}); \ No newline at end of file diff --git a/webapp/packages/core-view/src/Action/KeyBinding/getBindingLabel.ts b/webapp/packages/core-view/src/Action/KeyBinding/getBindingLabel.ts index 9028cc3193..64e8c9ec2f 100644 --- a/webapp/packages/core-view/src/Action/KeyBinding/getBindingLabel.ts +++ b/webapp/packages/core-view/src/Action/KeyBinding/getBindingLabel.ts @@ -1,18 +1,67 @@ /* * 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 { getOSSpecificKeys } from './getCommonAndOSSpecificKeys.js'; +import { getOS, OperatingSystem } from '@cloudbeaver/core-utils'; +import { getCommonAndOSSpecificKeys } from './getCommonAndOSSpecificKeys.js'; import type { IKeyBinding } from './IKeyBinding.js'; -export function getBindingLabel(binding: IKeyBinding) { - let bindingLabel = getOSSpecificKeys(binding) ?? binding.keys; - if (Array.isArray(bindingLabel)) { - bindingLabel = bindingLabel[0]; - } +const FORMAT_SHORTCUT_KEYS_MAP: Record = { + comma: ',', + slash: '/', + backslash: '\\', + backspace: '⌫', + tab: 'tab', + clear: 'clear', + enter: '↵', + return: '↵', + escape: 'escape', + esc: 'escape', + space: '␣', + up: '↑', + down: '↓', + left: '←', + right: '→', + pageup: 'pageup', + pagedown: 'pagedown', + del: '⌦', + delete: '⌦', +}; +const SOURCE_DIVIDER_REGEXP = /\+/gi; +const APPLIED_DIVIDER = ' + '; - return bindingLabel?.replace(/\+/g, ' + ').replace(/\b\w/g, letter => letter.toUpperCase()); +function transformKeys(keyBinding: IKeyBinding): string[] { + return getCommonAndOSSpecificKeys(keyBinding).map(shortcut => + shortcut.split(SOURCE_DIVIDER_REGEXP).map(formatKeyToDisplayKey).join(APPLIED_DIVIDER).toLocaleUpperCase(), + ); +} + +function formatKeyToDisplayKey(code: string): string { + const lowerCaseCode = code.toLowerCase(); + const OS = getOS(); + + switch (lowerCaseCode) { + case 'mod': + if (OS === OperatingSystem.windowsOS || OS === OperatingSystem.linuxOS) { + return 'CTRL'; + } + if (OS === OperatingSystem.macOS) { + return 'CMD'; + } + return code; + case 'alt': + if (OS === OperatingSystem.macOS) { + return 'OPTION'; + } + return 'ALT'; + default: + return FORMAT_SHORTCUT_KEYS_MAP[lowerCaseCode] ?? code; + } +} + +export function getBindingLabel(binding: IKeyBinding): string | undefined { + return transformKeys(binding)[0]; } diff --git a/webapp/packages/core-view/src/index.ts b/webapp/packages/core-view/src/index.ts index d62072c7b8..d82e1c2163 100644 --- a/webapp/packages/core-view/src/index.ts +++ b/webapp/packages/core-view/src/index.ts @@ -37,6 +37,8 @@ export * from './Action/KeyBinding/Bindings/KEY_BINDING_OPEN_IN_TAB.js'; export * from './Action/KeyBinding/Bindings/KEY_BINDING_REDO.js'; export * from './Action/KeyBinding/Bindings/KEY_BINDING_UNDO.js'; export * from './Action/KeyBinding/Bindings/KEY_BINDING_SAVE.js'; +export * from './Action/KeyBinding/Bindings/KEY_BINDING_ADD.js'; +export * from './Action/KeyBinding/Bindings/KEY_BINDING_DUPLICATE.js'; export * from './Action/KeyBinding/KeyBindingService.js'; export * from './Action/KeyBinding/createKeyBinding.js'; export * from './Action/ActionService.js'; diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts index f7563b8297..5d9995faf0 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ import { injectable } from '@cloudbeaver/core-di'; -import { ACTION_EDIT, ActionService, MenuService } from '@cloudbeaver/core-view'; +import { ACTION_EDIT, ActionService, getBindingLabel, KEY_BINDING_ADD, KEY_BINDING_DUPLICATE, MenuService, type IAction } from '@cloudbeaver/core-view'; import { DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX, @@ -22,6 +22,8 @@ import { ResultSetSelectAction, ResultSetViewAction, } from '@cloudbeaver/plugin-data-viewer'; +import type { IDataContextProvider } from '@cloudbeaver/core-data-context'; +import { LocalizationService } from '@cloudbeaver/core-localization'; import { ACTION_DATA_GRID_EDITING_ADD_ROW } from '../Actions/Editing/ACTION_DATA_GRID_EDITING_ADD_ROW.js'; import { ACTION_DATA_GRID_EDITING_DELETE_ROW } from '../Actions/Editing/ACTION_DATA_GRID_EDITING_DELETE_ROW.js'; @@ -32,13 +34,16 @@ import { ACTION_DATA_GRID_EDITING_REVERT_SELECTED_ROW } from '../Actions/Editing import { ACTION_DATA_GRID_EDITING_SET_TO_NULL } from '../Actions/Editing/ACTION_DATA_GRID_EDITING_SET_TO_NULL.js'; import { MENU_DATA_GRID_EDITING } from './MENU_DATA_GRID_EDITING.js'; -@injectable(() => [ActionService, MenuService]) +@injectable(() => [ActionService, LocalizationService, MenuService]) export class DataGridContextMenuCellEditingService { constructor( private readonly actionService: ActionService, + private readonly localizationService: LocalizationService, private readonly menuService: MenuService, ) { } + + register(): void { this.menuService.addCreator({ root: true, @@ -140,13 +145,7 @@ export class DataGridContextMenuCellEditingService { ACTION_DATA_GRID_EDITING_REVERT_SELECTED_ROW, ].includes(action); }, - getActionInfo(context, action) { - if (action === ACTION_EDIT) { - return { ...action.info, label: 'data_grid_table_editing_open_inline_editor', icon: 'edit' }; - } - - return action.info; - }, + getActionInfo: this.getActionInfo.bind(this), handler(context, action) { const model = context.get(DATA_CONTEXT_DV_DDM)!; const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; @@ -188,4 +187,20 @@ export class DataGridContextMenuCellEditingService { }, }); } + + private getActionInfo(context: IDataContextProvider, action: IAction) { + const t = this.localizationService.translate; + if (action === ACTION_DATA_GRID_EDITING_ADD_ROW) { + return { ...action.info, label: 'data_grid_table_editing_row_add', tooltip: t('data_grid_table_editing_row_add') + ' (' + getBindingLabel(KEY_BINDING_ADD) + ')' }; + } + if (action === ACTION_DATA_GRID_EDITING_DUPLICATE_ROW) { + return { ...action.info, label: 'data_grid_table_editing_row_add_copy', tooltip: t('data_grid_table_editing_row_add_copy') + ' (' + getBindingLabel(KEY_BINDING_DUPLICATE) + ')' }; + } + + if (action === ACTION_EDIT) { + return { ...action.info, label: t('data_grid_table_editing_open_inline_editor'), icon: 'edit' }; + } + + return action.info; + } } diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts index 35a9da483b..70118919f2 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts @@ -14,8 +14,14 @@ import { ACTION_REVERT, ACTION_SAVE, ActionService, + getBindingLabel, + KEY_BINDING_ADD, + KEY_BINDING_DUPLICATE, MenuService, + type IAction, } from '@cloudbeaver/core-view'; +import { LocalizationService } from '@cloudbeaver/core-localization'; +import type { IDataContextProvider } from '@cloudbeaver/core-data-context'; import { DatabaseEditAction } from '../../../DatabaseDataModel/Actions/DatabaseEditAction.js'; import { DatabaseSelectAction } from '../../../DatabaseDataModel/Actions/DatabaseSelectAction.js'; @@ -26,14 +32,15 @@ import { DATA_CONTEXT_DV_PRESENTATION, DataViewerPresentationType } from '../../ import type { IDatabaseDataModel } from '../../../DatabaseDataModel/IDatabaseDataModel.js'; import { DATA_VIEWER_DATA_MODEL_ACTIONS_MENU } from './DATA_VIEWER_DATA_MODEL_ACTIONS_MENU.js'; -@injectable(() => [ActionService, MenuService]) +@injectable(() => [ActionService, LocalizationService, MenuService]) export class TableFooterMenuService { constructor( private readonly actionService: ActionService, + private readonly localizationService: LocalizationService, private readonly menuService: MenuService, - ) {} + ) { } - register() { + register(): void { this.registerEditingActions(); } @@ -144,63 +151,69 @@ export class TableFooterMenuService { return false; }, - getActionInfo(context, action) { - switch (action) { - case ACTION_ADD: - return { ...action.info, label: '', icon: '/icons/data_add_sm.svg', tooltip: 'data_viewer_action_edit_add' }; - case ACTION_DUPLICATE: - return { ...action.info, label: '', icon: '/icons/data_add_copy_sm.svg', tooltip: 'data_viewer_action_edit_add_copy' }; - case ACTION_DELETE: - return { ...action.info, label: '', icon: '/icons/data_delete_sm.svg', tooltip: 'data_viewer_action_edit_delete' }; - case ACTION_REVERT: - return { ...action.info, label: '', icon: '/icons/data_revert_sm.svg', tooltip: 'data_viewer_action_edit_revert' }; - case ACTION_SAVE: - return { ...action.info, icon: 'table-save' }; - case ACTION_CANCEL: - return { ...action.info, icon: '/icons/data_revert_all_sm.svg', tooltip: 'data_viewer_value_revert_title' }; - } - - return action.info; - }, - handler: (context, action) => { - const model = context.get(DATA_CONTEXT_DV_DDM)!; - const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; - const editor = model.source.getActionImplementation(resultIndex, DatabaseEditAction); - - if (!editor) { - return; - } - const select = model.source.getActionImplementation(resultIndex, DatabaseSelectAction); - const selectedElements = getActiveElements(model, resultIndex); - - switch (action) { - case ACTION_ADD: { - editor.add(select?.getFocusedElement()); - break; - } - case ACTION_DUPLICATE: { - editor.duplicate(...selectedElements); - break; - } - case ACTION_DELETE: { - editor.delete(...selectedElements); - break; - } - case ACTION_REVERT: { - editor.revert(...selectedElements); - break; - } - case ACTION_SAVE: - model.save().catch(() => {}); - break; - case ACTION_CANCEL: { - editor.clear(); - break; - } - } - }, + getActionInfo: this.tableFooterMenuGetActionInfo.bind(this), + handler: this.tableFooterMenuActionHandler.bind(this), }); } + + private tableFooterMenuActionHandler(context: IDataContextProvider, action: IAction) { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const editor = model.source.getActionImplementation(resultIndex, DatabaseEditAction); + + if (!editor) { + return; + } + const select = model.source.getActionImplementation(resultIndex, DatabaseSelectAction); + const selectedElements = getActiveElements(model, resultIndex); + + switch (action) { + case ACTION_ADD: { + editor.add(select?.getFocusedElement()); + break; + } + case ACTION_DUPLICATE: { + editor.duplicate(...selectedElements); + break; + } + case ACTION_DELETE: { + editor.delete(...selectedElements); + break; + } + case ACTION_REVERT: { + editor.revert(...selectedElements); + break; + } + case ACTION_SAVE: + model.save().catch(() => { }); + break; + case ACTION_CANCEL: { + editor.clear(); + break; + } + } + + } + + private tableFooterMenuGetActionInfo(context: IDataContextProvider, action: IAction) { + const t = this.localizationService.translate; + switch (action) { + case ACTION_ADD: + return { ...action.info, label: '', icon: '/icons/data_add_sm.svg', tooltip: t('data_viewer_action_edit_add') + ' (' + getBindingLabel(KEY_BINDING_ADD) + ')' }; + case ACTION_DUPLICATE: + return { ...action.info, label: '', icon: '/icons/data_add_copy_sm.svg', tooltip: t('data_viewer_action_edit_add_copy') + ' (' + getBindingLabel(KEY_BINDING_DUPLICATE) + ')' }; + case ACTION_DELETE: + return { ...action.info, label: '', icon: '/icons/data_delete_sm.svg', tooltip: t('data_viewer_action_edit_delete') }; + case ACTION_REVERT: + return { ...action.info, label: '', icon: '/icons/data_revert_sm.svg', tooltip: t('data_viewer_action_edit_revert') }; + case ACTION_SAVE: + return { ...action.info, icon: 'table-save' }; + case ACTION_CANCEL: + return { ...action.info, icon: '/icons/data_revert_all_sm.svg', tooltip: t('data_viewer_value_revert_title') }; + } + + return action.info; + } } function getActiveElements(model: IDatabaseDataModel, resultIndex: number): unknown[] { diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeToolsMenuService.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeToolsMenuService.ts index e317a92172..e743cfb3f2 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeToolsMenuService.ts +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeToolsMenuService.ts @@ -8,12 +8,10 @@ import { UserDataService } from '@cloudbeaver/core-authentication'; import type { IDataContextProvider } from '@cloudbeaver/core-data-context'; import { injectable } from '@cloudbeaver/core-di'; -import { LocalizationService } from '@cloudbeaver/core-localization'; import { ACTION_COLLAPSE_ALL, ACTION_FILTER, ActionService, - getBindingLabel, type IAction, KeyBindingService, MenuService, @@ -27,15 +25,14 @@ import { createElementsTreeSettings, validateElementsTreeSettings } from './Navi import { DATA_CONTEXT_NAV_TREE_ROOT } from './NavigationTreeSettings/DATA_CONTEXT_NAV_TREE_ROOT.js'; import { KEY_BINDING_ENABLE_FILTER } from './NavigationTreeSettings/KEY_BINDING_ENABLE_FILTER.js'; -@injectable(() => [ActionService, KeyBindingService, UserDataService, MenuService, LocalizationService]) +@injectable(() => [ActionService, KeyBindingService, UserDataService, MenuService]) export class ElementsTreeToolsMenuService { constructor( private readonly actionService: ActionService, private readonly keyBindingService: KeyBindingService, private readonly userDataService: UserDataService, private readonly menuService: MenuService, - private readonly localizationService: LocalizationService, - ) {} + ) { } register() { this.actionService.addHandler({ @@ -53,20 +50,6 @@ export class ElementsTreeToolsMenuService { return false; }, - getActionInfo: (context, action) => { - switch (action) { - case ACTION_COLLAPSE_ALL: { - const bindingLabel = getBindingLabel(KEY_BINDING_COLLAPSE_ALL); - const tooltip = this.localizationService.translate('app_navigationTree_action_collapse_all') + (bindingLabel ? ` (${bindingLabel})` : ''); - return { - ...action.info, - tooltip, - }; - } - } - - return action.info; - }, handler: this.elementsTreeActionHandler.bind(this), }); @@ -135,7 +118,7 @@ export class ElementsTreeToolsMenuService { state.filter = !state.filter; } - private async elementsTreeActionHandler(contexts: IDataContextProvider, action: IAction) { + private elementsTreeActionHandler(contexts: IDataContextProvider, action: IAction) { const tree = contexts.get(DATA_CONTEXT_ELEMENTS_TREE); if (tree === undefined) { diff --git a/webapp/packages/plugin-object-viewer-nav-tree-link/src/ACTION_LINK_OBJECT.ts b/webapp/packages/plugin-object-viewer-nav-tree-link/src/ACTION_LINK_OBJECT.ts index ffe62794f1..04f52ab91c 100644 --- a/webapp/packages/plugin-object-viewer-nav-tree-link/src/ACTION_LINK_OBJECT.ts +++ b/webapp/packages/plugin-object-viewer-nav-tree-link/src/ACTION_LINK_OBJECT.ts @@ -8,6 +8,6 @@ import { createAction } from '@cloudbeaver/core-view'; export const ACTION_LINK_OBJECT = createAction('link-object', { - label: 'link-object', + label: 'app_navigationTree_action_link_with_editor', icon: '/icons/reveal_in_explorer_view_sm.svg', }); diff --git a/webapp/packages/plugin-object-viewer-nav-tree-link/src/ObjectViewerNavTreeLinkMenuService.ts b/webapp/packages/plugin-object-viewer-nav-tree-link/src/ObjectViewerNavTreeLinkMenuService.ts index c95cb28102..2606bb4038 100644 --- a/webapp/packages/plugin-object-viewer-nav-tree-link/src/ObjectViewerNavTreeLinkMenuService.ts +++ b/webapp/packages/plugin-object-viewer-nav-tree-link/src/ObjectViewerNavTreeLinkMenuService.ts @@ -8,14 +8,13 @@ import type { IDataContextProvider } from '@cloudbeaver/core-data-context'; import { NavigationTreeService, DATA_CONTEXT_ELEMENTS_TREE, MENU_ELEMENTS_TREE_TOOLS } from '@cloudbeaver/plugin-navigation-tree'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; -import { LocalizationService } from '@cloudbeaver/core-localization'; -import { ActionService, getBindingLabel, type IAction, KeyBindingService, MenuService } from '@cloudbeaver/core-view'; +import { ActionService, type IAction, KeyBindingService, MenuService } from '@cloudbeaver/core-view'; import { ConnectionSchemaManagerService } from '@cloudbeaver/plugin-datasource-context-switch'; import { ACTION_LINK_OBJECT } from './ACTION_LINK_OBJECT.js'; import { KEY_BINDING_LINK_OBJECT } from './KEY_BINDING_LINK_OBJECT.js'; -@injectable(() => [ActionService, KeyBindingService, NavigationTreeService, ConnectionSchemaManagerService, MenuService, LocalizationService]) +@injectable(() => [ActionService, KeyBindingService, NavigationTreeService, ConnectionSchemaManagerService, MenuService]) export class ObjectViewerNavTreeLinkMenuService extends Bootstrap { constructor( private readonly actionService: ActionService, @@ -23,7 +22,6 @@ export class ObjectViewerNavTreeLinkMenuService extends Bootstrap { private readonly navigationTreeService: NavigationTreeService, private readonly connectionSchemaManagerService: ConnectionSchemaManagerService, private readonly menuService: MenuService, - private readonly localizationService: LocalizationService, ) { super(); } @@ -35,21 +33,6 @@ export class ObjectViewerNavTreeLinkMenuService extends Bootstrap { id: 'object-viewer-nav-tree-link-handler', contexts: [DATA_CONTEXT_ELEMENTS_TREE], actions: [ACTION_LINK_OBJECT], - getActionInfo: (context, action) => { - switch (action) { - case ACTION_LINK_OBJECT: { - const bindingLabel = getBindingLabel(KEY_BINDING_LINK_OBJECT); - const tooltip = - this.localizationService.translate('app_navigationTree_action_link_with_editor') + (bindingLabel ? ` (${bindingLabel})` : ''); - return { - ...action.info, - tooltip, - }; - } - } - - return action.info; - }, isHidden: context => { const tree = context.get(DATA_CONTEXT_ELEMENTS_TREE)!; diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/en.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/en.ts index 6835ae8e09..f324c2c930 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/en.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/en.ts @@ -1,5 +1,5 @@ export default [ - ['plugin_sql_editor_navigation_tab_resource_save_script_title', 'Save as script (Shift + Ctrl + S)'], + ['plugin_sql_editor_navigation_tab_resource_save_script_title', 'Save as script'], ['plugin_sql_editor_navigation_tab_script_state_renaming', 'Renaming script...'], ['plugin_sql_editor_navigation_tab_script_state_reading', 'Reading script...'], ['plugin_sql_editor_navigation_tab_script_state_saving', 'Saving script...'], diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/fr.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/fr.ts index fd1dc67b5f..8fe06ed6a7 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/fr.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/fr.ts @@ -1,5 +1,5 @@ export default [ - ['plugin_sql_editor_navigation_tab_resource_save_title', 'Enregistrer comme script (Shift + Ctrl + S)'], + ['plugin_sql_editor_navigation_tab_resource_save_title', 'Enregistrer comme script'], ['plugin_sql_editor_navigation_tab_script_state_renaming', 'Renommer le script...'], ['plugin_sql_editor_navigation_tab_script_state_reading', 'Lecture du script...'], ['plugin_sql_editor_navigation_tab_script_state_saving', 'Enregistrement du script...'], diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/it.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/it.ts index 6835ae8e09..f324c2c930 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/it.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/it.ts @@ -1,5 +1,5 @@ export default [ - ['plugin_sql_editor_navigation_tab_resource_save_script_title', 'Save as script (Shift + Ctrl + S)'], + ['plugin_sql_editor_navigation_tab_resource_save_script_title', 'Save as script'], ['plugin_sql_editor_navigation_tab_script_state_renaming', 'Renaming script...'], ['plugin_sql_editor_navigation_tab_script_state_reading', 'Reading script...'], ['plugin_sql_editor_navigation_tab_script_state_saving', 'Saving script...'], diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/ru.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/ru.ts index 75155e4307..5527769668 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/ru.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/ru.ts @@ -1,5 +1,5 @@ export default [ - ['plugin_sql_editor_navigation_tab_resource_save_script_title', 'Сохранить как скрипт (Shift + Ctrl + S)'], + ['plugin_sql_editor_navigation_tab_resource_save_script_title', 'Сохранить как скрипт'], ['plugin_sql_editor_navigation_tab_script_state_renaming', 'Переименовывание скрипта...'], ['plugin_sql_editor_navigation_tab_script_state_reading', 'Чтение скрипта...'], ['plugin_sql_editor_navigation_tab_script_state_saving', 'Сохранение скрипта...'], diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/vi.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/vi.ts index 2559ab6575..51ee8e4c1e 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/vi.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/vi.ts @@ -1,5 +1,5 @@ export default [ - ['plugin_sql_editor_navigation_tab_resource_save_script_title', 'Lưu dưới dạng script (Shift + Ctrl + S)'], + ['plugin_sql_editor_navigation_tab_resource_save_script_title', 'Lưu dưới dạng script'], ['plugin_sql_editor_navigation_tab_script_state_renaming', 'Đang đổi tên script...'], ['plugin_sql_editor_navigation_tab_script_state_reading', 'Đang đọc script...'], ['plugin_sql_editor_navigation_tab_script_state_saving', 'Đang lưu script...'], diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/zh.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/zh.ts index d1c4a654cc..50e1d6720f 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/zh.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/zh.ts @@ -1,5 +1,5 @@ export default [ - ['plugin_sql_editor_navigation_tab_resource_save_script_title', '保存为脚本 (Shift + Ctrl + S)'], + ['plugin_sql_editor_navigation_tab_resource_save_script_title', '保存为脚本'], ['plugin_sql_editor_navigation_tab_script_state_renaming', '重命名脚本中...'], ['plugin_sql_editor_navigation_tab_script_state_reading', '读取脚本中...'], ['plugin_sql_editor_navigation_tab_script_state_saving', '保存脚本中...'], diff --git a/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts b/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts index 38b7fa4810..8db3a7f483 100644 --- a/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts +++ b/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts @@ -46,6 +46,7 @@ import { SqlDataSourceService } from './SqlDataSource/SqlDataSourceService.js'; import { DATA_CONTEXT_SQL_EDITOR_DATA } from './SqlEditor/DATA_CONTEXT_SQL_EDITOR_DATA.js'; import { SQL_EDITOR_TOOLS_MENU } from './SqlEditor/SQL_EDITOR_TOOLS_MENU.js'; import { SQL_EDITOR_TOOLS_MORE_MENU } from './SqlEditor/SQL_EDITOR_TOOLS_MORE_MENU.js'; +import { SQL_EDITOR_ACTIONS_MENU } from './SqlEditor/SQL_EDITOR_ACTIONS_MENU.js'; import { getSqlEditorName } from './getSqlEditorName.js'; import type { ISqlEditorTabState } from './ISqlEditorTabState.js'; import { SqlEditorSettingsService } from './SqlEditorSettingsService.js'; @@ -53,6 +54,12 @@ import { SqlEditorSettingsService } from './SqlEditorSettingsService.js'; const SYNC_DELAY = 5 * 60 * 1000; const ScriptImportDialog = importLazyComponent(() => import('./SqlEditor/ScriptImportDialog.js').then(m => m.ScriptImportDialog)); +const EXECUTIONS_ACTIONS = [ + ACTION_SQL_EDITOR_EXECUTE, + ACTION_SQL_EDITOR_EXECUTE_NEW, + ACTION_SQL_EDITOR_EXECUTE_SCRIPT, + ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN, +]; @injectable(() => [ MenuService, @@ -207,6 +214,15 @@ export class MenuBootstrap extends Bootstrap { getItems: (context, items) => [...items, ACTION_SAVE], }); + 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, + ], + }); + this.keyBindingService.addKeyBindingHandler({ id: 'sql-editor-save', binding: KEY_BINDING_SAVE, @@ -227,13 +243,10 @@ export class MenuBootstrap extends Bootstrap { this.actionService.addHandler({ id: 'sql-editor-actions', actions: [ - ACTION_SQL_EDITOR_EXECUTE, - ACTION_SQL_EDITOR_EXECUTE_NEW, - ACTION_SQL_EDITOR_EXECUTE_SCRIPT, + ...EXECUTIONS_ACTIONS, ACTION_SQL_EDITOR_FORMAT, ACTION_REDO, ACTION_UNDO, - ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN, ], contexts: [DATA_CONTEXT_SQL_EDITOR_DATA], isActionApplicable: (contexts, action): boolean => { @@ -245,12 +258,7 @@ export class MenuBootstrap extends Bootstrap { if ( !sqlEditorData.isExecutionAllowed && - [ - ACTION_SQL_EDITOR_EXECUTE, - ACTION_SQL_EDITOR_EXECUTE_NEW, - ACTION_SQL_EDITOR_EXECUTE_SCRIPT, - ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN, - ].includes(action) + EXECUTIONS_ACTIONS.includes(action) ) { return false; } @@ -259,6 +267,11 @@ export class MenuBootstrap extends Bootstrap { return !!sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.script) && !sqlEditorData.activeSegmentMode.activeSegmentMode; } + if (action === ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN) { + return !!sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.query) && + !!sqlEditorData.dialect?.supportsExplainExecutionPlan; + } + // TODO we have to add check for output action ? if ( !sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.query) && @@ -271,6 +284,11 @@ export class MenuBootstrap extends Bootstrap { }, isDisabled: (context, action) => { const data = context.get(DATA_CONTEXT_SQL_EDITOR_DATA)!; + + if (EXECUTIONS_ACTIONS.includes(action)) { + return data.isDisabled || data.isScriptEmpty; + } + switch (action) { case ACTION_SQL_EDITOR_FORMAT: return data.isDisabled || data.isScriptEmpty || data.readonly; @@ -278,6 +296,15 @@ export class MenuBootstrap extends Bootstrap { return false; }, + getActionInfo: (context, action) => { + if (EXECUTIONS_ACTIONS.includes(action)) { + return { + ...action.info, + label: '', + }; + } + return action.info; + }, handler: this.sqlEditorActionHandler.bind(this), }); diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditor/SQLEditorActions.tsx b/webapp/packages/plugin-sql-editor/src/SqlEditor/SQLEditorActions.tsx index d850bda01d..df5ed9bef4 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditor/SQLEditorActions.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlEditor/SQLEditorActions.tsx @@ -7,14 +7,18 @@ */ import { observer } from 'mobx-react-lite'; -import { ActionIconButton, getComputed, preventFocusHandler, s, useS, useTranslate } from '@cloudbeaver/core-blocks'; +import { preventFocusHandler, s, useS } from '@cloudbeaver/core-blocks'; +import { useDataContextLink } from '@cloudbeaver/core-data-context'; +import { useMenu } from '@cloudbeaver/core-view'; import type { ISqlEditorTabState } from '../ISqlEditorTabState.js'; -import { ESqlDataSourceFeatures } from '../SqlDataSource/ESqlDataSourceFeatures.js'; +import { DATA_CONTEXT_SQL_EDITOR_DATA } from './DATA_CONTEXT_SQL_EDITOR_DATA.js'; +import { DATA_CONTEXT_SQL_EDITOR_STATE } from '../DATA_CONTEXT_SQL_EDITOR_STATE.js'; import type { ISQLEditorData } from './ISQLEditorData.js'; import style from './SQLEditorActions.module.css'; import { SqlEditorActionsMenu } from './SqlEditorActionsMenu.js'; import { SqlEditorTools } from './SqlEditorTools.js'; +import { SQL_EDITOR_ACTIONS_MENU } from './SQL_EDITOR_ACTIONS_MENU.js'; interface Props { data: ISQLEditorData; @@ -22,58 +26,21 @@ interface Props { className?: string; } -export const SQLEditorActions = observer(function SQLEditorActions({ data, state }) { +export const SQLEditorActions = observer(function SQLEditorActions({ data, state, className }) { const styles = useS(style); - const translate = useTranslate(); - const isActiveSegmentMode = getComputed(() => data.activeSegmentMode.activeSegmentMode); - const disabled = getComputed(() => data.isScriptEmpty || data.isDisabled); - const isQuery = data.dataSource?.hasFeature(ESqlDataSourceFeatures.query); - const isExecutable = data.isExecutionAllowed; + const menu = useMenu({ menu: SQL_EDITOR_ACTIONS_MENU }); + + useDataContextLink(menu.context, (context, id) => { + context.set(DATA_CONTEXT_SQL_EDITOR_STATE, state, id); + }); + useDataContextLink(menu.context, (context, id) => { + context.set(DATA_CONTEXT_SQL_EDITOR_DATA, data, id); + }); return ( -
+
- {isExecutable && ( - <> - {isQuery && ( - <> - - - - )} -
diff --git a/webapp/packages/plugin-sql-editor/src/actions/ACTION_SQL_EDITOR_EXECUTE.ts b/webapp/packages/plugin-sql-editor/src/actions/ACTION_SQL_EDITOR_EXECUTE.ts index 405a2e7332..83df49609c 100644 --- a/webapp/packages/plugin-sql-editor/src/actions/ACTION_SQL_EDITOR_EXECUTE.ts +++ b/webapp/packages/plugin-sql-editor/src/actions/ACTION_SQL_EDITOR_EXECUTE.ts @@ -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. @@ -10,4 +10,5 @@ import { createAction } from '@cloudbeaver/core-view'; export const ACTION_SQL_EDITOR_EXECUTE = createAction('sql-editor-execute', { icon: '/icons/sql_exec.svg', label: 'sql_editor_sql_execution_button_tooltip', + tooltip: 'sql_editor_sql_execution_button_tooltip', }); diff --git a/webapp/packages/plugin-sql-editor/src/actions/ACTION_SQL_EDITOR_EXECUTE_NEW.ts b/webapp/packages/plugin-sql-editor/src/actions/ACTION_SQL_EDITOR_EXECUTE_NEW.ts index 0798132271..3d6980b271 100644 --- a/webapp/packages/plugin-sql-editor/src/actions/ACTION_SQL_EDITOR_EXECUTE_NEW.ts +++ b/webapp/packages/plugin-sql-editor/src/actions/ACTION_SQL_EDITOR_EXECUTE_NEW.ts @@ -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. @@ -10,4 +10,5 @@ import { createAction } from '@cloudbeaver/core-view'; export const ACTION_SQL_EDITOR_EXECUTE_NEW = createAction('sql-editor-execute-new', { icon: '/icons/sql_exec_new.svg', label: 'sql_editor_sql_execution_new_tab_button_tooltip', + tooltip: 'sql_editor_sql_execution_new_tab_button_tooltip', }); diff --git a/webapp/packages/plugin-sql-editor/src/actions/ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN.ts b/webapp/packages/plugin-sql-editor/src/actions/ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN.ts index 229e9db1b6..275f726f1c 100644 --- a/webapp/packages/plugin-sql-editor/src/actions/ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN.ts +++ b/webapp/packages/plugin-sql-editor/src/actions/ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN.ts @@ -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. @@ -10,4 +10,5 @@ import { createAction } from '@cloudbeaver/core-view'; export const ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN = createAction('sql-editor-show-execution-plan', { icon: '/icons/sql_execution_plan.svg', label: 'sql_editor_execution_plan_button_tooltip', + tooltip: 'sql_editor_execution_plan_button_tooltip', }); diff --git a/webapp/packages/plugin-sql-editor/src/locales/en.ts b/webapp/packages/plugin-sql-editor/src/locales/en.ts index 209e66319b..68bd1d9f77 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/en.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/en.ts @@ -14,15 +14,15 @@ export default [ ['sql_editor_placeholder', 'Execute query with Ctrl+Enter to see results'], ['sql_editor_hint_empty', 'There is no proposals...'], ['sql_editor_tools_more_menu_tooltip', 'More tools'], - ['sql_editor_execution_plan_button_tooltip', 'Explain execution plan (Shift + Ctrl + E)'], - ['sql_editor_output_logs_button_tooltip', 'Show server output (Shift + Ctrl + O)'], + ['sql_editor_execution_plan_button_tooltip', 'Explain execution plan'], + ['sql_editor_output_logs_button_tooltip', 'Show server output'], ['sql_editor_output_logs_tab_title', 'Output'], ['sql_editor_output_logs_input_placeholder', 'Enter a part of a message to search for here'], ['sql_editor_output_logs_wrap_mode', 'Wrap mode'], - ['sql_editor_sql_execution_button_tooltip', 'Execute SQL Statement (Ctrl + Enter)'], - ['sql_editor_sql_execution_new_tab_button_tooltip', 'Execute SQL Statement in new tab (Ctrl + \\)(Shift + Ctrl + Enter)'], - ['sql_editor_sql_execution_script_button_tooltip', 'Execute SQL Script (Alt + X)'], - ['sql_editor_sql_format_button_tooltip', 'Format SQL Script (Shift + Ctrl + F)'], + ['sql_editor_sql_execution_button_tooltip', 'Execute SQL Statement'], + ['sql_editor_sql_execution_new_tab_button_tooltip', 'Execute SQL Statement in new tab'], + ['sql_editor_sql_execution_script_button_tooltip', 'Execute SQL Script'], + ['sql_editor_sql_format_button_tooltip', 'Format SQL Script'], ['sql_editor_sql_execution_script_lock_tooltip', 'Lock editor to prevent modifications'], ['sql_editor_sql_statistics_unavailable', 'Statistics is not available'], ['sql_editor_sql_execution_executed_queries', 'Executed queries:'], diff --git a/webapp/packages/plugin-sql-editor/src/locales/fr.ts b/webapp/packages/plugin-sql-editor/src/locales/fr.ts index 6c64b227f8..1b8d19bb8d 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/fr.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/fr.ts @@ -13,21 +13,21 @@ export default [ ['sql_editor_placeholder', 'Exécuter une requête (appuyez sur Ctrl+Entrée pour voir les résultats)'], ['sql_editor_hint_empty', "Il n'y a aucune proposition..."], ['sql_editor_tools_more_menu_tooltip', "Plus d'outils"], - ['sql_editor_execution_plan_button', "Afficher le plan d'exécution (Shift + Ctrl + E)"], - ['sql_editor_output_logs_button_tooltip', 'Afficher la sortie du serveur (Shift + Ctrl + O)'], + ['sql_editor_execution_plan_button', "Afficher le plan d'exécution"], + ['sql_editor_output_logs_button_tooltip', 'Afficher la sortie du serveur'], ['sql_editor_output_logs_tab_title', 'Journaux'], ['sql_editor_output_logs_hint', 'Aucun message du serveur'], ['sql_editor_output_logs_wrap_mode', 'Mode Wrap'], - ['sql_editor_execution_plan_button_tooltip', "Afficher le plan d'exécution (Shift + Ctrl + E)"], + ['sql_editor_execution_plan_button_tooltip', "Afficher le plan d'exécution"], ['sql_editor_execution_plan_button', "Afficher le plan d'exécution"], - ['sql_editor_execution_script_button_tooltip', 'Exécuter le script SQL (Alt + X)'], + ['sql_editor_execution_script_button_tooltip', 'Exécuter le script SQL'], ['sql_editor_execution_script_button', 'Exécuter le script SQL'], - ['sql_editor_execution_query_button_tooltip', 'Exécuter la déclaration SQL (Ctrl + Entrée)'], + ['sql_editor_execution_query_button_tooltip', 'Exécuter la déclaration SQL'], ['sql_editor_execution_query_button', 'Exécuter la déclaration SQL'], - ['sql_editor_execution_query_new_tab_button_tooltip', 'Exécuter la déclaration SQL dans un nouvel onglet (Ctrl + \\)(Shift + Ctrl + Entrée)'], + ['sql_editor_execution_query_new_tab_button_tooltip', 'Exécuter la déclaration SQL dans un nouvel onglet'], ['sql_editor_execution_query_new_tab_button', 'Exécuter dans un nouvel onglet'], ['sql_editor_execution_query_tooltip', 'Exécuter les requêtes'], - ['sql_editor_lock_button_tooltip', "Verrouiller l'éditeur pour éviter les modifications (Shift + Ctrl + F)"], + ['sql_editor_lock_button_tooltip', "Verrouiller l'éditeur pour éviter les modifications"], ['sql_editor_lock_button', "Verrouiller l'éditeur"], ['sql_editor_sql_statistics_unavailable', 'Les statistiques SQL ne sont pas disponibles'], ['sql_editor_queries_tab_title', 'Requêtes exécutées'], diff --git a/webapp/packages/plugin-sql-editor/src/locales/it.ts b/webapp/packages/plugin-sql-editor/src/locales/it.ts index a4c964ff40..3b91328921 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/it.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/it.ts @@ -14,15 +14,15 @@ export default [ ['sql_editor_placeholder', 'Esegui la query con Ctrl+Enter per vedere i risultati'], ['sql_editor_hint_empty', 'There is no proposals...'], ['sql_editor_tools_more_menu_tooltip', 'Più strumenti'], - ['sql_editor_execution_plan_button_tooltip', 'Mostra il piano di esecuzione (Shift + Ctrl + E)'], - ['sql_editor_output_logs_button_tooltip', 'Show server output (Shift + Ctrl + O)'], + ['sql_editor_execution_plan_button_tooltip', 'Mostra il piano di esecuzione'], + ['sql_editor_output_logs_button_tooltip', 'Show server output'], ['sql_editor_output_logs_tab_title', 'Output'], ['sql_editor_output_logs_input_placeholder', 'Enter a part of a message to search for here'], ['sql_editor_output_logs_wrap_mode', 'Wrap mode'], - ['sql_editor_sql_execution_button_tooltip', "Esegui l'istruzione SQL (Ctrl + Enter)"], - ['sql_editor_sql_execution_new_tab_button_tooltip', "Esegui l'istruzione SQL in una nuova tab (Ctrl + \\)(Shift + Ctrl + Enter)"], - ['sql_editor_sql_execution_script_button_tooltip', 'Esegui lo script SQL (Alt + X)'], - ['sql_editor_sql_format_button_tooltip', 'Format SQL Script (Shift + Ctrl + F)'], + ['sql_editor_sql_execution_button_tooltip', "Esegui l'istruzione SQL"], + ['sql_editor_sql_execution_new_tab_button_tooltip', "Esegui l'istruzione SQL in una nuova tab"], + ['sql_editor_sql_execution_script_button_tooltip', 'Esegui lo script SQL'], + ['sql_editor_sql_format_button_tooltip', 'Format SQL Script'], ['sql_editor_sql_execution_script_lock_tooltip', 'Lock editor to prevent modifications'], ['sql_editor_sql_statistics_unavailable', 'Statistiche non disponibili'], ['sql_editor_sql_execution_executed_queries', 'Executed queries:'], diff --git a/webapp/packages/plugin-sql-editor/src/locales/ru.ts b/webapp/packages/plugin-sql-editor/src/locales/ru.ts index 4f4e3f13e7..707c0442c3 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/ru.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/ru.ts @@ -14,15 +14,15 @@ export default [ ['sql_editor_placeholder', 'Нажмите Ctrl+Enter, чтобы выполнить запрос и увидеть результат'], ['sql_editor_hint_empty', 'Нет авто-дополнений...'], ['sql_editor_tools_more_menu_tooltip', 'Больше инструментов'], - ['sql_editor_execution_plan_button_tooltip', 'Посмотреть информацию о плане выполнения запроса (Shift + Ctrl + E)'], - ['sql_editor_output_logs_button_tooltip', 'Показать вывод сервера (Shift + Ctrl + O)'], + ['sql_editor_execution_plan_button_tooltip', 'Посмотреть информацию о плане выполнения запроса'], + ['sql_editor_output_logs_button_tooltip', 'Показать вывод сервера'], ['sql_editor_output_logs_tab_title', 'Вывод логов'], ['sql_editor_output_logs_input_placeholder', 'Введите часть сообщения для поиска'], ['sql_editor_output_logs_wrap_mode', 'Перенос строк'], - ['sql_editor_sql_execution_button_tooltip', 'Выполнить SQL Выражение (Ctrl + Enter)'], - ['sql_editor_sql_execution_new_tab_button_tooltip', 'Выполнить SQL Выражение в новой вкладке (Ctrl + \\)(Shift + Ctrl + Enter)'], - ['sql_editor_sql_execution_script_button_tooltip', 'Исполнить SQL Скрипт (Alt + X)'], - ['sql_editor_sql_format_button_tooltip', 'Форматировать SQL скрипт (Shift + Ctrl + F)'], + ['sql_editor_sql_execution_button_tooltip', 'Выполнить SQL Выражение'], + ['sql_editor_sql_execution_new_tab_button_tooltip', 'Выполнить SQL Выражение в новой вкладке'], + ['sql_editor_sql_execution_script_button_tooltip', 'Исполнить SQL Скрипт'], + ['sql_editor_sql_format_button_tooltip', 'Форматировать SQL скрипт'], ['sql_editor_sql_execution_script_lock_tooltip', 'Заблокировать скрипт во избежание изменений'], ['sql_editor_sql_statistics_unavailable', 'Статистика не доступна'], ['sql_editor_sql_execution_executed_queries', 'Выполненные запросы:'], diff --git a/webapp/packages/plugin-sql-editor/src/locales/vi.ts b/webapp/packages/plugin-sql-editor/src/locales/vi.ts index ec346b91b0..5285a40865 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/vi.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/vi.ts @@ -14,15 +14,15 @@ export default [ ['sql_editor_placeholder', 'Thực thi truy vấn với Ctrl+Enter để xem kết quả'], ['sql_editor_hint_empty', 'Không có đề xuất nào...'], ['sql_editor_tools_more_menu_tooltip', 'Nhiều công cụ hơn'], - ['sql_editor_execution_plan_button_tooltip', 'Giải thích kế hoạch thực thi (Shift + Ctrl + E)'], - ['sql_editor_output_logs_button_tooltip', 'Hiển thị đầu ra máy chủ (Shift + Ctrl + O)'], + ['sql_editor_execution_plan_button_tooltip', 'Giải thích kế hoạch thực thi'], + ['sql_editor_output_logs_button_tooltip', 'Hiển thị đầu ra máy chủ'], ['sql_editor_output_logs_tab_title', 'Đầu ra'], ['sql_editor_output_logs_input_placeholder', 'Nhập một phần của tin nhắn để tìm kiếm tại đây'], ['sql_editor_output_logs_wrap_mode', 'Chế độ wrap'], - ['sql_editor_sql_execution_button_tooltip', 'Thực thi câu lệnh SQL (Ctrl + Enter)'], - ['sql_editor_sql_execution_new_tab_button_tooltip', 'Thực thi câu lệnh SQL trong tab mới (Ctrl + \\)(Shift + Ctrl + Enter)'], - ['sql_editor_sql_execution_script_button_tooltip', 'Thực thi kịch bản SQL (Alt + X)'], - ['sql_editor_sql_format_button_tooltip', 'Định dạng kịch bản SQL (Shift + Ctrl + F)'], + ['sql_editor_sql_execution_button_tooltip', 'Thực thi câu lệnh SQL'], + ['sql_editor_sql_execution_new_tab_button_tooltip', 'Thực thi câu lệnh SQL trong tab mới'], + ['sql_editor_sql_execution_script_button_tooltip', 'Thực thi kịch bản SQL'], + ['sql_editor_sql_format_button_tooltip', 'Định dạng kịch bản SQL'], ['sql_editor_sql_execution_script_lock_tooltip', 'Khóa trình soạn thảo để ngăn sửa đổi'], ['sql_editor_sql_statistics_unavailable', 'Thống kê không khả dụng'], ['sql_editor_sql_execution_executed_queries', 'Các truy vấn đã thực thi:'], diff --git a/webapp/packages/plugin-sql-editor/src/locales/zh.ts b/webapp/packages/plugin-sql-editor/src/locales/zh.ts index 1f25f216b3..2adb35a4f1 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/zh.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/zh.ts @@ -14,15 +14,15 @@ export default [ ['sql_editor_placeholder', '使用Ctrl + Enter执行查询以查看结果'], ['sql_editor_hint_empty', '无提示信息...'], ['sql_editor_tools_more_menu_tooltip', '更多工具'], - ['sql_editor_execution_plan_button_tooltip', '解释执行计划(Shift + Ctrl + E)'], - ['sql_editor_output_logs_button_tooltip', '显示服务端输出 (Shift + Ctrl + O)'], + ['sql_editor_execution_plan_button_tooltip', '解释执行计划'], + ['sql_editor_output_logs_button_tooltip', '显示服务端输出'], ['sql_editor_output_logs_tab_title', '输出'], ['sql_editor_output_logs_input_placeholder', '在此处输入要搜索的消息关键词'], ['sql_editor_output_logs_wrap_mode', '换行模式'], - ['sql_editor_sql_execution_button_tooltip', '执行SQL语句(Ctrl + Enter)'], - ['sql_editor_sql_execution_new_tab_button_tooltip', '在新选项卡中执行SQL语句(Ctrl + \\)(Shift + Ctrl + Enter)'], - ['sql_editor_sql_execution_script_button_tooltip', '执行SQL脚本(Alt + X)'], - ['sql_editor_sql_format_button_tooltip', '格式化SQL脚本(Shift + Ctrl + F)'], + ['sql_editor_sql_execution_button_tooltip', '执行SQL语句'], + ['sql_editor_sql_execution_new_tab_button_tooltip', '在新选项卡中执行SQL语句'], + ['sql_editor_sql_execution_script_button_tooltip', '执行SQL脚本'], + ['sql_editor_sql_format_button_tooltip', '格式化SQL脚本'], ['sql_editor_sql_execution_script_lock_tooltip', '锁定编辑器阻止修改'], ['sql_editor_sql_statistics_unavailable', '统计不可用'], ['sql_editor_sql_execution_queries', '查询:'], From d4e2f2365a857b4f4d15e26e13f4cf93a780c4cb Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Wed, 10 Sep 2025 15:06:54 +0200 Subject: [PATCH 3/6] [CB] application freezes during loading on heavy resultsets (ORIGINAL FIX) (#3717) * dbeaver/pro#6731 enables virtualization always * removes default virtualization prop * [CB] application freezes during loading on heavy resultsets (OPTIONAL FIXES) (#3715) * dbeaver/pro#6731 adds some optimizations * cleanup * pr fixes * cleanup * removes optimizations * linter fixes --- .../@dbeaver/react-data-grid/src/DataGrid.tsx | 39 ++++++++++++------- .../src/LocalizationService.ts | 7 ++-- .../Actions/ResultSet/ResultSetViewAction.ts | 2 +- .../src/TableViewer/TableViewer.tsx | 4 +- .../navigationTreeDuplicateIdFilter.ts | 6 +-- .../NavNodeView/NavNodeViewService.ts | 29 +++++++------- .../VirtualFolder/VirtualFolderPanel.tsx | 6 +-- .../ObjectPropertyTable.tsx | 6 +-- 8 files changed, 54 insertions(+), 45 deletions(-) diff --git a/webapp/common-react/@dbeaver/react-data-grid/src/DataGrid.tsx b/webapp/common-react/@dbeaver/react-data-grid/src/DataGrid.tsx index 03afecd9ff..540b66a4f3 100644 --- a/webapp/common-react/@dbeaver/react-data-grid/src/DataGrid.tsx +++ b/webapp/common-react/@dbeaver/react-data-grid/src/DataGrid.tsx @@ -1,5 +1,20 @@ +/* + * 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 { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react'; -import { DataGrid as DataGridBase, type ColumnOrColumnGroup, type CellSelectArgs, type DataGridHandle, type ColumnWidth, type ColumnWidths } from 'react-data-grid'; +import { + DataGrid as DataGridBase, + type ColumnOrColumnGroup, + type CellSelectArgs, + type DataGridHandle, + type ColumnWidth, + type ColumnWidths, +} from 'react-data-grid'; import { rowRenderer } from './renderers/rowRenderer.js'; import { cellRenderer } from './renderers/cellRenderer.js'; import { DataGridCellHeaderContext, type IDataGridHeaderCellContext } from './DataGridHeaderCellContext.js'; @@ -111,9 +126,12 @@ export const DataGrid = forwardRef(function DataGrid innerGridRef.current?.scrollToCell({ idx: position.colIdx && dndHeaderContext.getDataColIdx(position.colIdx), rowIdx: position.rowIdx }); }, openEditor: (position: ICellPosition) => { - innerGridRef.current?.selectCell({ idx: dndHeaderContext.getDataColIdx(position.colIdx), rowIdx: position.rowIdx }, { - enableEditor: true, - }); + innerGridRef.current?.selectCell( + { idx: dndHeaderContext.getDataColIdx(position.colIdx), rowIdx: position.rowIdx }, + { + enableEditor: true, + }, + ); }, })); @@ -126,7 +144,7 @@ export const DataGrid = forwardRef(function DataGrid } } - let rows = useMemo( + const rows = useMemo( () => new Array(rowsCount).fill({ idx: 0 }).map((_, i) => ({ idx: i, @@ -142,21 +160,16 @@ export const DataGrid = forwardRef(function DataGrid onCellKeyDown?.({ colIdx: dndHeaderContext.getDataColIdx(args.column.idx), rowIdx: args.rowIdx }, event); } - const isMeasurementRender = columnWidths.size === 0 && columnCount.get() > 0; - - if (isMeasurementRender) { - rows = rows.slice(0, 100); - } - return ( - + ({ index: this.data.columns.indexOf(c) })); + return this.columnsOrder.map(index => ({ index })); } get rows(): IResultSetValue[][] { diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx index 6a3ecc1d11..cbefdce39b 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx @@ -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. @@ -197,7 +197,7 @@ export const TableViewer = observer( const isStatistics = result?.loadedFully && !result.data; const resultExist = dataModel.source.hasResult(resultIndex); - const overlay = dataModel.source.results.length > 0 && presentation.dataFormat === dataFormat; + const overlay = getComputed(() => dataModel.source.results.length > 0 && presentation.dataFormat === dataFormat); const valuePanelDisplayed = valuePresentation && (valuePresentation.dataFormat === undefined || valuePresentation.dataFormat === dataFormat) && diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/navigationTreeDuplicateIdFilter.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/navigationTreeDuplicateIdFilter.ts index 4c55206db6..a875fe5e52 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/navigationTreeDuplicateIdFilter.ts +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/navigationTreeDuplicateIdFilter.ts @@ -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. @@ -15,9 +15,9 @@ export function navigationTreeDuplicateFilter(navNodeViewService: NavNodeViewSer const { nodes, duplicates } = navNodeViewService.filterDuplicates(children); untracked(() => { - navNodeViewService.logDuplicates(node.id, duplicates); + navNodeViewService.logDuplicates(node.id, Array.from(duplicates)); }); - return nodes; + return Array.from(nodes); }; } diff --git a/webapp/packages/plugin-navigation-tree/src/NodesManager/NavNodeView/NavNodeViewService.ts b/webapp/packages/plugin-navigation-tree/src/NodesManager/NavNodeView/NavNodeViewService.ts index af264cee2f..c6e7a1261b 100644 --- a/webapp/packages/plugin-navigation-tree/src/NodesManager/NavNodeView/NavNodeViewService.ts +++ b/webapp/packages/plugin-navigation-tree/src/NodesManager/NavNodeView/NavNodeViewService.ts @@ -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. @@ -13,8 +13,8 @@ import { NotificationService } from '@cloudbeaver/core-events'; import type { INavNodeFolderTransform, NavNodeFolderTransformFn, NavNodeTransformView } from './IFolderTransform.js'; export interface INodeDuplicateList { - nodes: string[]; - duplicates: string[]; + nodes: Set; + duplicates: Set; } export interface INodeLimitedList { @@ -59,10 +59,10 @@ export class NavNodeViewService { const { nodes, duplicates } = this.filterDuplicates(children); untracked(() => { - this.logDuplicates(nodeId, duplicates); + this.logDuplicates(nodeId, Array.from(duplicates)); }); - return nodes; + return Array.from(nodes); }, }); } @@ -80,25 +80,24 @@ export class NavNodeViewService { } filterDuplicates(nodes: string[]): INodeDuplicateList { + const seen = new Set(); + const duplicatesSet = new Set(); const nextChildren: string[] = []; - const duplicates: string[] = []; for (const child of nodes) { - const isDuplicate = duplicates.includes(child); - - if (nextChildren.includes(child) || isDuplicate) { - if (!isDuplicate) { - duplicates.push(child); - nextChildren.splice(nextChildren.indexOf(child), 1); - } + if (seen.has(child)) { + duplicatesSet.add(child); } else { + seen.add(child); nextChildren.push(child); } } + const uniqueChildren = new Set(nextChildren.filter(child => !duplicatesSet.has(child))); + return { - nodes: nextChildren, - duplicates, + nodes: uniqueChildren, + duplicates: duplicatesSet, }; } diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/NavNodeView/VirtualFolder/VirtualFolderPanel.tsx b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/NavNodeView/VirtualFolder/VirtualFolderPanel.tsx index cdc9afd080..ef3f477544 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/NavNodeView/VirtualFolder/VirtualFolderPanel.tsx +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/NavNodeView/VirtualFolder/VirtualFolderPanel.tsx @@ -35,12 +35,10 @@ export const VirtualFolderPanel: NavNodeTransformViewComponent = observer(functi const allData = dbObjectLoader.resource.get(pagination.allPages).filter(isDefined); const { nodes, duplicates } = navNodeViewService.filterDuplicates(allData.map(node => node?.id) || []); - const objects = allData.filter( - object => object && nodes.includes(object.id) && navNodeInfoResource.get(object.id)?.nodeType === nodeType, - ) as DBObject[]; + const objects = allData.filter(object => object && nodes.has(object.id) && navNodeInfoResource.get(object.id)?.nodeType === nodeType) as DBObject[]; useEffect(() => { - navNodeViewService.logDuplicates(nodeId, duplicates); + navNodeViewService.logDuplicates(nodeId, Array.from(duplicates)); }); return ( diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/ObjectPropertyTable.tsx b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/ObjectPropertyTable.tsx index afd051eb92..1c0b97c493 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/ObjectPropertyTable.tsx +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/ObjectPropertyTable.tsx @@ -38,15 +38,15 @@ export const ObjectPropertyTable = observer(function O const allData = dbObjectLoader.resource.get(pagination.allPages).filter(isDefined); const { nodes, duplicates } = navNodeViewService.filterDuplicates(allData.map(node => node?.id) || []); - const objects = allData.filter(node => nodes.includes(node.id)) as DBObject[]; + const objects = allData.filter(node => nodes.has(node.id)) as DBObject[]; useEffect(() => { - navNodeViewService.logDuplicates(objectId, duplicates); + navNodeViewService.logDuplicates(objectId, Array.from(duplicates)); }); return ( <> - {nodes.length === 0 ? ( + {nodes.size === 0 ? ( {translate('plugin_object_viewer_table_no_items')} ) : (
From a1404e3495de222596705b87f78dbc6c884b0093 Mon Sep 17 00:00:00 2001 From: Ruslan Musaev <43766501+HocKu7@users.noreply.github.com> Date: Wed, 10 Sep 2025 22:44:11 +0200 Subject: [PATCH 4/6] dbeaver/pro#6224 Add soring completion alphabetically for web (#3697) * dbeaver/pro#6224 Add soring completion alphabetically for web * dbeaver/pro#6224 Add soring completion alphabetically for web --------- Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com> Co-authored-by: Elizabeth <28875055+E1izabeth@users.noreply.github.com> --- .../io/cloudbeaver/service/sql/WebSQLCompletionContext.java | 2 +- .../cloudbeaver/service/sql/WebSQLCompletionProposal.java | 4 ++++ .../src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLCompletionContext.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLCompletionContext.java index 0ba1d10507..5a303d37e5 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLCompletionContext.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLCompletionContext.java @@ -103,7 +103,7 @@ public class WebSQLCompletionContext implements SQLCompletionContext { @Override public boolean isSortAlphabetically() { - return false; + return true; } @Override diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLCompletionProposal.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLCompletionProposal.java index c34729c1ee..fc18d8240c 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLCompletionProposal.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLCompletionProposal.java @@ -67,4 +67,8 @@ public class WebSQLCompletionProposal { return null; } + public CompletionProposalBase getProposal() { + return proposal; + } + } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java index 20c183203d..e9615cbc5e 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java @@ -55,6 +55,7 @@ import org.jkiss.dbeaver.model.sql.parser.SQLParserContext; import org.jkiss.dbeaver.model.sql.parser.SQLScriptParser; import org.jkiss.dbeaver.model.sql.registry.SQLGeneratorConfigurationRegistry; import org.jkiss.dbeaver.model.sql.registry.SQLGeneratorDescriptor; +import org.jkiss.dbeaver.model.sql.semantics.completion.SQLCompletionProposalComparator; import org.jkiss.dbeaver.model.sql.semantics.completion.SQLQueryCompletionAnalyzer; import org.jkiss.dbeaver.model.struct.DBSDataContainer; import org.jkiss.dbeaver.model.struct.DBSObject; @@ -200,6 +201,11 @@ public class WebServiceSQL implements DBWServiceSQL { for (int i = 0; i < proposals.size(); i++) { result[i] = new WebSQLCompletionProposal(proposals.get(i)); } + SQLCompletionProposalComparator sqlCompletionProposalComparator = new SQLCompletionProposalComparator( + completionContext.isSortAlphabetically(), + completionContext.isSearchInsideNames() + ); + Arrays.sort(result, (o1, o2) -> sqlCompletionProposalComparator.compare(o1.getProposal(), o2.getProposal())); return result; } catch (Exception e) { throw new DBWebException("Error processing SQL proposals", e); From 6ce5ef235c84f7dc09f4775201a7e4c6d313072b Mon Sep 17 00:00:00 2001 From: Ruslan Musaev <43766501+HocKu7@users.noreply.github.com> Date: Thu, 11 Sep 2025 08:13:20 +0200 Subject: [PATCH 5/6] dbeaver/pro#6259 fix query logging for import (#3733) Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com> --- .../service/data/transfer/impl/WebServiceDataTransfer.java | 1 + 1 file changed, 1 insertion(+) 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 3e1047d82b..8b0fedb330 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 @@ -313,6 +313,7 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer { DatabaseTransferConsumer consumer = new DatabaseTransferConsumer(dataContainer); DatabaseConsumerSettings databaseConsumerSettings = new DatabaseConsumerSettings(); databaseConsumerSettings.setContainer((DBSObjectContainer) dataContainer.getDataSource()); + databaseConsumerSettings.setEnableQmLogging(true); consumer.setSettings(databaseConsumerSettings); StreamProducerSettings producerSettings = new StreamProducerSettings(); From 96d6dba83a0283b770768ca5afbdd46265e41325 Mon Sep 17 00:00:00 2001 From: Ruslan Musaev <43766501+HocKu7@users.noreply.github.com> Date: Thu, 11 Sep 2025 08:20:14 +0200 Subject: [PATCH 6/6] dbeaver/pro#6644 update licence status after users decrease (#3711) * dbeaver/pro#6644 update licence status after users decrease * dbeaver/pro#6644 update licence status after user decrease * dbeaver/pro#6644 update license status after users decrease --------- Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com> Co-authored-by: Ainur <59531286+yagudin10@users.noreply.github.com> --- .../server/events/WSUserEventHandler.java | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/events/WSUserEventHandler.java b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/events/WSUserEventHandler.java index 8509247870..9b68c14f2d 100644 --- a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/events/WSUserEventHandler.java +++ b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/events/WSUserEventHandler.java @@ -41,30 +41,5 @@ public class WSUserEventHandler implements WSEven case WSUserDisabledEvent e -> sessionManager.closeUserSession(e); default -> { } } - - switch (event.getId()) { - case WSUserCloseSessionsEvent.ID: - if (event instanceof WSUserCloseSessionsEvent closeSessionsEvent) { - if (closeSessionsEvent.getSessionIds().isEmpty()) { - sessionManager.closeAllSessions(closeSessionsEvent.getSessionId()); - } else { - sessionManager.closeSessions(closeSessionsEvent.getSessionIds()); - } - } - break; - case WSUserDeletedEvent.ID: - if (event instanceof WSUserDeletedEvent userDeletedEvent) { - sessionManager.closeUserSession(userDeletedEvent); - } - break; - case WSUserDisabledEvent.ID: - if (event instanceof WSUserDisabledEvent userDisabledEvent) { - sessionManager.closeUserSession(userDisabledEvent); - } - break; - default: - break; - } - } }