Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { createDataContext } from '@cloudbeaver/core-data-context';

import type { IExecutionPlanTab } from '../../ISqlEditorTabState.js';

export const DATA_CONTEXT_SQL_EXECUTION_PLAN_TAB = createDataContext<IExecutionPlanTab>('sql-execution-plan-tab');
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 SQL_EXECUTION_PLAN_ACTIONS_MENU = createMenu('sql-execution-plan-actions', { label: '' });
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/

.executionPlanActions {
display: flex;
flex-direction: column;
}

.executionPlanActions.menuBar {
height: unset;
}

.executionPlanActions .menuBarItem {
width: auto;
height: auto;
}

.executionPlanActions .menuBarItemBox {
flex-direction: column-reverse;
}

.executionPlanActions .menuBarItemIcon {
transform: rotate(-90deg);
}

.executionPlanActions .menuBarItemLabel {
writing-mode: vertical-rl;
text-orientation: mixed;
transform: rotate(180deg);
white-space: nowrap;
font-weight: normal;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';

import { s, SContext, type StyleRegistry, useS } from '@cloudbeaver/core-blocks';
import type { IDataContext } from '@cloudbeaver/core-data-context';
import { MenuBar, MenuBarGroupStyles, MenuBarItemStyles, MenuBarStyles } from '@cloudbeaver/core-ui';
import { useMenu } from '@cloudbeaver/core-view';

import { SQL_EXECUTION_PLAN_ACTIONS_MENU } from './SQL_EXECUTION_PLAN_ACTIONS_MENU.js';
import style from './SqlExecutionPlanActionsMenu.module.css';

const registry: StyleRegistry = [
[
MenuBarStyles,
{
mode: 'append',
styles: [style],
},
],
[
MenuBarItemStyles,
{
mode: 'append',
styles: [style],
},
],
];

interface Props {
context: IDataContext;
}

export const SqlExecutionPlanActionsMenu = observer<Props>(function SqlExecutionPlanActionsMenu({ context }) {
const menuBarStyles = useS(style, MenuBarStyles, MenuBarItemStyles, MenuBarGroupStyles);
const menu = useMenu({ menu: SQL_EXECUTION_PLAN_ACTIONS_MENU, context });

if (!menu.items.length) {
return null;
}

return (
<SContext registry={registry}>
<MenuBar
menu={menu}
className={s(menuBarStyles, { toolsMenu: true, floating: true, withLabel: true, executionPlanActions: true })}
compact={false}
/>
</SContext>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@
* you may not use this file except in compliance with the License.
*/

.tabsLayout {
display: flex;
flex-direction: row;
flex: 1;
overflow: hidden;
height: 100%;
}

.actionsBar {
composes: theme-border-color-background from global;
display: flex;
flex-direction: column;
align-items: center;
overflow: auto;
border-right: solid 1px;
}

.tabPanelList {
flex: 1;
overflow: hidden;
}

.pane {
&:first-child {
display: flex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ import { observer } from 'mobx-react-lite';
import { useState } from 'react';

import { Loader, Pane, ResizerControls, s, Split, useS, useSplitUserState } from '@cloudbeaver/core-blocks';
import { useDataContextLink } from '@cloudbeaver/core-data-context';
import { useService } from '@cloudbeaver/core-di';
import { TabList, TabPanelList, TabsState } from '@cloudbeaver/core-ui';
import { TabPanelList, TabsState } from '@cloudbeaver/core-ui';
import { useMenu } from '@cloudbeaver/core-view';

import type { IExecutionPlanTab } from '../../ISqlEditorTabState.js';
import { PropertiesPanel } from './PropertiesPanel/PropertiesPanel.js';
import { DATA_CONTEXT_SQL_EXECUTION_PLAN_TAB } from './DATA_CONTEXT_SQL_EXECUTION_PLAN_TAB.js';
import { SQL_EXECUTION_PLAN_ACTIONS_MENU } from './SQL_EXECUTION_PLAN_ACTIONS_MENU.js';
import { SqlExecutionPlanActionsMenu } from './SqlExecutionPlanActionsMenu.js';
import { SqlExecutionPlanService } from './SqlExecutionPlanService.js';
import { SqlExecutionPlanViewBar } from './SqlExecutionPlanViewBar.js';
import { SqlExecutionPlanViewService } from './SqlExecutionPlanViewService.js';
import style from './SqlExecutionPlanPanel.module.css';

Expand All @@ -29,6 +35,11 @@ export const SqlExecutionPlanPanel = observer<Props>(function SqlExecutionPlanPa
const data = sqlExecutionPlanService.data.get(executionPlanTab.tabId);
const [selectedNode, setSelectedNode] = useState<string | null>(null);
const splitState = useSplitUserState('execution-plan');
const menu = useMenu({ menu: SQL_EXECUTION_PLAN_ACTIONS_MENU });

useDataContextLink(menu.context, (context, id) => {
context.set(DATA_CONTEXT_SQL_EXECUTION_PLAN_TAB, executionPlanTab, id);
});

if (data?.task.executing || !data?.executionPlan) {
return <Loader cancelDisabled={!data?.task.cancellable} onCancel={() => data?.task.cancel()} />;
Expand All @@ -45,8 +56,13 @@ export const SqlExecutionPlanPanel = observer<Props>(function SqlExecutionPlanPa
lazy
onNodeSelect={setSelectedNode}
>
<TabList underline />
<TabPanelList />
<div className={s(styles, { tabsLayout: true })}>
<div className={s(styles, { actionsBar: true })}>
<SqlExecutionPlanActionsMenu context={menu.context} />
</div>
<TabPanelList className={s(styles, { tabPanelList: true })} />
<SqlExecutionPlanViewBar />
</div>
</TabsState>
</Pane>
<ResizerControls />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/

.tabBar {
composes: theme-background-secondary theme-text-on-secondary from global;
overflow-x: hidden;
padding-top: 4px;
}

.tab {
composes: theme-ripple theme-background-background theme-text-text-primary-on-light theme-typography--body2 from global;
text-transform: uppercase;
font-weight: normal;

&:global([aria-selected='true']) {
font-weight: normal !important;
}
}

.tabList {
margin-right: 8px;
margin-left: 4px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { s, SContext, type StyleRegistry, useS } from '@cloudbeaver/core-blocks';
import { TabList, TabListStyles, TabStyles } from '@cloudbeaver/core-ui';

import style from './SqlExecutionPlanViewBar.module.css';

const registry: StyleRegistry = [
[TabListStyles, { mode: 'append', styles: [style] }],
[TabStyles, { mode: 'append', styles: [style] }],
];

export function SqlExecutionPlanViewBar() {

Check warning on line 18 in webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanViewBar.tsx

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Missing return type on function
const styles = useS(style);

return (
<div className={s(styles, { tabBar: true })}>
<SContext registry={registry}>
<TabList vertical rotated />
</SContext>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export class SqlExecutionPlanViewBootstrap extends Bootstrap {
this.sqlExecutionPlanViewService.tabs.add({
key: 'table',
name: 'plugin_sql_execution_plan_view_table',
icon: 'table-icon',
order: 0,
panel: () => ExecutionPlanTreeView,
});
Expand Down
3 changes: 3 additions & 0 deletions webapp/packages/plugin-sql-editor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,8 @@ export * from './SqlEditorSettingsService.js';
export * from './SqlEditorView.js';
export * from './SqlResultTabs/SqlQueryService.js';
export * from './SqlResultTabs/ExecutionPlan/SqlExecutionPlanViewService.js';
export * from './SqlResultTabs/ExecutionPlan/SqlExecutionPlanService.js';
export * from './SqlResultTabs/ExecutionPlan/SQL_EXECUTION_PLAN_ACTIONS_MENU.js';
export * from './SqlResultTabs/ExecutionPlan/DATA_CONTEXT_SQL_EXECUTION_PLAN_TAB.js';
export * from './downloadSql.js';
export type { ISqlExecutionPlanViewProps } from './SqlResultTabs/ExecutionPlan/ISqlExecutionPlanViewProps.js';
Loading