mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Cb 1036 server output log sql editor (#1999)
* CB-2551. Fixed additional auth for disabled user * CB-3923. Added output reader logs * CB-3923. Added output reader logs * CB-3923. Refactor after review * CB-3923. Refactor after review * CB-3923. Refactor after review * CB-3923. Refactor after review * CB-3923. Refactor after review * CB-3923. Refactor after review * CB-3923. Refactor after review * CB-3923. Refactor after review * CB-3923. Refactor after review * CB-3924: add show output button * CB-3924: add output event handler and resource * CB-3924: add server output logs panel * CB-3924: fix bugs * CB-3923. Fixed checkstyle * CB-3924: fixes after review * CB-3923. Refactor after review * CB-3924: fixes after review * CB-3924: add line wrapper extension for editor * CB-3923. Refactor after review * CB-3924: show output logs for current context * CB-3924: fixes after review * CB-3924: add output logs icon * CB-3924: fixes after review * CB-3924: fixes after review * CB-3924: fixes after review * CB-3924: fix bug with not updated menus * CB-3924. Fix boolean type --------- Co-authored-by: Dmitry Osipov <teunlao@gmail.com> Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
co-authored by
Dmitry Osipov
EvgeniaBzzz
parent
a981d37a2d
commit
8a685ef47e
@@ -28,7 +28,9 @@ enum CBServerEventId {
|
||||
cb_rm_project_removed,
|
||||
|
||||
cb_object_permissions_updated,
|
||||
cb_subject_permissions_updated
|
||||
cb_subject_permissions_updated,
|
||||
|
||||
cb_database_output_log_updated
|
||||
}
|
||||
|
||||
# Events sent by client
|
||||
@@ -48,7 +50,8 @@ enum CBEventTopic {
|
||||
cb_scripts,
|
||||
cb_projects,
|
||||
cb_object_permissions,
|
||||
cb_subject_permissions
|
||||
cb_subject_permissions,
|
||||
cb_database_output_log
|
||||
}
|
||||
|
||||
# Base server event interface
|
||||
@@ -153,6 +156,22 @@ type CBProjectsActiveEvent implements CBClientEvent {
|
||||
projectIds: [String!]! # list of active projects
|
||||
}
|
||||
|
||||
# Database output log event
|
||||
type CBDatabaseOutputLogEvent implements CBServerEvent {
|
||||
id: CBServerEventId!
|
||||
topicId: CBEventTopic
|
||||
contextId: String!
|
||||
messages: [WSOutputLogInfo!]!
|
||||
eventTimestamp: Int!
|
||||
}
|
||||
|
||||
# Define the type for WSOutputLogInfo
|
||||
type WSOutputLogInfo {
|
||||
severity: String
|
||||
message: String
|
||||
# Add more fields as needed
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
emptyEvent: Boolean
|
||||
}
|
||||
|
||||
@@ -289,7 +289,8 @@ extend type Mutation {
|
||||
sql: String!,
|
||||
resultId: ID,
|
||||
filter: SQLDataFilter,
|
||||
dataFormat: ResultDataFormat # requested data format. May be ignored by server
|
||||
dataFormat: ResultDataFormat, # requested data format. May be ignored by server
|
||||
readLogs: Boolean # added 23.2.1
|
||||
): AsyncTaskInfo!
|
||||
|
||||
# Read data from table
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2023 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.server.jobs;
|
||||
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.exec.DBCException;
|
||||
import org.jkiss.dbeaver.model.exec.DBCExecutionContext;
|
||||
import org.jkiss.dbeaver.model.exec.DBCStatement;
|
||||
import org.jkiss.dbeaver.model.exec.output.DBCOutputSeverity;
|
||||
import org.jkiss.dbeaver.model.exec.output.DBCOutputWriter;
|
||||
import org.jkiss.dbeaver.model.exec.output.DBCServerOutputReader;
|
||||
import org.jkiss.dbeaver.model.runtime.AbstractJob;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.websocket.event.WSOutputLogInfo;
|
||||
import org.jkiss.dbeaver.model.websocket.event.session.WSOutputDBLogEvent;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class SqlOutputLogReaderJob extends AbstractJob {
|
||||
|
||||
private static final Log log = Log.getLog(SqlOutputLogReaderJob.class);
|
||||
|
||||
@NotNull
|
||||
private final WebSession webSession;
|
||||
@NotNull
|
||||
private final DBCExecutionContext dbcExecutionContext;
|
||||
@NotNull
|
||||
private final DBCStatement dbcStatement;
|
||||
@NotNull
|
||||
private final DBCServerOutputReader dbcServerOutputReader;
|
||||
@NotNull
|
||||
private final String contextInfoId;
|
||||
|
||||
public SqlOutputLogReaderJob(@NotNull WebSession webSession,
|
||||
@NotNull DBCExecutionContext dbcExecutionContext,
|
||||
@NotNull DBCStatement dbcStatement,
|
||||
@NotNull DBCServerOutputReader dbcServerOutputReader,
|
||||
@NotNull String contextInfoId) {
|
||||
super("Sql log state job");
|
||||
this.webSession = webSession;
|
||||
this.dbcExecutionContext = dbcExecutionContext;
|
||||
this.dbcStatement = dbcStatement;
|
||||
this.dbcServerOutputReader = dbcServerOutputReader;
|
||||
this.contextInfoId = contextInfoId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IStatus run(DBRProgressMonitor monitor) {
|
||||
if (!DBWorkbench.getPlatform().isShuttingDown()) {
|
||||
try {
|
||||
if (!dbcStatement.isStatementClosed()) {
|
||||
dumpOutput(monitor);
|
||||
schedule(100);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to execute job " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return Status.OK_STATUS;
|
||||
}
|
||||
|
||||
private void dumpOutput(DBRProgressMonitor monitor) {
|
||||
if (!monitor.isCanceled()) {
|
||||
if (dbcServerOutputReader.isAsyncOutputReadSupported()) {
|
||||
try {
|
||||
if (!dbcStatement.isStatementClosed()) {
|
||||
List<WSOutputLogInfo> messages = new ArrayList<>();
|
||||
dbcServerOutputReader.readServerOutput(monitor, dbcExecutionContext, null, dbcStatement, new DBCOutputWriter() {
|
||||
@Override
|
||||
public void println(@Nullable DBCOutputSeverity severity, @Nullable String message) {
|
||||
if (message != null && severity != null) {
|
||||
messages.add(new WSOutputLogInfo(severity.getName(), message));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
messages.clear();
|
||||
}
|
||||
});
|
||||
webSession.addSessionEvent(new WSOutputDBLogEvent(
|
||||
contextInfoId,
|
||||
messages,
|
||||
System.currentTimeMillis()));
|
||||
}
|
||||
} catch (DBCException e) {
|
||||
log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -94,7 +94,9 @@ public interface DBWServiceSQL extends DBWService {
|
||||
@NotNull String sql,
|
||||
@Nullable String resultId,
|
||||
@Nullable WebSQLDataFilter filter,
|
||||
@Nullable WebDataFormat dataFormat) throws DBException;
|
||||
@Nullable WebDataFormat dataFormat,
|
||||
boolean readLogs,
|
||||
@NotNull WebSession webSession) throws DBException;
|
||||
|
||||
@WebAction
|
||||
WebAsyncTaskInfo asyncReadDataFromContainer(
|
||||
|
||||
+22
-1
@@ -20,6 +20,7 @@ import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.model.WebConnectionInfo;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.model.session.WebSessionProvider;
|
||||
import io.cloudbeaver.server.jobs.SqlOutputLogReaderJob;
|
||||
import org.eclipse.jface.text.Document;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
@@ -31,10 +32,12 @@ import org.jkiss.dbeaver.model.DBUtils;
|
||||
import org.jkiss.dbeaver.model.data.*;
|
||||
import org.jkiss.dbeaver.model.edit.DBEPersistAction;
|
||||
import org.jkiss.dbeaver.model.exec.*;
|
||||
import org.jkiss.dbeaver.model.exec.output.DBCServerOutputReader;
|
||||
import org.jkiss.dbeaver.model.exec.plan.DBCPlan;
|
||||
import org.jkiss.dbeaver.model.exec.plan.DBCQueryPlanner;
|
||||
import org.jkiss.dbeaver.model.exec.plan.DBCQueryPlannerConfiguration;
|
||||
import org.jkiss.dbeaver.model.impl.AbstractExecutionSource;
|
||||
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.runtime.DBRProgressMonitor;
|
||||
@@ -152,7 +155,9 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
@NotNull String sql,
|
||||
@Nullable String resultId,
|
||||
@Nullable WebSQLDataFilter filter,
|
||||
@Nullable WebDataFormat dataFormat) throws DBWebException {
|
||||
@Nullable WebDataFormat dataFormat,
|
||||
@NotNull WebSession webSession,
|
||||
boolean readLogs) throws DBWebException {
|
||||
if (filter == null) {
|
||||
// Use default filter
|
||||
filter = new WebSQLDataFilter();
|
||||
@@ -203,6 +208,17 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
webDataFilter.getOffset(),
|
||||
webDataFilter.getLimit()))
|
||||
{
|
||||
SqlOutputLogReaderJob sqlOutputLogReaderJob = null;
|
||||
if (readLogs) {
|
||||
DBPDataSource dataSource = context.getDataSource();
|
||||
DBCServerOutputReader dbcServerOutputReader = DBUtils.getAdapter(DBCServerOutputReader.class, dataSource);
|
||||
if (dbcServerOutputReader == null) {
|
||||
dbcServerOutputReader = new DefaultServerOutputReader();
|
||||
}
|
||||
sqlOutputLogReaderJob = new SqlOutputLogReaderJob(
|
||||
webSession, context, dbStat, dbcServerOutputReader, contextInfo.getId());
|
||||
sqlOutputLogReaderJob.schedule();
|
||||
}
|
||||
// Set query timeout
|
||||
int queryTimeout = (int) session.getDataSource().getContainer().getPreferenceStore()
|
||||
.getDouble(WebSQLConstants.QUOTA_PROP_SQL_QUERY_TIMEOUT);
|
||||
@@ -220,6 +236,11 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
}
|
||||
|
||||
boolean hasResultSet = dbStat.executeStatement();
|
||||
|
||||
// Wait SqlLogStateJob, if its starts
|
||||
if (sqlOutputLogReaderJob != null) {
|
||||
sqlOutputLogReaderJob.join();
|
||||
}
|
||||
fillQueryResults(contextInfo, dataContainer, dbStat, hasResultSet, executeInfo, webDataFilter, dataFilter, dataFormat);
|
||||
} catch (DBException e) {
|
||||
throw new InvocationTargetException(e);
|
||||
|
||||
+3
-1
@@ -160,7 +160,9 @@ public class WebServiceBindingSQL extends WebServiceBindingBase<DBWServiceSQL> i
|
||||
env.getArgument("sql"),
|
||||
env.getArgument("resultId"),
|
||||
getDataFilter(env),
|
||||
getDataFormat(env)))
|
||||
getDataFormat(env),
|
||||
CommonUtils.toBoolean(env.getArgument("readLogs")),
|
||||
getWebSession(env)))
|
||||
.dataFetcher("asyncReadDataFromContainer", env ->
|
||||
getService(env).asyncReadDataFromContainer(
|
||||
getSQLContext(env),
|
||||
|
||||
+6
-4
@@ -363,16 +363,18 @@ public class WebServiceSQL implements DBWServiceSQL {
|
||||
@NotNull String sql,
|
||||
@Nullable String resultId,
|
||||
@Nullable WebSQLDataFilter filter,
|
||||
@Nullable WebDataFormat dataFormat)
|
||||
@Nullable WebDataFormat dataFormat,
|
||||
boolean readLogs,
|
||||
@NotNull WebSession webSession)
|
||||
{
|
||||
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<String>() {
|
||||
WebAsyncTaskProcessor<String> runnable = new WebAsyncTaskProcessor<>() {
|
||||
@Override
|
||||
public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
|
||||
public void run(DBRProgressMonitor monitor) throws InvocationTargetException {
|
||||
try {
|
||||
monitor.beginTask("Execute query", 1);
|
||||
monitor.subTask("Process query " + sql);
|
||||
WebSQLExecuteInfo executeResults = contextInfo.getProcessor().processQuery(
|
||||
monitor, contextInfo, sql, resultId, filter, dataFormat);
|
||||
monitor, contextInfo, sql, resultId, filter, dataFormat, webSession, readLogs);
|
||||
this.result = executeResults.getStatusMessage();
|
||||
this.extendedResults = executeResults;
|
||||
} catch (Throwable e) {
|
||||
|
||||
@@ -29,6 +29,15 @@
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.customIconContainer {
|
||||
composes: iconContainer;
|
||||
right: 4px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.input[disabled] + .iconContainer {
|
||||
cursor: auto;
|
||||
opacity: 0.8;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { forwardRef, useCallback, useContext, useLayoutEffect, useRef, useState } from 'react';
|
||||
import React, { forwardRef, useCallback, useContext, useLayoutEffect, useRef, useState } from 'react';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
@@ -41,6 +41,7 @@ type BaseProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' |
|
||||
style?: ComponentStyle;
|
||||
canShowPassword?: boolean;
|
||||
onCustomCopy?: () => void;
|
||||
icon?: React.ReactElement;
|
||||
};
|
||||
|
||||
type ControlledProps = BaseProps & {
|
||||
@@ -90,6 +91,7 @@ export const InputField: InputFieldType = observer(
|
||||
canShowPassword = true,
|
||||
onChange,
|
||||
onCustomCopy,
|
||||
icon,
|
||||
...rest
|
||||
}: ControlledProps | ObjectProps<any, any>,
|
||||
ref,
|
||||
@@ -196,6 +198,7 @@ export const InputField: InputFieldType = observer(
|
||||
<Icon name="copy" viewBox="0 0 32 32" className={styles.icon} />
|
||||
</div>
|
||||
)}
|
||||
{icon && <div data-testid="icon-container" className={styles.customIconContainer}>{icon}</div>}
|
||||
</div>
|
||||
{(description || passwordType) && (
|
||||
<div data-testid="field-description" className={s(styles, { fieldDescription: true, valid: !error, invalid: error })}>
|
||||
|
||||
@@ -5,6 +5,7 @@ mutation asyncSqlExecuteQuery(
|
||||
$resultId: ID
|
||||
$filter: SQLDataFilter
|
||||
$dataFormat: ResultDataFormat
|
||||
$readLogs: Boolean
|
||||
) {
|
||||
taskInfo: asyncSqlExecuteQuery(
|
||||
connectionId: $connectionId
|
||||
@@ -13,6 +14,7 @@ mutation asyncSqlExecuteQuery(
|
||||
resultId: $resultId
|
||||
filter: $filter
|
||||
dataFormat: $dataFormat
|
||||
readLogs: $readLogs
|
||||
) {
|
||||
...AsyncTaskInfo
|
||||
}
|
||||
|
||||
@@ -19,9 +19,49 @@ import { useCodemirrorExtensions } from './useCodemirrorExtensions';
|
||||
import { type IDefaultExtensions, useEditorDefaultExtensions } from './useEditorDefaultExtensions';
|
||||
|
||||
export const Editor = observer<IEditorProps & IDefaultExtensions, IEditorRef>(
|
||||
forwardRef(function Editor({ lineNumbers, extensions, ...rest }, ref) {
|
||||
forwardRef(function Editor(
|
||||
{
|
||||
extensions,
|
||||
lineNumbers,
|
||||
tooltips,
|
||||
highlightSpecialChars,
|
||||
syntaxHighlighting,
|
||||
bracketMatching,
|
||||
dropCursor,
|
||||
crosshairCursor,
|
||||
foldGutter,
|
||||
highlightActiveLineGutter,
|
||||
highlightSelectionMatches,
|
||||
highlightActiveLine,
|
||||
indentOnInput,
|
||||
rectangularSelection,
|
||||
keymap,
|
||||
lineWrapping,
|
||||
...rest
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
extensions = useCodemirrorExtensions(extensions);
|
||||
const defaultExtensions = useEditorDefaultExtensions({ lineNumbers });
|
||||
|
||||
|
||||
const defaultExtensions = useEditorDefaultExtensions({
|
||||
lineNumbers,
|
||||
tooltips,
|
||||
highlightSpecialChars,
|
||||
syntaxHighlighting,
|
||||
bracketMatching,
|
||||
dropCursor,
|
||||
crosshairCursor,
|
||||
foldGutter,
|
||||
highlightActiveLineGutter,
|
||||
highlightSelectionMatches,
|
||||
highlightActiveLine,
|
||||
indentOnInput,
|
||||
rectangularSelection,
|
||||
keymap,
|
||||
lineWrapping,
|
||||
});
|
||||
|
||||
extensions.set(...defaultExtensions);
|
||||
|
||||
return styled(EDITOR_BASE_STYLES)(
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Compartment, Extension } from '@codemirror/state';
|
||||
import {
|
||||
crosshairCursor,
|
||||
dropCursor,
|
||||
EditorView,
|
||||
highlightActiveLine,
|
||||
highlightActiveLineGutter,
|
||||
highlightSpecialChars,
|
||||
@@ -21,9 +22,9 @@ import {
|
||||
tooltips,
|
||||
} from '@codemirror/view';
|
||||
import { classHighlighter } from '@lezer/highlight';
|
||||
import { useMemo } from 'react';
|
||||
import { useRef } from 'react';
|
||||
|
||||
import { clsx, GlobalConstants } from '@cloudbeaver/core-utils';
|
||||
import { clsx, GlobalConstants, isObjectsEqual } from '@cloudbeaver/core-utils';
|
||||
|
||||
// @TODO allow to configure bindings outside of the component
|
||||
const DEFAULT_KEY_MAP = defaultKeymap.filter(binding => binding.mac !== 'Ctrl-f' && binding.key !== 'Mod-Enter');
|
||||
@@ -34,55 +35,101 @@ DEFAULT_KEY_MAP.push({
|
||||
run: () => true,
|
||||
});
|
||||
|
||||
const defaultExtensionsFlags: IDefaultExtensions = {
|
||||
lineNumbers: false,
|
||||
tooltips: true,
|
||||
highlightSpecialChars: true,
|
||||
syntaxHighlighting: true,
|
||||
bracketMatching: true,
|
||||
dropCursor: true,
|
||||
crosshairCursor: true,
|
||||
foldGutter: true,
|
||||
highlightActiveLineGutter: true,
|
||||
highlightSelectionMatches: true,
|
||||
highlightActiveLine: true,
|
||||
indentOnInput: true,
|
||||
rectangularSelection: true,
|
||||
keymap: true,
|
||||
lineWrapping: false,
|
||||
};
|
||||
|
||||
export interface IDefaultExtensions {
|
||||
lineNumbers?: boolean;
|
||||
tooltips?: boolean;
|
||||
highlightSpecialChars?: boolean;
|
||||
syntaxHighlighting?: boolean;
|
||||
bracketMatching?: boolean;
|
||||
dropCursor?: boolean;
|
||||
crosshairCursor?: boolean;
|
||||
foldGutter?: boolean;
|
||||
highlightActiveLineGutter?: boolean;
|
||||
highlightSelectionMatches?: boolean;
|
||||
highlightActiveLine?: boolean;
|
||||
indentOnInput?: boolean;
|
||||
rectangularSelection?: boolean;
|
||||
keymap?: boolean;
|
||||
lineWrapping?: boolean;
|
||||
}
|
||||
|
||||
const extensionMap = {
|
||||
lineNumbers,
|
||||
tooltips: () => tooltips({ parent: document.body }),
|
||||
highlightSpecialChars,
|
||||
syntaxHighlighting: () => syntaxHighlighting(classHighlighter),
|
||||
bracketMatching,
|
||||
dropCursor,
|
||||
highlightSelectionMatches,
|
||||
crosshairCursor,
|
||||
foldGutter: () =>
|
||||
foldGutter({
|
||||
markerDOM: (open: boolean) => {
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttributeNS(null, 'viewBox', '0 0 15 8');
|
||||
svg.style.maxWidth = '100%';
|
||||
svg.style.maxHeight = '100%';
|
||||
|
||||
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
|
||||
use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', GlobalConstants.absoluteUrl('/icons/icons.svg#angle'));
|
||||
svg.appendChild(use);
|
||||
|
||||
const element = document.createElement('div');
|
||||
element.appendChild(svg);
|
||||
element.className = clsx('cm-gutterElement-icon', open ? 'cm-foldGutter-open' : 'cm-foldGutter-folded');
|
||||
|
||||
return element;
|
||||
},
|
||||
}),
|
||||
highlightActiveLineGutter,
|
||||
highlightActiveLine,
|
||||
indentOnInput,
|
||||
rectangularSelection,
|
||||
keymap: () => keymap.of(DEFAULT_KEY_MAP),
|
||||
lineWrapping: () => EditorView.lineWrapping,
|
||||
};
|
||||
|
||||
const DEFAULT_EXTENSIONS_COMPARTMENT = new Compartment();
|
||||
|
||||
/** Provides the necessary extensions to establish a basic editor */
|
||||
export function useEditorDefaultExtensions(options?: IDefaultExtensions): [Compartment, Extension] {
|
||||
return useMemo(() => {
|
||||
const extensions = [];
|
||||
if (options?.lineNumbers) {
|
||||
extensions.push(lineNumbers());
|
||||
}
|
||||
const previousOptions = useRef(options);
|
||||
const isOptionsChanged = !isObjectsEqual(options, previousOptions.current);
|
||||
const extensions = useRef<[Compartment, Extension] | null>(null);
|
||||
|
||||
extensions.push(
|
||||
tooltips({
|
||||
parent: document.body,
|
||||
}),
|
||||
highlightSpecialChars(),
|
||||
highlightSelectionMatches(),
|
||||
syntaxHighlighting(classHighlighter),
|
||||
bracketMatching(),
|
||||
dropCursor(),
|
||||
crosshairCursor(),
|
||||
foldGutter({
|
||||
markerDOM: (open: boolean) => {
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttributeNS(null, 'viewBox', '0 0 15 8');
|
||||
svg.style.maxWidth = '100%';
|
||||
svg.style.maxHeight = '100%';
|
||||
if (isOptionsChanged || extensions.current === null) {
|
||||
previousOptions.current = options;
|
||||
extensions.current = createExtensions(options);
|
||||
}
|
||||
|
||||
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
|
||||
use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', GlobalConstants.absoluteUrl('/icons/icons.svg#angle'));
|
||||
svg.appendChild(use);
|
||||
return extensions.current;
|
||||
}
|
||||
|
||||
const element = document.createElement('div');
|
||||
element.appendChild(svg);
|
||||
element.className = clsx('cm-gutterElement-icon', open ? 'cm-foldGutter-open' : 'cm-foldGutter-folded');
|
||||
|
||||
return element;
|
||||
},
|
||||
}),
|
||||
highlightActiveLineGutter(),
|
||||
highlightActiveLine(),
|
||||
indentOnInput(),
|
||||
rectangularSelection(),
|
||||
keymap.of(DEFAULT_KEY_MAP),
|
||||
);
|
||||
|
||||
return [DEFAULT_EXTENSIONS_COMPARTMENT, extensions];
|
||||
}, [options?.lineNumbers]);
|
||||
function createExtensions(options?: IDefaultExtensions): [Compartment, Extension] {
|
||||
const extensions = Object.entries(defaultExtensionsFlags)
|
||||
.filter(([key, isEnabled]) => options?.[key as keyof typeof options] ?? isEnabled)
|
||||
.map(([key]) => {
|
||||
const extensionFunction = extensionMap[key as keyof typeof extensionMap];
|
||||
return extensionFunction?.();
|
||||
})
|
||||
.filter(Boolean);
|
||||
return [DEFAULT_EXTENSIONS_COMPARTMENT, extensions];
|
||||
}
|
||||
|
||||
@@ -14,4 +14,5 @@ export interface IDatabaseDataOptions {
|
||||
catalog?: string;
|
||||
whereFilter: string;
|
||||
constraints: SqlDataFilterConstraint[];
|
||||
readLogs?: boolean;
|
||||
}
|
||||
|
||||
@@ -309,6 +309,7 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
tab.handlerState.resultTabs = observable([]);
|
||||
tab.handlerState.executionPlanTabs = observable([]);
|
||||
tab.handlerState.statisticsTabs = observable([]);
|
||||
tab.handlerState.outputLogsTab = undefined;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><defs><style>.cls-1{fill:#338ecc;}</style></defs><path class="cls-1" d="M20.33,2H6A2,2,0,0,0,4,4V28a2,2,0,0,0,2,2H26a2,2,0,0,0,2-2V9.67ZM20,4.5,25.46,10H20ZM26,28H6V4H18v6a2,2,0,0,0,2,2h6Z"/><polygon class="cls-1" points="17 23 17 15 23 19 17 23"/><rect class="cls-1" x="15" y="14" width="2" height="10" transform="translate(35 3) rotate(90)"/></svg>
|
||||
|
After Width: | Height: | Size: 443 B |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><style>.cls-1{fill:#338ecc;}</style></defs><path class="cls-1" d="M15.25,2H4.5A1.5,1.5,0,0,0,3,3.5v18A1.49,1.49,0,0,0,4.49,23h15A1.49,1.49,0,0,0,21,21.52V7.75ZM15,3.87,19.09,8H15Zm4.5,17.6H4.5V3.5h9V8A1.49,1.49,0,0,0,15,9.5H19.5Z"/><polygon class="cls-1" points="12.75 17.75 12.75 11.75 17.25 14.75 12.75 17.75"/><rect class="cls-1" x="11.25" y="11" width="1.5" height="7.5" transform="translate(26.75 2.75) rotate(90)"/></svg>
|
||||
|
After Width: | Height: | Size: 526 B |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#338ecc;}</style></defs><path class="cls-1" d="M10.16,1H3A1,1,0,0,0,2,2V14a1,1,0,0,0,1,1H13a1,1,0,0,0,1-1V4.84ZM10,2.25,12.73,5H10ZM13,14H3V2H9V5a1,1,0,0,0,1,1h3Z"/><polygon class="cls-1" points="8.5 11.5 8.5 7.5 11.5 9.5 8.5 11.5"/><rect class="cls-1" x="7.5" y="7" width="1" height="5" transform="translate(17.5 1.5) rotate(90)"/></svg>
|
||||
|
After Width: | Height: | Size: 456 B |
@@ -5,6 +5,7 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import type { IOutputLogType } from './SqlResultTabs/OutputLogs/IOutputLogTypes';
|
||||
|
||||
export interface IResultTab {
|
||||
tabId: string;
|
||||
@@ -41,6 +42,10 @@ export interface IExecutionPlanTab {
|
||||
options?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface IOutputLogsTab extends ISqlEditorResultTab {
|
||||
selectedLogTypes: IOutputLogType[];
|
||||
}
|
||||
|
||||
export interface ISqlEditorTabState {
|
||||
editorId: string;
|
||||
datasourceKey: string;
|
||||
@@ -54,6 +59,7 @@ export interface ISqlEditorTabState {
|
||||
resultTabs: IResultTab[];
|
||||
statisticsTabs: IStatisticsTab[];
|
||||
executionPlanTabs: IExecutionPlanTab[];
|
||||
outputLogsTab?: IOutputLogsTab;
|
||||
|
||||
// mode
|
||||
currentModeId?: string;
|
||||
|
||||
@@ -22,11 +22,13 @@ import { ACTION_SQL_EDITOR_EXECUTE_NEW } from './actions/ACTION_SQL_EDITOR_EXECU
|
||||
import { ACTION_SQL_EDITOR_EXECUTE_SCRIPT } from './actions/ACTION_SQL_EDITOR_EXECUTE_SCRIPT';
|
||||
import { ACTION_SQL_EDITOR_FORMAT } from './actions/ACTION_SQL_EDITOR_FORMAT';
|
||||
import { ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN } from './actions/ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN';
|
||||
import { ACTION_SQL_EDITOR_SHOW_OUTPUT } from './actions/ACTION_SQL_EDITOR_SHOW_OUTPUT';
|
||||
import { KEY_BINDING_SQL_EDITOR_EXECUTE } from './actions/bindings/KEY_BINDING_SQL_EDITOR_EXECUTE';
|
||||
import { KEY_BINDING_SQL_EDITOR_EXECUTE_NEW } from './actions/bindings/KEY_BINDING_SQL_EDITOR_EXECUTE_NEW';
|
||||
import { KEY_BINDING_SQL_EDITOR_EXECUTE_SCRIPT } from './actions/bindings/KEY_BINDING_SQL_EDITOR_EXECUTE_SCRIPT';
|
||||
import { KEY_BINDING_SQL_EDITOR_FORMAT } from './actions/bindings/KEY_BINDING_SQL_EDITOR_FORMAT';
|
||||
import { KEY_BINDING_SQL_EDITOR_SHOW_EXECUTION_PLAN } from './actions/bindings/KEY_BINDING_SQL_EDITOR_SHOW_EXECUTION_PLAN';
|
||||
import { KEY_BINDING_SQL_EDITOR_SHOW_OUTPUT } from './actions/bindings/KEY_BINDING_SQL_EDITOR_SHOW_OUTPUT';
|
||||
import { ESqlDataSourceFeatures } from './SqlDataSource/ESqlDataSourceFeatures';
|
||||
import { DATA_CONTEXT_SQL_EDITOR_DATA } from './SqlEditor/DATA_CONTEXT_SQL_EDITOR_DATA';
|
||||
|
||||
@@ -57,11 +59,13 @@ export class MenuBootstrap extends Bootstrap {
|
||||
ACTION_SQL_EDITOR_EXECUTE_NEW,
|
||||
ACTION_SQL_EDITOR_EXECUTE_SCRIPT,
|
||||
ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN,
|
||||
ACTION_SQL_EDITOR_SHOW_OUTPUT,
|
||||
].includes(action)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO we have to add check for output action ?
|
||||
if (
|
||||
!sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.query) &&
|
||||
[ACTION_SQL_EDITOR_EXECUTE, ACTION_SQL_EDITOR_EXECUTE_NEW, ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN].includes(action)
|
||||
@@ -77,6 +81,7 @@ export class MenuBootstrap extends Bootstrap {
|
||||
ACTION_REDO,
|
||||
ACTION_UNDO,
|
||||
ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN,
|
||||
ACTION_SQL_EDITOR_SHOW_OUTPUT,
|
||||
].includes(action);
|
||||
},
|
||||
isDisabled: (context, action) => !context.has(DATA_CONTEXT_SQL_EDITOR_DATA),
|
||||
|
||||
@@ -199,6 +199,7 @@ export class QueryDataSource<TOptions extends IDataQueryOptions = IDataQueryOpti
|
||||
where: options.whereFilter || undefined,
|
||||
},
|
||||
dataFormat: this.dataFormat,
|
||||
readLogs: options.readLogs,
|
||||
});
|
||||
|
||||
return taskInfo;
|
||||
|
||||
@@ -11,7 +11,6 @@ import { s, SContext, StyleRegistry, useS } from '@cloudbeaver/core-blocks';
|
||||
import { MenuBar, MenuBarItemStyles, MenuBarStyles } from '@cloudbeaver/core-ui';
|
||||
import { IDataContext, useMenu } from '@cloudbeaver/core-view';
|
||||
|
||||
import { DATA_CONTEXT_SQL_EDITOR_STATE } from '../DATA_CONTEXT_SQL_EDITOR_STATE';
|
||||
import type { ISqlEditorTabState } from '../ISqlEditorTabState';
|
||||
import { SQL_EDITOR_ACTIONS_MENU } from './SQL_EDITOR_ACTIONS_MENU';
|
||||
import SqlEditorActionsMenuBarStyles from './SqlEditorActionsMenuBar.m.css';
|
||||
@@ -43,7 +42,6 @@ const registry: StyleRegistry = [
|
||||
export const SqlEditorActionsMenu = observer<Props>(function SqlEditorActionsMenu({ state, context, className }) {
|
||||
const styles = useS(SqlEditorActionsMenuBarStyles, SqlEditorActionsMenuBarItemStyles);
|
||||
const menu = useMenu({ menu: SQL_EDITOR_ACTIONS_MENU, context });
|
||||
menu.context.set(DATA_CONTEXT_SQL_EDITOR_STATE, state);
|
||||
|
||||
return (
|
||||
<SContext registry={registry}>
|
||||
|
||||
@@ -25,6 +25,8 @@ import { SqlDialectInfoService } from '../SqlDialectInfoService';
|
||||
import { SqlEditorService } from '../SqlEditorService';
|
||||
import { ISQLScriptSegment, SQLParser } from '../SQLParser';
|
||||
import { SqlExecutionPlanService } from '../SqlResultTabs/ExecutionPlan/SqlExecutionPlanService';
|
||||
import { OUTPUT_LOGS_TAB_ID } from '../SqlResultTabs/OutputLogs/OUTPUT_LOGS_TAB_ID';
|
||||
import { OutputLogsService } from '../SqlResultTabs/OutputLogs/OutputLogsService';
|
||||
import { SqlQueryService } from '../SqlResultTabs/SqlQueryService';
|
||||
import { SqlResultTabsService } from '../SqlResultTabs/SqlResultTabsService';
|
||||
import type { ICursor, ISQLEditorData } from './ISQLEditorData';
|
||||
@@ -68,6 +70,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
const sqlResultTabsService = useService(SqlResultTabsService);
|
||||
const commonDialogService = useService(CommonDialogService);
|
||||
const sqlDataSourceService = useService(SqlDataSourceService);
|
||||
const sqlOutputLogsService = useService(OutputLogsService);
|
||||
|
||||
const data = useObservableRef<ISQLEditorDataPrivate>(
|
||||
() => ({
|
||||
@@ -310,9 +313,11 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
}
|
||||
|
||||
if (this.state.tabs.length) {
|
||||
const processableTabs = this.state.tabs.filter(tab => tab.id !== OUTPUT_LOGS_TAB_ID);
|
||||
|
||||
const result = await this.commonDialogService.open(ConfirmationDialog, {
|
||||
title: 'sql_editor_close_result_tabs_dialog_title',
|
||||
message: `Do you want to close ${this.state.tabs.length} tabs before executing script?`,
|
||||
message: `Do you want to close ${processableTabs.length} tabs before executing script?`,
|
||||
confirmActionText: 'ui_yes',
|
||||
extraStatus: 'no',
|
||||
});
|
||||
@@ -324,7 +329,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sqlResultTabsService.removeResultTabs(this.state);
|
||||
this.sqlResultTabsService.removeResultTabs(this.state, [OUTPUT_LOGS_TAB_ID]);
|
||||
} else if (result === DialogueStateResult.Rejected) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ export class SqlEditorService {
|
||||
resultTabs: observable([]),
|
||||
executionPlanTabs: observable([]),
|
||||
statisticsTabs: observable([]),
|
||||
outputLogsTab: undefined,
|
||||
currentModeId: undefined,
|
||||
modeState: observable([]),
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ACTION_SQL_EDITOR_EXECUTE_NEW } from './actions/ACTION_SQL_EDITOR_EXECU
|
||||
import { ACTION_SQL_EDITOR_EXECUTE_SCRIPT } from './actions/ACTION_SQL_EDITOR_EXECUTE_SCRIPT';
|
||||
import { ACTION_SQL_EDITOR_FORMAT } from './actions/ACTION_SQL_EDITOR_FORMAT';
|
||||
import { ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN } from './actions/ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN';
|
||||
import { ACTION_SQL_EDITOR_SHOW_OUTPUT } from './actions/ACTION_SQL_EDITOR_SHOW_OUTPUT';
|
||||
|
||||
@injectable()
|
||||
export class SqlEditorView extends View<ITab> {
|
||||
@@ -27,6 +28,7 @@ export class SqlEditorView extends View<ITab> {
|
||||
ACTION_UNDO,
|
||||
ACTION_REDO,
|
||||
ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN,
|
||||
ACTION_SQL_EDITOR_SHOW_OUTPUT,
|
||||
ACTION_SAVE,
|
||||
);
|
||||
}
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { createAction } from '@cloudbeaver/core-view';
|
||||
|
||||
export const ACTION_SHOW_OUTPUT_LOGS = createAction('action-show_output_logs', {
|
||||
label: 'sql_editor_output_logs_button_tooltip',
|
||||
icon: '/icons/sql_output_logs.svg',
|
||||
tooltip: 'sql_editor_output_logs_button_tooltip',
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
export const OUTPUT_LOG_TYPES = ['Debug', 'Log', 'Info', 'Notice', 'Warning', 'Error'] as const;
|
||||
export type IOutputLogType = (typeof OUTPUT_LOG_TYPES)[number];
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { createMenu } from '@cloudbeaver/core-view';
|
||||
|
||||
export const OUTPUT_LOGS_FILTER_MENU = createMenu('output_logs_filter_menu', 'Output Logs');
|
||||
@@ -0,0 +1 @@
|
||||
export const OUTPUT_LOGS_TAB_ID = 'output_logs';
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
.contextMenu {
|
||||
padding: 0;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
& .icon {
|
||||
width: 16px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 React, { useEffect } from 'react';
|
||||
|
||||
import { Icon, s, useS } from '@cloudbeaver/core-blocks';
|
||||
import { ContextMenu } from '@cloudbeaver/core-ui';
|
||||
import { useMenu } from '@cloudbeaver/core-view';
|
||||
|
||||
import { DATA_CONTEXT_SQL_EDITOR_STATE } from '../../DATA_CONTEXT_SQL_EDITOR_STATE';
|
||||
import type { ISqlEditorTabState } from '../../ISqlEditorTabState';
|
||||
import { OUTPUT_LOGS_FILTER_MENU } from './OUTPUT_LOGS_FILTER_MENU';
|
||||
import style from './OutputLogTypesFilterMenu.m.css';
|
||||
|
||||
interface Props {
|
||||
sqlEditorTabState: ISqlEditorTabState;
|
||||
}
|
||||
|
||||
export const OutputLogsFilterMenu = observer<Props>(function OutputLogTypesFilterMenu({ sqlEditorTabState }) {
|
||||
const styles = useS(style);
|
||||
const menu = useMenu({
|
||||
menu: OUTPUT_LOGS_FILTER_MENU,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
menu.context.set(DATA_CONTEXT_SQL_EDITOR_STATE, sqlEditorTabState);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ContextMenu className={s(styles, { contextMenu: true })} menu={menu} placement="bottom-end" modal>
|
||||
<Icon className={s(styles, { icon: true })} name="filter" viewBox="0 0 16 16" />
|
||||
</ContextMenu>
|
||||
);
|
||||
});
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { ISessionEvent, SessionEventSource, SessionEventTopic, TopicEventHandler } from '@cloudbeaver/core-root';
|
||||
import type { CbDatabaseOutputLogEvent, CbSessionLogEvent as ISessionLogEvent } from '@cloudbeaver/core-sdk';
|
||||
|
||||
export { type ISessionLogEvent };
|
||||
|
||||
@injectable()
|
||||
export class OutputLogsEventHandler extends TopicEventHandler<ISessionLogEvent, ISessionEvent> {
|
||||
constructor(sessionEventSource: SessionEventSource) {
|
||||
super(SessionEventTopic.CbDatabaseOutputLog, sessionEventSource);
|
||||
}
|
||||
|
||||
map(event: CbDatabaseOutputLogEvent) {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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, Group, s, useResource, useS } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { EditorLoader } from '@cloudbeaver/plugin-codemirror6';
|
||||
|
||||
import type { ISqlEditorTabState } from '../../ISqlEditorTabState';
|
||||
import { OutputLogsResource } from './OutputLogsResource';
|
||||
import { OutputLogsService } from './OutputLogsService';
|
||||
import { OutputLogsToolbar } from './OutputLogsToolbar';
|
||||
import { useOutputLogsPanelState } from './useOutputLogsPanelState';
|
||||
|
||||
interface Props {
|
||||
sqlEditorTabState: ISqlEditorTabState;
|
||||
}
|
||||
|
||||
export const OutputLogsPanel = observer<Props>(function SqlOutputLogsPanel({ sqlEditorTabState }) {
|
||||
const outputLogsService = useService(OutputLogsService);
|
||||
const { data } = useResource(SqlOutputLogsPanel, OutputLogsResource, undefined);
|
||||
const outputLogs = outputLogsService.getOutputLogs(data, sqlEditorTabState);
|
||||
|
||||
const state = useOutputLogsPanelState(outputLogs, sqlEditorTabState);
|
||||
|
||||
return (
|
||||
<Container className="theme-background-secondary" parent vertical gap dense>
|
||||
<OutputLogsToolbar state={state} sqlEditorTabState={sqlEditorTabState}/>
|
||||
<Group overflow box>
|
||||
{data && <EditorLoader value={state.resultValue} foldGutter={false} highlightActiveLine={false} lineWrapping readonly />}
|
||||
</Group>
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { ConnectionExecutionContextResource } from '@cloudbeaver/core-connections';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { ServerEventId } from '@cloudbeaver/core-root';
|
||||
import { CachedDataResource, CbDatabaseOutputLogEvent } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { IOutputLogType } from './IOutputLogTypes';
|
||||
import { OutputLogsEventHandler } from './OutputLogsEventHandler';
|
||||
|
||||
export interface IOutputLog {
|
||||
message: string;
|
||||
severity: IOutputLogType;
|
||||
contextId: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class OutputLogsResource extends CachedDataResource<IOutputLog[]> {
|
||||
constructor(
|
||||
sqlOutputLogsEventHandler: OutputLogsEventHandler,
|
||||
private readonly connectionExecutionContextResource: ConnectionExecutionContextResource,
|
||||
) {
|
||||
super(() => []);
|
||||
|
||||
sqlOutputLogsEventHandler.onEvent(
|
||||
ServerEventId.CbDatabaseOutputLogUpdated,
|
||||
(event: CbDatabaseOutputLogEvent) => {
|
||||
this.collectMessagesFromEvent(event);
|
||||
},
|
||||
undefined,
|
||||
this,
|
||||
);
|
||||
|
||||
// hack, we need to call this.use() to initialize resource at startup
|
||||
this.use(undefined);
|
||||
|
||||
this.connectionExecutionContextResource.onItemDelete.addHandler(key => {
|
||||
this.setData(this.data.filter(log => log.contextId !== key));
|
||||
});
|
||||
}
|
||||
|
||||
private collectMessagesFromEvent(event: CbDatabaseOutputLogEvent) {
|
||||
const newLogs = event.messages.map(message => ({
|
||||
message: message.message,
|
||||
severity: message.severity,
|
||||
contextId: event.contextId,
|
||||
timestamp: event.eventTimestamp,
|
||||
})) as IOutputLog[];
|
||||
|
||||
const updatedData: IOutputLog[] = (this.data || []).concat(newLogs);
|
||||
|
||||
this.setData(updatedData);
|
||||
}
|
||||
|
||||
protected async loader(): Promise<IOutputLog[]> {
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { ISqlEditorTabState } from '../../ISqlEditorTabState';
|
||||
import { SqlDataSourceService } from '../../SqlDataSource/SqlDataSourceService';
|
||||
import { OUTPUT_LOG_TYPES } from './IOutputLogTypes';
|
||||
import { OUTPUT_LOGS_TAB_ID } from './OUTPUT_LOGS_TAB_ID';
|
||||
import type { IOutputLog } from './OutputLogsResource';
|
||||
|
||||
@injectable()
|
||||
export class OutputLogsService {
|
||||
constructor(private readonly sqlDataSourceService: SqlDataSourceService) {}
|
||||
|
||||
async showOutputLogs(editorState: ISqlEditorTabState): Promise<void> {
|
||||
this.createOutputLogsTab(editorState);
|
||||
editorState.currentTabId = OUTPUT_LOGS_TAB_ID;
|
||||
}
|
||||
|
||||
removeOutputLogsTab(state: ISqlEditorTabState, tabId: string): void {
|
||||
if (tabId === OUTPUT_LOGS_TAB_ID) {
|
||||
state.outputLogsTab = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private createOutputLogsTab(state: ISqlEditorTabState) {
|
||||
const order = Math.max(0, ...state.tabs.map(tab => tab.order + 1));
|
||||
|
||||
if (state.tabs.find(tab => tab.id === OUTPUT_LOGS_TAB_ID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tab = {
|
||||
id: OUTPUT_LOGS_TAB_ID,
|
||||
name: 'Output',
|
||||
icon: '/icons/sql_output_logs.svg',
|
||||
order,
|
||||
};
|
||||
|
||||
state.outputLogsTab = { ...tab, selectedLogTypes: [...OUTPUT_LOG_TYPES] };
|
||||
state.tabs.push({ ...tab });
|
||||
}
|
||||
|
||||
getOutputLogs(events: IOutputLog[], editorState: ISqlEditorTabState) {
|
||||
const dataSource = this.sqlDataSourceService.get(editorState.editorId);
|
||||
|
||||
return events.filter(event => event.contextId === dataSource?.executionContext?.id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
.searchIcon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
border-radius: var(--theme-menu-bar-small-action-radius);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 React from 'react';
|
||||
|
||||
import { Container, Icon, InputField, s, useS, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import type { ISqlEditorTabState } from '../../ISqlEditorTabState';
|
||||
import style from './OutputLogsToolbar.m.css';
|
||||
import { OutputLogsFilterMenu } from './OutputLogTypesFilterMenu';
|
||||
import type { SqlOutputLogsPanelState } from './useOutputLogsPanelState';
|
||||
|
||||
interface Props {
|
||||
state: SqlOutputLogsPanelState;
|
||||
sqlEditorTabState: ISqlEditorTabState;
|
||||
}
|
||||
|
||||
export const OutputLogsToolbar = observer<Props>(function SqlOutputLogsToolbar({ state, sqlEditorTabState }) {
|
||||
const styles = useS(style);
|
||||
const translate = useTranslate();
|
||||
|
||||
return (
|
||||
<Container noWrap center gap dense keepSize>
|
||||
<InputField
|
||||
value={state.searchValue}
|
||||
placeholder={translate('sql_editor_output_logs_input_placeholder')}
|
||||
icon={
|
||||
<div className={s(styles, { searchIcon: true })}>
|
||||
<Icon name="search" viewBox="0 0 24 24" />
|
||||
</div>
|
||||
}
|
||||
fill
|
||||
onChange={value => state.setSearchValue(value.toString())}
|
||||
/>
|
||||
<Container keepSize>
|
||||
<OutputLogsFilterMenu sqlEditorTabState={sqlEditorTabState} />
|
||||
</Container>
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { ActionService, KeyBindingService, MenuCheckboxItem, MenuService } from '@cloudbeaver/core-view';
|
||||
|
||||
import { ACTION_SQL_EDITOR_SHOW_OUTPUT } from '../../actions/ACTION_SQL_EDITOR_SHOW_OUTPUT';
|
||||
import { KEY_BINDING_SQL_EDITOR_SHOW_OUTPUT } from '../../actions/bindings/KEY_BINDING_SQL_EDITOR_SHOW_OUTPUT';
|
||||
import { DATA_CONTEXT_SQL_EDITOR_STATE } from '../../DATA_CONTEXT_SQL_EDITOR_STATE';
|
||||
import { ESqlDataSourceFeatures } from '../../SqlDataSource/ESqlDataSourceFeatures';
|
||||
import { SqlDataSourceService } from '../../SqlDataSource/SqlDataSourceService';
|
||||
import { SQL_EDITOR_ACTIONS_MENU } from '../../SqlEditor/SQL_EDITOR_ACTIONS_MENU';
|
||||
import { ACTION_SHOW_OUTPUT_LOGS } from './ACTION_SHOW_OUTPUT_LOGS';
|
||||
import { OUTPUT_LOG_TYPES } from './IOutputLogTypes';
|
||||
import { OUTPUT_LOGS_FILTER_MENU } from './OUTPUT_LOGS_FILTER_MENU';
|
||||
import { OutputLogsService } from './OutputLogsService';
|
||||
|
||||
@injectable()
|
||||
export class OutputMenuBootstrap extends Bootstrap {
|
||||
constructor(
|
||||
private readonly actionService: ActionService,
|
||||
private readonly menuService: MenuService,
|
||||
private readonly outputLogsService: OutputLogsService,
|
||||
private readonly sqlDataSourceService: SqlDataSourceService,
|
||||
private readonly keyBindingService: KeyBindingService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
register(): void | Promise<void> {
|
||||
this.menuService.addCreator({
|
||||
menus: [OUTPUT_LOGS_FILTER_MENU],
|
||||
getItems: context => {
|
||||
const state = context.tryGet(DATA_CONTEXT_SQL_EDITOR_STATE);
|
||||
|
||||
if (!state) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const outputLogsTabState = state?.outputLogsTab;
|
||||
const items = [];
|
||||
|
||||
for (const logType of OUTPUT_LOG_TYPES) {
|
||||
items.push(
|
||||
new MenuCheckboxItem(
|
||||
{
|
||||
id: logType,
|
||||
label: logType,
|
||||
tooltip: logType,
|
||||
},
|
||||
{
|
||||
onSelect: () => {
|
||||
if (outputLogsTabState?.selectedLogTypes) {
|
||||
if (outputLogsTabState.selectedLogTypes.includes(logType)) {
|
||||
outputLogsTabState.selectedLogTypes = outputLogsTabState.selectedLogTypes.filter(type => type !== logType);
|
||||
return;
|
||||
}
|
||||
outputLogsTabState.selectedLogTypes = [...outputLogsTabState.selectedLogTypes, logType];
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
isChecked: () => !!outputLogsTabState?.selectedLogTypes.includes(logType),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
});
|
||||
|
||||
this.registerOutputLogsAction();
|
||||
}
|
||||
|
||||
private registerOutputLogsAction() {
|
||||
this.actionService.addHandler({
|
||||
id: 'output-logs-handler',
|
||||
isActionApplicable: (context, action): boolean => {
|
||||
const state = context.tryGet(DATA_CONTEXT_SQL_EDITOR_STATE);
|
||||
|
||||
if (state && action === ACTION_SHOW_OUTPUT_LOGS) {
|
||||
const sqlDataSource = this.sqlDataSourceService.get(state.editorId);
|
||||
const isQuery = sqlDataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
const isExecutable = sqlDataSource?.hasFeature(ESqlDataSourceFeatures.executable);
|
||||
|
||||
if (isQuery && isExecutable) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
handler: async (context, action) => {
|
||||
const state = context.get(DATA_CONTEXT_SQL_EDITOR_STATE);
|
||||
|
||||
if (action === ACTION_SHOW_OUTPUT_LOGS) {
|
||||
this.outputLogsService.showOutputLogs(state);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.menuService.addCreator({
|
||||
menus: [SQL_EDITOR_ACTIONS_MENU],
|
||||
getItems: (context, items) => [...items, ACTION_SHOW_OUTPUT_LOGS],
|
||||
});
|
||||
|
||||
this.keyBindingService.addKeyBindingHandler({
|
||||
id: 'sql-editor-show-output',
|
||||
binding: KEY_BINDING_SQL_EDITOR_SHOW_OUTPUT,
|
||||
isBindingApplicable: (contexts, action) => action === ACTION_SQL_EDITOR_SHOW_OUTPUT,
|
||||
handler: (context, action) => {
|
||||
const state = context.get(DATA_CONTEXT_SQL_EDITOR_STATE);
|
||||
|
||||
if (action === ACTION_SQL_EDITOR_SHOW_OUTPUT) {
|
||||
this.outputLogsService.showOutputLogs(state);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async load(): Promise<void> {}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { action, computed, observable } from 'mobx';
|
||||
|
||||
import { useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
import type { WsOutputLogInfo } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { ISqlEditorTabState } from '../../ISqlEditorTabState';
|
||||
import type { IOutputLog } from './OutputLogsResource';
|
||||
|
||||
export interface SqlOutputLogsPanelState {
|
||||
searchValue: string;
|
||||
setSearchValue: (value: string) => void;
|
||||
logMessages: WsOutputLogInfo['message'][];
|
||||
readonly resultValue: string;
|
||||
readonly filteredLogs: WsOutputLogInfo[];
|
||||
}
|
||||
export const useOutputLogsPanelState = (outputLogs: IOutputLog[], sqlEditorTabState: ISqlEditorTabState) =>
|
||||
useObservableRef<SqlOutputLogsPanelState>(
|
||||
() => ({
|
||||
searchValue: '',
|
||||
setSearchValue(value: string) {
|
||||
this.searchValue = value;
|
||||
},
|
||||
get filteredLogs() {
|
||||
const selectedLogTypes = sqlEditorTabState.outputLogsTab?.selectedLogTypes;
|
||||
|
||||
if (!selectedLogTypes?.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return outputLogs.filter(log => {
|
||||
if (!selectedLogTypes.includes(log.severity)) {
|
||||
return false;
|
||||
}
|
||||
if (this.searchValue.length > 0 && !log.message?.includes(this.searchValue)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
get resultValue() {
|
||||
return this.filteredLogs.map(log => `[${log.severity}] ${log.message}`).join('\n');
|
||||
},
|
||||
}),
|
||||
{
|
||||
searchValue: observable.ref,
|
||||
setSearchValue: action.bound,
|
||||
filteredLogs: computed,
|
||||
resultValue: computed,
|
||||
},
|
||||
false,
|
||||
);
|
||||
@@ -144,6 +144,8 @@ export class SqlQueryService {
|
||||
}
|
||||
|
||||
const editable = this.dataViewerService.isDataEditable(connectionInfo);
|
||||
const isOutputLogsTabOpened = !!editorState.outputLogsTab;
|
||||
|
||||
model
|
||||
.setAccess(editable ? DatabaseDataAccessMode.Default : DatabaseDataAccessMode.Readonly)
|
||||
.setOptions({
|
||||
@@ -151,6 +153,7 @@ export class SqlQueryService {
|
||||
connectionKey,
|
||||
constraints: [],
|
||||
whereFilter: '',
|
||||
readLogs: isOutputLogsTabOpened,
|
||||
})
|
||||
.source.setExecutionContext(executionContext)
|
||||
.setSupportedDataFormats(connectionInfo.supportedDataFormats);
|
||||
@@ -218,6 +221,8 @@ export class SqlQueryService {
|
||||
statistics.modelId = model.id;
|
||||
|
||||
const editable = this.dataViewerService.isDataEditable(connectionInfo);
|
||||
const isOutputLogsTabOpened = !!editorState.outputLogsTab;
|
||||
|
||||
model
|
||||
.setAccess(editable ? DatabaseDataAccessMode.Default : DatabaseDataAccessMode.Readonly)
|
||||
.setOptions({
|
||||
@@ -225,6 +230,7 @@ export class SqlQueryService {
|
||||
connectionKey,
|
||||
constraints: [],
|
||||
whereFilter: '',
|
||||
readLogs: isOutputLogsTabOpened,
|
||||
})
|
||||
.source.setExecutionContext(executionContext)
|
||||
.setSupportedDataFormats(connectionInfo.supportedDataFormats);
|
||||
|
||||
@@ -10,6 +10,7 @@ import styled, { css } from 'reshadow';
|
||||
|
||||
import type { ISqlEditorTabState } from '../ISqlEditorTabState';
|
||||
import { SqlExecutionPlanPanel } from './ExecutionPlan/SqlExecutionPlanPanel';
|
||||
import { OutputLogsPanel } from './OutputLogs/OutputLogsPanel';
|
||||
import { SqlResultSetPanel } from './SqlResultSetPanel';
|
||||
import { SqlScriptStatisticsPanel } from './SqlScriptStatisticsPanel';
|
||||
|
||||
@@ -59,5 +60,13 @@ export const SqlResultPanel = observer<Props>(function SqlResultPanel({ state, i
|
||||
);
|
||||
}
|
||||
|
||||
if (state.outputLogsTab) {
|
||||
return styled(style)(
|
||||
<result-panel>
|
||||
<OutputLogsPanel sqlEditorTabState={state} />
|
||||
</result-panel>,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import { injectable } from '@cloudbeaver/core-di';
|
||||
|
||||
import type { ISqlEditorResultTab, ISqlEditorTabState } from '../ISqlEditorTabState';
|
||||
import { SqlExecutionPlanService } from './ExecutionPlan/SqlExecutionPlanService';
|
||||
import { OutputLogsService } from './OutputLogs/OutputLogsService';
|
||||
import { SqlQueryResultService } from './SqlQueryResultService';
|
||||
import { SqlQueryService } from './SqlQueryService';
|
||||
|
||||
@@ -20,6 +21,7 @@ export class SqlResultTabsService {
|
||||
private readonly sqlQueryService: SqlQueryService,
|
||||
private readonly sqlQueryResultService: SqlQueryResultService,
|
||||
private readonly sqlExecutionPlanService: SqlExecutionPlanService,
|
||||
private readonly sqlOutputLogsService: OutputLogsService,
|
||||
) {
|
||||
makeObservable(this, {
|
||||
removeResultTabs: action,
|
||||
@@ -62,8 +64,11 @@ export class SqlResultTabsService {
|
||||
return true;
|
||||
}
|
||||
|
||||
removeResultTabs(state: ISqlEditorTabState): void {
|
||||
for (const tab of state.tabs.slice()) {
|
||||
removeResultTabs(state: ISqlEditorTabState, excludedTabIds?: string[]): void {
|
||||
for (const tab of state.tabs) {
|
||||
if (excludedTabIds?.includes(tab.id)) {
|
||||
continue;
|
||||
}
|
||||
this.removeTab(state, tab);
|
||||
}
|
||||
}
|
||||
@@ -74,6 +79,7 @@ export class SqlResultTabsService {
|
||||
this.sqlQueryService.removeStatisticsTab(state, tab.id);
|
||||
this.sqlQueryResultService.removeResultTab(state, tab.id);
|
||||
this.sqlExecutionPlanService.removeExecutionPlanTab(state, tab.id);
|
||||
this.sqlOutputLogsService.removeOutputLogsTab(state, tab.id);
|
||||
|
||||
if (state.currentTabId === tab.id) {
|
||||
if (state.tabs.length > 0) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { createAction } from '@cloudbeaver/core-view';
|
||||
|
||||
export const ACTION_SQL_EDITOR_SHOW_OUTPUT = createAction('sql-editor-show-output', {
|
||||
icon: '/icons/sql_output.svg', // todo change icon
|
||||
label: 'sql_editor_output_logs_button_tooltip',
|
||||
});
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 '@cloudbeaver/core-view';
|
||||
|
||||
export const KEY_BINDING_SQL_EDITOR_SHOW_OUTPUT = createKeyBinding({
|
||||
id: 'sql-editor-show-output',
|
||||
keys: 'shift+ctrl+o',
|
||||
preventDefault: true,
|
||||
});
|
||||
@@ -10,6 +10,7 @@ export * from './actions/ACTION_SQL_EDITOR_EXECUTE_SCRIPT';
|
||||
export * from './actions/ACTION_SQL_EDITOR_EXECUTE';
|
||||
export * from './actions/ACTION_SQL_EDITOR_FORMAT';
|
||||
export * from './actions/ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN';
|
||||
export * from './actions/ACTION_SQL_EDITOR_SHOW_OUTPUT';
|
||||
export * from './SqlDataSource/LocalStorage/ILocalStorageSqlDataSourceState';
|
||||
export * from './SqlDataSource/LocalStorage/LocalStorageSqlDataSource';
|
||||
export * from './SqlDataSource/LocalStorage/LocalStorageSqlDataSourceBootstrap';
|
||||
@@ -27,6 +28,9 @@ export * from './SqlEditor/SQL_EDITOR_TOOLS_MENU';
|
||||
export * from './SqlEditor/SQLEditorModeContext';
|
||||
export * from './SqlResultTabs/DATA_CONTEXT_SQL_EDITOR_RESULT_ID';
|
||||
export * from './SqlResultTabs/SqlResultTabsService';
|
||||
export * from './SqlResultTabs/OutputLogs/OutputLogsEventHandler';
|
||||
export * from './SqlResultTabs/OutputLogs/OutputLogsResource';
|
||||
export * from './SqlResultTabs/OutputLogs/OutputLogsService';
|
||||
export * from './DATA_CONTEXT_SQL_EDITOR_STATE';
|
||||
export * from './getSqlEditorName';
|
||||
export * from './QueryDataSource';
|
||||
|
||||
@@ -7,6 +7,9 @@ export default [
|
||||
['sql_editor_placeholder', 'Execute query with Ctrl+Enter to see results'],
|
||||
['sql_editor_hint_empty', 'There is no proposals...'],
|
||||
['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_output_logs_tab_title', 'Output'],
|
||||
['sql_editor_output_logs_input_placeholder', 'Enter a part of a message to search for here'],
|
||||
['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)'],
|
||||
|
||||
@@ -7,6 +7,9 @@ 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_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_output_logs_tab_title', 'Output'],
|
||||
['sql_editor_output_logs_input_placeholder', 'Enter a part of a message to search for here'],
|
||||
['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)'],
|
||||
|
||||
@@ -7,6 +7,9 @@ export default [
|
||||
['sql_editor_placeholder', 'Нажмите Ctrl+Enter, чтобы выполнить запрос и увидеть результат'],
|
||||
['sql_editor_hint_empty', 'Нет авто-дополнений...'],
|
||||
['sql_editor_execution_plan_button_tooltip', 'Посмотреть информацию о плане выполнения запроса (Shift + Ctrl + E)'],
|
||||
['sql_editor_output_logs_button_tooltip', 'Показать вывод сервера (Shift + Ctrl + O)'],
|
||||
['sql_editor_output_logs_tab_title', 'Вывод логов'],
|
||||
['sql_editor_output_logs_input_placeholder', 'Введите часть сообщения для поиска'],
|
||||
['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)'],
|
||||
|
||||
@@ -7,6 +7,9 @@ export default [
|
||||
['sql_editor_placeholder', '使用Ctrl + Enter执行查询以查看结果'],
|
||||
['sql_editor_hint_empty', 'There is no proposals...'],
|
||||
['sql_editor_execution_plan_button_tooltip', '解释执行计划(Shift + Ctrl + E)'],
|
||||
['sql_editor_output_logs_button_tooltip', 'Show server output (Shift + Ctrl + O)'],
|
||||
['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_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)'],
|
||||
|
||||
@@ -17,6 +17,10 @@ import { SqlEditorService } from './SqlEditorService';
|
||||
import { SqlEditorSettingsService } from './SqlEditorSettingsService';
|
||||
import { SqlEditorView } from './SqlEditorView';
|
||||
import { SqlExecutionPlanService } from './SqlResultTabs/ExecutionPlan/SqlExecutionPlanService';
|
||||
import { OutputMenuBootstrap } from './SqlResultTabs/OutputLogs/OutputMenuBootstrap';
|
||||
import { OutputLogsEventHandler } from './SqlResultTabs/OutputLogs/OutputLogsEventHandler';
|
||||
import { OutputLogsResource } from './SqlResultTabs/OutputLogs/OutputLogsResource';
|
||||
import { OutputLogsService } from './SqlResultTabs/OutputLogs/OutputLogsService';
|
||||
import { SqlQueryResultService } from './SqlResultTabs/SqlQueryResultService';
|
||||
import { SqlQueryService } from './SqlResultTabs/SqlQueryService';
|
||||
import { SqlResultTabsService } from './SqlResultTabs/SqlResultTabsService';
|
||||
@@ -40,5 +44,9 @@ export const sqlEditorPluginManifest: PluginManifest = {
|
||||
MenuBootstrap,
|
||||
SqlDataSourceService,
|
||||
LocalStorageSqlDataSourceBootstrap,
|
||||
OutputLogsEventHandler,
|
||||
OutputLogsResource,
|
||||
OutputLogsService,
|
||||
OutputMenuBootstrap,
|
||||
],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user