diff --git a/webapp/packages/core-blocks/src/FormControls/InputField.m.css b/webapp/packages/core-blocks/src/FormControls/InputField.m.css index 76d9ae3b4f..772a6ac4d5 100644 --- a/webapp/packages/core-blocks/src/FormControls/InputField.m.css +++ b/webapp/packages/core-blocks/src/FormControls/InputField.m.css @@ -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; diff --git a/webapp/packages/core-blocks/src/FormControls/InputField.tsx b/webapp/packages/core-blocks/src/FormControls/InputField.tsx index 77dfb6ed06..1d6f2ea509 100644 --- a/webapp/packages/core-blocks/src/FormControls/InputField.tsx +++ b/webapp/packages/core-blocks/src/FormControls/InputField.tsx @@ -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, '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, ref, @@ -196,6 +198,7 @@ export const InputField: InputFieldType = observer( )} + {icon &&
{icon}
} {(description || passwordType) && (
diff --git a/webapp/packages/core-sdk/src/queries/grid/asyncSqlExecuteQuery.gql b/webapp/packages/core-sdk/src/queries/grid/asyncSqlExecuteQuery.gql index b2e1d0ec18..7aabbc989e 100644 --- a/webapp/packages/core-sdk/src/queries/grid/asyncSqlExecuteQuery.gql +++ b/webapp/packages/core-sdk/src/queries/grid/asyncSqlExecuteQuery.gql @@ -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 } diff --git a/webapp/packages/plugin-codemirror6/src/Editor.tsx b/webapp/packages/plugin-codemirror6/src/Editor.tsx index 1e6b7526c8..10be8ade09 100644 --- a/webapp/packages/plugin-codemirror6/src/Editor.tsx +++ b/webapp/packages/plugin-codemirror6/src/Editor.tsx @@ -19,9 +19,49 @@ import { useCodemirrorExtensions } from './useCodemirrorExtensions'; import { type IDefaultExtensions, useEditorDefaultExtensions } from './useEditorDefaultExtensions'; export const Editor = observer( - 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)( diff --git a/webapp/packages/plugin-codemirror6/src/useEditorDefaultExtensions.ts b/webapp/packages/plugin-codemirror6/src/useEditorDefaultExtensions.ts index cbfd1ca01b..8830f64967 100644 --- a/webapp/packages/plugin-codemirror6/src/useEditorDefaultExtensions.ts +++ b/webapp/packages/plugin-codemirror6/src/useEditorDefaultExtensions.ts @@ -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); - - 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 extensions.current; +} - 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]; } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataOptions.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataOptions.ts index 2b6c946d3a..50f56e43e0 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataOptions.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataOptions.ts @@ -14,4 +14,5 @@ export interface IDatabaseDataOptions { catalog?: string; whereFilter: string; constraints: SqlDataFilterConstraint[]; + readLogs?: boolean; } diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts index 400126f0e3..f1fd0fe203 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts @@ -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; } diff --git a/webapp/packages/plugin-sql-editor/public/icons/sql_output_logs.svg b/webapp/packages/plugin-sql-editor/public/icons/sql_output_logs.svg new file mode 100644 index 0000000000..bc9e4420c2 --- /dev/null +++ b/webapp/packages/plugin-sql-editor/public/icons/sql_output_logs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webapp/packages/plugin-sql-editor/public/icons/sql_output_logs_m.svg b/webapp/packages/plugin-sql-editor/public/icons/sql_output_logs_m.svg new file mode 100644 index 0000000000..d4d1f74ad4 --- /dev/null +++ b/webapp/packages/plugin-sql-editor/public/icons/sql_output_logs_m.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webapp/packages/plugin-sql-editor/public/icons/sql_output_logs_sm.svg b/webapp/packages/plugin-sql-editor/public/icons/sql_output_logs_sm.svg new file mode 100644 index 0000000000..bda167f39f --- /dev/null +++ b/webapp/packages/plugin-sql-editor/public/icons/sql_output_logs_sm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts b/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts index 7c2c59154c..a117d81396 100644 --- a/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts +++ b/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts @@ -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; } +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; diff --git a/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts b/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts index 2652018a18..a99f4a8511 100644 --- a/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts +++ b/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts @@ -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), diff --git a/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts b/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts index 461544edba..95355bff36 100644 --- a/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts +++ b/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts @@ -199,6 +199,7 @@ export class QueryDataSource( () => ({ @@ -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; } diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts b/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts index 07d65961e8..7d625aaa36 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts @@ -72,6 +72,7 @@ export class SqlEditorService { resultTabs: observable([]), executionPlanTabs: observable([]), statisticsTabs: observable([]), + outputLogsTab: undefined, currentModeId: undefined, modeState: observable([]), }); diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditorView.ts b/webapp/packages/plugin-sql-editor/src/SqlEditorView.ts index 58f8bb152e..0178d42e5c 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditorView.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditorView.ts @@ -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 { @@ -27,6 +28,7 @@ export class SqlEditorView extends View { ACTION_UNDO, ACTION_REDO, ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN, + ACTION_SQL_EDITOR_SHOW_OUTPUT, ACTION_SAVE, ); } diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/ACTION_SHOW_OUTPUT_LOGS.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/ACTION_SHOW_OUTPUT_LOGS.ts new file mode 100644 index 0000000000..ffe85a38f9 --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/ACTION_SHOW_OUTPUT_LOGS.ts @@ -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', +}); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/IOutputLogTypes.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/IOutputLogTypes.ts new file mode 100644 index 0000000000..5078ee9ff3 --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/IOutputLogTypes.ts @@ -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]; diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OUTPUT_LOGS_FILTER_MENU.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OUTPUT_LOGS_FILTER_MENU.ts new file mode 100644 index 0000000000..e1f53931bd --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OUTPUT_LOGS_FILTER_MENU.ts @@ -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'); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OUTPUT_LOGS_TAB_ID.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OUTPUT_LOGS_TAB_ID.ts new file mode 100644 index 0000000000..b88237948f --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OUTPUT_LOGS_TAB_ID.ts @@ -0,0 +1 @@ +export const OUTPUT_LOGS_TAB_ID = 'output_logs'; diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogTypesFilterMenu.m.css b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogTypesFilterMenu.m.css new file mode 100644 index 0000000000..83bd17790b --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogTypesFilterMenu.m.css @@ -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%; + } +} diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogTypesFilterMenu.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogTypesFilterMenu.tsx new file mode 100644 index 0000000000..f36865afee --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogTypesFilterMenu.tsx @@ -0,0 +1,33 @@ +/* + * 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 { Icon, s, useS } from '@cloudbeaver/core-blocks'; +import { ContextMenu } from '@cloudbeaver/core-ui'; +import { useMenu } from '@cloudbeaver/core-view'; + +import { OUTPUT_LOGS_FILTER_MENU } from './OUTPUT_LOGS_FILTER_MENU'; +import style from './OutputLogTypesFilterMenu.m.css'; +import type { SqlOutputLogsPanelState } from './useOutputLogsPanelState'; + +interface Props { + state: SqlOutputLogsPanelState; +} + +export const OutputLogsFilterMenu = observer(function OutputLogTypesFilterMenu({ state }) { + const styles = useS(style); + const menu = useMenu({ + menu: OUTPUT_LOGS_FILTER_MENU, + }); + + return ( + + + + ); +}); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsEventHandler.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsEventHandler.ts new file mode 100644 index 0000000000..e800c4a631 --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsEventHandler.ts @@ -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 { + constructor(sessionEventSource: SessionEventSource) { + super(SessionEventTopic.CbDatabaseOutputLog, sessionEventSource); + } + + map(event: CbDatabaseOutputLogEvent) { + return event; + } +} diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsPanel.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsPanel.tsx new file mode 100644 index 0000000000..61fd377c2e --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsPanel.tsx @@ -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(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 ( + + + + {data && } + + + ); +}); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsResource.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsResource.ts new file mode 100644 index 0000000000..4a03614823 --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsResource.ts @@ -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 { + 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 { + return this.data; + } +} diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsService.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsService.ts new file mode 100644 index 0000000000..f518517bf7 --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsService.ts @@ -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 { + 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); + } +} diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsToolbar.m.css b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsToolbar.m.css new file mode 100644 index 0000000000..e5faba5c7a --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsToolbar.m.css @@ -0,0 +1,6 @@ +.searchIcon { + width: 24px; + height: 24px; + display: flex; + border-radius: var(--theme-menu-bar-small-action-radius); +} diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsToolbar.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsToolbar.tsx new file mode 100644 index 0000000000..c55048b365 --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsToolbar.tsx @@ -0,0 +1,43 @@ +/* + * 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 style from './OutputLogsToolbar.m.css'; +import { OutputLogsFilterMenu } from './OutputLogTypesFilterMenu'; +import type { SqlOutputLogsPanelState } from './useOutputLogsPanelState'; + +interface Props { + state: SqlOutputLogsPanelState; +} + +export const OutputLogsToolbar = observer(function SqlOutputLogsToolbar({ state }) { + const styles = useS(style); + const translate = useTranslate(); + + return ( + + + +
+ } + fill + onChange={value => state.setSearchValue(value.toString())} + /> + + + + + ); +}); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputMenuBootstrap.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputMenuBootstrap.ts new file mode 100644 index 0000000000..0f898e38dd --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputMenuBootstrap.ts @@ -0,0 +1,123 @@ +/* + * 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 { + this.menuService.addCreator({ + menus: [OUTPUT_LOGS_FILTER_MENU], + getItems: context => { + const state = context.get(DATA_CONTEXT_SQL_EDITOR_STATE); + 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 {} +} diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/useOutputLogsPanelState.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/useOutputLogsPanelState.ts new file mode 100644 index 0000000000..f2cfa2e591 --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/useOutputLogsPanelState.ts @@ -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( + () => ({ + 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, + ); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts index 58ca739f4e..5599760742 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts @@ -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); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultPanel.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultPanel.tsx index 523fa6876d..89b6caeb03 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultPanel.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultPanel.tsx @@ -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(function SqlResultPanel({ state, i ); } + if (state.outputLogsTab) { + return styled(style)( + + + , + ); + } + return null; }); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabsService.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabsService.ts index 2c2a214b20..138c7f9806 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabsService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabsService.ts @@ -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) { diff --git a/webapp/packages/plugin-sql-editor/src/actions/ACTION_SQL_EDITOR_SHOW_OUTPUT.ts b/webapp/packages/plugin-sql-editor/src/actions/ACTION_SQL_EDITOR_SHOW_OUTPUT.ts new file mode 100644 index 0000000000..ef3a9deee5 --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/actions/ACTION_SQL_EDITOR_SHOW_OUTPUT.ts @@ -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', +}); diff --git a/webapp/packages/plugin-sql-editor/src/actions/bindings/KEY_BINDING_SQL_EDITOR_SHOW_OUTPUT.ts b/webapp/packages/plugin-sql-editor/src/actions/bindings/KEY_BINDING_SQL_EDITOR_SHOW_OUTPUT.ts new file mode 100644 index 0000000000..2508b02b88 --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/actions/bindings/KEY_BINDING_SQL_EDITOR_SHOW_OUTPUT.ts @@ -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, +}); diff --git a/webapp/packages/plugin-sql-editor/src/index.ts b/webapp/packages/plugin-sql-editor/src/index.ts index 5ee2f5bd1d..755f70e50a 100644 --- a/webapp/packages/plugin-sql-editor/src/index.ts +++ b/webapp/packages/plugin-sql-editor/src/index.ts @@ -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'; diff --git a/webapp/packages/plugin-sql-editor/src/locales/en.ts b/webapp/packages/plugin-sql-editor/src/locales/en.ts index 3c20f7b3bd..54308fd446 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/en.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/en.ts @@ -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)'], diff --git a/webapp/packages/plugin-sql-editor/src/locales/it.ts b/webapp/packages/plugin-sql-editor/src/locales/it.ts index e0649ef0a6..8071d17e7e 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/it.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/it.ts @@ -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)'], diff --git a/webapp/packages/plugin-sql-editor/src/locales/ru.ts b/webapp/packages/plugin-sql-editor/src/locales/ru.ts index f16baf789f..f639337962 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/ru.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/ru.ts @@ -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)'], diff --git a/webapp/packages/plugin-sql-editor/src/locales/zh.ts b/webapp/packages/plugin-sql-editor/src/locales/zh.ts index ca1124c0f8..3c6d9ac298 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/zh.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/zh.ts @@ -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)'], diff --git a/webapp/packages/plugin-sql-editor/src/manifest.ts b/webapp/packages/plugin-sql-editor/src/manifest.ts index 1118d92010..25de7423c0 100644 --- a/webapp/packages/plugin-sql-editor/src/manifest.ts +++ b/webapp/packages/plugin-sql-editor/src/manifest.ts @@ -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, ], };