diff --git a/server/bundles/io.cloudbeaver.server/schema/service.events.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.events.graphqls index b82da85fdd..04ad4a20ab 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.events.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.events.graphqls @@ -35,6 +35,8 @@ enum CBServerEventId { cb_database_output_log_updated, + cb_transaction_count @since(version: "24.3.3") + cb_session_task_info_updated @since(version: "24.3.1") } @@ -62,7 +64,9 @@ enum CBEventTopic { cb_session_task, @since(version: "24.3.1") cb_datasource_connection, - cb_delete_temp_folder + cb_delete_temp_folder, + + cb_transaction @since(version: "24.3.3") } # Base server event interface @@ -212,6 +216,16 @@ type WSDataSourceConnectEvent implements CBServerEvent { timestamp: Int! } +# Datasource count event in transactional mode +type WSTransactionalCountEvent implements CBServerEvent { + id: CBServerEventId! + topicId: CBEventTopic + contextId: String! + projectId: String! + connectionId: String! + transactionalCount: Int! +} + extend type Query { emptyEvent: Boolean } diff --git a/server/bundles/io.cloudbeaver.server/schema/service.sql.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.sql.graphqls index a1d12c3dd5..c9f8acd073 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.sql.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.sql.graphqls @@ -223,6 +223,24 @@ type DynamicTraceProperty { description: String } +#################################################### +# Transactional info +#################################################### +type TransactionLogInfoItem { + id: Int! + time: DateTime! + type: String! + queryString: String! + durationMs: Int! + rows: Int! + result: String! +} +type TransactionLogInfos { + count: Int! + transactionLogInfos: [TransactionLogInfoItem!]! +} + + #################################################### # Query and Mutation #################################################### @@ -331,6 +349,12 @@ extend type Mutation { dataFormat: ResultDataFormat ): AsyncTaskInfo! + getTransactionLogInfo( + projectId: ID!, + connectionId: ID!, + contextId: ID! + ): TransactionLogInfos! + # Close results (free resources) sqlResultClose(projectId: ID, connectionId: ID!, contextId: ID!, resultId: ID!): Boolean! diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebTransactionLogInfo.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebTransactionLogInfo.java new file mode 100644 index 0000000000..651fb4ba64 --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebTransactionLogInfo.java @@ -0,0 +1,24 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2025 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.cloudbeaver.model; + +import org.jkiss.code.NotNull; + +import java.util.List; + +public record WebTransactionLogInfo(@NotNull List transactionLogInfos, int count) { +} \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebTransactionLogItemInfo.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebTransactionLogItemInfo.java new file mode 100644 index 0000000000..926387a5ea --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebTransactionLogItemInfo.java @@ -0,0 +1,31 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2025 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.cloudbeaver.model; + +import org.jkiss.code.NotNull; +import org.jkiss.code.Nullable; + +public record WebTransactionLogItemInfo( + @Nullable Integer id, + @NotNull String time, + @NotNull String type, + @NotNull String queryString, + long durationMs, + long rows, + String result +) { +} \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/DBWServiceSQL.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/DBWServiceSQL.java index a7d5a95fc1..8685f096da 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/DBWServiceSQL.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/DBWServiceSQL.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import io.cloudbeaver.WebAction; import io.cloudbeaver.model.WebAsyncTaskInfo; import io.cloudbeaver.model.WebConnectionInfo; +import io.cloudbeaver.model.WebTransactionLogInfo; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.service.DBWService; import org.jkiss.code.NotNull; @@ -202,4 +203,10 @@ WebAsyncTaskInfo asyncSqlRollbackTransaction( WebAsyncTaskInfo asyncSqlCommitTransaction( @NotNull WebSession webSession, @NotNull WebSQLContextInfo sqlContext); + + @WebAction + WebTransactionLogInfo getTransactionLogInfo( + @NotNull WebSession webSession, + @NotNull WebSQLContextInfo sqlContext + ); } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLContextInfo.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLContextInfo.java index f0115c6cef..4ad2905dba 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLContextInfo.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLContextInfo.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,22 +26,36 @@ import org.jkiss.code.NotNull; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.DBConstants; import org.jkiss.dbeaver.model.DBUtils; import org.jkiss.dbeaver.model.data.DBDAttributeBinding; import org.jkiss.dbeaver.model.exec.*; import org.jkiss.dbeaver.model.exec.trace.DBCTrace; +import org.jkiss.dbeaver.model.messages.ModelMessages; import org.jkiss.dbeaver.model.meta.Property; +import io.cloudbeaver.model.WebTransactionLogInfo; +import io.cloudbeaver.model.WebTransactionLogItemInfo; import org.jkiss.dbeaver.model.qm.QMTransactionState; import org.jkiss.dbeaver.model.qm.QMUtils; +import org.jkiss.dbeaver.model.qm.meta.QMMConnectionInfo; +import org.jkiss.dbeaver.model.qm.meta.QMMStatementExecuteInfo; +import org.jkiss.dbeaver.model.qm.meta.QMMTransactionInfo; +import org.jkiss.dbeaver.model.qm.meta.QMMTransactionSavepointInfo; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.struct.DBSDataContainer; import org.jkiss.dbeaver.model.struct.rdb.DBSCatalog; import org.jkiss.dbeaver.model.struct.rdb.DBSSchema; +import org.jkiss.dbeaver.model.websocket.event.WSTransactionalCountEvent; import org.jkiss.dbeaver.utils.RuntimeUtils; import org.jkiss.utils.CommonUtils; import java.lang.reflect.InvocationTargetException; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -59,6 +73,9 @@ public class WebSQLContextInfo implements WebSessionProvider { private final AtomicInteger resultId = new AtomicInteger(); + public static final DateTimeFormatter ISO_DATE_FORMAT = DateTimeFormatter.ofPattern(DBConstants.DEFAULT_ISO_TIMESTAMP_FORMAT) + .withZone(ZoneId.of("UTC")); + public WebSQLContextInfo( WebSQLProcessor processor, String id, String catalogName, String schemaName, String projectId ) throws DBCException { @@ -216,6 +233,70 @@ public void run(DBRProgressMonitor monitor) throws InvocationTargetException, In } + public WebTransactionLogInfo getTransactionLogInfo() { + DBCExecutionContext context = processor.getExecutionContext(); + return getTransactionLogInfo(context); + } + + @NotNull + private WebTransactionLogInfo getTransactionLogInfo(DBCExecutionContext executionContext) { + int updateCount = 0; + List logItemInfos = new ArrayList<>(); + QMMConnectionInfo sessionInfo = QMUtils.getCurrentConnection(executionContext); + if (sessionInfo.isTransactional()) { + QMMTransactionInfo txnInfo = sessionInfo.getTransaction(); + if (txnInfo != null) { + QMMTransactionSavepointInfo sp = txnInfo.getCurrentSavepoint(); + QMMStatementExecuteInfo execInfo = sp.getLastExecute(); + for (QMMStatementExecuteInfo exec = execInfo; exec != null && exec.getSavepoint() == sp; exec = exec.getPrevious()) { + if (exec.getUpdateRowCount() > 0 ) { + DBCExecutionPurpose purpose = exec.getStatement().getPurpose(); + if (!exec.hasError() && purpose != DBCExecutionPurpose.META && purpose != DBCExecutionPurpose.UTIL) { + updateCount++; + } + generateLogInfo(logItemInfos, exec, purpose, updateCount); + } + } + } + } else { + QMMStatementExecuteInfo execInfo = sessionInfo.getExecutionStack(); + for (QMMStatementExecuteInfo exec = execInfo; exec != null; exec = exec.getPrevious()) { + if (exec.getUpdateRowCount() > 0) { + updateCount++; + DBCExecutionPurpose purpose = exec.getStatement().getPurpose(); + generateLogInfo(logItemInfos, exec, purpose, updateCount); + } + } + } + return new WebTransactionLogInfo(logItemInfos, updateCount); + } + + private void generateLogInfo( + @NotNull List logItemInfos, + @NotNull QMMStatementExecuteInfo exec, + @NotNull DBCExecutionPurpose purpose, + int id + ) { + String type = "SQL / " + purpose.getTitle(); + String dateTime = ISO_DATE_FORMAT.format(Instant.ofEpochMilli(exec.getCloseTime())); + String result = ModelMessages.controls_querylog_success; + if (exec.hasError()) { + if (exec.getErrorCode() == 0) { + result = exec.getErrorMessage(); + } else if (exec.getErrorMessage() == null) { + result = ModelMessages.controls_querylog_error + exec.getErrorCode() + "]"; //$NON-NLS-1$ + } else { + result = "[" + exec.getErrorCode() + "] " + exec.getErrorMessage(); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + logItemInfos.add( + new WebTransactionLogItemInfo(id, dateTime, type, exec.getQueryString(), + exec.getDuration(), exec.getUpdateRowCount(), result) + ); + } + + public WebAsyncTaskInfo commitTransaction() { DBCExecutionContext context = processor.getExecutionContext(); DBCTransactionManager txnManager = DBUtils.getTransactionManager(context); @@ -238,6 +319,17 @@ public void run(DBRProgressMonitor monitor) throws InvocationTargetException, In RuntimeUtils.formatExecutionTime(System.currentTimeMillis() - txnInfo.getTransactionStartTime()) ); } + processor.getWebSession().addSessionEvent( + new WSTransactionalCountEvent( + processor.getWebSession().getSessionId(), + processor.getWebSession().getUserId(), + getProjectId(), + getId(), + getConnectionId(), + 0 + ) + ); + } }; return getWebSession().createAndRunAsyncTask("Commit transaction", runnable); @@ -265,6 +357,16 @@ public void run(DBRProgressMonitor monitor) throws InvocationTargetException, In txnInfo.getUpdateCount(), RuntimeUtils.formatExecutionTime(System.currentTimeMillis() - txnInfo.getTransactionStartTime()) ); + processor.getWebSession().addSessionEvent( + new WSTransactionalCountEvent( + processor.getWebSession().getSessionId(), + processor.getWebSession().getUserId(), + getProjectId(), + getId(), + getConnectionId(), + 0 + ) + ); } } }; diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java index d0f55ca283..e7e3ba42c4 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,12 +41,14 @@ import org.jkiss.dbeaver.model.impl.DefaultServerOutputReader; import org.jkiss.dbeaver.model.navigator.DBNDatabaseItem; import org.jkiss.dbeaver.model.navigator.DBNNode; +import org.jkiss.dbeaver.model.qm.QMUtils; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.sql.*; import org.jkiss.dbeaver.model.sql.parser.SQLParserContext; import org.jkiss.dbeaver.model.sql.parser.SQLRuleManager; import org.jkiss.dbeaver.model.sql.parser.SQLScriptParser; import org.jkiss.dbeaver.model.struct.*; +import org.jkiss.dbeaver.model.websocket.event.WSTransactionalCountEvent; import org.jkiss.dbeaver.utils.GeneralUtils; import org.jkiss.utils.ArrayUtils; import org.jkiss.utils.CommonUtils; @@ -164,7 +166,7 @@ public WebSQLExecuteInfo processQuery( @Nullable WebSQLDataFilter filter, @Nullable WebDataFormat dataFormat, @NotNull WebSession webSession, - boolean readLogs) throws DBWebException { + boolean readLogs) throws DBWebException, DBCException { if (filter == null) { // Use default filter filter = new WebSQLDataFilter(); @@ -270,6 +272,11 @@ public WebSQLExecuteInfo processQuery( } catch (DBException e) { throw new DBWebException("Error executing query", e); } + DBCTransactionManager txnManager = DBUtils.getTransactionManager(context); + if (txnManager != null && !txnManager.isAutoCommit()) { + sendTransactionalEvent(contextInfo); + } + executeInfo.setDuration(System.currentTimeMillis() - startTime); if (executeInfo.getResults().length == 0) { executeInfo.setStatusMessage("No Data"); @@ -354,17 +361,17 @@ public WebSQLExecuteInfo updateResultsDataBatch( WebSQLExecuteInfo result = new WebSQLExecuteInfo(); List queryResults = new ArrayList<>(); + boolean isAutoCommitEnabled = true; + for (var rowIdentifier : rowIdentifierList) { Map resultBatches = new LinkedHashMap<>(); DBSDataManipulator dataManipulator = generateUpdateResultsDataBatch( monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, dataFormat, resultBatches, keyReceiver); - DBCExecutionContext executionContext = getExecutionContext(dataManipulator); try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Update data in container")) { DBCTransactionManager txnManager = DBUtils.getTransactionManager(executionContext); boolean revertToAutoCommit = false; - boolean isAutoCommitEnabled = true; DBCSavepoint savepoint = null; if (txnManager != null) { isAutoCommitEnabled = txnManager.isAutoCommit(); @@ -421,6 +428,10 @@ public WebSQLExecuteInfo updateResultsDataBatch( } getUpdatedRowsInfo(resultsInfo, newResultSetRows, dataFormat, monitor); + if (!isAutoCommitEnabled) { + sendTransactionalEvent(contextInfo); + } + WebSQLQueryResultSet updatedResultSet = new WebSQLQueryResultSet(); updatedResultSet.setResultsInfo(resultsInfo); updatedResultSet.setColumns(resultsInfo.getAttributes()); @@ -437,6 +448,20 @@ public WebSQLExecuteInfo updateResultsDataBatch( return result; } + private void sendTransactionalEvent(WebSQLContextInfo contextInfo) { + int count = QMUtils.getTransactionState(getExecutionContext()).getUpdateCount(); + webSession.addSessionEvent( + new WSTransactionalCountEvent( + contextInfo.getWebSession().getSessionId(), + contextInfo.getWebSession().getUserId(), + contextInfo.getProjectId(), + contextInfo.getId(), + contextInfo.getConnectionId(), + count + ) + ); + } + private void getUpdatedRowsInfo( @NotNull WebSQLResultsInfo resultsInfo, @NotNull Set newResultSetRows, diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java index c183c7d350..108eedd137 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -226,6 +226,11 @@ public void bindWiring(DBWBindingContext model) throws DBWebException { getWebSession(env), getSQLContext(env) )) + .dataFetcher("getTransactionLogInfo", env -> + getService(env).getTransactionLogInfo( + getWebSession(env), + getSQLContext(env) + )) .dataFetcher("asyncSqlRollbackTransaction", env -> getService(env).asyncSqlRollbackTransaction( getWebSession(env), 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 0308509dec..353040434f 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 @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ import org.jkiss.dbeaver.model.impl.sql.BasicSQLDialect; import org.jkiss.dbeaver.model.navigator.DBNModel; import org.jkiss.dbeaver.model.navigator.DBNNode; +import io.cloudbeaver.model.WebTransactionLogInfo; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.sql.*; import org.jkiss.dbeaver.model.sql.completion.SQLCompletionAnalyzer; @@ -615,4 +616,8 @@ public WebAsyncTaskInfo asyncSqlCommitTransaction(@NotNull WebSession webSession return contextInfo.commitTransaction(); } + @Override + public WebTransactionLogInfo getTransactionLogInfo(@NotNull WebSession webSession, @NotNull WebSQLContextInfo sqlContext) { + return sqlContext.getTransactionLogInfo(); + } } diff --git a/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContext.ts b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContext.ts index 2416dd02ee..47dcd65b09 100644 --- a/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContext.ts +++ b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContext.ts @@ -162,6 +162,20 @@ export class ConnectionExecutionContext implements IConnectionExecutionContext { return mapAsyncTaskInfo(result); } + async getLog() { + const result = await this.withContext(async context => { + const { log } = await this.graphQLService.sdk.getTransactionLog({ + projectId: context.projectId, + connectionId: context.connectionId, + contextId: context.id, + }); + + return log; + }); + + return result?.transactionLogInfos; + } + private withContext(callback: (context: IConnectionExecutionContextInfo) => Promise): Promise { if (!this.context) { throw new Error('Execution Context not found'); diff --git a/webapp/packages/core-sdk/src/queries/transactions/getTransactionCount.gql b/webapp/packages/core-sdk/src/queries/transactions/getTransactionCount.gql new file mode 100644 index 0000000000..0289c29a88 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/transactions/getTransactionCount.gql @@ -0,0 +1,5 @@ +mutation getTransactionCount($projectId: ID!, $connectionId: ID!, $contextId: ID!) { + info: getTransactionLogInfo(projectId: $projectId, connectionId: $connectionId, contextId: $contextId) { + count + } +} diff --git a/webapp/packages/core-sdk/src/queries/transactions/getTransactionLog.gql b/webapp/packages/core-sdk/src/queries/transactions/getTransactionLog.gql new file mode 100644 index 0000000000..116e622bd1 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/transactions/getTransactionLog.gql @@ -0,0 +1,13 @@ +mutation getTransactionLog($projectId: ID!, $connectionId: ID!, $contextId: ID!) { + log: getTransactionLogInfo(projectId: $projectId, connectionId: $connectionId, contextId: $contextId) { + transactionLogInfos { + id + time + type + queryString + durationMs + rows + result + } + } +} diff --git a/webapp/packages/plugin-datasource-transaction-manager/package.json b/webapp/packages/plugin-datasource-transaction-manager/package.json index 50655af823..f63cb4e288 100644 --- a/webapp/packages/plugin-datasource-transaction-manager/package.json +++ b/webapp/packages/plugin-datasource-transaction-manager/package.json @@ -25,15 +25,26 @@ "@cloudbeaver/core-events": "^0", "@cloudbeaver/core-executor": "^0", "@cloudbeaver/core-localization": "^0", + "@cloudbeaver/core-resource": "^0", + "@cloudbeaver/core-root": "^0", + "@cloudbeaver/core-sdk": "^0", "@cloudbeaver/core-settings": "^0", "@cloudbeaver/core-ui": "^0", "@cloudbeaver/core-utils": "^0", "@cloudbeaver/core-view": "^0", + "@cloudbeaver/plugin-codemirror6": "^0", + "@cloudbeaver/plugin-data-grid": "^0", "@cloudbeaver/plugin-datasource-context-switch": "^0", - "@cloudbeaver/plugin-top-app-bar": "^0" + "@cloudbeaver/plugin-sql-editor-new": "^0", + "@cloudbeaver/plugin-top-app-bar": "^0", + "mobx": "^6", + "mobx-react-lite": "^4", + "react": "^18" }, "peerDependencies": {}, "devDependencies": { - "typescript": "^5" + "@types/react": "^18", + "typescript": "^5", + "typescript-plugin-css-modules": "^5" } } diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TRANSACTION_INFO_PARAM_SCHEMA.ts b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TRANSACTION_INFO_PARAM_SCHEMA.ts new file mode 100644 index 0000000000..b4e0b04890 --- /dev/null +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TRANSACTION_INFO_PARAM_SCHEMA.ts @@ -0,0 +1,27 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { schema } from '@cloudbeaver/core-utils'; + +export const TRANSACTION_INFO_PARAM_SCHEMA = schema + .object({ + connectionId: schema.string(), + projectId: schema.string(), + contextId: schema.string(), + }) + .required() + .strict(); + +export type ITransactionInfoParam = schema.infer; + +export function createTransactionInfoParam(connectionId: string, projectId: string, contextId: string): ITransactionInfoParam { + return { + connectionId, + projectId, + contextId, + }; +} diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionInfoAction.module.css b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionInfoAction.module.css new file mode 100644 index 0000000000..3408475ccf --- /dev/null +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionInfoAction.module.css @@ -0,0 +1,38 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +.container { + cursor: pointer; + padding: 0 12px; + position: relative; + + &:after { + position: absolute; + background: #236ea0; + height: 32px; + width: 1px; + top: 8px; + right: -1px; + opacity: 1; + content: ''; + } + + &:hover { + background: #338ecc; + } +} + +.count { + border: 1px solid var(--theme-on-primary); + border-radius: var(--theme-form-element-radius); + height: 26px; + width: 50px; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionInfoAction.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionInfoAction.tsx new file mode 100644 index 0000000000..e225f12d0a --- /dev/null +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionInfoAction.tsx @@ -0,0 +1,41 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observer } from 'mobx-react-lite'; + +import { Container, s, useResource, useS, useTranslate } from '@cloudbeaver/core-blocks'; +import { useService } from '@cloudbeaver/core-di'; +import type { ICustomMenuItemComponent } from '@cloudbeaver/core-view'; + +import { TransactionManagerService } from '../TransactionManagerService.js'; +import { createTransactionInfoParam } from './TRANSACTION_INFO_PARAM_SCHEMA.js'; +import classes from './TransactionInfoAction.module.css'; +import { TransactionLogCountResource } from './TransactionLogCountResource.js'; + +export const TransactionInfoAction: ICustomMenuItemComponent = observer(function TransactionInfoAction(props) { + const styles = useS(classes); + const translate = useTranslate(); + const transactionManagerService = useService(TransactionManagerService); + const transaction = transactionManagerService.getActiveContextTransaction(); + const context = transaction?.context; + const key = context ? createTransactionInfoParam(context.connectionId, context.projectId, context.id) : null; + + const transactionLogCountResource = useResource(TransactionInfoAction, TransactionLogCountResource, key); + + return ( + props.item.events?.onSelect?.()} + > + {transactionLogCountResource.data} + + ); +}); diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogCountEventHandler.ts b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogCountEventHandler.ts new file mode 100644 index 0000000000..e2d81ffbb3 --- /dev/null +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogCountEventHandler.ts @@ -0,0 +1,25 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { injectable } from '@cloudbeaver/core-di'; +import { type ISessionEvent, type SessionEventId, SessionEventSource, SessionEventTopic, TopicEventHandler } from '@cloudbeaver/core-root'; +import type { WsTransactionalCountEvent } from '@cloudbeaver/core-sdk'; + +export type IWsTransactionCountEvent = WsTransactionalCountEvent; + +type TransactionCountEvent = IWsTransactionCountEvent; + +@injectable() +export class TransactionLogCountEventHandler extends TopicEventHandler { + constructor(sessionEventSource: SessionEventSource) { + super(SessionEventTopic.CbTransaction, sessionEventSource); + } + + map(event: any): TransactionCountEvent { + return event; + } +} diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogCountResource.ts b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogCountResource.ts new file mode 100644 index 0000000000..d72f03a851 --- /dev/null +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogCountResource.ts @@ -0,0 +1,63 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { toJS } from 'mobx'; + +import { injectable } from '@cloudbeaver/core-di'; +import { CachedMapResource } from '@cloudbeaver/core-resource'; +import { ServerEventId } from '@cloudbeaver/core-root'; +import { GraphQLService } from '@cloudbeaver/core-sdk'; +import { schemaValidationError } from '@cloudbeaver/core-utils'; + +import { createTransactionInfoParam, type ITransactionInfoParam, TRANSACTION_INFO_PARAM_SCHEMA } from './TRANSACTION_INFO_PARAM_SCHEMA.js'; +import { type IWsTransactionCountEvent, TransactionLogCountEventHandler } from './TransactionLogCountEventHandler.js'; + +@injectable() +export class TransactionLogCountResource extends CachedMapResource { + constructor( + private readonly graphQLService: GraphQLService, + transactionLogCountEventHandler: TransactionLogCountEventHandler, + ) { + super(); + + transactionLogCountEventHandler.onEvent( + ServerEventId.CbTransactionCount, + async data => { + const key = createTransactionInfoParam(data.connectionId, data.projectId, data.contextId); + this.set(key, data.transactionalCount); + }, + undefined, + this, + ); + } + + protected async loader(key: ITransactionInfoParam) { + const { info } = await this.graphQLService.sdk.getTransactionCount({ + connectionId: key.connectionId, + projectId: key.projectId, + contextId: key.contextId, + }); + + this.set(key, info.count); + + return this.data; + } + + override isKeyEqual(key: ITransactionInfoParam, secondKey: ITransactionInfoParam): boolean { + return key.connectionId === secondKey.connectionId && key.projectId === secondKey.projectId; + } + + protected override validateKey(key: ITransactionInfoParam): boolean { + const parse = TRANSACTION_INFO_PARAM_SCHEMA.safeParse(toJS(key)); + + if (!parse.success) { + this.logger.warn(`Invalid resource key ${(schemaValidationError(parse.error).toString(), { prefix: null })}`); + } + + return parse.success; + } +} diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogDialog.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogDialog.tsx new file mode 100644 index 0000000000..469772405e --- /dev/null +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogDialog.tsx @@ -0,0 +1,82 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observer } from 'mobx-react-lite'; + +import { + Button, + CommonDialogBody, + CommonDialogFooter, + CommonDialogHeader, + CommonDialogWrapper, + Container, + Flex, + useAutoLoad, + useResource, + useTranslate, +} from '@cloudbeaver/core-blocks'; +import { type ConnectionExecutionContext, ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections'; +import type { DialogComponent } from '@cloudbeaver/core-dialogs'; + +import { TransactionLogTable } from './TransactionLogTable/TransactionLogTable.js'; +import { useTransactionLog } from './useTransactionLog.js'; + +interface IPayload { + transaction: ConnectionExecutionContext; + onCommit: () => void; + onRollback: () => void; +} + +export const TransactionLogDialog: DialogComponent = observer(function TransactionLogDialog(props) { + const translate = useTranslate(); + const context = props.payload.transaction.context; + const connectionParam = context ? createConnectionParam(context.projectId, context.connectionId) : null; + + const state = useTransactionLog(props.payload); + const connectionInfoResource = useResource(useTransactionLog, ConnectionInfoResource, connectionParam); + let title: string = translate('plugin_datasource_transaction_manager_logs'); + + if (connectionInfoResource.data?.name) { + title = `${title} (${connectionInfoResource.data.name})`; + } + + useAutoLoad(TransactionLogDialog, state); + + function handleRollback() { + props.payload.onRollback(); + props.resolveDialog(); + } + + function handleCommit() { + props.payload.onCommit(); + props.resolveDialog(); + } + + return ( + + + + + + + + + + + + + + + + ); +}); diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/HeaderCell.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/HeaderCell.tsx new file mode 100644 index 0000000000..66579a0f70 --- /dev/null +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/HeaderCell.tsx @@ -0,0 +1,17 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observer } from 'mobx-react-lite'; + +import { useTranslate } from '@cloudbeaver/core-blocks'; +import { type TransactionLogInfoItem } from '@cloudbeaver/core-sdk'; +import type { RenderHeaderCellProps } from '@cloudbeaver/plugin-data-grid'; + +export const HeaderCell = observer>(function HeaderCell(props) { + const translate = useTranslate(); + return
{typeof props.column.name === 'string' ? translate(props.column.name) : props.column.name}
; +}); diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryCell.module.css b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryCell.module.css new file mode 100644 index 0000000000..52479f1294 --- /dev/null +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryCell.module.css @@ -0,0 +1,10 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +.cell { + cursor: pointer; +} diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryCell.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryCell.tsx new file mode 100644 index 0000000000..1800556829 --- /dev/null +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryCell.tsx @@ -0,0 +1,33 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observer } from 'mobx-react-lite'; + +import { useService } from '@cloudbeaver/core-di'; +import { CommonDialogService } from '@cloudbeaver/core-dialogs'; +import type { TransactionLogInfoItem } from '@cloudbeaver/core-sdk'; +import { type RenderCellProps } from '@cloudbeaver/plugin-data-grid'; + +import classes from './QueryCell.module.css'; +import { QueryDetailsDialog } from './QueryDetailsDialog.js'; + +export const QueryCell = observer>(function QueryCell(props) { + const commonDialogService = useService(CommonDialogService); + const value = props.row.queryString; + + async function openDetails() { + await commonDialogService.open(QueryDetailsDialog, { + text: value, + }); + } + + return ( +
+ {value} +
+ ); +}); diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryDetailsDialog.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryDetailsDialog.tsx new file mode 100644 index 0000000000..19848d8bc7 --- /dev/null +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/QueryDetailsDialog.tsx @@ -0,0 +1,40 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observer } from 'mobx-react-lite'; + +import { Button, CommonDialogBody, CommonDialogFooter, CommonDialogHeader, CommonDialogWrapper, Group, useTranslate } from '@cloudbeaver/core-blocks'; +import type { DialogComponent } from '@cloudbeaver/core-dialogs'; +import { useCodemirrorExtensions } from '@cloudbeaver/plugin-codemirror6'; +import { SQLCodeEditorLoader, useSqlDialectExtension } from '@cloudbeaver/plugin-sql-editor-new'; + +interface IPayload { + text: string; +} + +export const QueryDetailsDialog: DialogComponent = observer(function QueryDetailsDialog(props) { + const translate = useTranslate(); + const sqlDialect = useSqlDialectExtension(undefined); + const extensions = useCodemirrorExtensions(); + extensions.set(...sqlDialect); + + return ( + + + + + + + + + + + + ); +}); diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TimeCell.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TimeCell.tsx new file mode 100644 index 0000000000..201a7f3375 --- /dev/null +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TimeCell.tsx @@ -0,0 +1,20 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observer } from 'mobx-react-lite'; + +import type { TransactionLogInfoItem } from '@cloudbeaver/core-sdk'; +import { isSameDay } from '@cloudbeaver/core-utils'; +import { type RenderCellProps } from '@cloudbeaver/plugin-data-grid'; + +export const TimeCell = observer>(function TimeCell(props) { + const date = new Date(props.row.time); + const fullTime = date.toLocaleString(); + const displayTime = isSameDay(date, new Date()) ? date.toLocaleTimeString() : fullTime; + + return
{displayTime}
; +}); diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TransactionLogTable.module.css b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TransactionLogTable.module.css new file mode 100644 index 0000000000..ee44e57732 --- /dev/null +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TransactionLogTable.module.css @@ -0,0 +1,12 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +.container { + composes: theme-typography--caption theme-border-color-background from global; + border: 1px solid; + height: 100%; +} diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TransactionLogTable.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TransactionLogTable.tsx new file mode 100644 index 0000000000..6d86bb911d --- /dev/null +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/TransactionLogTable/TransactionLogTable.tsx @@ -0,0 +1,76 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observer } from 'mobx-react-lite'; + +import { s, useS } from '@cloudbeaver/core-blocks'; +import type { TransactionLogInfoItem } from '@cloudbeaver/core-sdk'; +import { type Column, DataGrid } from '@cloudbeaver/plugin-data-grid'; + +import { HeaderCell } from './HeaderCell.js'; +import { QueryCell } from './QueryCell.js'; +import { TimeCell } from './TimeCell.js'; +import classes from './TransactionLogTable.module.css'; + +interface Props { + log: TransactionLogInfoItem[]; +} + +const COLUMNS: Column[] = [ + { + key: 'time', + name: 'plugin_datasource_transaction_manager_logs_table_column_time', + resizable: true, + renderCell: props => , + renderHeaderCell: props => , + }, + { + key: 'type', + name: 'plugin_datasource_transaction_manager_logs_table_column_type', + resizable: true, + renderCell: props =>
{props.row.type}
, + renderHeaderCell: props => , + }, + { + key: 'text', + name: 'plugin_datasource_transaction_manager_logs_table_column_text', + resizable: true, + renderCell: props => , + renderHeaderCell: props => , + }, + { + key: 'duration', + name: 'plugin_datasource_transaction_manager_logs_table_column_duration', + resizable: true, + renderCell: props =>
{props.row.durationMs}
, + renderHeaderCell: props => , + }, + { + key: 'rows', + name: 'plugin_datasource_transaction_manager_logs_table_column_rows', + resizable: true, + renderCell: props =>
{props.row.rows}
, + renderHeaderCell: props => , + }, + { + key: 'result', + name: 'plugin_datasource_transaction_manager_logs_table_column_result', + resizable: true, + renderCell: props =>
{props.row.result}
, + renderHeaderCell: props => , + }, +]; + +export const TransactionLogTable = observer(function TransactionLogTable(props) { + const styles = useS(classes); + + return ( +
+ row.id} columns={COLUMNS} rowHeight={30} /> +
+ ); +}); diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/useTransactionLog.tsx b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/useTransactionLog.tsx new file mode 100644 index 0000000000..1d75c1c0c8 --- /dev/null +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionLog/useTransactionLog.tsx @@ -0,0 +1,60 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { observable } from 'mobx'; + +import { useObservableRef } from '@cloudbeaver/core-blocks'; +import type { ConnectionExecutionContext } from '@cloudbeaver/core-connections'; +import type { TransactionLogInfoItem } from '@cloudbeaver/core-sdk'; +import type { ILoadableState } from '@cloudbeaver/core-utils'; + +interface Payload { + transaction: ConnectionExecutionContext; +} + +interface State extends ILoadableState { + log: TransactionLogInfoItem[] | null; + exception: Error | null; + promise: Promise | null; + payload: Payload; +} + +export function useTransactionLog(payload: Payload) { + const state = useObservableRef( + () => ({ + log: null, + exception: null, + promise: null, + isLoaded() { + return this.log !== null; + }, + isError() { + return this.exception !== null; + }, + isLoading() { + return this.promise !== null; + }, + async load() { + try { + this.exception = null; + + this.promise = payload.transaction.getLog(); + const log = await this.promise; + this.log = log; + } catch (exception: any) { + this.exception = exception; + } finally { + this.promise = null; + } + }, + }), + { log: observable.ref, promise: observable.ref, exception: observable.ref, payload: observable.ref }, + { payload }, + ); + + return state; +} diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionManagerBootstrap.ts b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionManagerBootstrap.ts index b3a8c343de..fd485cdf9d 100644 --- a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionManagerBootstrap.ts +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionManagerBootstrap.ts @@ -5,7 +5,7 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import { ConfirmationDialog } from '@cloudbeaver/core-blocks'; +import { ConfirmationDialog, importLazyComponent } from '@cloudbeaver/core-blocks'; import { ConnectionExecutionContext, ConnectionExecutionContextResource, @@ -24,15 +24,25 @@ import { ExecutorInterrupter, type IExecutionContextProvider } from '@cloudbeave import { LocalizationService } from '@cloudbeaver/core-localization'; import { OptionsPanelService } from '@cloudbeaver/core-ui'; import { isNotNullDefined } from '@cloudbeaver/core-utils'; -import { ActionService, MenuService } from '@cloudbeaver/core-view'; +import { ActionService, MenuCustomItem, MenuService } from '@cloudbeaver/core-view'; import { ConnectionSchemaManagerService } from '@cloudbeaver/plugin-datasource-context-switch'; import { MENU_APP_ACTIONS } from '@cloudbeaver/plugin-top-app-bar'; import { ACTION_DATASOURCE_TRANSACTION_COMMIT } from './actions/ACTION_DATASOURCE_TRANSACTION_COMMIT.js'; import { ACTION_DATASOURCE_TRANSACTION_COMMIT_MODE_TOGGLE } from './actions/ACTION_DATASOURCE_TRANSACTION_COMMIT_MODE_TOGGLE.js'; import { ACTION_DATASOURCE_TRANSACTION_ROLLBACK } from './actions/ACTION_DATASOURCE_TRANSACTION_ROLLBACK.js'; +import { createTransactionInfoParam } from './TransactionLog/TRANSACTION_INFO_PARAM_SCHEMA.js'; +import { TransactionLogCountResource } from './TransactionLog/TransactionLogCountResource.js'; import { TransactionManagerSettingsService } from './TransactionManagerSettingsService.js'; +const TransactionInfoAction = importLazyComponent(() => + import('./TransactionLog/TransactionInfoAction.js').then(module => module.TransactionInfoAction), +); + +const TransactionLogDialog = importLazyComponent(() => + import('./TransactionLog/TransactionLogDialog.js').then(module => module.TransactionLogDialog), +); + @injectable() export class TransactionManagerBootstrap extends Bootstrap { constructor( @@ -48,6 +58,7 @@ export class TransactionManagerBootstrap extends Bootstrap { private readonly commonDialogService: CommonDialogService, private readonly localizationService: LocalizationService, private readonly transactionManagerSettingsService: TransactionManagerSettingsService, + private readonly transactionLogCountResource: TransactionLogCountResource, ) { super(); } @@ -68,12 +79,38 @@ export class TransactionManagerBootstrap extends Bootstrap { isNotNullDefined(transaction.autoCommit) ); }, - getItems: (_, items) => [ - ...items, - ACTION_DATASOURCE_TRANSACTION_COMMIT, - ACTION_DATASOURCE_TRANSACTION_ROLLBACK, - ACTION_DATASOURCE_TRANSACTION_COMMIT_MODE_TOGGLE, - ], + getItems: (_, items) => { + const transaction = this.getContextTransaction(); + + const result = [ + ...items, + ACTION_DATASOURCE_TRANSACTION_COMMIT, + ACTION_DATASOURCE_TRANSACTION_ROLLBACK, + ACTION_DATASOURCE_TRANSACTION_COMMIT_MODE_TOGGLE, + ]; + + if (transaction && transaction.autoCommit === false) { + result.push( + new MenuCustomItem( + { + id: 'transaction-info', + getComponent: () => TransactionInfoAction, + }, + { + onSelect: async () => { + await this.commonDialogService.open(TransactionLogDialog, { + transaction, + onCommit: () => this.commit(transaction), + onRollback: () => this.rollback(transaction), + }); + }, + }, + ), + ); + } + + return result; + }, }); this.actionService.addHandler({ @@ -127,19 +164,19 @@ export class TransactionManagerBootstrap extends Bootstrap { break; } case ACTION_DATASOURCE_TRANSACTION_ROLLBACK: { - try { - const result = await transaction.rollback(); - this.showTransactionResult(transaction, result); - } catch (exception: any) { - this.notificationService.logException(exception, 'plugin_datasource_transaction_manager_rollback_fail'); - } - + await this.rollback(transaction); break; } case ACTION_DATASOURCE_TRANSACTION_COMMIT_MODE_TOGGLE: try { await transaction.setAutoCommit(!transaction.autoCommit); await this.connectionExecutionContextResource.refresh(); + + const context = transaction.context; + + if (transaction.autoCommit === true && context) { + this.transactionLogCountResource.markOutdated(createTransactionInfoParam(context.connectionId, context.projectId, context.id)); + } } catch (exception: any) { this.notificationService.logException(exception, 'plugin_datasource_transaction_manager_commit_mode_fail'); } @@ -200,6 +237,15 @@ export class TransactionManagerBootstrap extends Bootstrap { } } + private async rollback(transaction: ConnectionExecutionContext) { + try { + const result = await transaction.rollback(); + this.showTransactionResult(transaction, result); + } catch (exception: any) { + this.notificationService.logException(exception, 'plugin_datasource_transaction_manager_rollback_fail'); + } + } + private async commit(transaction: ConnectionExecutionContext, onError?: (exception: any) => void) { try { const result = await transaction.commit(); diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/TransactionManagerService.ts b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionManagerService.ts new file mode 100644 index 0000000000..e3b9ae4699 --- /dev/null +++ b/webapp/packages/plugin-datasource-transaction-manager/src/TransactionManagerService.ts @@ -0,0 +1,28 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { ConnectionExecutionContextService } from '@cloudbeaver/core-connections'; +import { injectable } from '@cloudbeaver/core-di'; +import { ConnectionSchemaManagerService } from '@cloudbeaver/plugin-datasource-context-switch'; + +@injectable() +export class TransactionManagerService { + constructor( + private readonly connectionSchemaManagerService: ConnectionSchemaManagerService, + private readonly connectionExecutionContextService: ConnectionExecutionContextService, + ) {} + + getActiveContextTransaction() { + const context = this.connectionSchemaManagerService.activeExecutionContext; + + if (!context) { + return; + } + + return this.connectionExecutionContextService.get(context.id); + } +} diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/locales/en.ts b/webapp/packages/plugin-datasource-transaction-manager/src/locales/en.ts index f113567706..b9ec6e443f 100644 --- a/webapp/packages/plugin-datasource-transaction-manager/src/locales/en.ts +++ b/webapp/packages/plugin-datasource-transaction-manager/src/locales/en.ts @@ -1,3 +1,10 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ export default [ ['plugin_datasource_transaction_manager_commit', 'Commit'], ['plugin_datasource_transaction_manager_rollback', 'Rollback'], @@ -7,4 +14,13 @@ export default [ ['plugin_datasource_transaction_manager_rollback_fail', 'Failed to rollback transaction'], ['plugin_datasource_transaction_manager_commit_mode_fail', 'Failed to change commit mode'], ['plugin_datasource_transaction_manager_commit_confirmation_message', 'Do you want to commit changes?'], + + ['plugin_datasource_transaction_manager_logs', 'Transaction log'], + ['plugin_datasource_transaction_manager_logs_tooltip', 'Open transaction log'], + ['plugin_datasource_transaction_manager_logs_table_column_time', 'Time'], + ['plugin_datasource_transaction_manager_logs_table_column_type', 'Type'], + ['plugin_datasource_transaction_manager_logs_table_column_text', 'Query'], + ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Duration (ms)'], + ['plugin_datasource_transaction_manager_logs_table_column_rows', 'Rows'], + ['plugin_datasource_transaction_manager_logs_table_column_result', 'Result'], ]; diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/locales/fr.ts b/webapp/packages/plugin-datasource-transaction-manager/src/locales/fr.ts index 76278e2a5c..662db9253f 100644 --- a/webapp/packages/plugin-datasource-transaction-manager/src/locales/fr.ts +++ b/webapp/packages/plugin-datasource-transaction-manager/src/locales/fr.ts @@ -14,4 +14,13 @@ export default [ ['plugin_datasource_transaction_manager_rollback_fail', "Échec de l'annulation de la transaction"], ['plugin_datasource_transaction_manager_commit_mode_fail', 'Échec du changement de mode de commit'], ['plugin_datasource_transaction_manager_commit_confirmation_message', 'Voulez-vous commiter les modifications ?'], + + ['plugin_datasource_transaction_manager_logs', 'Transaction log'], + ['plugin_datasource_transaction_manager_logs_tooltip', 'Open transaction log'], + ['plugin_datasource_transaction_manager_logs_table_column_time', 'Time'], + ['plugin_datasource_transaction_manager_logs_table_column_type', 'Type'], + ['plugin_datasource_transaction_manager_logs_table_column_text', 'Query'], + ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Duration (ms)'], + ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Rows'], + ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Result'], ]; diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/locales/it.ts b/webapp/packages/plugin-datasource-transaction-manager/src/locales/it.ts index f113567706..c398f786d2 100644 --- a/webapp/packages/plugin-datasource-transaction-manager/src/locales/it.ts +++ b/webapp/packages/plugin-datasource-transaction-manager/src/locales/it.ts @@ -1,3 +1,10 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ export default [ ['plugin_datasource_transaction_manager_commit', 'Commit'], ['plugin_datasource_transaction_manager_rollback', 'Rollback'], @@ -7,4 +14,13 @@ export default [ ['plugin_datasource_transaction_manager_rollback_fail', 'Failed to rollback transaction'], ['plugin_datasource_transaction_manager_commit_mode_fail', 'Failed to change commit mode'], ['plugin_datasource_transaction_manager_commit_confirmation_message', 'Do you want to commit changes?'], + + ['plugin_datasource_transaction_manager_logs', 'Transaction log'], + ['plugin_datasource_transaction_manager_logs_tooltip', 'Open transaction log'], + ['plugin_datasource_transaction_manager_logs_table_column_time', 'Time'], + ['plugin_datasource_transaction_manager_logs_table_column_type', 'Type'], + ['plugin_datasource_transaction_manager_logs_table_column_text', 'Query'], + ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Duration (ms)'], + ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Rows'], + ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Result'], ]; diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/locales/ru.ts b/webapp/packages/plugin-datasource-transaction-manager/src/locales/ru.ts index 921b0e3d58..cc5098fecf 100644 --- a/webapp/packages/plugin-datasource-transaction-manager/src/locales/ru.ts +++ b/webapp/packages/plugin-datasource-transaction-manager/src/locales/ru.ts @@ -1,3 +1,10 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ export default [ ['plugin_datasource_transaction_manager_commit', 'Commit'], ['plugin_datasource_transaction_manager_rollback', 'Rollback'], @@ -7,4 +14,13 @@ export default [ ['plugin_datasource_transaction_manager_rollback_fail', 'Не удалось выполнить откат'], ['plugin_datasource_transaction_manager_commit_mode_fail', 'Не удалось переключить режим коммита'], ['plugin_datasource_transaction_manager_commit_confirmation_message', 'Вы хотите зафиксировать изменения?'], + + ['plugin_datasource_transaction_manager_logs', 'Журнал транзакции'], + ['plugin_datasource_transaction_manager_logs_tooltip', 'Открыть журнал транзакции'], + ['plugin_datasource_transaction_manager_logs_table_column_time', 'Время'], + ['plugin_datasource_transaction_manager_logs_table_column_type', 'Тип'], + ['plugin_datasource_transaction_manager_logs_table_column_text', 'Запрос'], + ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Продолжительность (мс)'], + ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Строки'], + ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Результат'], ]; diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/locales/zh.ts b/webapp/packages/plugin-datasource-transaction-manager/src/locales/zh.ts index ad0fc52178..b86156f6a5 100644 --- a/webapp/packages/plugin-datasource-transaction-manager/src/locales/zh.ts +++ b/webapp/packages/plugin-datasource-transaction-manager/src/locales/zh.ts @@ -14,4 +14,13 @@ export default [ ['plugin_datasource_transaction_manager_rollback_fail', '回滚事务失败'], ['plugin_datasource_transaction_manager_commit_mode_fail', '切换提交方式失败'], ['plugin_datasource_transaction_manager_commit_confirmation_message', '是否提交更改?'], + + ['plugin_datasource_transaction_manager_logs', 'Transaction log'], + ['plugin_datasource_transaction_manager_logs_tooltip', 'Open transaction log'], + ['plugin_datasource_transaction_manager_logs_table_column_time', 'Time'], + ['plugin_datasource_transaction_manager_logs_table_column_type', 'Type'], + ['plugin_datasource_transaction_manager_logs_table_column_text', 'Query'], + ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Duration (ms)'], + ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Rows'], + ['plugin_datasource_transaction_manager_logs_table_column_duration', 'Result'], ]; diff --git a/webapp/packages/plugin-datasource-transaction-manager/src/manifest.ts b/webapp/packages/plugin-datasource-transaction-manager/src/manifest.ts index bf9094704d..7e8fdf8576 100644 --- a/webapp/packages/plugin-datasource-transaction-manager/src/manifest.ts +++ b/webapp/packages/plugin-datasource-transaction-manager/src/manifest.ts @@ -16,5 +16,8 @@ export const datasourceTransactionManagerPlugin: PluginManifest = { () => import('./TransactionManagerBootstrap.js').then(m => m.TransactionManagerBootstrap), () => import('./TransactionManagerSettingsService.js').then(m => m.TransactionManagerSettingsService), () => import('./LocaleService.js').then(m => m.LocaleService), + () => import('./TransactionManagerService.js').then(m => m.TransactionManagerService), + () => import('./TransactionLog/TransactionLogCountResource.js').then(m => m.TransactionLogCountResource), + () => import('./TransactionLog/TransactionLogCountEventHandler.js').then(m => m.TransactionLogCountEventHandler), ], }; diff --git a/webapp/packages/plugin-datasource-transaction-manager/tsconfig.json b/webapp/packages/plugin-datasource-transaction-manager/tsconfig.json index 31735c5e23..ac5cc4afcd 100644 --- a/webapp/packages/plugin-datasource-transaction-manager/tsconfig.json +++ b/webapp/packages/plugin-datasource-transaction-manager/tsconfig.json @@ -27,6 +27,9 @@ { "path": "../core-localization/tsconfig.json" }, + { + "path": "../core-sdk/tsconfig.json" + }, { "path": "../core-settings/tsconfig.json" }, @@ -39,9 +42,18 @@ { "path": "../core-view/tsconfig.json" }, + { + "path": "../plugin-codemirror6/tsconfig.json" + }, + { + "path": "../plugin-data-grid/tsconfig.json" + }, { "path": "../plugin-datasource-context-switch/tsconfig.json" }, + { + "path": "../plugin-sql-editor-new/tsconfig.json" + }, { "path": "../plugin-top-app-bar/tsconfig.json" }