diff --git a/licenses.yaml b/licenses.yaml
index c7053f17ff27..40305fc55c66 100644
--- a/licenses.yaml
+++ b/licenses.yaml
@@ -6329,16 +6329,6 @@ license_file_path: licenses/bin/react-router.MIT
---
-name: "react-splitter-layout"
-license_category: binary
-module: web-console
-license_name: MIT License
-copyright: Yang Liu
-version: 4.0.0
-license_file_path: licenses/bin/react-splitter-layout.MIT
-
----
-
name: "react-table"
license_category: binary
module: web-console
diff --git a/web-console/package-lock.json b/web-console/package-lock.json
index 3431d44e4a51..95768117648c 100644
--- a/web-console/package-lock.json
+++ b/web-console/package-lock.json
@@ -42,7 +42,6 @@
"react-dom": "^18.3.1",
"react-router": "^5.3.4",
"react-router-dom": "^5.3.4",
- "react-splitter-layout": "^4.0.0",
"react-table": "~6.11.5",
"regenerator-runtime": "^0.13.7",
"tslib": "^2.8.0",
@@ -75,7 +74,6 @@
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@types/react-router-dom": "^5.3.3",
- "@types/react-splitter-layout": "^3.0.5",
"@types/react-table": "6.8.5",
"@types/uuid": "^7.0.2",
"autoprefixer": "^10.4.20",
@@ -4064,15 +4062,6 @@
"@types/react-router": "*"
}
},
- "node_modules/@types/react-splitter-layout": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/@types/react-splitter-layout/-/react-splitter-layout-3.0.5.tgz",
- "integrity": "sha512-J3NKVdPguGcSSN41/EDV3BYdYfndxUil2SX4wDBJiVfcnyopCegdhIrC2kcaa4CxjpaPChe1bja2k8LLDxP0ZQ==",
- "dev": true,
- "dependencies": {
- "@types/react": "*"
- }
- },
"node_modules/@types/react-table": {
"version": "6.8.5",
"resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.5.tgz",
@@ -14964,15 +14953,6 @@
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
"dev": true
},
- "node_modules/react-splitter-layout": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/react-splitter-layout/-/react-splitter-layout-4.0.0.tgz",
- "integrity": "sha512-SLqOjBOxRuizWUa83w6q5/u9cDWa9/yj9Iko9V9JFN8x+cqIXiDlUFWSx+icz3IIgvsN/oRIw3za5/32RjIwrA==",
- "peerDependencies": {
- "prop-types": "^15.5.0",
- "react": "^15.5.0 || ^16.0.0"
- }
- },
"node_modules/react-table": {
"version": "6.11.5",
"resolved": "https://registry.npmjs.org/react-table/-/react-table-6.11.5.tgz",
@@ -21157,15 +21137,6 @@
"@types/react-router": "*"
}
},
- "@types/react-splitter-layout": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/@types/react-splitter-layout/-/react-splitter-layout-3.0.5.tgz",
- "integrity": "sha512-J3NKVdPguGcSSN41/EDV3BYdYfndxUil2SX4wDBJiVfcnyopCegdhIrC2kcaa4CxjpaPChe1bja2k8LLDxP0ZQ==",
- "dev": true,
- "requires": {
- "@types/react": "^18.3.11"
- }
- },
"@types/react-table": {
"version": "6.8.5",
"resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.5.tgz",
@@ -28826,11 +28797,6 @@
}
}
},
- "react-splitter-layout": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/react-splitter-layout/-/react-splitter-layout-4.0.0.tgz",
- "integrity": "sha512-SLqOjBOxRuizWUa83w6q5/u9cDWa9/yj9Iko9V9JFN8x+cqIXiDlUFWSx+icz3IIgvsN/oRIw3za5/32RjIwrA=="
- },
"react-table": {
"version": "6.11.5",
"resolved": "https://registry.npmjs.org/react-table/-/react-table-6.11.5.tgz",
diff --git a/web-console/package.json b/web-console/package.json
index 96de0349e2fc..1e76234841ab 100644
--- a/web-console/package.json
+++ b/web-console/package.json
@@ -83,7 +83,6 @@
"react-dom": "^18.3.1",
"react-router": "^5.3.4",
"react-router-dom": "^5.3.4",
- "react-splitter-layout": "^4.0.0",
"react-table": "~6.11.5",
"regenerator-runtime": "^0.13.7",
"tslib": "^2.8.0",
@@ -116,7 +115,6 @@
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@types/react-router-dom": "^5.3.3",
- "@types/react-splitter-layout": "^3.0.5",
"@types/react-table": "6.8.5",
"@types/uuid": "^7.0.2",
"autoprefixer": "^10.4.20",
diff --git a/web-console/src/components/index.ts b/web-console/src/components/index.ts
index 437017a8c767..d885bf07434a 100644
--- a/web-console/src/components/index.ts
+++ b/web-console/src/components/index.ts
@@ -51,6 +51,7 @@ export * from './segment-timeline/segment-timeline';
export * from './show-json/show-json';
export * from './show-log/show-log';
export * from './show-value/show-value';
+export * from './splitter-layout/splitter-layout';
export * from './suggestion-menu/suggestion-menu';
export * from './supervisor-history-panel/supervisor-history-panel';
export * from './table-cell/table-cell';
diff --git a/web-console/src/components/splitter-layout/__snapshots__/splitter-layout.spec.tsx.snap b/web-console/src/components/splitter-layout/__snapshots__/splitter-layout.spec.tsx.snap
new file mode 100644
index 000000000000..7cb7b2ceca26
--- /dev/null
+++ b/web-console/src/components/splitter-layout/__snapshots__/splitter-layout.spec.tsx.snap
@@ -0,0 +1,41 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SplitterLayout matches snapshot one child 1`] = `
+
+`;
+
+exports[`SplitterLayout matches snapshot two children 1`] = `
+
+`;
diff --git a/web-console/src/components/splitter-layout/layout-pane.tsx b/web-console/src/components/splitter-layout/layout-pane.tsx
new file mode 100644
index 000000000000..eda20111bd4f
--- /dev/null
+++ b/web-console/src/components/splitter-layout/layout-pane.tsx
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import classNames from 'classnames';
+import type { ReactNode } from 'react';
+
+export interface LayoutPaneProps {
+ vertical?: boolean;
+ size: number | undefined;
+ percentage?: boolean;
+ children: ReactNode;
+}
+
+export function LayoutPane(props: LayoutPaneProps) {
+ return (
+
+ {props.children}
+
+ );
+}
diff --git a/web-console/src/components/splitter-layout/splitter-layout.scss b/web-console/src/components/splitter-layout/splitter-layout.scss
new file mode 100644
index 000000000000..1c3993a110d8
--- /dev/null
+++ b/web-console/src/components/splitter-layout/splitter-layout.scss
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+// Originally copied from https://github.com/zesik/react-splitter-layout/blob/master/src/stylesheets/index.css and heavily refactored
+
+$default-splitter-size: 8px;
+
+.splitter-layout {
+ display: flex;
+ overflow: hidden;
+
+ &.splitter-layout-horizontal {
+ flex-direction: row;
+
+ &.layout-changing {
+ cursor: col-resize;
+ }
+
+ & > .layout-splitter {
+ width: $default-splitter-size;
+ height: 100%;
+ cursor: col-resize;
+ }
+ }
+
+ &.splitter-layout-vertical {
+ flex-direction: column;
+
+ &.layout-changing {
+ cursor: row-resize;
+ }
+
+ & > .layout-splitter {
+ width: 100%;
+ height: $default-splitter-size;
+ cursor: row-resize;
+ }
+ }
+
+ & > .layout-pane {
+ position: relative;
+ flex: 0 0 auto;
+ overflow: auto;
+
+ &.layout-pane-primary {
+ flex: 1 1 auto;
+ }
+ }
+
+ & > .layout-splitter {
+ flex: 0 0 auto;
+ }
+}
diff --git a/web-console/src/components/splitter-layout/splitter-layout.spec.tsx b/web-console/src/components/splitter-layout/splitter-layout.spec.tsx
new file mode 100644
index 000000000000..492e6e6dfd06
--- /dev/null
+++ b/web-console/src/components/splitter-layout/splitter-layout.spec.tsx
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import { render } from '@testing-library/react';
+
+import { SplitterLayout } from './splitter-layout';
+
+describe('SplitterLayout', () => {
+ it('matches snapshot one child', () => {
+ const splitterLayout = (
+
+
+
+ );
+
+ const { container } = render(splitterLayout);
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ it('matches snapshot two children', () => {
+ const splitterLayout = (
+
+
+
+
+ );
+
+ const { container } = render(splitterLayout);
+ expect(container.firstChild).toMatchSnapshot();
+ });
+});
diff --git a/web-console/src/components/splitter-layout/splitter-layout.tsx b/web-console/src/components/splitter-layout/splitter-layout.tsx
new file mode 100644
index 000000000000..dc1701b7ef64
--- /dev/null
+++ b/web-console/src/components/splitter-layout/splitter-layout.tsx
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+// Originally copied from https://github.com/zesik/react-splitter-layout/blob/master/src/components/SplitterLayout.jsx and heavily refactored
+
+import classNames from 'classnames';
+import type { ReactNode } from 'react';
+import { Children, Component } from 'react';
+
+import { clamp } from '../../utils';
+
+import { LayoutPane } from './layout-pane';
+
+import './splitter-layout.scss';
+
+function clearSelection() {
+ if (window.getSelection) {
+ const selection = window.getSelection();
+ if (selection) {
+ if (selection.empty) {
+ selection.empty();
+ } else if (selection.removeAllRanges) {
+ selection.removeAllRanges();
+ }
+ }
+ }
+}
+
+export interface SplitterLayoutProps {
+ className?: string;
+ vertical?: boolean;
+ percentage?: boolean;
+ primaryIndex?: 0 | 1;
+ primaryMinSize?: number;
+ secondaryInitialSize: number;
+ secondaryMinSize?: number;
+ secondaryMaxSize?: number;
+ splitterSize?: number;
+ onSecondaryPaneSizeChange?: (size: number) => void;
+ children: ReactNode | ReactNode[];
+}
+
+interface SplitterLayoutState {
+ secondaryPaneSize: number;
+ resizing: boolean;
+}
+
+export class SplitterLayout extends Component {
+ private container: HTMLDivElement | null = null;
+ private splitter: HTMLDivElement | null = null;
+
+ constructor(props: SplitterLayoutProps) {
+ super(props);
+
+ this.handleResize = this.handleResize.bind(this);
+ this.handleMouseMove = this.handleMouseMove.bind(this);
+ this.handleMouseUp = this.handleMouseUp.bind(this);
+ this.handleTouchMove = this.handleTouchMove.bind(this);
+ this.handleSplitterMouseDown = this.handleSplitterMouseDown.bind(this);
+
+ this.state = {
+ secondaryPaneSize: props.secondaryInitialSize,
+ resizing: false,
+ };
+ }
+
+ componentDidMount() {
+ window.addEventListener('resize', this.handleResize);
+ document.addEventListener('mouseup', this.handleMouseUp);
+ document.addEventListener('mousemove', this.handleMouseMove);
+ document.addEventListener('touchend', this.handleMouseUp);
+ document.addEventListener('touchmove', this.handleTouchMove);
+ }
+
+ componentDidUpdate(_prevProps: SplitterLayoutProps, prevState: SplitterLayoutState) {
+ if (prevState.secondaryPaneSize !== this.state.secondaryPaneSize) {
+ this.props.onSecondaryPaneSizeChange?.(this.state.secondaryPaneSize);
+ }
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('resize', this.handleResize);
+ document.removeEventListener('mouseup', this.handleMouseUp);
+ document.removeEventListener('mousemove', this.handleMouseMove);
+ document.removeEventListener('touchend', this.handleMouseUp);
+ document.removeEventListener('touchmove', this.handleTouchMove);
+ }
+
+ getSecondaryPaneSize(
+ containerRect: DOMRect,
+ splitterRect: DOMRect,
+ clientPosition: { top: number; left: number },
+ offsetMouse: boolean,
+ ) {
+ const {
+ vertical,
+ percentage,
+ primaryIndex,
+ primaryMinSize = 0,
+ secondaryMinSize = 0,
+ secondaryMaxSize = Infinity,
+ } = this.props;
+
+ let totalSize;
+ let splitterSize;
+ let offset;
+ if (vertical) {
+ totalSize = containerRect.height;
+ splitterSize = splitterRect.height;
+ offset = clientPosition.top - containerRect.top;
+ } else {
+ totalSize = containerRect.width;
+ splitterSize = splitterRect.width;
+ offset = clientPosition.left - containerRect.left;
+ }
+ if (offsetMouse) {
+ offset -= splitterSize / 2;
+ }
+ offset = clamp(offset, 0, totalSize - splitterSize);
+
+ let secondaryPaneSize = primaryIndex === 1 ? offset : totalSize - splitterSize - offset;
+ if (percentage) {
+ secondaryPaneSize = (secondaryPaneSize * 100) / totalSize;
+ splitterSize = (splitterSize * 100) / totalSize;
+ totalSize = 100;
+ }
+
+ return clamp(
+ secondaryPaneSize,
+ secondaryMinSize,
+ Math.min(secondaryMaxSize, totalSize - splitterSize - primaryMinSize),
+ );
+ }
+
+ handleResize() {
+ if (this.container && this.splitter && !this.props.percentage) {
+ const containerRect = this.container.getBoundingClientRect();
+ const splitterRect = this.splitter.getBoundingClientRect();
+ const secondaryPaneSize = this.getSecondaryPaneSize(
+ containerRect,
+ splitterRect,
+ {
+ left: splitterRect.left,
+ top: splitterRect.top,
+ },
+ false,
+ );
+ this.setState({ secondaryPaneSize });
+ }
+ }
+
+ handleMouseMove(e: MouseEvent | Touch) {
+ if (this.container && this.splitter && this.state.resizing) {
+ const containerRect = this.container.getBoundingClientRect();
+ const splitterRect = this.splitter.getBoundingClientRect();
+ const secondaryPaneSize = this.getSecondaryPaneSize(
+ containerRect,
+ splitterRect,
+ {
+ left: e.clientX,
+ top: e.clientY,
+ },
+ true,
+ );
+ clearSelection();
+ this.setState({ secondaryPaneSize });
+ }
+ }
+
+ handleTouchMove(e: TouchEvent) {
+ this.handleMouseMove(e.changedTouches[0]);
+ }
+
+ handleSplitterMouseDown() {
+ clearSelection();
+ this.setState({ resizing: true });
+ }
+
+ handleMouseUp() {
+ this.setState(prevState => (prevState.resizing ? { resizing: false } : null));
+ }
+
+ render() {
+ const { className, vertical, percentage, primaryIndex, splitterSize, children } = this.props;
+ const { resizing } = this.state;
+
+ const childrenArray = Children.toArray(children).slice(0, 2);
+ if (childrenArray.length === 0) return null;
+
+ const effectivePrimaryIndex = primaryIndex === 1 ? 1 : 0;
+ const wrappedChildren = childrenArray.map((child, i) => {
+ const isSecondary = childrenArray.length > 1 && i !== effectivePrimaryIndex;
+ return (
+
+ {child}
+
+ );
+ });
+
+ return (
+ {
+ this.container = c;
+ }}
+ >
+ {wrappedChildren[0]}
+ {wrappedChildren.length > 1 && (
+
{
+ this.splitter = c;
+ }}
+ onMouseDown={this.handleSplitterMouseDown}
+ onTouchStart={this.handleSplitterMouseDown}
+ style={splitterSize ? { [vertical ? 'height' : 'width']: splitterSize } : undefined}
+ />
+ )}
+ {wrappedChildren[1]}
+
+ );
+ }
+}
diff --git a/web-console/src/druid-models/execution/execution.ts b/web-console/src/druid-models/execution/execution.ts
index f218acc0386d..f4a73eb97207 100644
--- a/web-console/src/druid-models/execution/execution.ts
+++ b/web-console/src/druid-models/execution/execution.ts
@@ -285,6 +285,10 @@ export class Execution {
});
}
+ static fromDartReport(dartReport: MsqTaskReportResponse): Execution {
+ return Execution.fromTaskReport(dartReport).changeEngine('sql-msq-dart');
+ }
+
static fromTaskReport(taskReport: MsqTaskReportResponse): Execution {
// Must have status set for a valid report
const id = deepGet(taskReport, 'multiStageQuery.taskId');
@@ -327,6 +331,7 @@ export class Execution {
new Column({ name: sig.name, nativeType: sig.type, sqlType: sqlTypeNames?.[i] }),
),
rows: results,
+ queryDuration: durationMs,
}).inflateDatesFromSqlTypes();
}
diff --git a/web-console/src/entry.scss b/web-console/src/entry.scss
index 73cbb238eb16..8d368a4146b7 100644
--- a/web-console/src/entry.scss
+++ b/web-console/src/entry.scss
@@ -24,7 +24,6 @@
@import '@blueprintjs/datetime/src/blueprint-datetime';
@import '@blueprintjs/datetime2/src/blueprint-datetime2';
@import '@blueprintjs/select/src/blueprint-select';
-@import 'react-splitter-layout/lib/index';
@import './react-table/react-table-base-styles';
@import './react-table/react-table-extra';
diff --git a/web-console/src/utils/local-storage-keys.tsx b/web-console/src/utils/local-storage-keys.tsx
index a178fc2c009c..9181e4be0d31 100644
--- a/web-console/src/utils/local-storage-keys.tsx
+++ b/web-console/src/utils/local-storage-keys.tsx
@@ -51,6 +51,8 @@ export const LocalStorageKeys = {
WORKBENCH_QUERIES: 'workbench-queries' as const,
WORKBENCH_LAST_TAB: 'workbench-last-tab' as const,
WORKBENCH_PANE_SIZE: 'workbench-pane-size' as const,
+ WORKBENCH_LEFT_SIZE: 'workbench-left-size' as const,
+ WORKBENCH_RIGHT_SIZE: 'workbench-right-size' as const,
WORKBENCH_HISTORY: 'workbench-history' as const,
WORKBENCH_TASK_PANEL: 'workbench-task-panel' as const,
WORKBENCH_DART_PANEL: 'workbench-dart-panel' as const,
diff --git a/web-console/src/variables.scss b/web-console/src/variables.scss
index 4ccc4b5e8298..3157ac9cbb85 100644
--- a/web-console/src/variables.scss
+++ b/web-console/src/variables.scss
@@ -55,3 +55,11 @@ $table-cell-h-padding: 5px;
box-shadow: $pt-dark-elevation-shadow-1;
}
}
+
+@mixin pin-full {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
diff --git a/web-console/src/views/datasources-view/__snapshots__/datasources-view.spec.tsx.snap b/web-console/src/views/datasources-view/__snapshots__/datasources-view.spec.tsx.snap
index 2444d1a4f236..6da41f6eb763 100644
--- a/web-console/src/views/datasources-view/__snapshots__/datasources-view.spec.tsx.snap
+++ b/web-console/src/views/datasources-view/__snapshots__/datasources-view.spec.tsx.snap
@@ -108,335 +108,344 @@ exports[`DatasourcesView matches snapshot 1`] = `
}
/>
-
- Datasource
-
- name
- ,
- "accessor": "datasource",
- "show": true,
- "width": 150,
- },
- {
- "Cell": [Function],
- "Header": "Availability",
- "accessor": "num_segments",
- "className": "padded",
- "filterable": false,
- "show": true,
- "sortMethod": [Function],
- "width": 220,
- },
- {
- "Cell": [Function],
- "Header":
- Historical
-
- load/drop queues
- ,
- "accessor": "num_segments_to_load",
- "className": "padded",
- "filterable": false,
- "show": true,
- "width": 180,
- },
- {
- "Cell": [Function],
- "Header":
- Total
-
- data size
- ,
- "accessor": "total_data_size",
- "className": "padded",
- "filterable": false,
- "show": true,
- "width": 100,
- },
- {
- "Cell": [Function],
- "Header": "Running tasks",
- "accessor": [Function],
- "filterable": false,
- "id": "running_tasks",
- "show": true,
- "width": 200,
- },
- {
- "Cell": [Function],
- "Header":
- Segment rows
-
- minimum / average / maximum
- ,
- "accessor": "avg_segment_rows",
- "className": "padded",
- "filterable": false,
- "show": true,
- "width": 230,
- },
- {
- "Cell": [Function],
- "Header":
- Segment size
-
- minimum / average / maximum
- ,
- "accessor": "avg_segment_size",
- "className": "padded",
- "filterable": false,
- "show": false,
- "width": 270,
- },
- {
- "Cell": [Function],
- "Header":
- Segment
-
- granularity
- ,
- "accessor": [Function],
- "className": "padded",
- "filterable": false,
- "id": "segment_granularity",
- "show": false,
- "width": 100,
- },
- {
- "Cell": [Function],
- "Header":
- Total
-
- rows
- ,
- "accessor": "total_rows",
- "className": "padded",
- "filterable": false,
- "show": true,
- "width": 110,
- },
- {
- "Cell": [Function],
- "Header":
- Avg. row size
-
- (bytes)
- ,
- "accessor": "avg_row_size",
- "className": "padded",
- "filterable": false,
- "show": true,
- "width": 100,
- },
- {
- "Cell": [Function],
- "Header":
- Replicated
-
- size
- ,
- "accessor": "replicated_size",
- "className": "padded",
- "filterable": false,
- "show": true,
- "width": 100,
- },
- {
- "Cell": [Function],
- "Header": "Compaction",
- "accessor": [Function],
- "filterable": false,
- "id": "compactionStatus",
- "show": true,
- "width": 180,
- },
- {
- "Cell": [Function],
- "Header":
- % Compacted
-
- bytes / segments / intervals
- ,
- "accessor": [Function],
- "className": "padded",
- "filterable": false,
- "id": "percentCompacted",
- "show": true,
- "width": 200,
- },
- {
- "Cell": [Function],
- "Header":
- Left to be
-
- compacted
- ,
- "accessor": [Function],
- "className": "padded",
- "filterable": false,
- "id": "leftToBeCompacted",
- "show": true,
- "width": 100,
- },
+
+
+ Datasource
+
+ name
+ ,
+ "accessor": "datasource",
+ "show": true,
+ "width": 150,
+ },
+ {
+ "Cell": [Function],
+ "Header": "Availability",
+ "accessor": "num_segments",
+ "className": "padded",
+ "filterable": false,
+ "show": true,
+ "sortMethod": [Function],
+ "width": 220,
+ },
+ {
+ "Cell": [Function],
+ "Header":
+ Historical
+
+ load/drop queues
+ ,
+ "accessor": "num_segments_to_load",
+ "className": "padded",
+ "filterable": false,
+ "show": true,
+ "width": 180,
+ },
+ {
+ "Cell": [Function],
+ "Header":
+ Total
+
+ data size
+ ,
+ "accessor": "total_data_size",
+ "className": "padded",
+ "filterable": false,
+ "show": true,
+ "width": 100,
+ },
+ {
+ "Cell": [Function],
+ "Header": "Running tasks",
+ "accessor": [Function],
+ "filterable": false,
+ "id": "running_tasks",
+ "show": true,
+ "width": 200,
+ },
+ {
+ "Cell": [Function],
+ "Header":
+ Segment rows
+
+ minimum / average / maximum
+ ,
+ "accessor": "avg_segment_rows",
+ "className": "padded",
+ "filterable": false,
+ "show": true,
+ "width": 230,
+ },
+ {
+ "Cell": [Function],
+ "Header":
+ Segment size
+
+ minimum / average / maximum
+ ,
+ "accessor": "avg_segment_size",
+ "className": "padded",
+ "filterable": false,
+ "show": false,
+ "width": 270,
+ },
+ {
+ "Cell": [Function],
+ "Header":
+ Segment
+
+ granularity
+ ,
+ "accessor": [Function],
+ "className": "padded",
+ "filterable": false,
+ "id": "segment_granularity",
+ "show": false,
+ "width": 100,
+ },
+ {
+ "Cell": [Function],
+ "Header":
+ Total
+
+ rows
+ ,
+ "accessor": "total_rows",
+ "className": "padded",
+ "filterable": false,
+ "show": true,
+ "width": 110,
+ },
+ {
+ "Cell": [Function],
+ "Header":
+ Avg. row size
+
+ (bytes)
+ ,
+ "accessor": "avg_row_size",
+ "className": "padded",
+ "filterable": false,
+ "show": true,
+ "width": 100,
+ },
+ {
+ "Cell": [Function],
+ "Header":
+ Replicated
+
+ size
+ ,
+ "accessor": "replicated_size",
+ "className": "padded",
+ "filterable": false,
+ "show": true,
+ "width": 100,
+ },
+ {
+ "Cell": [Function],
+ "Header": "Compaction",
+ "accessor": [Function],
+ "filterable": false,
+ "id": "compactionStatus",
+ "show": true,
+ "width": 180,
+ },
+ {
+ "Cell": [Function],
+ "Header":
+ % Compacted
+
+ bytes / segments / intervals
+ ,
+ "accessor": [Function],
+ "className": "padded",
+ "filterable": false,
+ "id": "percentCompacted",
+ "show": true,
+ "width": 200,
+ },
+ {
+ "Cell": [Function],
+ "Header":
+ Left to be
+
+ compacted
+ ,
+ "accessor": [Function],
+ "className": "padded",
+ "filterable": false,
+ "id": "leftToBeCompacted",
+ "show": true,
+ "width": 100,
+ },
+ {
+ "Cell": [Function],
+ "Header": "Retention",
+ "accessor": [Function],
+ "filterable": false,
+ "id": "retention",
+ "show": true,
+ "width": 200,
+ },
+ {
+ "Cell": [Function],
+ "Header": "Actions",
+ "accessor": "datasource",
+ "filterable": false,
+ "id": "actions",
+ "sortable": false,
+ "width": 70,
+ },
+ ]
+ }
+ data={[]}
+ defaultExpanded={{}}
+ defaultFilterMethod={[Function]}
+ defaultFiltered={[]}
+ defaultPage={0}
+ defaultPageSize={50}
+ defaultResized={[]}
+ defaultSortDesc={false}
+ defaultSortMethod={[Function]}
+ defaultSorted={[]}
+ expanderDefaults={
{
- "Cell": [Function],
- "Header": "Actions",
- "accessor": "datasource",
"filterable": false,
- "id": "actions",
+ "resizable": false,
"sortable": false,
- "width": 70,
- },
- ]
- }
- data={[]}
- defaultExpanded={{}}
- defaultFilterMethod={[Function]}
- defaultFiltered={[]}
- defaultPage={0}
- defaultPageSize={50}
- defaultResized={[]}
- defaultSortDesc={false}
- defaultSortMethod={[Function]}
- defaultSorted={[]}
- expanderDefaults={
- {
- "filterable": false,
- "resizable": false,
- "sortable": false,
- "width": 35,
+ "width": 35,
+ }
}
- }
- filterable={true}
- filtered={[]}
- freezeWhenExpanded={false}
- getLoadingProps={[Function]}
- getNoDataProps={[Function]}
- getPaginationProps={[Function]}
- getProps={[Function]}
- getResizerProps={[Function]}
- getTableProps={[Function]}
- getTbodyProps={[Function]}
- getTdProps={[Function]}
- getTfootProps={[Function]}
- getTfootTdProps={[Function]}
- getTfootTrProps={[Function]}
- getTheadFilterProps={[Function]}
- getTheadFilterThProps={[Function]}
- getTheadFilterTrProps={[Function]}
- getTheadGroupProps={[Function]}
- getTheadGroupThProps={[Function]}
- getTheadGroupTrProps={[Function]}
- getTheadProps={[Function]}
- getTheadThProps={[Function]}
- getTheadTrProps={[Function]}
- getTrGroupProps={[Function]}
- getTrProps={[Function]}
- groupedByPivotKey="_groupedByPivot"
- indexKey="_index"
- loading={true}
- loadingText="Loading..."
- multiSort={true}
- nestingLevelKey="_nestingLevel"
- nextText="Next"
- noDataText=""
- ofText="of"
- onFetchData={[Function]}
- onFilteredChange={[Function]}
- originalKey="_original"
- pageJumpText="jump to page"
- pageSizeOptions={
- [
- 50,
- 100,
- 200,
- ]
- }
- pageText="Page"
- pivotDefaults={{}}
- pivotIDKey="_pivotID"
- pivotValKey="_pivotVal"
- previousText="Previous"
- resizable={true}
- resolveData={[Function]}
- rowsSelectorText="rows per page"
- rowsText="rows"
- showPageJump={true}
- showPageSizeOptions={true}
- showPagination={false}
- showPaginationBottom={true}
- showPaginationTop={false}
- sortable={true}
- style={{}}
- subRowsKey="_subRows"
- />
+ filterable={true}
+ filtered={[]}
+ freezeWhenExpanded={false}
+ getLoadingProps={[Function]}
+ getNoDataProps={[Function]}
+ getPaginationProps={[Function]}
+ getProps={[Function]}
+ getResizerProps={[Function]}
+ getTableProps={[Function]}
+ getTbodyProps={[Function]}
+ getTdProps={[Function]}
+ getTfootProps={[Function]}
+ getTfootTdProps={[Function]}
+ getTfootTrProps={[Function]}
+ getTheadFilterProps={[Function]}
+ getTheadFilterThProps={[Function]}
+ getTheadFilterTrProps={[Function]}
+ getTheadGroupProps={[Function]}
+ getTheadGroupThProps={[Function]}
+ getTheadGroupTrProps={[Function]}
+ getTheadProps={[Function]}
+ getTheadThProps={[Function]}
+ getTheadTrProps={[Function]}
+ getTrGroupProps={[Function]}
+ getTrProps={[Function]}
+ groupedByPivotKey="_groupedByPivot"
+ indexKey="_index"
+ loading={true}
+ loadingText="Loading..."
+ multiSort={true}
+ nestingLevelKey="_nestingLevel"
+ nextText="Next"
+ noDataText=""
+ ofText="of"
+ onFetchData={[Function]}
+ onFilteredChange={[Function]}
+ originalKey="_original"
+ pageJumpText="jump to page"
+ pageSizeOptions={
+ [
+ 50,
+ 100,
+ 200,
+ ]
+ }
+ pageText="Page"
+ pivotDefaults={{}}
+ pivotIDKey="_pivotID"
+ pivotValKey="_pivotVal"
+ previousText="Previous"
+ resizable={true}
+ resolveData={[Function]}
+ rowsSelectorText="rows per page"
+ rowsText="rows"
+ showPageJump={true}
+ showPageSizeOptions={true}
+ showPagination={false}
+ showPaginationBottom={true}
+ showPaginationTop={false}
+ sortable={true}
+ style={{}}
+ subRowsKey="_subRows"
+ />
+
`;
diff --git a/web-console/src/views/datasources-view/datasources-view.scss b/web-console/src/views/datasources-view/datasources-view.scss
index 497be39b3c58..b0abe78ec56a 100644
--- a/web-console/src/views/datasources-view/datasources-view.scss
+++ b/web-console/src/views/datasources-view/datasources-view.scss
@@ -23,25 +23,30 @@
width: 100%;
overflow: auto;
- .clickable-cell {
- cursor: pointer;
- }
-
- .ReactTable {
+ .splitter-layout {
position: absolute;
top: $view-control-bar-height + $standard-padding;
bottom: 0;
width: 100%;
- }
- &.show-segment-timeline {
- .segment-timeline {
- height: calc(50% - 55px);
- margin-top: 10px;
+ & > .layout-splitter:hover {
+ background: black;
+ opacity: 0.1;
+ border-radius: 2px;
}
+ }
+
+ .segment-timeline {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ }
+
+ .ReactTable {
+ @include pin-full;
- .ReactTable {
- top: 50%;
+ .clickable-cell {
+ cursor: pointer;
}
}
}
diff --git a/web-console/src/views/datasources-view/datasources-view.tsx b/web-console/src/views/datasources-view/datasources-view.tsx
index b4a435e0f993..3fb6a5be40d8 100644
--- a/web-console/src/views/datasources-view/datasources-view.tsx
+++ b/web-console/src/views/datasources-view/datasources-view.tsx
@@ -19,7 +19,6 @@
import { FormGroup, InputGroup, Intent, MenuItem, Switch, Tag } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import { SqlQuery, T } from '@druid-toolkit/query';
-import classNames from 'classnames';
import { sum } from 'd3-array';
import React from 'react';
import type { Filter } from 'react-table';
@@ -34,6 +33,7 @@ import {
MoreButton,
RefreshButton,
SegmentTimeline,
+ SplitterLayout,
TableClickableCell,
TableColumnSelector,
type TableColumnSelectorColumn,
@@ -1678,11 +1678,7 @@ GROUP BY 1, 2`;
} = this.state;
return (
-
+
{
@@ -1717,8 +1713,17 @@ GROUP BY 1, 2`;
tableColumnsHidden={visibleColumns.getHiddenColumns()}
/>
- {showSegmentTimeline &&
}
- {this.renderDatasourcesTable()}
+
+ {showSegmentTimeline && }
+ {this.renderDatasourcesTable()}
+
{datasourceTableActionDialogId && (
+
void this.initializeLookup()}
+ large
+ intent={Intent.PRIMARY}
/>
);
diff --git a/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap b/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap
index e91e6d44eafb..e177003b7eed 100755
--- a/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap
+++ b/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap
@@ -1,90 +1,97 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SegmentsView matches snapshot 1`] = `
-
-
+
-
-
-
- Group by
-
-
-
- None
-
-
- Interval
-
-
-
-
-
-
-
+
+ Group by
+
+
+
+ None
+
+
+ Interval
+
+
+
+
-
+
+
+
+
+
-
-
+
+
`;
diff --git a/web-console/src/views/segments-view/segments-view.scss b/web-console/src/views/segments-view/segments-view.scss
index 5f9cb4fc6cab..a45d8860db1f 100644
--- a/web-console/src/views/segments-view/segments-view.scss
+++ b/web-console/src/views/segments-view/segments-view.scss
@@ -22,12 +22,28 @@
height: 100%;
width: 100%;
- .ReactTable {
+ .splitter-layout {
position: absolute;
top: $view-control-bar-height + $standard-padding;
bottom: 0;
width: 100%;
+ & > .layout-splitter:hover {
+ background: black;
+ opacity: 0.1;
+ border-radius: 2px;
+ }
+ }
+
+ .segment-timeline {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ }
+
+ .ReactTable {
+ @include pin-full;
+
.-totalPages {
display: none;
}
@@ -60,15 +76,4 @@
}
}
}
-
- &.show-segment-timeline {
- .segment-timeline {
- height: calc(50% - 55px);
- margin-top: 10px;
- }
-
- .ReactTable {
- top: 50%;
- }
- }
}
diff --git a/web-console/src/views/segments-view/segments-view.tsx b/web-console/src/views/segments-view/segments-view.tsx
index b3793cc44631..9e0d680c8547 100644
--- a/web-console/src/views/segments-view/segments-view.tsx
+++ b/web-console/src/views/segments-view/segments-view.tsx
@@ -19,7 +19,6 @@
import { Button, ButtonGroup, Intent, Label, MenuItem, Switch, Tag } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import { C, L, SqlComparison, SqlExpression } from '@druid-toolkit/query';
-import classNames from 'classnames';
import * as JSONBig from 'json-bigint-native';
import React from 'react';
import type { Filter } from 'react-table';
@@ -34,6 +33,7 @@ import {
MoreButton,
RefreshButton,
SegmentTimeline,
+ SplitterLayout,
TableClickableCell,
TableColumnSelector,
type TableColumnSelectorColumn,
@@ -969,65 +969,68 @@ END AS "time_span"`,
const { groupByInterval } = this.state;
return (
- <>
-
-
- {
- if (auto && hasPopoverOpen()) return;
- this.segmentsQueryManager.rerunLastQuery(auto);
+
+
+ {
+ if (auto && hasPopoverOpen()) return;
+ this.segmentsQueryManager.rerunLastQuery(auto);
+ }}
+ localStorageKey={LocalStorageKeys.SEGMENTS_REFRESH_RATE}
+ />
+ Group by
+
+ {
+ this.setState({ groupByInterval: false });
+ this.fetchData(false);
}}
- localStorageKey={LocalStorageKeys.SEGMENTS_REFRESH_RATE}
- />
- Group by
-
- {
- this.setState({ groupByInterval: false });
- this.fetchData(false);
- }}
- >
- None
-
- {
- this.setState({ groupByInterval: true });
- this.fetchData(true);
- }}
- >
- Interval
-
-
- {this.renderBulkSegmentsActions()}
- this.setState({ showSegmentTimeline: !showSegmentTimeline })}
- disabled={!capabilities.hasSqlOrCoordinatorAccess()}
- />
-
- this.setState(prevState => ({
- visibleColumns: prevState.visibleColumns.toggle(column),
- }))
- }
- onClose={added => {
- if (!added) return;
- this.fetchData(groupByInterval);
+ >
+ None
+
+ {
+ this.setState({ groupByInterval: true });
+ this.fetchData(true);
}}
- tableColumnsHidden={visibleColumns.getHiddenColumns()}
- />
-
+ >
+ Interval
+
+
+ {this.renderBulkSegmentsActions()}
+
this.setState({ showSegmentTimeline: !showSegmentTimeline })}
+ disabled={!capabilities.hasSqlOrCoordinatorAccess()}
+ />
+
+ this.setState(prevState => ({
+ visibleColumns: prevState.visibleColumns.toggle(column),
+ }))
+ }
+ onClose={added => {
+ if (!added) return;
+ this.fetchData(groupByInterval);
+ }}
+ tableColumnsHidden={visibleColumns.getHiddenColumns()}
+ />
+
+
{showSegmentTimeline && }
{this.renderSegmentsTable()}
-
+
{this.renderTerminateSegmentAction()}
{segmentTableActionDialogId && datasourceTableActionDialogId && (
this.setState({ showFullShardSpec: undefined })}
/>
)}
- >
+
);
}
}
diff --git a/web-console/src/views/workbench-view/column-tree/column-tree-menu/number-menu-items/number-menu-items.tsx b/web-console/src/views/workbench-view/column-tree/column-tree-menu/number-menu-items/number-menu-items.tsx
index 8ec4573c2adc..bf383f99254a 100644
--- a/web-console/src/views/workbench-view/column-tree/column-tree-menu/number-menu-items/number-menu-items.tsx
+++ b/web-console/src/views/workbench-view/column-tree/column-tree-menu/number-menu-items/number-menu-items.tsx
@@ -135,10 +135,11 @@ export const NumberMenuItems = React.memo(function NumberMenuItems(props: Number
return (
- {aggregateMenuItem(F('SUM', column), `sum_${columnName}`)}
- {aggregateMenuItem(F('MIN', column), `min_${columnName}`)}
- {aggregateMenuItem(F('MAX', column), `max_${columnName}`)}
- {aggregateMenuItem(F('AVG', column), `avg_${columnName}`)}
+ {aggregateMenuItem(F.sum(column), `sum_${columnName}`)}
+ {aggregateMenuItem(F.min(column), `min_${columnName}`)}
+ {aggregateMenuItem(F.max(column), `max_${columnName}`)}
+ {aggregateMenuItem(F.avg(column), `avg_${columnName}`)}
+ {aggregateMenuItem(F.countDistinct(column), `dist_${columnName}`)}
{aggregateMenuItem(F('APPROX_QUANTILE', column, 0.98), `p98_${columnName}`)}
{aggregateMenuItem(F('LATEST', column), `latest_${columnName}`)}
diff --git a/web-console/src/views/workbench-view/column-tree/column-tree.tsx b/web-console/src/views/workbench-view/column-tree/column-tree.tsx
index a89b4da57d1c..385cab937cef 100644
--- a/web-console/src/views/workbench-view/column-tree/column-tree.tsx
+++ b/web-console/src/views/workbench-view/column-tree/column-tree.tsx
@@ -104,7 +104,7 @@ type SearchMode = 'tables-and-columns' | 'tables-only' | 'columns-only';
const SEARCH_MODES: SearchMode[] = ['tables-and-columns', 'tables-only', 'columns-only'];
-const SEARCH_MDOE_TITLE: Record
= {
+const SEARCH_MODE_TITLE: Record = {
'tables-and-columns': 'Tables and columns',
'tables-only': 'Tables only',
'columns-only': 'Columns only',
@@ -641,7 +641,7 @@ export class ColumnTree extends React.PureComponent this.setState({ searchMode: mode })}
/>
))}
diff --git a/web-console/src/views/workbench-view/execution-summary-panel/execution-summary-panel.tsx b/web-console/src/views/workbench-view/execution-summary-panel/execution-summary-panel.tsx
index b1930ae50d2b..62cc9c3a375b 100644
--- a/web-console/src/views/workbench-view/execution-summary-panel/execution-summary-panel.tsx
+++ b/web-console/src/views/workbench-view/execution-summary-panel/execution-summary-panel.tsx
@@ -142,7 +142,7 @@ export const ExecutionSummaryPanel = React.memo(function ExecutionSummaryPanel(
,
diff --git a/web-console/src/views/workbench-view/query-tab/query-tab.scss b/web-console/src/views/workbench-view/query-tab/query-tab.scss
index 4c143194a872..54c0d780da14 100644
--- a/web-console/src/views/workbench-view/query-tab/query-tab.scss
+++ b/web-console/src/views/workbench-view/query-tab/query-tab.scss
@@ -19,6 +19,7 @@
@import '../../../variables';
$vertical-gap: 6px;
+$visible-splitter-size: 3px;
.query-tab {
position: relative;
@@ -27,14 +28,26 @@ $vertical-gap: 6px;
background: $dark-gray2;
}
- .splitter-layout {
- position: absolute;
- width: 100%;
+ .splitter-layout.splitter-layout-vertical {
height: 100%;
- &.splitter-layout-vertical > .layout-splitter {
- height: 3px;
- background-color: $gray1;
+ & > .layout-splitter {
+ height: 3px + $vertical-gap * 2;
+ position: relative;
+
+ &::after {
+ content: '';
+ background-color: $gray1;
+ position: absolute;
+ top: $vertical-gap;
+ width: 100%;
+ height: $visible-splitter-size;
+ border-radius: 2px;
+ }
+
+ &:hover::after {
+ background-color: $gray2;
+ }
}
}
@@ -42,7 +55,7 @@ $vertical-gap: 6px;
position: absolute;
width: 100%;
top: 0;
- bottom: $vertical-gap;
+ bottom: 0;
.query-section {
position: absolute;
@@ -84,7 +97,7 @@ $vertical-gap: 6px;
.output-section {
position: absolute;
width: 100%;
- top: $vertical-gap;
+ top: 0;
bottom: 0;
@include card-like;
diff --git a/web-console/src/views/workbench-view/query-tab/query-tab.tsx b/web-console/src/views/workbench-view/query-tab/query-tab.tsx
index af8cc0bcde92..63865054446f 100644
--- a/web-console/src/views/workbench-view/query-tab/query-tab.tsx
+++ b/web-console/src/views/workbench-view/query-tab/query-tab.tsx
@@ -22,10 +22,9 @@ import { QueryResult, QueryRunner, SqlQuery } from '@druid-toolkit/query';
import axios from 'axios';
import type { JSX } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
-import SplitterLayout from 'react-splitter-layout';
import { useStore } from 'zustand';
-import { Loader, QueryErrorPane } from '../../../components';
+import { Loader, QueryErrorPane, SplitterLayout } from '../../../components';
import type { CapacityInfo, DruidEngine, LastExecution, QueryContext } from '../../../druid-models';
import { DEFAULT_SERVER_QUERY_CONTEXT, Execution, WorkbenchQuery } from '../../../druid-models';
import {
@@ -44,9 +43,9 @@ import {
deepGet,
DruidError,
findAllSqlQueriesInText,
- localStorageGet,
+ localStorageGetJson,
LocalStorageKeys,
- localStorageSet,
+ localStorageSetJson,
QueryManager,
} from '../../../utils';
import { CapacityAlert } from '../capacity-alert/capacity-alert';
@@ -70,6 +69,10 @@ const queryRunner = new QueryRunner({
inflateDateStrategy: 'none',
});
+function handleSecondaryPaneSizeChange(secondaryPaneSize: number) {
+ localStorageSetJson(LocalStorageKeys.WORKBENCH_PANE_SIZE, secondaryPaneSize);
+}
+
export interface QueryTabProps
extends Pick<
RunPanelProps,
@@ -180,15 +183,12 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
);
function shouldAutoRun(): boolean {
- if (query.getEffectiveEngine() !== 'sql-native') return false;
+ const effectiveEngine = query.getEffectiveEngine();
+ if (effectiveEngine !== 'sql-native' && effectiveEngine !== 'sql-msq-dart') return false;
const queryDuration = executionState.data?.result?.queryDuration;
return Boolean(queryDuration && queryDuration < 10000);
}
- const handleSecondaryPaneSizeChange = useCallback((secondaryPaneSize: number) => {
- localStorageSet(LocalStorageKeys.WORKBENCH_PANE_SIZE, String(secondaryPaneSize));
- }, []);
-
const queryInputRef = useRef(null);
const cachedExecutionState = ExecutionStateCache.getState(id);
@@ -296,9 +296,10 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
if (deepGet(query, 'context.fullReport') && dartResponse[0][0] === 'fullReport') {
const dartReport = dartResponse[dartResponse.length - 1][0];
- return Execution.fromTaskReport(dartReport)
- .changeEngine('sql-msq-dart')
- .changeSqlQuery(query.query, query.context);
+ return Execution.fromDartReport(dartReport).changeSqlQuery(
+ query.query,
+ query.context,
+ );
} else {
return Execution.fromResult(
engine,
@@ -308,7 +309,7 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
query.header,
query.typesHeader,
query.sqlTypesHeader,
- ),
+ ).changeQueryDuration(Date.now() - startTime.valueOf()),
).changeSqlQuery(query.query, query.context);
}
},
@@ -372,6 +373,16 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
const execution = executionState.data;
+ // This is the execution that would be shown in the output pane, it is either the actual execution or a result
+ // execution that will be shown under the loader
+ const executionToShow =
+ execution ||
+ (() => {
+ if (executionState.intermediate) return;
+ const e = executionState.getSomeData();
+ return e?.result ? e : undefined;
+ })();
+
const incrementMetadataVersion = useStore(
metadataStateStore,
useCallback(state => state.increment, []),
@@ -456,7 +467,9 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
)}
- {execution &&
- (execution.error ? (
+ {executionToShow &&
+ (executionToShow.error ? (
-
- {execution.stages && (
+
+ {executionToShow.stages && (
onDetails(execution, 'error')}
- onWarningClick={() => onDetails(execution, 'warnings')}
+ execution={executionToShow}
+ onErrorClick={() => onDetails(executionToShow, 'error')}
+ onWarningClick={() => onDetails(executionToShow, 'warnings')}
goToTask={goToTask}
/>
)}
- ) : execution.result ? (
+ ) : executionToShow.result ? (
- ) : execution.isSuccessfulIngest() ? (
+ ) : executionToShow.isSuccessfulIngest() ? (
) : (
- {`Execution completed with status: ${execution.status}`}
+ {`Execution completed with status: ${executionToShow.status}`}
onDetails(execution, 'error')}
- onWarningClick={() => onDetails(execution, 'warnings')}
+ execution={executionToShow}
+ onErrorClick={() => onDetails(executionToShow, 'error')}
+ onWarningClick={() => onDetails(executionToShow, 'warnings')}
goToTask={goToTask}
/>
diff --git a/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.scss b/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.scss
index 7f729111cc4c..8a84a7670734 100644
--- a/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.scss
+++ b/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.scss
@@ -37,6 +37,9 @@
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
padding: 8px 10px;
user-select: none;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
.close-button {
position: absolute;
diff --git a/web-console/src/views/workbench-view/workbench-view.scss b/web-console/src/views/workbench-view/workbench-view.scss
index be006239f699..914fc155dcbb 100644
--- a/web-console/src/views/workbench-view/workbench-view.scss
+++ b/web-console/src/views/workbench-view/workbench-view.scss
@@ -19,18 +19,27 @@
@import '../../variables';
$column-tree-width: 250px;
-$recent-query-task-panel-width: 250px;
.workbench-view {
height: 100%;
width: 100%;
+ .workbench-splitter {
+ height: 100%;
+
+ & > .layout-splitter:hover {
+ background: black;
+ opacity: 0.1;
+ border-radius: 2px;
+ }
+ }
+
.column-tree {
position: absolute;
top: 0;
left: 0;
height: 100%;
- width: $column-tree-width;
+ width: 100%;
@include card-like;
}
@@ -39,7 +48,7 @@ $recent-query-task-panel-width: 250px;
top: 0;
right: 0;
height: 100%;
- width: $recent-query-task-panel-width;
+ width: 100%;
display: flex;
flex-direction: column;
gap: 2px;
@@ -54,8 +63,7 @@ $recent-query-task-panel-width: 250px;
position: absolute;
top: 0;
height: 100%;
- left: $column-tree-width + $thin-padding;
- right: $recent-query-task-panel-width + $thin-padding;
+ width: 100%;
.tab-and-tool-bar {
position: absolute;
diff --git a/web-console/src/views/workbench-view/workbench-view.tsx b/web-console/src/views/workbench-view/workbench-view.tsx
index 106fa3bff7ae..f647bfbc5c7c 100644
--- a/web-console/src/views/workbench-view/workbench-view.tsx
+++ b/web-console/src/views/workbench-view/workbench-view.tsx
@@ -32,7 +32,7 @@ import classNames from 'classnames';
import copy from 'copy-to-clipboard';
import React from 'react';
-import { MenuCheckbox } from '../../components';
+import { MenuCheckbox, SplitterLayout } from '../../components';
import { SpecDialog, StringInputDialog } from '../../dialogs';
import type {
CapacityInfo,
@@ -87,6 +87,14 @@ import './workbench-view.scss';
const LAST_DAY = SqlExpression.parse(`__time >= CURRENT_TIMESTAMP - INTERVAL '1' DAY`);
+function handleLeftPaneSizeChange(paneSize: number) {
+ localStorageSetJson(LocalStorageKeys.WORKBENCH_LEFT_SIZE, paneSize);
+}
+
+function handleRightPaneSizeChange(paneSize: number) {
+ localStorageSetJson(LocalStorageKeys.WORKBENCH_RIGHT_SIZE, paneSize);
+}
+
function cleanupTabEntry(tabEntry: TabEntry): void {
const discardedId = tabEntry.id;
WorkbenchRunningPromises.deletePromise(discardedId);
@@ -912,40 +920,60 @@ export class WorkbenchView extends React.PureComponent
- {!columnMetadataState.isError() && (
-
- )}
- {this.renderCenterPanel()}
- {showRightPanel && (
-
- {showRecentQueryTaskPanel && (
-
+
+
+ {!columnMetadataState.isError() && (
+
)}
- {showCurrentDartPanel && (
-
- )}
-
- )}
+ {this.renderCenterPanel()}
+
+ {showRightPanel && (
+
+ {showRecentQueryTaskPanel && (
+
+ )}
+ {showCurrentDartPanel && (
+
+ )}
+
+ )}
+
+
{this.renderExecutionDetailsDialog()}
{this.renderExplainDialog()}
{this.renderHistoryDialog()}