Web console: add support for Dart engine (#17147)

* add console support for Dart engine

This reverts commit 6e46edf15dd55e5c51a1a4068e83deba4f22529b.

* feedback fixes

* surface new fields

* prioratize error over results

* better metadata refresh

* feedback fixes
diff --git a/web-console/script/druid b/web-console/script/druid
index 122feba..e7e575a 100755
--- a/web-console/script/druid
+++ b/web-console/script/druid
@@ -67,6 +67,7 @@
     && echo -e "\n\ndruid.extensions.loadList=[\"druid-hdfs-storage\", \"druid-kafka-indexing-service\", \"druid-multi-stage-query\", \"druid-testing-tools\", \"druid-bloom-filter\", \"druid-datasketches\", \"druid-histogram\", \"druid-stats\", \"druid-compressed-bigdecimal\", \"druid-parquet-extensions\", \"druid-deltalake-extensions\"]" >> conf/druid/auto/_common/common.runtime.properties \
     && echo -e "\n\ndruid.server.http.allowedHttpMethods=[\"HEAD\"]" >> conf/druid/auto/_common/common.runtime.properties \
     && echo -e "\n\ndruid.export.storage.baseDir=/" >> conf/druid/auto/_common/common.runtime.properties \
+    && echo -e "\n\ndruid.msq.dart.enabled=true" >> conf/druid/auto/_common/common.runtime.properties \
   )
 }
 
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 c3310e2..d3e24a6 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 @@
         Capabilities {
           "coordinator": true,
           "maxTaskSlots": undefined,
+          "multiStageQueryDart": true,
           "multiStageQueryTask": true,
           "overlord": true,
           "queryType": "nativeAndSql",
diff --git a/web-console/src/druid-models/dart/dart-query-entry.mock.ts b/web-console/src/druid-models/dart/dart-query-entry.mock.ts
new file mode 100644
index 0000000..f2409ab
--- /dev/null
+++ b/web-console/src/druid-models/dart/dart-query-entry.mock.ts
@@ -0,0 +1,49 @@
+/*
+ * 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 type { DartQueryEntry } from './dart-query-entry';
+
+export const DART_QUERIES: DartQueryEntry[] = [
+  {
+    sqlQueryId: '77b2344c-0a1f-4aa0-b127-de6fbc0c2b57',
+    dartQueryId: '99cdba0d-ed77-433d-9adc-0562d816e105',
+    sql: 'SELECT\n  "URL",\n  COUNT(*)\nFROM "c"\nGROUP BY 1\nORDER BY 2 DESC\nLIMIT 50\n',
+    authenticator: 'allowAll',
+    identity: 'allowAll',
+    startTime: '2024-09-28T07:41:21.194Z',
+    state: 'RUNNING',
+  },
+  {
+    sqlQueryId: '45441cf5-d8b7-46cb-b6d8-682334f056ef',
+    dartQueryId: '25af9bff-004d-494e-b562-2752dc3779c8',
+    sql: 'SELECT\n  "URL",\n  COUNT(*)\nFROM "c"\nGROUP BY 1\nORDER BY 2 DESC\nLIMIT 50\n',
+    authenticator: 'allowAll',
+    identity: 'allowAll',
+    startTime: '2024-09-28T07:41:22.854Z',
+    state: 'CANCELED',
+  },
+  {
+    sqlQueryId: 'f7257c78-6bbe-439d-99ba-f4998b300770',
+    dartQueryId: 'f7c2d644-9c40-4d61-9fdb-7b0e15219886',
+    sql: 'SELECT\n  "URL",\n  COUNT(*)\nFROM "c"\nGROUP BY 1\nORDER BY 2 DESC\nLIMIT 50\n',
+    authenticator: 'allowAll',
+    identity: 'allowAll',
+    startTime: '2024-09-28T07:41:24.425Z',
+    state: 'ACCEPTED',
+  },
+];
diff --git a/web-console/src/druid-models/dart/dart-query-entry.ts b/web-console/src/druid-models/dart/dart-query-entry.ts
new file mode 100644
index 0000000..472248b
--- /dev/null
+++ b/web-console/src/druid-models/dart/dart-query-entry.ts
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+export interface DartQueryEntry {
+  sqlQueryId: string;
+  dartQueryId: string;
+  sql: string;
+  authenticator: string;
+  identity: string;
+  startTime: string;
+  state: 'ACCEPTED' | 'RUNNING' | 'CANCELED';
+}
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 f1942e5..335d22e 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/index.ts b/web-console/src/druid-models/index.ts
index e768afe..dfeeeea 100644
--- a/web-console/src/druid-models/index.ts
+++ b/web-console/src/druid-models/index.ts
@@ -20,6 +20,7 @@
 export * from './compaction-config/compaction-config';
 export * from './compaction-status/compaction-status';
 export * from './coordinator-dynamic-config/coordinator-dynamic-config';
+export * from './dart/dart-query-entry';
 export * from './dimension-spec/dimension-spec';
 export * from './druid-engine/druid-engine';
 export * from './execution/execution';
diff --git a/web-console/src/druid-models/stages/stages.ts b/web-console/src/druid-models/stages/stages.ts
index fbb2c1c..ddddeab 100644
--- a/web-console/src/druid-models/stages/stages.ts
+++ b/web-console/src/druid-models/stages/stages.ts
@@ -18,6 +18,7 @@
 
 import { max, sum } from 'd3-array';
 
+import { AutoForm } from '../../components';
 import { countBy, deleteKeys, filterMap, groupByAsMap, oneOf, zeroDivide } from '../../utils';
 import type { InputFormat } from '../input-format/input-format';
 import type { InputSource } from '../input-source/input-source';
@@ -252,26 +253,16 @@
 
 export function cpusCounterFieldTitle(k: CpusCounterFields) {
   switch (k) {
-    case 'main':
-      return 'Main';
-
     case 'collectKeyStatistics':
       return 'Collect key stats';
 
-    case 'mergeInput':
-      return 'Merge input';
-
-    case 'hashPartitionOutput':
-      return 'Hash partition out';
-
-    case 'mixOutput':
-      return 'Mix output';
-
-    case 'sortOutput':
-      return 'Sort output';
-
     default:
-      return k;
+      // main
+      // mergeInput
+      // hashPartitionOutput
+      // mixOutput
+      // sortOutput
+      return AutoForm.makeLabelName(k);
   }
 }
 
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 dd75c94..716fe57 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 @@
     };
 
     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 @@
       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 fe125b6..013f936 100644
--- a/web-console/src/helpers/capabilities.ts
+++ b/web-console/src/helpers/capabilities.ts
@@ -37,6 +37,7 @@
 export interface CapabilitiesValue {
   queryType: QueryType;
   multiStageQueryTask: boolean;
+  multiStageQueryDart: boolean;
   coordinator: boolean;
   overlord: boolean;
   maxTaskSlots?: number;
@@ -53,6 +54,7 @@
 
   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 @@
     }
   }
 
+  static async detectMultiStageQueryDart(): Promise<boolean> {
+    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<Capabilities | undefined> {
     const queryType = await Capabilities.detectQueryType();
     if (typeof queryType === 'undefined') return;
@@ -154,11 +165,15 @@
       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 @@
   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 @@
     return {
       queryType: this.queryType,
       multiStageQueryTask: this.multiStageQueryTask,
+      multiStageQueryDart: this.multiStageQueryDart,
       coordinator: this.coordinator,
       overlord: this.overlord,
       maxTaskSlots: this.maxTaskSlots,
@@ -248,6 +265,10 @@
     return this.multiStageQueryTask;
   }
 
+  public hasMultiStageQueryDart(): boolean {
+    return this.multiStageQueryDart;
+  }
+
   public getSupportedQueryEngines(): DruidEngine[] {
     const queryEngines: DruidEngine[] = ['native'];
     if (this.hasSql()) {
@@ -256,6 +277,9 @@
     if (this.hasMultiStageQueryTask()) {
       queryEngines.push('sql-msq-task');
     }
+    if (this.hasMultiStageQueryDart()) {
+      queryEngines.push('sql-msq-dart');
+    }
     return queryEngines;
   }
 
@@ -282,36 +306,42 @@
 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 fba63b9..d148136 100644
--- a/web-console/src/utils/druid-query.ts
+++ b/web-console/src/utils/druid-query.ts
@@ -342,6 +342,19 @@
   return sqlResultResp.data;
 }
 
+export async function queryDruidSqlDart<T = any>(
+  sqlQueryPayload: Record<string, any>,
+  cancelToken?: CancelToken,
+): Promise<T[]> {
+  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 d4efec0..8a8fee9 100644
--- a/web-console/src/utils/local-storage-keys.tsx
+++ b/web-console/src/utils/local-storage-keys.tsx
@@ -53,6 +53,7 @@
   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 9223fb7..02ac850 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 @@
       Capabilities {
         "coordinator": true,
         "maxTaskSlots": undefined,
+        "multiStageQueryDart": false,
         "multiStageQueryTask": false,
         "overlord": false,
         "queryType": "none",
@@ -21,6 +22,7 @@
         Capabilities {
           "coordinator": true,
           "maxTaskSlots": undefined,
+          "multiStageQueryDart": false,
           "multiStageQueryTask": false,
           "overlord": false,
           "queryType": "none",
@@ -32,6 +34,7 @@
         Capabilities {
           "coordinator": true,
           "maxTaskSlots": undefined,
+          "multiStageQueryDart": false,
           "multiStageQueryTask": false,
           "overlord": false,
           "queryType": "none",
@@ -44,6 +47,7 @@
       Capabilities {
         "coordinator": true,
         "maxTaskSlots": undefined,
+        "multiStageQueryDart": false,
         "multiStageQueryTask": false,
         "overlord": false,
         "queryType": "none",
@@ -55,6 +59,7 @@
       Capabilities {
         "coordinator": true,
         "maxTaskSlots": undefined,
+        "multiStageQueryDart": false,
         "multiStageQueryTask": false,
         "overlord": false,
         "queryType": "none",
@@ -73,6 +78,7 @@
       Capabilities {
         "coordinator": true,
         "maxTaskSlots": undefined,
+        "multiStageQueryDart": true,
         "multiStageQueryTask": true,
         "overlord": true,
         "queryType": "nativeAndSql",
@@ -85,6 +91,7 @@
         Capabilities {
           "coordinator": true,
           "maxTaskSlots": undefined,
+          "multiStageQueryDart": true,
           "multiStageQueryTask": true,
           "overlord": true,
           "queryType": "nativeAndSql",
@@ -96,6 +103,7 @@
         Capabilities {
           "coordinator": true,
           "maxTaskSlots": undefined,
+          "multiStageQueryDart": true,
           "multiStageQueryTask": true,
           "overlord": true,
           "queryType": "nativeAndSql",
@@ -109,6 +117,7 @@
         Capabilities {
           "coordinator": true,
           "maxTaskSlots": undefined,
+          "multiStageQueryDart": true,
           "multiStageQueryTask": true,
           "overlord": true,
           "queryType": "nativeAndSql",
@@ -120,6 +129,7 @@
         Capabilities {
           "coordinator": true,
           "maxTaskSlots": undefined,
+          "multiStageQueryDart": true,
           "multiStageQueryTask": true,
           "overlord": true,
           "queryType": "nativeAndSql",
@@ -132,6 +142,7 @@
       Capabilities {
         "coordinator": true,
         "maxTaskSlots": undefined,
+        "multiStageQueryDart": true,
         "multiStageQueryTask": true,
         "overlord": true,
         "queryType": "nativeAndSql",
@@ -143,6 +154,7 @@
       Capabilities {
         "coordinator": true,
         "maxTaskSlots": undefined,
+        "multiStageQueryDart": true,
         "multiStageQueryTask": true,
         "overlord": true,
         "queryType": "nativeAndSql",
@@ -161,6 +173,7 @@
       Capabilities {
         "coordinator": false,
         "maxTaskSlots": undefined,
+        "multiStageQueryDart": false,
         "multiStageQueryTask": false,
         "overlord": true,
         "queryType": "none",
@@ -173,6 +186,7 @@
         Capabilities {
           "coordinator": false,
           "maxTaskSlots": undefined,
+          "multiStageQueryDart": false,
           "multiStageQueryTask": false,
           "overlord": true,
           "queryType": "none",
@@ -184,6 +198,7 @@
         Capabilities {
           "coordinator": false,
           "maxTaskSlots": undefined,
+          "multiStageQueryDart": false,
           "multiStageQueryTask": false,
           "overlord": true,
           "queryType": "none",
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 6ac11bc..a89b4da 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
@@ -688,10 +688,10 @@
   };
 
   render() {
-    const { columnMetadataLoading } = this.props;
+    const { columnMetadata, columnMetadataLoading } = this.props;
     const { currentSchemaSubtree, searchString } = this.state;
 
-    if (columnMetadataLoading) {
+    if (columnMetadataLoading && !columnMetadata) {
       return (
         <div className="column-tree">
           <Loader />
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 0000000..a2dac44
--- /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;
+      }
+
+      .identity-icon {
+        opacity: 0.6;
+      }
+
+      .identity-identity {
+        margin-left: 5px;
+        display: inline-block;
+
+        &.anonymous {
+          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 0000000..aae0020
--- /dev/null
+++ b/web-console/src/views/workbench-view/current-dart-panel/current-dart-panel.tsx
@@ -0,0 +1,194 @@
+/*
+ * 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 { type IconName, 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 type { DartQueryEntry } from '../../../druid-models';
+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';
+
+function stateToIconAndColor(status: DartQueryEntry['state']): [IconName, string] {
+  switch (status) {
+    case 'RUNNING':
+      return [IconNames.REFRESH, '#2167d5'];
+    case 'ACCEPTED':
+      return [IconNames.CIRCLE, '#8d8d8d'];
+    case 'CANCELED':
+      return [IconNames.DISABLE, '#8d8d8d'];
+    default:
+      return [IconNames.CIRCLE, '#8d8d8d'];
+  }
+}
+
+export interface CurrentViberPanelProps {
+  onClose(): void;
+}
+
+export const CurrentDartPanel = React.memo(function CurrentViberPanel(
+  props: CurrentViberPanelProps,
+) {
+  const { onClose } = props;
+
+  const [showSql, setShowSql] = useState<string | undefined>();
+  const [confirmCancelId, setConfirmCancelId] = useState<string | undefined>();
+
+  const workStateVersion = useStore(
+    workStateStore,
+    useCallback(state => state.version, []),
+  );
+
+  const [dartQueryEntriesState, queryManager] = useQueryManager<number, DartQueryEntry[]>({
+    query: workStateVersion,
+    processQuery: async _ => {
+      return (await Api.instance.get('/druid/v2/sql/dart')).data.queries;
+    },
+  });
+
+  useInterval(() => {
+    queryManager.rerunLastQuery(true);
+  }, 3000);
+
+  const now = useClock();
+
+  const dartQueryEntries = dartQueryEntriesState.getSomeData();
+  return (
+    <div className="current-dart-panel">
+      <div className="title">
+        Current Dart queries
+        <Button className="close-button" icon={IconNames.CROSS} minimal onClick={onClose} />
+      </div>
+      {dartQueryEntries ? (
+        <div className="work-entries">
+          {dartQueryEntries.map(w => {
+            const menu = (
+              <Menu>
+                <MenuItem
+                  icon={IconNames.EYE_OPEN}
+                  text="Show SQL"
+                  onClick={() => {
+                    setShowSql(w.sql);
+                  }}
+                />
+                <MenuItem
+                  icon={IconNames.DUPLICATE}
+                  text="Copy SQL ID"
+                  onClick={() => {
+                    copy(w.sqlQueryId, { format: 'text/plain' });
+                    AppToaster.show({
+                      message: `${w.sqlQueryId} copied to clipboard`,
+                      intent: Intent.SUCCESS,
+                    });
+                  }}
+                />
+                <MenuItem
+                  icon={IconNames.DUPLICATE}
+                  text="Copy Dart ID"
+                  onClick={() => {
+                    copy(w.dartQueryId, { format: 'text/plain' });
+                    AppToaster.show({
+                      message: `${w.dartQueryId} copied to clipboard`,
+                      intent: Intent.SUCCESS,
+                    });
+                  }}
+                />
+                <MenuDivider />
+                <MenuItem
+                  icon={IconNames.CROSS}
+                  text="Cancel query"
+                  intent={Intent.DANGER}
+                  onClick={() => setConfirmCancelId(w.sqlQueryId)}
+                />
+              </Menu>
+            );
+
+            const duration = now.valueOf() - new Date(w.startTime).valueOf();
+
+            const [icon, color] = stateToIconAndColor(w.state);
+            const anonymous = w.identity === 'allowAll' && w.authenticator === 'allowAll';
+            return (
+              <Popover className="work-entry" key={w.sqlQueryId} position="left" content={menu}>
+                <div>
+                  <div className="line1">
+                    <Icon
+                      className={'status-icon ' + w.state.toLowerCase()}
+                      icon={icon}
+                      style={{ color }}
+                      data-tooltip={`State: ${w.state}`}
+                    />
+                    <div className="timing">
+                      {prettyFormatIsoDate(w.startTime) +
+                        ((w.state === 'RUNNING' || w.state === 'ACCEPTED') && duration > 0
+                          ? ` (${formatDuration(duration)})`
+                          : '')}
+                    </div>
+                  </div>
+                  <div className="line2">
+                    <Icon className="identity-icon" icon={IconNames.MUGSHOT} />
+                    <div
+                      className={classNames('identity-identity', { anonymous })}
+                      data-tooltip={`Identity: ${w.identity}\nAuthenticator: ${w.authenticator}`}
+                    >
+                      {anonymous ? 'anonymous' : `${w.identity} (${w.authenticator})`}
+                    </div>
+                  </div>
+                </div>
+              </Popover>
+            );
+          })}
+        </div>
+      ) : dartQueryEntriesState.isLoading() ? (
+        <Loader />
+      ) : undefined}
+      {confirmCancelId && (
+        <CancelQueryDialog
+          // eslint-disable-next-line @typescript-eslint/no-misused-promises
+          onCancel={async () => {
+            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 && <DartDetailsDialog sql={showSql} onClose={() => setShowSql(undefined)} />}
+    </div>
+  );
+});
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 0000000..f1f380d
--- /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 0000000..0637d6b
--- /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 (
+    <Dialog className="dart-details-dialog" isOpen onClose={onClose} title="Dart SQL">
+      <div className={Classes.DIALOG_BODY}>
+        <FlexibleQueryInput queryString={sql} leaveBackground />
+      </div>
+      <div className={Classes.DIALOG_FOOTER}>
+        <div className={Classes.DIALOG_FOOTER_ACTIONS}>
+          <Button text="Close" onClick={onClose} />
+        </div>
+      </div>
+    </Dialog>
+  );
+});
diff --git a/web-console/src/views/workbench-view/execution-stages-pane/__snapshots__/execution-stages-pane.spec.tsx.snap b/web-console/src/views/workbench-view/execution-stages-pane/__snapshots__/execution-stages-pane.spec.tsx.snap
index 3ab5ab1..4f97069 100644
--- a/web-console/src/views/workbench-view/execution-stages-pane/__snapshots__/execution-stages-pane.spec.tsx.snap
+++ b/web-console/src/views/workbench-view/execution-stages-pane/__snapshots__/execution-stages-pane.spec.tsx.snap
@@ -134,12 +134,12 @@
             <span
               className="cpu-label"
             >
-              counter
+              Counter
             </span>
             <span
               className="cpu-counter"
             >
-              wall time
+              Wall time
             </span>
           </i>
         </React.Fragment>,
@@ -147,7 +147,7 @@
         "className": "padded",
         "id": "cpu",
         "show": false,
-        "width": 220,
+        "width": 240,
       },
       {
         "Header": <React.Fragment>
diff --git a/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.scss b/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.scss
index 6a4ffce..e584de3 100644
--- a/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.scss
+++ b/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.scss
@@ -129,7 +129,7 @@
 
   .cpu-label {
     display: inline-block;
-    width: 120px;
+    width: 140px;
   }
 
   .cpu-counter {
diff --git a/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx b/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx
index 4ba0ed5..322b9a8 100644
--- a/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx
+++ b/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx
@@ -263,8 +263,8 @@
             Header: twoLines(
               'CPU utilization',
               <i>
-                <span className="cpu-label">counter</span>
-                <span className="cpu-counter">wall time</span>
+                <span className="cpu-label">Counter</span>
+                <span className="cpu-counter">Wall time</span>
               </i>,
             ),
             id: 'cpu',
@@ -863,14 +863,14 @@
           Header: twoLines(
             'CPU utilization',
             <i>
-              <span className="cpu-label">counter</span>
-              <span className="cpu-counter">wall time</span>
+              <span className="cpu-label">Counter</span>
+              <span className="cpu-counter">Wall time</span>
             </i>,
           ),
           id: 'cpu',
           accessor: () => null,
           className: 'padded',
-          width: 220,
+          width: 240,
           show: stages.hasCounter('cpu'),
           Cell({ original }) {
             const cpuTotals = stages.getCpuTotalsForStage(original);
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 d0f3619..b1930ae 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 @@
         }
         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 2080bf4..3f9c3ea 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 @@
   getDruidErrorMessage,
   nonEmptyArray,
   queryDruidSql,
+  queryDruidSqlDart,
 } from '../../../utils';
 
 import './explain-dialog.scss';
@@ -108,6 +109,10 @@
           }
           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 f477a51..59b4625 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 { WorkbenchRunningPromises } from '../../../singletons/workbench-running-promises';
 import type { ColumnMetadata, QueryAction, QuerySlice, RowColumn } from '../../../utils';
 import {
+  deepGet,
   DruidError,
   findAllSqlQueriesInText,
   localStorageGet,
@@ -271,6 +272,67 @@
 
             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;
@@ -463,13 +525,7 @@
             </div>
           )}
           {execution &&
-            (execution.result ? (
-              <ResultTablePane
-                runeMode={execution.engine === 'native'}
-                queryResult={execution.result}
-                onQueryAction={handleQueryAction}
-              />
-            ) : execution.error ? (
+            (execution.error ? (
               <div className="error-container">
                 <ExecutionErrorPane execution={execution} />
                 {execution.stages && (
@@ -481,6 +537,12 @@
                   />
                 )}
               </div>
+            ) : execution.result ? (
+              <ResultTablePane
+                runeMode={execution.engine === 'native'}
+                queryResult={execution.result}
+                onQueryAction={handleQueryAction}
+              />
             ) : execution.isSuccessfulIngest() ? (
               <IngestSuccessPane
                 execution={execution}
diff --git a/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.tsx b/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.tsx
index 1380335..1a08fff 100644
--- a/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.tsx
+++ b/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.tsx
@@ -225,6 +225,7 @@
                       className={'status-icon ' + w.taskStatus.toLowerCase()}
                       icon={icon}
                       style={{ color }}
+                      data-tooltip={`Task status: ${w.taskStatus}`}
                     />
                     <div className="timing">
                       {prettyFormatIsoDate(w.createdTime) +
diff --git a/web-console/src/views/workbench-view/run-panel/__snapshots__/run-panel.spec.tsx.snap b/web-console/src/views/workbench-view/run-panel/__snapshots__/run-panel.spec.tsx.snap
index 51e5f34..620185d 100644
--- a/web-console/src/views/workbench-view/run-panel/__snapshots__/run-panel.spec.tsx.snap
+++ b/web-console/src/views/workbench-view/run-panel/__snapshots__/run-panel.spec.tsx.snap
@@ -46,7 +46,7 @@
         <span
           class="bp5-button-text"
         >
-          Engine: SQL MSQ-task
+          Engine: SQL (task)
         </span>
         <span
           aria-hidden="true"
@@ -150,7 +150,7 @@
         <span
           class="bp5-button-text"
         >
-          Engine: Auto (SQL native)
+          Engine: Auto [SQL (native)]
         </span>
         <span
           aria-hidden="true"
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 ddec827..d586959 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
@@ -103,13 +103,16 @@
   if (!engine) return { text: 'Auto' };
   switch (engine) {
     case 'native':
-      return { text: 'Native' };
+      return { text: 'JSON (native)' };
 
     case 'sql-native':
-      return { text: 'SQL native' };
+      return { text: 'SQL (native)' };
 
     case 'sql-msq-task':
-      return { text: 'SQL MSQ-task', label: 'multi-stage-query' };
+      return { text: 'SQL (task)', label: 'multi-stage-query' };
+
+    case 'sql-msq-dart':
+      return { text: 'SQL (Dart)', label: 'multi-stage-query' };
 
     default:
       return { text: engine };
@@ -121,8 +124,6 @@
   durableStorage: 'Durable storage',
 };
 
-const EXPERIMENTAL_ICON = <Icon icon={IconNames.WARNING_SIGN} title="Experimental" />;
-
 export type EnginesMenuOption =
   | 'edit-query-context'
   | 'define-parameters'
@@ -135,7 +136,6 @@
   | 'finalize-aggregations'
   | 'group-by-enable-multi-value-unnesting'
   | 'durable-shuffle-storage'
-  | 'include-all-counters'
   | 'use-cache'
   | 'approximate-top-n'
   | 'limit-inline-results';
@@ -158,21 +158,24 @@
     case 'finalize-aggregations':
     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';
 
     case 'approximate-top-n':
-    case 'limit-inline-results':
       return engine === 'sql-native';
 
+    case 'limit-inline-results':
+      return engine === 'sql-native' || engine === 'sql-msq-dart';
+
     default:
       console.warn(`Unknown option: ${option}`);
       return false;
@@ -251,16 +254,6 @@
     queryContext,
     defaultQueryContext,
   );
-  const useConcurrentLocks = getQueryContextKey(
-    'useConcurrentLocks',
-    queryContext,
-    defaultQueryContext,
-  );
-  const forceSegmentSortByTime = getQueryContextKey(
-    'forceSegmentSortByTime',
-    queryContext,
-    defaultQueryContext,
-  );
   const finalizeAggregations = queryContext.finalizeAggregations;
   const waitUntilSegmentsLoad = queryContext.waitUntilSegmentsLoad;
   const groupByEnableMultiValueUnnesting = queryContext.groupByEnableMultiValueUnnesting;
@@ -279,11 +272,6 @@
     queryContext,
     defaultQueryContext,
   );
-  const includeAllCounters = getQueryContextKey(
-    'includeAllCounters',
-    queryContext,
-    defaultQueryContext,
-  );
 
   const indexSpec: IndexSpec | undefined = deepGet(queryContext, 'indexSpec');
 
@@ -385,7 +373,7 @@
               <Menu>
                 {queryEngines.length > 1 && (
                   <>
-                    <MenuDivider title="Select engine" />
+                    <MenuDivider title="Select language and engine" />
                     <MenuItem
                       key="auto"
                       icon={tickIcon(queryEngine === undefined)}
@@ -469,81 +457,33 @@
                     />
                   </MenuItem>
                 )}
-
-                {show('insert-replace-specific-context') && (
-                  <MenuItem icon={IconNames.BRING_DATA} text="INSERT / REPLACE specific context">
-                    <MenuBoolean
-                      text="Force segment sort by time"
-                      value={forceSegmentSortByTime}
-                      onValueChange={forceSegmentSortByTime =>
-                        changeQueryContext({
-                          ...queryContext,
-                          forceSegmentSortByTime,
-                        })
-                      }
-                      optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
-                      optionsLabelElement={{ false: EXPERIMENTAL_ICON }}
-                    />
-                    <MenuBoolean
-                      text="Use concurrent locks"
-                      value={useConcurrentLocks}
-                      onValueChange={useConcurrentLocks =>
-                        changeQueryContext({
-                          ...queryContext,
-                          useConcurrentLocks,
-                        })
-                      }
-                      optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
-                      optionsLabelElement={{ true: EXPERIMENTAL_ICON }}
-                    />
-                    <MenuBoolean
-                      text="Fail on empty insert"
-                      value={failOnEmptyInsert}
-                      showUndefined
-                      undefinedEffectiveValue={false}
-                      onValueChange={failOnEmptyInsert =>
-                        changeQueryContext({ ...queryContext, failOnEmptyInsert })
-                      }
-                      optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
-                    />
-                    <MenuBoolean
-                      text="Wait until segments have loaded"
-                      value={waitUntilSegmentsLoad}
-                      showUndefined
-                      undefinedEffectiveValue={ingestMode}
-                      onValueChange={waitUntilSegmentsLoad =>
-                        changeQueryContext({ ...queryContext, waitUntilSegmentsLoad })
-                      }
-                      optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
-                    />
-                    <MenuItem
-                      text="Edit index spec..."
-                      label={summarizeIndexSpec(indexSpec)}
-                      shouldDismissPopover={false}
-                      onClick={() => {
-                        setIndexSpecDialogSpec(indexSpec || {});
-                      }}
-                    />
-                  </MenuItem>
+                {show('approximate-count-distinct') && (
+                  <MenuBoolean
+                    icon={IconNames.ROCKET_SLANT}
+                    text="Approximate COUNT(DISTINCT)"
+                    value={useApproximateCountDistinct}
+                    onValueChange={useApproximateCountDistinct =>
+                      changeQueryContext({
+                        ...queryContext,
+                        useApproximateCountDistinct,
+                      })
+                    }
+                    optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
+                  />
                 )}
-                {show('max-parse-exceptions') && (
-                  <MenuItem
-                    icon={IconNames.ERROR}
-                    text="Max parse exceptions"
-                    label={String(maxParseExceptions)}
-                  >
-                    {[0, 1, 5, 10, 1000, 10000, -1].map(v => (
-                      <MenuItem
-                        key={String(v)}
-                        icon={tickIcon(v === maxParseExceptions)}
-                        text={v === -1 ? '∞ (-1)' : String(v)}
-                        onClick={() =>
-                          changeQueryContext({ ...queryContext, maxParseExceptions: v })
-                        }
-                        shouldDismissPopover={false}
-                      />
-                    ))}
-                  </MenuItem>
+                {show('approximate-top-n') && (
+                  <MenuBoolean
+                    icon={IconNames.HORIZONTAL_BAR_CHART_DESC}
+                    text="Approximate TopN"
+                    value={useApproximateTopN}
+                    onValueChange={useApproximateTopN =>
+                      changeQueryContext({
+                        ...queryContext,
+                        useApproximateTopN,
+                      })
+                    }
+                    optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
+                  />
                 )}
                 {show('join-algorithm') && (
                   <MenuItem
@@ -566,6 +506,125 @@
                   </MenuItem>
                 )}
 
+                {show('insert-replace-specific-context') && (
+                  <MenuItem
+                    icon={IconNames.BRING_DATA}
+                    text="INSERT / REPLACE / EXTERN specific context"
+                  >
+                    <MenuBoolean
+                      text="Fail on empty insert"
+                      value={failOnEmptyInsert}
+                      showUndefined
+                      undefinedEffectiveValue={false}
+                      onValueChange={failOnEmptyInsert =>
+                        changeQueryContext({ ...queryContext, failOnEmptyInsert })
+                      }
+                      optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
+                    />
+                    <MenuBoolean
+                      text="Wait until segments have loaded"
+                      value={waitUntilSegmentsLoad}
+                      showUndefined
+                      undefinedEffectiveValue={ingestMode}
+                      onValueChange={waitUntilSegmentsLoad =>
+                        changeQueryContext({ ...queryContext, waitUntilSegmentsLoad })
+                      }
+                      optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
+                    />
+                    <MenuItem text="Max parse exceptions" label={String(maxParseExceptions)}>
+                      {[0, 1, 5, 10, 1000, 10000, -1].map(v => (
+                        <MenuItem
+                          key={String(v)}
+                          icon={tickIcon(v === maxParseExceptions)}
+                          text={v === -1 ? '∞ (-1)' : String(v)}
+                          onClick={() =>
+                            changeQueryContext({ ...queryContext, maxParseExceptions: v })
+                          }
+                          shouldDismissPopover={false}
+                        />
+                      ))}
+                    </MenuItem>
+                    <MenuItem
+                      text="Edit index spec..."
+                      label={summarizeIndexSpec(indexSpec)}
+                      shouldDismissPopover={false}
+                      onClick={() => {
+                        setIndexSpecDialogSpec(indexSpec || {});
+                      }}
+                    />
+                  </MenuItem>
+                )}
+
+                {show('finalize-aggregations') && (
+                  <MenuBoolean
+                    icon={IconNames.TRANSLATE}
+                    text="Finalize aggregations"
+                    value={finalizeAggregations}
+                    showUndefined
+                    undefinedEffectiveValue={!ingestMode}
+                    onValueChange={finalizeAggregations =>
+                      changeQueryContext({ ...queryContext, finalizeAggregations })
+                    }
+                    optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
+                  />
+                )}
+                {show('group-by-enable-multi-value-unnesting') && (
+                  <MenuBoolean
+                    icon={IconNames.FORK}
+                    text="GROUP BY multi-value unnesting"
+                    value={groupByEnableMultiValueUnnesting}
+                    showUndefined
+                    undefinedEffectiveValue={!ingestMode}
+                    onValueChange={groupByEnableMultiValueUnnesting =>
+                      changeQueryContext({ ...queryContext, groupByEnableMultiValueUnnesting })
+                    }
+                    optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
+                  />
+                )}
+
+                {show('use-cache') && (
+                  <MenuBoolean
+                    icon={IconNames.DATA_CONNECTION}
+                    text="Use cache"
+                    value={useCache}
+                    onValueChange={useCache =>
+                      changeQueryContext({
+                        ...queryContext,
+                        useCache,
+                        populateCache: useCache,
+                      })
+                    }
+                    optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
+                  />
+                )}
+                {show('limit-inline-results') && (
+                  <MenuCheckbox
+                    checked={!query.unlimited}
+                    intent={query.unlimited ? Intent.WARNING : undefined}
+                    text="Limit inline results"
+                    labelElement={
+                      query.unlimited ? <Icon icon={IconNames.WARNING_SIGN} /> : undefined
+                    }
+                    onChange={() => {
+                      onQueryChange(query.toggleUnlimited());
+                    }}
+                  />
+                )}
+
+                {show('durable-shuffle-storage') && (
+                  <MenuBoolean
+                    icon={IconNames.CLOUD_TICK}
+                    text="Durable shuffle storage"
+                    value={durableShuffleStorage}
+                    onValueChange={durableShuffleStorage =>
+                      changeQueryContext({
+                        ...queryContext,
+                        durableShuffleStorage,
+                      })
+                    }
+                    optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
+                  />
+                )}
                 {show('select-destination') && (
                   <MenuItem
                     icon={IconNames.MANUALLY_ENTERED_DATA}
@@ -602,119 +661,6 @@
                     />
                   </MenuItem>
                 )}
-
-                {show('finalize-aggregations') && (
-                  <MenuBoolean
-                    icon={IconNames.TRANSLATE}
-                    text="Finalize aggregations"
-                    value={finalizeAggregations}
-                    showUndefined
-                    undefinedEffectiveValue={!ingestMode}
-                    onValueChange={finalizeAggregations =>
-                      changeQueryContext({ ...queryContext, finalizeAggregations })
-                    }
-                    optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
-                  />
-                )}
-                {show('group-by-enable-multi-value-unnesting') && (
-                  <MenuBoolean
-                    icon={IconNames.FORK}
-                    text="GROUP BY multi-value unnesting"
-                    value={groupByEnableMultiValueUnnesting}
-                    showUndefined
-                    undefinedEffectiveValue={!ingestMode}
-                    onValueChange={groupByEnableMultiValueUnnesting =>
-                      changeQueryContext({ ...queryContext, groupByEnableMultiValueUnnesting })
-                    }
-                    optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
-                  />
-                )}
-                {show('durable-shuffle-storage') && (
-                  <MenuBoolean
-                    icon={IconNames.CLOUD_TICK}
-                    text="Durable shuffle storage"
-                    value={durableShuffleStorage}
-                    onValueChange={durableShuffleStorage =>
-                      changeQueryContext({
-                        ...queryContext,
-                        durableShuffleStorage,
-                      })
-                    }
-                    optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
-                  />
-                )}
-
-                {show('use-cache') && (
-                  <MenuBoolean
-                    icon={IconNames.DATA_CONNECTION}
-                    text="Use cache"
-                    value={useCache}
-                    onValueChange={useCache =>
-                      changeQueryContext({
-                        ...queryContext,
-                        useCache,
-                        populateCache: useCache,
-                      })
-                    }
-                    optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
-                  />
-                )}
-                {show('approximate-top-n') && (
-                  <MenuBoolean
-                    icon={IconNames.HORIZONTAL_BAR_CHART_DESC}
-                    text="Approximate TopN"
-                    value={useApproximateTopN}
-                    onValueChange={useApproximateTopN =>
-                      changeQueryContext({
-                        ...queryContext,
-                        useApproximateTopN,
-                      })
-                    }
-                    optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
-                  />
-                )}
-
-                {show('approximate-count-distinct') && (
-                  <MenuBoolean
-                    icon={IconNames.ROCKET_SLANT}
-                    text="Approximate COUNT(DISTINCT)"
-                    value={useApproximateCountDistinct}
-                    onValueChange={useApproximateCountDistinct =>
-                      changeQueryContext({
-                        ...queryContext,
-                        useApproximateCountDistinct,
-                      })
-                    }
-                    optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
-                  />
-                )}
-                {show('limit-inline-results') && (
-                  <MenuCheckbox
-                    checked={!query.unlimited}
-                    intent={query.unlimited ? Intent.WARNING : undefined}
-                    text="Limit inline results"
-                    labelElement={
-                      query.unlimited ? <Icon icon={IconNames.WARNING_SIGN} /> : undefined
-                    }
-                    onChange={() => {
-                      onQueryChange(query.toggleUnlimited());
-                    }}
-                  />
-                )}
-                {show('include-all-counters') && (
-                  <MenuBoolean
-                    icon={IconNames.DIAGNOSIS}
-                    text="Include all counters"
-                    value={includeAllCounters}
-                    onValueChange={includeAllCounters =>
-                      changeQueryContext({
-                        ...queryContext,
-                        includeAllCounters,
-                      })
-                    }
-                    optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
-                  />
-                )}
               </Menu>
             }
           >
@@ -722,7 +668,7 @@
               text={`Engine: ${
                 queryEngine
                   ? enginesLabelFn(queryEngine).text
-                  : `${autoEngineLabel.text} (${enginesLabelFn(effectiveEngine).text})`
+                  : `${autoEngineLabel.text} [${enginesLabelFn(effectiveEngine).text}]`
               }`}
               rightIcon={IconNames.CARET_DOWN}
               intent={intent}
diff --git a/web-console/src/views/workbench-view/workbench-view.scss b/web-console/src/views/workbench-view/workbench-view.scss
index 1287dab..be00623 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 @@
     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 cd4afb8..5250373 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 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 { 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 @@
   renamingTab?: TabEntry;
 
   showRecentQueryTaskPanel: boolean;
+  showCurrentDartPanel: boolean;
 }
 
 export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, WorkbenchViewState> {
@@ -166,6 +169,11 @@
       hasSqlTask && localStorageGetJson(LocalStorageKeys.WORKBENCH_TASK_PANEL),
     );
 
+    const showCurrentDartPanel = Boolean(
+      queryEngines.includes('sql-msq-dart') &&
+        localStorageGetJson(LocalStorageKeys.WORKBENCH_DART_PANEL),
+    );
+
     const tabEntries =
       Array.isArray(possibleTabEntries) && possibleTabEntries.length
         ? possibleTabEntries.map(q => ({ ...q, query: new WorkbenchQuery(q.query) }))
@@ -198,6 +206,7 @@
       taskIdSubmitDialogOpen: false,
 
       showRecentQueryTaskPanel,
+      showCurrentDartPanel,
     };
   }
 
@@ -264,6 +273,11 @@
     localStorageSetJson(LocalStorageKeys.WORKBENCH_TASK_PANEL, false);
   };
 
+  private readonly handleCurrentDartPanelClose = () => {
+    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 @@
     if (!queryEngines.includes('sql-msq-task')) return;
     if (hideToolbar) return;
 
-    const { showRecentQueryTaskPanel } = this.state;
+    const { showRecentQueryTaskPanel, showCurrentDartPanel } = this.state;
     return (
       <ButtonGroup className="toolbar">
         <Button
@@ -669,16 +683,35 @@
           }}
           minimal
         />
-        <Button
-          icon={IconNames.DRAWER_RIGHT}
-          minimal
-          data-tooltip="Open recent query task panel"
-          onClick={() => {
-            const n = !showRecentQueryTaskPanel;
-            this.setState({ showRecentQueryTaskPanel: n });
-            localStorageSetJson(LocalStorageKeys.WORKBENCH_TASK_PANEL, n);
-          }}
-        />
+        <Popover
+          position="bottom-right"
+          content={
+            <Menu>
+              <MenuCheckbox
+                text="Recent query task panel"
+                checked={showRecentQueryTaskPanel}
+                shouldDismissPopover
+                onChange={() => {
+                  const n = !showRecentQueryTaskPanel;
+                  this.setState({ showRecentQueryTaskPanel: n });
+                  localStorageSetJson(LocalStorageKeys.WORKBENCH_TASK_PANEL, n);
+                }}
+              />
+              <MenuCheckbox
+                text="Current Dart query panel"
+                checked={showCurrentDartPanel}
+                shouldDismissPopover
+                onChange={() => {
+                  const n = !showCurrentDartPanel;
+                  this.setState({ showCurrentDartPanel: n });
+                  localStorageSetJson(LocalStorageKeys.WORKBENCH_DART_PANEL, n);
+                }}
+              />
+            </Menu>
+          }
+        >
+          <Button icon={IconNames.DRAWER_RIGHT} minimal data-tooltip="Open helper panels" />
+        </Popover>
       </ButtonGroup>
     );
   }
@@ -744,7 +777,9 @@
           runMoreMenu={
             <Menu>
               {!hiddenMoreMenuItems.includes('explain') &&
-                (effectiveEngine === 'sql-native' || effectiveEngine === 'sql-msq-task') && (
+                (effectiveEngine === 'sql-native' ||
+                  effectiveEngine === 'sql-msq-task' ||
+                  effectiveEngine === 'sql-msq-dart') && (
                   <MenuItem
                     icon={IconNames.CLEAN}
                     text="Explain SQL query"
@@ -861,7 +896,7 @@
   };
 
   render() {
-    const { columnMetadataState, showRecentQueryTaskPanel } = this.state;
+    const { columnMetadataState, showRecentQueryTaskPanel, showCurrentDartPanel } = this.state;
     const query = this.getCurrentQuery();
 
     let defaultSchema: string | undefined;
@@ -872,7 +907,7 @@
       defaultTables = parsedQuery.getUsedTableNames();
     }
 
-    const showRightPanel = showRecentQueryTaskPanel;
+    const showRightPanel = showRecentQueryTaskPanel || showCurrentDartPanel;
     return (
       <div
         className={classNames('workbench-view app-view', {
@@ -883,8 +918,8 @@
         {!columnMetadataState.isError() && (
           <ColumnTree
             getParsedQuery={this.getParsedQuery}
+            columnMetadata={columnMetadataState.getSomeData()}
             columnMetadataLoading={columnMetadataState.loading}
-            columnMetadata={columnMetadataState.data}
             onQueryChange={this.handleSqlQueryChange}
             defaultSchema={defaultSchema ? defaultSchema : 'druid'}
             defaultTables={defaultTables}
@@ -903,6 +938,9 @@
                 onNewTab={this.handleNewTab}
               />
             )}
+            {showCurrentDartPanel && (
+              <CurrentDartPanel onClose={this.handleCurrentDartPanelClose} />
+            )}
           </div>
         )}
         {this.renderExecutionDetailsDialog()}