From 86392965e698f9adaf2f5cf615c12cbedd7b83e8 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Mon, 23 Sep 2024 16:21:54 -0700 Subject: [PATCH 1/6] add console support for Dart engine This reverts commit 6e46edf15dd55e5c51a1a4068e83deba4f22529b. --- .../__snapshots__/header-bar.spec.tsx.snap | 1 + .../druid-models/druid-engine/druid-engine.ts | 9 +- .../workbench-query/workbench-query.ts | 6 +- web-console/src/helpers/capabilities.ts | 32 +++- web-console/src/utils/druid-query.ts | 13 ++ web-console/src/utils/local-storage-keys.tsx | 1 + .../__snapshots__/home-view.spec.tsx.snap | 15 ++ .../current-dart-panel.scss | 121 +++++++++++++ .../current-dart-panel/current-dart-panel.tsx | 166 ++++++++++++++++++ .../dart-details-dialog.scss | 35 ++++ .../dart-details-dialog.tsx | 48 +++++ .../execution-summary-panel.tsx | 2 +- .../explain-dialog/explain-dialog.tsx | 5 + .../workbench-view/query-tab/query-tab.tsx | 64 ++++++- .../workbench-view/run-panel/run-panel.tsx | 9 +- .../views/workbench-view/workbench-view.scss | 2 +- .../views/workbench-view/workbench-view.tsx | 66 +++++-- 17 files changed, 572 insertions(+), 23 deletions(-) create mode 100644 web-console/src/views/workbench-view/current-dart-panel/current-dart-panel.scss create mode 100644 web-console/src/views/workbench-view/current-dart-panel/current-dart-panel.tsx create mode 100644 web-console/src/views/workbench-view/dart-details-dialog/dart-details-dialog.scss create mode 100644 web-console/src/views/workbench-view/dart-details-dialog/dart-details-dialog.tsx diff --git a/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap b/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap index c3310e2c590e..d3e24a6c35ac 100644 --- a/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap +++ b/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap @@ -213,6 +213,7 @@ exports[`HeaderBar matches snapshot 1`] = ` Capabilities { "coordinator": true, "maxTaskSlots": undefined, + "multiStageQueryDart": true, "multiStageQueryTask": true, "overlord": true, "queryType": "nativeAndSql", diff --git a/web-console/src/druid-models/druid-engine/druid-engine.ts b/web-console/src/druid-models/druid-engine/druid-engine.ts index f1942e50c543..335d22e96c00 100644 --- a/web-console/src/druid-models/druid-engine/druid-engine.ts +++ b/web-console/src/druid-models/druid-engine/druid-engine.ts @@ -16,9 +16,14 @@ * limitations under the License. */ -export type DruidEngine = 'native' | 'sql-native' | 'sql-msq-task'; +export type DruidEngine = 'native' | 'sql-native' | 'sql-msq-task' | 'sql-msq-dart'; -export const DRUID_ENGINES: DruidEngine[] = ['native', 'sql-native', 'sql-msq-task']; +export const DRUID_ENGINES: DruidEngine[] = [ + 'native', + 'sql-native', + 'sql-msq-task', + 'sql-msq-dart', +]; export function validDruidEngine( possibleDruidEngine: string | undefined, diff --git a/web-console/src/druid-models/workbench-query/workbench-query.ts b/web-console/src/druid-models/workbench-query/workbench-query.ts index dd75c94b75e7..716fe573a064 100644 --- a/web-console/src/druid-models/workbench-query/workbench-query.ts +++ b/web-console/src/druid-models/workbench-query/workbench-query.ts @@ -528,7 +528,7 @@ export class WorkbenchQuery { }; let cancelQueryId: string | undefined; - if (engine === 'sql-native') { + if (engine === 'sql-native' || engine === 'sql-msq-dart') { cancelQueryId = apiQuery.context.sqlQueryId; if (!cancelQueryId) { // If the sqlQueryId is not explicitly set on the context generate one, so it is possible to cancel the query. @@ -550,6 +550,10 @@ export class WorkbenchQuery { apiQuery.context.sqlStringifyArrays ??= false; } + if (engine === 'sql-msq-dart') { + apiQuery.context.fullReport ??= true; + } + if (Array.isArray(queryParameters) && queryParameters.length) { apiQuery.parameters = queryParameters; } diff --git a/web-console/src/helpers/capabilities.ts b/web-console/src/helpers/capabilities.ts index fe125b67231a..013f9368c58c 100644 --- a/web-console/src/helpers/capabilities.ts +++ b/web-console/src/helpers/capabilities.ts @@ -37,6 +37,7 @@ export type QueryType = 'none' | 'nativeOnly' | 'nativeAndSql'; export interface CapabilitiesValue { queryType: QueryType; multiStageQueryTask: boolean; + multiStageQueryDart: boolean; coordinator: boolean; overlord: boolean; maxTaskSlots?: number; @@ -53,6 +54,7 @@ export class Capabilities { private readonly queryType: QueryType; private readonly multiStageQueryTask: boolean; + private readonly multiStageQueryDart: boolean; private readonly coordinator: boolean; private readonly overlord: boolean; private readonly maxTaskSlots?: number; @@ -139,6 +141,15 @@ export class Capabilities { } } + static async detectMultiStageQueryDart(): Promise { + try { + const resp = await Api.instance.get(`/druid/v2/sql/dart/enabled?capabilities`); + return Boolean(resp.data.enabled); + } catch { + return false; + } + } + static async detectCapabilities(): Promise { const queryType = await Capabilities.detectQueryType(); if (typeof queryType === 'undefined') return; @@ -154,11 +165,15 @@ export class Capabilities { coordinator = overlord = await Capabilities.detectManagementProxy(); } - const multiStageQueryTask = await Capabilities.detectMultiStageQueryTask(); + const [multiStageQueryTask, multiStageQueryDart] = await Promise.all([ + Capabilities.detectMultiStageQueryTask(), + Capabilities.detectMultiStageQueryDart(), + ]); return new Capabilities({ queryType, multiStageQueryTask, + multiStageQueryDart, coordinator, overlord, }); @@ -179,6 +194,7 @@ export class Capabilities { constructor(value: CapabilitiesValue) { this.queryType = value.queryType; this.multiStageQueryTask = value.multiStageQueryTask; + this.multiStageQueryDart = value.multiStageQueryDart; this.coordinator = value.coordinator; this.overlord = value.overlord; this.maxTaskSlots = value.maxTaskSlots; @@ -188,6 +204,7 @@ export class Capabilities { return { queryType: this.queryType, multiStageQueryTask: this.multiStageQueryTask, + multiStageQueryDart: this.multiStageQueryDart, coordinator: this.coordinator, overlord: this.overlord, maxTaskSlots: this.maxTaskSlots, @@ -248,6 +265,10 @@ export class Capabilities { return this.multiStageQueryTask; } + public hasMultiStageQueryDart(): boolean { + return this.multiStageQueryDart; + } + public getSupportedQueryEngines(): DruidEngine[] { const queryEngines: DruidEngine[] = ['native']; if (this.hasSql()) { @@ -256,6 +277,9 @@ export class Capabilities { if (this.hasMultiStageQueryTask()) { queryEngines.push('sql-msq-task'); } + if (this.hasMultiStageQueryDart()) { + queryEngines.push('sql-msq-dart'); + } return queryEngines; } @@ -282,36 +306,42 @@ export class Capabilities { Capabilities.FULL = new Capabilities({ queryType: 'nativeAndSql', multiStageQueryTask: true, + multiStageQueryDart: true, coordinator: true, overlord: true, }); Capabilities.NO_SQL = new Capabilities({ queryType: 'nativeOnly', multiStageQueryTask: false, + multiStageQueryDart: false, coordinator: true, overlord: true, }); Capabilities.COORDINATOR_OVERLORD = new Capabilities({ queryType: 'none', multiStageQueryTask: false, + multiStageQueryDart: false, coordinator: true, overlord: true, }); Capabilities.COORDINATOR = new Capabilities({ queryType: 'none', multiStageQueryTask: false, + multiStageQueryDart: false, coordinator: true, overlord: false, }); Capabilities.OVERLORD = new Capabilities({ queryType: 'none', multiStageQueryTask: false, + multiStageQueryDart: false, coordinator: false, overlord: true, }); Capabilities.NO_PROXY = new Capabilities({ queryType: 'nativeAndSql', multiStageQueryTask: true, + multiStageQueryDart: false, coordinator: false, overlord: false, }); diff --git a/web-console/src/utils/druid-query.ts b/web-console/src/utils/druid-query.ts index fba63b946000..d1481366fa78 100644 --- a/web-console/src/utils/druid-query.ts +++ b/web-console/src/utils/druid-query.ts @@ -342,6 +342,19 @@ export async function queryDruidSql( return sqlResultResp.data; } +export async function queryDruidSqlDart( + sqlQueryPayload: Record, + cancelToken?: CancelToken, +): Promise { + let sqlResultResp: AxiosResponse; + try { + sqlResultResp = await Api.instance.post('/druid/v2/sql/dart', sqlQueryPayload, { cancelToken }); + } catch (e) { + throw new Error(getDruidErrorMessage(e)); + } + return sqlResultResp.data; +} + export interface QueryExplanation { query: any; signature: { name: string; type: string }[]; diff --git a/web-console/src/utils/local-storage-keys.tsx b/web-console/src/utils/local-storage-keys.tsx index 7c797d2c89ad..3d42ddc151e3 100644 --- a/web-console/src/utils/local-storage-keys.tsx +++ b/web-console/src/utils/local-storage-keys.tsx @@ -53,6 +53,7 @@ export const LocalStorageKeys = { WORKBENCH_PANE_SIZE: 'workbench-pane-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, SQL_DATA_LOADER_CONTENT: 'sql-data-loader-content' as const, diff --git a/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap b/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap index 9223fb7eb5c2..02ac85096a65 100644 --- a/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap +++ b/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap @@ -9,6 +9,7 @@ exports[`HomeView matches snapshot (coordinator) 1`] = ` Capabilities { "coordinator": true, "maxTaskSlots": undefined, + "multiStageQueryDart": false, "multiStageQueryTask": false, "overlord": false, "queryType": "none", @@ -21,6 +22,7 @@ exports[`HomeView matches snapshot (coordinator) 1`] = ` Capabilities { "coordinator": true, "maxTaskSlots": undefined, + "multiStageQueryDart": false, "multiStageQueryTask": false, "overlord": false, "queryType": "none", @@ -32,6 +34,7 @@ exports[`HomeView matches snapshot (coordinator) 1`] = ` Capabilities { "coordinator": true, "maxTaskSlots": undefined, + "multiStageQueryDart": false, "multiStageQueryTask": false, "overlord": false, "queryType": "none", @@ -44,6 +47,7 @@ exports[`HomeView matches snapshot (coordinator) 1`] = ` Capabilities { "coordinator": true, "maxTaskSlots": undefined, + "multiStageQueryDart": false, "multiStageQueryTask": false, "overlord": false, "queryType": "none", @@ -55,6 +59,7 @@ exports[`HomeView matches snapshot (coordinator) 1`] = ` Capabilities { "coordinator": true, "maxTaskSlots": undefined, + "multiStageQueryDart": false, "multiStageQueryTask": false, "overlord": false, "queryType": "none", @@ -73,6 +78,7 @@ exports[`HomeView matches snapshot (full) 1`] = ` Capabilities { "coordinator": true, "maxTaskSlots": undefined, + "multiStageQueryDart": true, "multiStageQueryTask": true, "overlord": true, "queryType": "nativeAndSql", @@ -85,6 +91,7 @@ exports[`HomeView matches snapshot (full) 1`] = ` Capabilities { "coordinator": true, "maxTaskSlots": undefined, + "multiStageQueryDart": true, "multiStageQueryTask": true, "overlord": true, "queryType": "nativeAndSql", @@ -96,6 +103,7 @@ exports[`HomeView matches snapshot (full) 1`] = ` Capabilities { "coordinator": true, "maxTaskSlots": undefined, + "multiStageQueryDart": true, "multiStageQueryTask": true, "overlord": true, "queryType": "nativeAndSql", @@ -109,6 +117,7 @@ exports[`HomeView matches snapshot (full) 1`] = ` Capabilities { "coordinator": true, "maxTaskSlots": undefined, + "multiStageQueryDart": true, "multiStageQueryTask": true, "overlord": true, "queryType": "nativeAndSql", @@ -120,6 +129,7 @@ exports[`HomeView matches snapshot (full) 1`] = ` Capabilities { "coordinator": true, "maxTaskSlots": undefined, + "multiStageQueryDart": true, "multiStageQueryTask": true, "overlord": true, "queryType": "nativeAndSql", @@ -132,6 +142,7 @@ exports[`HomeView matches snapshot (full) 1`] = ` Capabilities { "coordinator": true, "maxTaskSlots": undefined, + "multiStageQueryDart": true, "multiStageQueryTask": true, "overlord": true, "queryType": "nativeAndSql", @@ -143,6 +154,7 @@ exports[`HomeView matches snapshot (full) 1`] = ` Capabilities { "coordinator": true, "maxTaskSlots": undefined, + "multiStageQueryDart": true, "multiStageQueryTask": true, "overlord": true, "queryType": "nativeAndSql", @@ -161,6 +173,7 @@ exports[`HomeView matches snapshot (overlord) 1`] = ` Capabilities { "coordinator": false, "maxTaskSlots": undefined, + "multiStageQueryDart": false, "multiStageQueryTask": false, "overlord": true, "queryType": "none", @@ -173,6 +186,7 @@ exports[`HomeView matches snapshot (overlord) 1`] = ` Capabilities { "coordinator": false, "maxTaskSlots": undefined, + "multiStageQueryDart": false, "multiStageQueryTask": false, "overlord": true, "queryType": "none", @@ -184,6 +198,7 @@ exports[`HomeView matches snapshot (overlord) 1`] = ` Capabilities { "coordinator": false, "maxTaskSlots": undefined, + "multiStageQueryDart": false, "multiStageQueryTask": false, "overlord": true, "queryType": "none", diff --git a/web-console/src/views/workbench-view/current-dart-panel/current-dart-panel.scss b/web-console/src/views/workbench-view/current-dart-panel/current-dart-panel.scss new file mode 100644 index 000000000000..d56ed373642a --- /dev/null +++ b/web-console/src/views/workbench-view/current-dart-panel/current-dart-panel.scss @@ -0,0 +1,121 @@ +/* + * 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 '../../../variables'; + +.current-dart-panel { + position: relative; + @include card-like; + overflow: auto; + + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + + .title { + position: relative; + border-bottom: 1px solid rgba(255, 255, 255, 0.3); + padding: 8px 10px; + user-select: none; + + .close-button { + position: absolute; + top: 2px; + right: 2px; + } + } + + .work-entries { + position: absolute; + top: 30px; + left: 0; + right: 0; + bottom: 0; + padding: 10px; + + &:empty:after { + content: 'No current queries'; + position: absolute; + top: 45%; + left: 50%; + transform: translate(-50%, -50%); + } + + .work-entry { + display: block; + border-bottom: 1px solid rgba(255, 255, 255, 0.3); + padding-top: 8px; + padding-bottom: 8px; + cursor: pointer; + + &:hover { + background-color: rgba(255, 255, 255, 0.1); + } + + .line1 { + margin-bottom: 4px; + + .status-icon { + display: inline-block; + margin-right: 5px; + + &.running { + svg { + animation-name: spin; + animation-duration: 10s; + animation-iteration-count: infinite; + animation-timing-function: linear; + } + } + } + + .timing { + display: inline-block; + } + } + + .line2 { + white-space: nowrap; + overflow: hidden; + } + + .output-icon { + margin-right: 5px; + opacity: 0.6; + } + + .output-datasource { + display: inline-block; + + &.query { + font-style: italic; + } + } + + .query-indicator { + display: inline-block; + margin-left: 10px; + } + } + } +} diff --git a/web-console/src/views/workbench-view/current-dart-panel/current-dart-panel.tsx b/web-console/src/views/workbench-view/current-dart-panel/current-dart-panel.tsx new file mode 100644 index 000000000000..13998e7f1c4a --- /dev/null +++ b/web-console/src/views/workbench-view/current-dart-panel/current-dart-panel.tsx @@ -0,0 +1,166 @@ +/* + * 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 { Button, Icon, Intent, Menu, MenuDivider, MenuItem, Popover } from '@blueprintjs/core'; +import { IconNames } from '@blueprintjs/icons'; +import classNames from 'classnames'; +import copy from 'copy-to-clipboard'; +import React, { useCallback, useState } from 'react'; +import { useStore } from 'zustand'; + +import { Loader } from '../../../components'; +import { useClock, useInterval, useQueryManager } from '../../../hooks'; +import { Api, AppToaster } from '../../../singletons'; +import { formatDuration, prettyFormatIsoDate } from '../../../utils'; +import { CancelQueryDialog } from '../cancel-query-dialog/cancel-query-dialog'; +import { DartDetailsDialog } from '../dart-details-dialog/dart-details-dialog'; +import { workStateStore } from '../work-state-store'; + +import './current-dart-panel.scss'; + +interface CurrentDartEntry { + sqlQueryId: string; + sql: string; + identity: string; + startTime: string; +} + +export interface CurrentViberPanelProps { + onClose(): void; +} + +export const CurrentDartPanel = React.memo(function CurrentViberPanel( + props: CurrentViberPanelProps, +) { + const { onClose } = props; + + const [showSql, setShowSql] = useState(); + const [confirmCancelId, setConfirmCancelId] = useState(); + + const workStateVersion = useStore( + workStateStore, + useCallback(state => state.version, []), + ); + + const [currentDartState, queryManager] = useQueryManager({ + query: workStateVersion, + processQuery: async _ => { + return (await Api.instance.get('/druid/v2/sql/dart')).data.queries; + }, + }); + + useInterval(() => { + queryManager.rerunLastQuery(true); + }, 3000); + + const now = useClock(); + + const currentDart = currentDartState.getSomeData(); + return ( +
+
+ Current Dart queries +
+ {currentDart ? ( +
+ {currentDart.map(w => { + const menu = ( + + { + setShowSql(w.sql); + }} + /> + { + copy(w.sqlQueryId, { format: 'text/plain' }); + AppToaster.show({ + message: `${w.sqlQueryId} copied to clipboard`, + intent: Intent.SUCCESS, + }); + }} + /> + + setConfirmCancelId(w.sqlQueryId)} + /> + + ); + + const duration = now.valueOf() - new Date(w.startTime).valueOf(); + + return ( + +
+
+ +
+ {prettyFormatIsoDate(w.startTime) + + (duration > 0 ? ` (${formatDuration(duration)})` : '')} +
+
+
+ +
select query
+
+
+
+ ); + })} +
+ ) : currentDartState.isLoading() ? ( + + ) : undefined} + {confirmCancelId && ( + { + if (!confirmCancelId) return; + try { + await Api.instance.delete(`/druid/v2/sql/dart/${Api.encodePath(confirmCancelId)}`); + + AppToaster.show({ + message: 'Query canceled', + intent: Intent.SUCCESS, + }); + } catch { + AppToaster.show({ + message: 'Could not cancel query', + intent: Intent.DANGER, + }); + } + }} + onDismiss={() => setConfirmCancelId(undefined)} + /> + )} + {showSql && setShowSql(undefined)} />} +
+ ); +}); diff --git a/web-console/src/views/workbench-view/dart-details-dialog/dart-details-dialog.scss b/web-console/src/views/workbench-view/dart-details-dialog/dart-details-dialog.scss new file mode 100644 index 000000000000..f1f380dc4ec8 --- /dev/null +++ b/web-console/src/views/workbench-view/dart-details-dialog/dart-details-dialog.scss @@ -0,0 +1,35 @@ +/* + * 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 '../../../variables'; + +.dart-details-dialog { + &.#{$bp-ns}-dialog { + width: 95vw; + } + + .#{$bp-ns}-dialog-body { + height: 70vh; + position: relative; + margin: 0; + + .flexible-query-input { + height: 100%; + } + } +} diff --git a/web-console/src/views/workbench-view/dart-details-dialog/dart-details-dialog.tsx b/web-console/src/views/workbench-view/dart-details-dialog/dart-details-dialog.tsx new file mode 100644 index 000000000000..0637d6b9644b --- /dev/null +++ b/web-console/src/views/workbench-view/dart-details-dialog/dart-details-dialog.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 { Button, Classes, Dialog } from '@blueprintjs/core'; +import React from 'react'; + +import { FlexibleQueryInput } from '../flexible-query-input/flexible-query-input'; + +import './dart-details-dialog.scss'; + +export interface DartDetailsDialogProps { + sql: string; + onClose(): void; +} + +export const DartDetailsDialog = React.memo(function DartDetailsDialog( + props: DartDetailsDialogProps, +) { + const { sql, onClose } = props; + + return ( + +
+ +
+
+
+
+
+
+ ); +}); 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 d0f361931dc9..b1930ae50d2b 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 @@ -96,7 +96,7 @@ export const ExecutionSummaryPanel = React.memo(function ExecutionSummaryPanel( } onClick={() => { if (!execution) return; - if (oneOf(execution.engine, 'sql-msq-task')) { + if (oneOf(execution.engine, 'sql-msq-task', 'sql-msq-dart')) { onExecutionDetail(); } }} diff --git a/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx b/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx index 2080bf47256b..3f9c3ea9a3d1 100644 --- a/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx +++ b/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx @@ -45,6 +45,7 @@ import { getDruidErrorMessage, nonEmptyArray, queryDruidSql, + queryDruidSqlDart, } from '../../../utils'; import './explain-dialog.scss'; @@ -108,6 +109,10 @@ export const ExplainDialog = React.memo(function ExplainDialog(props: ExplainDia } break; + case 'sql-msq-dart': + result = await queryDruidSqlDart(payload); + break; + default: throw new Error(`Explain not supported for engine ${engine}`); } 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 f477a51650b6..1af424935012 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 @@ -18,7 +18,7 @@ import { Code, Intent } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import { QueryRunner, SqlQuery } from '@druid-toolkit/query'; +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'; @@ -41,6 +41,7 @@ import type { WorkbenchRunningPromise } from '../../../singletons/workbench-runn import { WorkbenchRunningPromises } from '../../../singletons/workbench-running-promises'; import type { ColumnMetadata, QueryAction, QuerySlice, RowColumn } from '../../../utils'; import { + deepGet, DruidError, findAllSqlQueriesInText, localStorageGet, @@ -271,6 +272,67 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) { return execution; } + + case 'sql-msq-dart': { + if (cancelQueryId) { + void cancelToken.promise + .then(cancel => { + if (cancel.message === QueryManager.TERMINATION_MESSAGE) return; + return Api.instance.delete(`/druid/v2/sql/dart/${Api.encodePath(cancelQueryId)}`); + }) + .catch(() => {}); + } + + onQueryChange(props.query.changeLastExecution(undefined)); + + const executionPromise = Api.instance + .post(`/druid/v2/sql/dart`, query, { + cancelToken: new axios.CancelToken(cancelFn => { + nativeQueryCancelFnRef.current = cancelFn; + }), + }) + .then( + ({ data: dartResponse }) => { + 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); + } else { + return Execution.fromResult( + engine, + QueryResult.fromRawResult( + dartResponse, + false, + query.header, + query.typesHeader, + query.sqlTypesHeader, + ), + ).changeSqlQuery(query.query, query.context); + } + }, + e => { + throw new DruidError(e, prefixLines); + }, + ); + + WorkbenchRunningPromises.storePromise(id, { + executionPromise, + startTime, + }); + + let execution: Execution; + try { + execution = await executionPromise; + nativeQueryCancelFnRef.current = undefined; + } catch (e) { + nativeQueryCancelFnRef.current = undefined; + throw e; + } + + return execution; + } } } else if (WorkbenchRunningPromises.isWorkbenchRunningPromise(q)) { return await q.executionPromise; diff --git a/web-console/src/views/workbench-view/run-panel/run-panel.tsx b/web-console/src/views/workbench-view/run-panel/run-panel.tsx index ddec827a0be9..54489dd218f1 100644 --- a/web-console/src/views/workbench-view/run-panel/run-panel.tsx +++ b/web-console/src/views/workbench-view/run-panel/run-panel.tsx @@ -111,6 +111,9 @@ const DEFAULT_ENGINES_LABEL_FN = (engine: DruidEngine | undefined) => { case 'sql-msq-task': return { text: 'SQL MSQ-task', label: 'multi-stage-query' }; + case 'sql-msq-dart': + return { text: 'SQL MSQ Dart', label: 'multi-stage-query' }; + default: return { text: engine }; } @@ -159,12 +162,14 @@ function optionVisible( case 'group-by-enable-multi-value-unnesting': case 'durable-shuffle-storage': case 'include-all-counters': - case 'join-algorithm': return engine === 'sql-msq-task'; + case 'join-algorithm': + return engine === 'sql-msq-task' || engine === 'sql-msq-dart'; + case 'timezone': case 'approximate-count-distinct': - return engine === 'sql-native' || engine === 'sql-msq-task'; + return engine === 'sql-native' || engine === 'sql-msq-task' || engine === 'sql-msq-dart'; case 'use-cache': return engine === 'native' || engine === 'sql-native'; diff --git a/web-console/src/views/workbench-view/workbench-view.scss b/web-console/src/views/workbench-view/workbench-view.scss index 1287dab1c19b..be006239f699 100644 --- a/web-console/src/views/workbench-view/workbench-view.scss +++ b/web-console/src/views/workbench-view/workbench-view.scss @@ -45,7 +45,7 @@ $recent-query-task-panel-width: 250px; gap: 2px; .recent-query-task-panel, - .current-viper-panel { + .current-dart-panel { flex: 1; } } diff --git a/web-console/src/views/workbench-view/workbench-view.tsx b/web-console/src/views/workbench-view/workbench-view.tsx index cd4afb8e02ef..5ee0c93a21a6 100644 --- a/web-console/src/views/workbench-view/workbench-view.tsx +++ b/web-console/src/views/workbench-view/workbench-view.tsx @@ -32,6 +32,7 @@ import classNames from 'classnames'; import copy from 'copy-to-clipboard'; import React from 'react'; +import { MenuCheckbox } from '../../components'; import { SpecDialog, StringInputDialog } from '../../dialogs'; import type { CapacityInfo, @@ -69,6 +70,7 @@ import { import { ColumnTree } from './column-tree/column-tree'; import { ConnectExternalDataDialog } from './connect-external-data-dialog/connect-external-data-dialog'; +import { CurrentDartPanel } from './current-dart-panel/current-dart-panel'; import { getDemoQueries } from './demo-queries'; import { ExecutionDetailsDialog } from './execution-details-dialog/execution-details-dialog'; import type { ExecutionDetailsTab } from './execution-details-pane/execution-details-pane'; @@ -148,6 +150,7 @@ export interface WorkbenchViewState { renamingTab?: TabEntry; showRecentQueryTaskPanel: boolean; + showCurrentDartPanel: boolean; } export class WorkbenchView extends React.PureComponent { @@ -166,6 +169,11 @@ export class WorkbenchView extends React.PureComponent ({ ...q, query: new WorkbenchQuery(q.query) })) @@ -198,6 +206,7 @@ export class WorkbenchView extends React.PureComponent { + this.setState({ showCurrentDartPanel: false }); + localStorageSetJson(LocalStorageKeys.WORKBENCH_DART_PANEL, false); + }; + private readonly handleDetailsWithId = (id: string, initTab?: ExecutionDetailsTab) => { this.setState({ details: { id, initTab }, @@ -656,7 +670,7 @@ export class WorkbenchView extends React.PureComponent