From fe9d2f8709cff2b934e87391541c05a30e32329a Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Mon, 28 Jun 2021 18:44:54 +0300 Subject: [PATCH] refactor(plugin-sql-editor): CB-1073 remove execution plan tree context --- .../packages/core-app/public/icons/icons.svg | 15 +- .../src/ISqlEditorTabState.ts | 1 - .../src/SqlEditor/SqlEditor.tsx | 33 ++-- .../src/SqlEditor/SqlEditorController.tsx | 2 +- .../ExecutionPlan/ExecutionPlanTree.tsx | 64 -------- .../ExecutionPlan/ExecutionPlanTreeBlock.tsx | 56 ++++++- .../ExecutionPlan/ExecutionPlanTreeContext.ts | 1 - .../ExecutionPlan/NestedNode.tsx | 18 +-- .../ExecutionPlan/PropertiesPanel.tsx | 151 ------------------ .../PropertiesPanel/PropertiesPanel.tsx | 91 +++++++++++ .../PropertiesPanel/PropertiesPanelItem.tsx | 73 +++++++++ .../ExecutionPlan/SqlExecutionPlanPanel.tsx | 67 +++----- .../ExecutionPlan/SqlExecutionPlanService.ts | 35 ++-- .../useExecutionPlanTreeState.ts | 24 +-- .../plugin-sql-editor/src/locales/en.ts | 2 + .../plugin-sql-editor/src/locales/ru.ts | 2 + 16 files changed, 303 insertions(+), 332 deletions(-) delete mode 100644 webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTree.tsx delete mode 100644 webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel.tsx create mode 100644 webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel/PropertiesPanel.tsx create mode 100644 webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel/PropertiesPanelItem.tsx diff --git a/webapp/packages/core-app/public/icons/icons.svg b/webapp/packages/core-app/public/icons/icons.svg index e70ea3855a..725f79c231 100644 --- a/webapp/packages/core-app/public/icons/icons.svg +++ b/webapp/packages/core-app/public/icons/icons.svg @@ -192,15 +192,12 @@ - - - - - - - - - + + + + + + diff --git a/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts b/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts index 467e88fe17..b6ec06aa36 100644 --- a/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts +++ b/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts @@ -39,7 +39,6 @@ export interface ISqlEditorResultTab { export interface IExecutionPlanTab { tabId: string; executionContext: IDatabaseExecutionContext; - query: string; order: number; options?: Record; } diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditor/SqlEditor.tsx b/webapp/packages/plugin-sql-editor/src/SqlEditor/SqlEditor.tsx index c4f8b54a55..e243324c9b 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditor/SqlEditor.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlEditor/SqlEditor.tsx @@ -85,35 +85,30 @@ export const SqlEditor = observer(function SqlEditor({ tabId, className }: SqlEd - + {controller.dialect?.supportsExplainExecutionPlan && ( + + )} => { - if (this.isActionsDisabled) { + if (this.isActionsDisabled || !this.dialect?.supportsExplainExecutionPlan) { return; } this.sqlExecutionPlanService.executeExecutionPlan( diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTree.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTree.tsx deleted file mode 100644 index 0fa206ac4e..0000000000 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTree.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2021 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 { useContext } from 'react'; -import styled, { css } from 'reshadow'; - -import { Table, TableHeader, TableColumnHeader, TableBody } from '@cloudbeaver/core-blocks'; -import { composes, useStyles } from '@cloudbeaver/core-theming'; - -import { ExecutionPlanTreeContext } from './ExecutionPlanTreeContext'; -import { NestedNode } from './NestedNode'; - -const styles = composes( - css` - TableHeader { - composes: theme-background-surface from global; - } - `, - css` - TableHeader { - position: sticky; - top: 0; - z-index: 1; - } - ` -); - -interface Props { - className?: string; -} - -export const ExecutionPlanTree: React.FC = function ExecutionPlanTree({ className }) { - const treeContext = useContext(ExecutionPlanTreeContext); - - if (!treeContext) { - throw new Error('Tree context must be provided'); - } - - return styled(useStyles(styles))( - - - {treeContext.columns.map(property => ( - - {property.displayName} - - ))} - - - {treeContext.nodes.map(node => ( - - ))} - -
- ); -}; diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTreeBlock.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTreeBlock.tsx index bd2a557245..98fb12ce4d 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTreeBlock.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTreeBlock.tsx @@ -6,21 +6,35 @@ * you may not use this file except in compliance with the License. */ +import { observer } from 'mobx-react-lite'; import styled, { css } from 'reshadow'; -import { Split, Pane, ResizerControls, splitStyles } from '@cloudbeaver/core-blocks'; -import { TextareaNew } from '@cloudbeaver/core-blocks'; +import { + Split, Pane, ResizerControls, splitStyles, TextPlaceholder, + Table, TableHeader, TableColumnHeader, TableBody, TextareaNew +} from '@cloudbeaver/core-blocks'; +import { useTranslate } from '@cloudbeaver/core-localization'; +import type { SqlExecutionPlanNode } from '@cloudbeaver/core-sdk'; import { composes, useStyles } from '@cloudbeaver/core-theming'; -import { ExecutionPlanTree } from './ExecutionPlanTree'; +import { NestedNode } from './NestedNode'; +import { useExecutionPlanTreeState } from './useExecutionPlanTreeState'; const styles = composes( css` Pane { composes: theme-background-surface theme-text-on-surface from global; } + TableHeader { + composes: theme-background-surface from global; + } `, css` + TableHeader { + position: sticky; + top: 0; + z-index: 1; + } Split { height: 100%; flex-direction: column; @@ -39,17 +53,47 @@ const styles = composes( ); interface Props { + nodeList: SqlExecutionPlanNode[]; query: string; + onNodeSelect: (nodeId: string) => void; className?: string; } -export const ExecutionPlanTreeBlock: React.FC = function ExecutionPlanTreeBlock({ className, query }) { +export const ExecutionPlanTreeBlock: React.FC = observer(function ExecutionPlanTreeBlock({ + nodeList, query, onNodeSelect, className, +}) { const style = useStyles(styles, splitStyles); + const translate = useTranslate(); + const { columns, nodes, selectedNodes, selectNode } = useExecutionPlanTreeState(nodeList, onNodeSelect); return styled(style)( - + {nodes.length && columns.length ? ( + + + {columns.map(property => { + const name = property.displayName; + const columnTooltip = `${name} ${property.description ? '(' + property.description + ')' : ''}`; + return ( + + {name} + + ); + })} + + + {nodes.map(node => ( + + ))} + +
+ ) : {translate('sql_execution_plan_placeholder')}}
@@ -64,4 +108,4 @@ export const ExecutionPlanTreeBlock: React.FC = function ExecutionPlanTre
); -}; +}); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTreeContext.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTreeContext.ts index e08c9f73bd..3e76ebbdb3 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTreeContext.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTreeContext.ts @@ -18,7 +18,6 @@ export interface IExecutionPlanTreeContext { selectedNodes: Map; readonly columns: ObjectPropertyInfo[]; readonly nodes: IExecutionPlanNode[]; - readonly selectedNode: SqlExecutionPlanNode | undefined; selectNode: (nodeId: string) => void; } diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/NestedNode.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/NestedNode.tsx index a4c5c2243a..50be864f7a 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/NestedNode.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/NestedNode.tsx @@ -6,13 +6,14 @@ * you may not use this file except in compliance with the License. */ -import { useCallback, useContext, useState } from 'react'; +import { useCallback, useState } from 'react'; import styled, { css } from 'reshadow'; import { TableItem, TableColumnValue } from '@cloudbeaver/core-blocks'; +import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk'; import { useStyles } from '@cloudbeaver/core-theming'; -import { ExecutionPlanTreeContext, IExecutionPlanNode } from './ExecutionPlanTreeContext'; +import type { IExecutionPlanNode } from './ExecutionPlanTreeContext'; import { Expand } from './Expand'; import { getPropertyValue } from './getPropertyValue'; @@ -34,20 +35,16 @@ const styles = css` `; interface Props { + columns: ObjectPropertyInfo[]; node: IExecutionPlanNode; depth: number; className?: string; } -export const NestedNode: React.FC = function NestedNode({ node, depth, className }) { +export const NestedNode: React.FC = function NestedNode({ columns, node, depth, className }) { const style = useStyles(styles); - const treeContext = useContext(ExecutionPlanTreeContext); const [expanded, setExpanded] = useState(true); - if (!treeContext) { - throw new Error('Tree context must be provided'); - } - const expand = useCallback((event: React.MouseEvent) => { event.stopPropagation(); setExpanded(prev => !prev); @@ -58,11 +55,11 @@ export const NestedNode: React.FC = function NestedNode({ node, depth, cl return styled(style)( <> - {treeContext.columns.map((column, idx) => { + {columns.map((column, idx) => { const property = node.properties.find(property => property.id === column.id); const value = property ? getPropertyValue(property) : ''; return ( - + {idx === 0 && ( <> @@ -83,6 +80,7 @@ export const NestedNode: React.FC = function NestedNode({ node, depth, cl {expanded && node.children.map(child => ( diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel.tsx deleted file mode 100644 index 63566f3434..0000000000 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel.tsx +++ /dev/null @@ -1,151 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2021 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 { computed } from 'mobx'; -import { observer } from 'mobx-react-lite'; -import { useMemo, useState } from 'react'; -import styled, { css } from 'reshadow'; - -import { - Table, TableHeader, TableColumnHeader, TableBody, - TableItem, - TableColumnValue, - TextPlaceholder, -} from '@cloudbeaver/core-blocks'; -import { useTranslate } from '@cloudbeaver/core-localization'; -import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk'; -import { composes, useStyles } from '@cloudbeaver/core-theming'; - -import { Expand } from './Expand'; -import { getPropertyValue } from './getPropertyValue'; -import { isVisibleProperty } from './useExecutionPlanTreeState'; - -const styles = composes( - css` - TableColumnHeader { - composes: theme-background-surface from global; - } - `, - css` - TableColumnHeader { - position: sticky; - top: 0; - z-index: 1; - } - TableColumnValue { - white-space: pre; - } - control { - display: flex; - align-items: center; - } - expand-container { - width: 24px; - height: 24px; - margin-right: 4px; - cursor: pointer; - display: flex; - } - ` -); - -interface Props { - properties: ObjectPropertyInfo[]; - className?: string; -} - -export const PropertiesPanel: React.FC = observer(function PropertiesPanel({ properties, className }) { - const style = useStyles(styles); - const translate = useTranslate(); - const [generalExpanded, setGeneralExpanded] = useState(true); - const [detailsExpanded, setDetailsExpanded] = useState(true); - - const { general, details } = useMemo(() => computed(() => { - const general: ObjectPropertyInfo[] = []; - const details: ObjectPropertyInfo[] = []; - - for (const property of properties) { - if (isVisibleProperty(property)) { - general.push(property); - } else { - details.push(property); - } - } - - return { general, details }; - }), [properties]).get(); - - if (!general.length && !details.length) { - return {translate('sql_execution_plan_properties_panel_placeholder')}; - } - - return styled(style)( - - - - {translate('sql_execution_plan_properties_panel_name')} - - - {translate('sql_execution_plan_properties_panel_value')} - - - - - - - - setGeneralExpanded(!generalExpanded)} /> - - {translate('sql_execution_plan_properties_panel_general')} - - - - - {generalExpanded && general.map(property => { - const name = property.displayName; - const value = getPropertyValue(property); - return ( - - - {'\t\t' + name} - - - {value} - - - ); - })} - - - - - setDetailsExpanded(!detailsExpanded)} /> - - {translate('sql_execution_plan_properties_panel_details')} - - - - - {detailsExpanded && details.map(property => { - const name = property.displayName; - const value = getPropertyValue(property); - return ( - - - {'\t\t' + name} - - - {value} - - - ); - })} - -
- ); -}); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel/PropertiesPanel.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel/PropertiesPanel.tsx new file mode 100644 index 0000000000..86696f5016 --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel/PropertiesPanel.tsx @@ -0,0 +1,91 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { computed } from 'mobx'; +import { observer } from 'mobx-react-lite'; +import { useMemo } from 'react'; +import styled, { css } from 'reshadow'; + +import { Table, TableHeader, TableColumnHeader, TableBody, TextPlaceholder } from '@cloudbeaver/core-blocks'; +import { useTranslate } from '@cloudbeaver/core-localization'; +import type { ObjectPropertyInfo, SqlExecutionPlanNode } from '@cloudbeaver/core-sdk'; +import { composes, useStyles } from '@cloudbeaver/core-theming'; + +import { isVisibleProperty } from '../useExecutionPlanTreeState'; +import { PropertiesPanelItem } from './PropertiesPanelItem'; + +const styles = composes( + css` + TableColumnHeader { + composes: theme-background-surface from global; + } + `, + css` + TableColumnHeader { + position: sticky; + top: 0; + z-index: 1; + } + ` +); + +interface Props { + selectedNode: string; + nodeList: SqlExecutionPlanNode[]; + className?: string; +} + +export const PropertiesPanel: React.FC = observer(function PropertiesPanel({ selectedNode, nodeList, className }) { + const style = useStyles(styles); + const translate = useTranslate(); + + const { general, details } = useMemo(() => computed(() => { + const general: ObjectPropertyInfo[] = []; + const details: ObjectPropertyInfo[] = []; + + const node = nodeList.find(node => node.id === selectedNode); + + if (!node) { + return { general, details }; + } + + for (const property of node.properties) { + if (isVisibleProperty(property)) { + general.push(property); + } else { + details.push(property); + } + } + + return { general, details }; + }), [selectedNode, nodeList]).get(); + + if (!general.length && !details.length) { + return {translate('sql_execution_plan_properties_panel_placeholder')}; + } + + const nameColumnTitle = translate('sql_execution_plan_properties_panel_name'); + const valueColumnTitle = translate('sql_execution_plan_properties_panel_value'); + + return styled(style)( + + + + {nameColumnTitle} + + + {valueColumnTitle} + + + + + + +
+ ); +}); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel/PropertiesPanelItem.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel/PropertiesPanelItem.tsx new file mode 100644 index 0000000000..9da53edeea --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel/PropertiesPanelItem.tsx @@ -0,0 +1,73 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { useState } from 'react'; +import styled, { css } from 'reshadow'; + +import { TableItem, TableColumnValue } from '@cloudbeaver/core-blocks'; +import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk'; + +import { Expand } from '../Expand'; +import { getPropertyValue } from '../getPropertyValue'; + +const styles = css` + TableColumnValue { + white-space: pre; + } + control { + display: flex; + align-items: center; + } + expand-container { + width: 24px; + height: 24px; + margin-right: 4px; + cursor: pointer; + display: flex; + } +`; + +interface Props { + properties: ObjectPropertyInfo[]; + rootTitle: string; +} + +export const PropertiesPanelItem: React.FC = function PropertiesPanelItem({ properties, rootTitle }) { + const [expanded, setExpanded] = useState(true); + + return styled(styles)( + <> + + + + + setExpanded(!expanded)} /> + + {rootTitle} + + + + + {expanded && properties.map(property => { + const name = property.displayName; + const tooltip = `${name} ${property.description ? '(' + property.description + ')' : ''}`; + const value = getPropertyValue(property); + return ( + + + {'\t\t' + name} + + + {value} + + + ); + })} + + ); +}; diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanPanel.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanPanel.tsx index 0f24732a11..c5e899e466 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanPanel.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanPanel.tsx @@ -7,22 +7,19 @@ */ import { observer } from 'mobx-react-lite'; -import { useCallback } from 'react'; +import { useCallback, useState } from 'react'; import styled, { css } from 'reshadow'; -import { Split, Pane, ResizerControls, splitStyles, TextPlaceholder } from '@cloudbeaver/core-blocks'; +import { Split, Pane, ResizerControls, splitStyles } from '@cloudbeaver/core-blocks'; import { Loader } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; -import { useTranslate } from '@cloudbeaver/core-localization'; import { composes, useStyles } from '@cloudbeaver/core-theming'; import { EDeferredState } from '@cloudbeaver/core-utils'; import type { IExecutionPlanTab } from '../../ISqlEditorTabState'; import { ExecutionPlanTreeBlock } from './ExecutionPlanTreeBlock'; -import { ExecutionPlanTreeContext } from './ExecutionPlanTreeContext'; -import { PropertiesPanel } from './PropertiesPanel'; +import { PropertiesPanel } from './PropertiesPanel/PropertiesPanel'; import { SqlExecutionPlanService } from './SqlExecutionPlanService'; -import { useExecutionPlanTreeState } from './useExecutionPlanTreeState'; const styles = composes( css` @@ -48,50 +45,36 @@ export const SqlExecutionPlanPanel: React.FC = observer(function SqlExecu executionPlanTab, }) { const style = useStyles(styles, splitStyles); - const translate = useTranslate(); - const tabId = executionPlanTab.tabId; const sqlExecutionPlanService = useService(SqlExecutionPlanService); - const executionPlan = sqlExecutionPlanService.results.get(tabId); - const process = sqlExecutionPlanService.processes.get(tabId); + const data = sqlExecutionPlanService.data.get(executionPlanTab.tabId); + const [selectedNode, setSelectedNode] = useState(null); - const executionPlanState = useExecutionPlanTreeState(executionPlan?.nodes || []); - const loading = process?.isInProgress || !executionPlan; - const canCancel = process?.getState() === EDeferredState.PENDING ?? false; - - const cancelTask = useCallback(() => { - if (process) { - process.cancel(); - } - }, [process]); - - if (loading) { + if (data?.process.isInProgress || !data?.executionPlan) { return ( data?.process.cancel()} /> ); } - if (!executionPlanState.nodes.length || !executionPlanState.columns.length) { - return {translate('sql_execution_plan_placeholder')}; - } - return styled(style)( - - - - - - {executionPlanState.selectedNode && ( - <> - - - - - - )} - - + + + + + {selectedNode && ( + <> + + + + + + )} + ); }); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanService.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanService.ts index 00ba9ab91e..e55127da85 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanService.ts @@ -18,10 +18,14 @@ import { SqlDialectInfoService } from '../../SqlDialectInfoService'; import type { SqlExecutionState } from '../../SqlExecutionState'; import { SQLExecutionPlanProcess } from './SQLExecutionPlanProcess'; +interface IExecutionPlanData { + process: SQLExecutionPlanProcess; + executionPlan: SqlExecutionPlan | null; +} + @injectable() export class SqlExecutionPlanService { - results: Map; - processes: Map; + data: Map; constructor( private sqlDialectInfoService: SqlDialectInfoService, @@ -29,11 +33,9 @@ export class SqlExecutionPlanService { private notificationService: NotificationService, ) { makeObservable(this, { - results: observable, - processes: observable, + data: observable, }); - this.results = new Map(); - this.processes = new Map(); + this.data = new Map(); } async executeExecutionPlan( @@ -46,12 +48,15 @@ export class SqlExecutionPlanService { return; } - const tabId = this.createExecutionPlanTab(editorState, query); + const tabId = this.createExecutionPlanTab(editorState); const subQuery = await this.getSubQuery(editorState.executionContext.connectionId, query); const task = new SQLExecutionPlanProcess(this.graphQLService, this.notificationService); + this.data.set(tabId, { + process: task, + executionPlan: null, + }); try { - this.processes.set(tabId, task); executionState.setExecutionTask(task); editorState.currentTabId = tabId; @@ -64,7 +69,10 @@ export class SqlExecutionPlanService { ); const executionPlan = await task.promise; - this.results.set(tabId, executionPlan); + this.data.set(tabId, { + process: task, + executionPlan, + }); } catch (exception) { const message = task.getState() === EDeferredState.CANCELLED ? 'Execution plan process has been canceled' : undefined; this.notificationService.logException(exception, 'Execution plan Error', message); @@ -103,14 +111,10 @@ export class SqlExecutionPlanService { state.executionPlanTabs.splice(state.executionPlanTabs.indexOf(executionPlanTab), 1); } - this.results.delete(tabId); - this.processes.delete(tabId); + this.data.delete(tabId); } - private createExecutionPlanTab( - state: ISqlEditorTabState, - query: string, - ) { + private createExecutionPlanTab(state: ISqlEditorTabState) { if (!state.executionContext) { throw new Error('ExecutionContext is not provided'); } @@ -125,7 +129,6 @@ export class SqlExecutionPlanService { connectionId: state.executionContext.connectionId, contextId: state.executionContext.contextId, }, - query, order: nameOrder, }); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/useExecutionPlanTreeState.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/useExecutionPlanTreeState.ts index c054310fd3..61bb689016 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/useExecutionPlanTreeState.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/useExecutionPlanTreeState.ts @@ -18,17 +18,21 @@ export function isVisibleProperty(property: ObjectPropertyInfo): boolean { } interface IPrivateExecutionPlanTreeState extends IExecutionPlanTreeContext { - list: SqlExecutionPlanNode[]; + nodeList: SqlExecutionPlanNode[]; + onNodeSelect: (nodeId: string) => void; } -export function useExecutionPlanTreeState(list: SqlExecutionPlanNode[]): IExecutionPlanTreeContext { +export function useExecutionPlanTreeState( + nodeList: SqlExecutionPlanNode[], onNodeSelect: (nodeId: string) => void +): IExecutionPlanTreeContext { return useObjectRef({ - list, + nodeList, + onNodeSelect, selectedNodes: new Map(), get columns() { const columns: ObjectPropertyInfo[] = []; - for (const node of this.list) { + for (const node of this.nodeList) { for (const property of node.properties) { if (property.id && isVisibleProperty(property) && !columns.find(column => column.id === property.id)) { columns.push(property); @@ -41,7 +45,7 @@ export function useExecutionPlanTreeState(list: SqlExecutionPlanNode[]): IExecut get nodes() { const map: Map = new Map(); - const tree: IExecutionPlanNode[] = this.list + const tree: IExecutionPlanNode[] = this.nodeList .map((node, idx) => { map.set(node.id, idx); return ({ ...node, children: [] }); @@ -60,19 +64,15 @@ export function useExecutionPlanTreeState(list: SqlExecutionPlanNode[]): IExecut return nodes; }, - get selectedNode(): SqlExecutionPlanNode | undefined { - const nodeId = Array.from(this.selectedNodes.keys())[0]; - return this.list.find(node => node.id === nodeId); - }, selectNode(nodeId: string) { this.selectedNodes.clear(); this.selectedNodes.set(nodeId, true); + this.onNodeSelect(nodeId); }, - }, { list }, { + }, { nodeList, onNodeSelect }, { selectedNodes: observable, - list: observable, + nodeList: observable, columns: computed, nodes: computed, - selectedNode: computed, }, ['selectNode']); } diff --git a/webapp/packages/plugin-sql-editor/src/locales/en.ts b/webapp/packages/plugin-sql-editor/src/locales/en.ts index 57ccd3ebcd..5ea4e42db1 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/en.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/en.ts @@ -1,6 +1,8 @@ export default [ ['sql_editor_placeholder', 'Execute query with Ctrl+Enter to see results'], ['sql_editor_execution_plan_button_tooltip', 'Explain execution plan (Shift + Ctrl + E)'], + ['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_execution_plan_placeholder', 'Execution plan is empty'], ['sql_execution_plan_properties_panel_placeholder', 'There are no items to show'], ['sql_execution_plan_properties_panel_name', 'Name'], diff --git a/webapp/packages/plugin-sql-editor/src/locales/ru.ts b/webapp/packages/plugin-sql-editor/src/locales/ru.ts index 425e40ee28..7205b709bc 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/ru.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/ru.ts @@ -1,6 +1,8 @@ export default [ ['sql_editor_placeholder', 'Нажмите Ctrl+Enter, чтобы выполнить запрос и увидеть результат'], ['sql_editor_execution_plan_button_tooltip', 'Посмотреть информацию о плане выполнения запроса (Shift + Ctrl + E)'], + ['sql_editor_sql_execution_button_tooltip', 'Выполнить SQL Выражение (Ctrl + Enter)'], + ['sql_editor_sql_execution_new_tab_button_tooltip', 'Выполнить SQL Выражение в новой вкладке (Ctrl + \\)(Shift + Ctrl + Enter)'], ['sql_execution_plan_placeholder', 'План выполнения пуст'], ['sql_execution_plan_properties_panel_placeholder', 'Информация не доступна'], ['sql_execution_plan_properties_panel_name', 'Название'],