mirror of https://github.com/apache/druid.git
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
This commit is contained in:
parent
1fc82a96bd
commit
c8529294eb
|
@ -67,6 +67,7 @@ function _build_distribution() {
|
||||||
&& 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.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.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.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 \
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -213,6 +213,7 @@ exports[`HeaderBar matches snapshot 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": true,
|
"coordinator": true,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": true,
|
||||||
"multiStageQueryTask": true,
|
"multiStageQueryTask": true,
|
||||||
"overlord": true,
|
"overlord": true,
|
||||||
"queryType": "nativeAndSql",
|
"queryType": "nativeAndSql",
|
||||||
|
|
|
@ -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',
|
||||||
|
},
|
||||||
|
];
|
|
@ -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';
|
||||||
|
}
|
|
@ -16,9 +16,14 @@
|
||||||
* limitations under the License.
|
* 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(
|
export function validDruidEngine(
|
||||||
possibleDruidEngine: string | undefined,
|
possibleDruidEngine: string | undefined,
|
||||||
|
|
|
@ -20,6 +20,7 @@ export * from './async-query/async-query';
|
||||||
export * from './compaction-config/compaction-config';
|
export * from './compaction-config/compaction-config';
|
||||||
export * from './compaction-status/compaction-status';
|
export * from './compaction-status/compaction-status';
|
||||||
export * from './coordinator-dynamic-config/coordinator-dynamic-config';
|
export * from './coordinator-dynamic-config/coordinator-dynamic-config';
|
||||||
|
export * from './dart/dart-query-entry';
|
||||||
export * from './dimension-spec/dimension-spec';
|
export * from './dimension-spec/dimension-spec';
|
||||||
export * from './druid-engine/druid-engine';
|
export * from './druid-engine/druid-engine';
|
||||||
export * from './execution/execution';
|
export * from './execution/execution';
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
import { max, sum } from 'd3-array';
|
import { max, sum } from 'd3-array';
|
||||||
|
|
||||||
|
import { AutoForm } from '../../components';
|
||||||
import { countBy, deleteKeys, filterMap, groupByAsMap, oneOf, zeroDivide } from '../../utils';
|
import { countBy, deleteKeys, filterMap, groupByAsMap, oneOf, zeroDivide } from '../../utils';
|
||||||
import type { InputFormat } from '../input-format/input-format';
|
import type { InputFormat } from '../input-format/input-format';
|
||||||
import type { InputSource } from '../input-source/input-source';
|
import type { InputSource } from '../input-source/input-source';
|
||||||
|
@ -252,26 +253,16 @@ export const CPUS_COUNTER_FIELDS: CpusCounterFields[] = [
|
||||||
|
|
||||||
export function cpusCounterFieldTitle(k: CpusCounterFields) {
|
export function cpusCounterFieldTitle(k: CpusCounterFields) {
|
||||||
switch (k) {
|
switch (k) {
|
||||||
case 'main':
|
|
||||||
return 'Main';
|
|
||||||
|
|
||||||
case 'collectKeyStatistics':
|
case 'collectKeyStatistics':
|
||||||
return 'Collect key stats';
|
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:
|
default:
|
||||||
return k;
|
// main
|
||||||
|
// mergeInput
|
||||||
|
// hashPartitionOutput
|
||||||
|
// mixOutput
|
||||||
|
// sortOutput
|
||||||
|
return AutoForm.makeLabelName(k);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -528,7 +528,7 @@ export class WorkbenchQuery {
|
||||||
};
|
};
|
||||||
|
|
||||||
let cancelQueryId: string | undefined;
|
let cancelQueryId: string | undefined;
|
||||||
if (engine === 'sql-native') {
|
if (engine === 'sql-native' || engine === 'sql-msq-dart') {
|
||||||
cancelQueryId = apiQuery.context.sqlQueryId;
|
cancelQueryId = apiQuery.context.sqlQueryId;
|
||||||
if (!cancelQueryId) {
|
if (!cancelQueryId) {
|
||||||
// If the sqlQueryId is not explicitly set on the context generate one, so it is possible to cancel the query.
|
// 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;
|
apiQuery.context.sqlStringifyArrays ??= false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (engine === 'sql-msq-dart') {
|
||||||
|
apiQuery.context.fullReport ??= true;
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(queryParameters) && queryParameters.length) {
|
if (Array.isArray(queryParameters) && queryParameters.length) {
|
||||||
apiQuery.parameters = queryParameters;
|
apiQuery.parameters = queryParameters;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ export type QueryType = 'none' | 'nativeOnly' | 'nativeAndSql';
|
||||||
export interface CapabilitiesValue {
|
export interface CapabilitiesValue {
|
||||||
queryType: QueryType;
|
queryType: QueryType;
|
||||||
multiStageQueryTask: boolean;
|
multiStageQueryTask: boolean;
|
||||||
|
multiStageQueryDart: boolean;
|
||||||
coordinator: boolean;
|
coordinator: boolean;
|
||||||
overlord: boolean;
|
overlord: boolean;
|
||||||
maxTaskSlots?: number;
|
maxTaskSlots?: number;
|
||||||
|
@ -53,6 +54,7 @@ export class Capabilities {
|
||||||
|
|
||||||
private readonly queryType: QueryType;
|
private readonly queryType: QueryType;
|
||||||
private readonly multiStageQueryTask: boolean;
|
private readonly multiStageQueryTask: boolean;
|
||||||
|
private readonly multiStageQueryDart: boolean;
|
||||||
private readonly coordinator: boolean;
|
private readonly coordinator: boolean;
|
||||||
private readonly overlord: boolean;
|
private readonly overlord: boolean;
|
||||||
private readonly maxTaskSlots?: number;
|
private readonly maxTaskSlots?: number;
|
||||||
|
@ -139,6 +141,15 @@ export class Capabilities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
static async detectCapabilities(): Promise<Capabilities | undefined> {
|
||||||
const queryType = await Capabilities.detectQueryType();
|
const queryType = await Capabilities.detectQueryType();
|
||||||
if (typeof queryType === 'undefined') return;
|
if (typeof queryType === 'undefined') return;
|
||||||
|
@ -154,11 +165,15 @@ export class Capabilities {
|
||||||
coordinator = overlord = await Capabilities.detectManagementProxy();
|
coordinator = overlord = await Capabilities.detectManagementProxy();
|
||||||
}
|
}
|
||||||
|
|
||||||
const multiStageQueryTask = await Capabilities.detectMultiStageQueryTask();
|
const [multiStageQueryTask, multiStageQueryDart] = await Promise.all([
|
||||||
|
Capabilities.detectMultiStageQueryTask(),
|
||||||
|
Capabilities.detectMultiStageQueryDart(),
|
||||||
|
]);
|
||||||
|
|
||||||
return new Capabilities({
|
return new Capabilities({
|
||||||
queryType,
|
queryType,
|
||||||
multiStageQueryTask,
|
multiStageQueryTask,
|
||||||
|
multiStageQueryDart,
|
||||||
coordinator,
|
coordinator,
|
||||||
overlord,
|
overlord,
|
||||||
});
|
});
|
||||||
|
@ -179,6 +194,7 @@ export class Capabilities {
|
||||||
constructor(value: CapabilitiesValue) {
|
constructor(value: CapabilitiesValue) {
|
||||||
this.queryType = value.queryType;
|
this.queryType = value.queryType;
|
||||||
this.multiStageQueryTask = value.multiStageQueryTask;
|
this.multiStageQueryTask = value.multiStageQueryTask;
|
||||||
|
this.multiStageQueryDart = value.multiStageQueryDart;
|
||||||
this.coordinator = value.coordinator;
|
this.coordinator = value.coordinator;
|
||||||
this.overlord = value.overlord;
|
this.overlord = value.overlord;
|
||||||
this.maxTaskSlots = value.maxTaskSlots;
|
this.maxTaskSlots = value.maxTaskSlots;
|
||||||
|
@ -188,6 +204,7 @@ export class Capabilities {
|
||||||
return {
|
return {
|
||||||
queryType: this.queryType,
|
queryType: this.queryType,
|
||||||
multiStageQueryTask: this.multiStageQueryTask,
|
multiStageQueryTask: this.multiStageQueryTask,
|
||||||
|
multiStageQueryDart: this.multiStageQueryDart,
|
||||||
coordinator: this.coordinator,
|
coordinator: this.coordinator,
|
||||||
overlord: this.overlord,
|
overlord: this.overlord,
|
||||||
maxTaskSlots: this.maxTaskSlots,
|
maxTaskSlots: this.maxTaskSlots,
|
||||||
|
@ -248,6 +265,10 @@ export class Capabilities {
|
||||||
return this.multiStageQueryTask;
|
return this.multiStageQueryTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hasMultiStageQueryDart(): boolean {
|
||||||
|
return this.multiStageQueryDart;
|
||||||
|
}
|
||||||
|
|
||||||
public getSupportedQueryEngines(): DruidEngine[] {
|
public getSupportedQueryEngines(): DruidEngine[] {
|
||||||
const queryEngines: DruidEngine[] = ['native'];
|
const queryEngines: DruidEngine[] = ['native'];
|
||||||
if (this.hasSql()) {
|
if (this.hasSql()) {
|
||||||
|
@ -256,6 +277,9 @@ export class Capabilities {
|
||||||
if (this.hasMultiStageQueryTask()) {
|
if (this.hasMultiStageQueryTask()) {
|
||||||
queryEngines.push('sql-msq-task');
|
queryEngines.push('sql-msq-task');
|
||||||
}
|
}
|
||||||
|
if (this.hasMultiStageQueryDart()) {
|
||||||
|
queryEngines.push('sql-msq-dart');
|
||||||
|
}
|
||||||
return queryEngines;
|
return queryEngines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,36 +306,42 @@ export class Capabilities {
|
||||||
Capabilities.FULL = new Capabilities({
|
Capabilities.FULL = new Capabilities({
|
||||||
queryType: 'nativeAndSql',
|
queryType: 'nativeAndSql',
|
||||||
multiStageQueryTask: true,
|
multiStageQueryTask: true,
|
||||||
|
multiStageQueryDart: true,
|
||||||
coordinator: true,
|
coordinator: true,
|
||||||
overlord: true,
|
overlord: true,
|
||||||
});
|
});
|
||||||
Capabilities.NO_SQL = new Capabilities({
|
Capabilities.NO_SQL = new Capabilities({
|
||||||
queryType: 'nativeOnly',
|
queryType: 'nativeOnly',
|
||||||
multiStageQueryTask: false,
|
multiStageQueryTask: false,
|
||||||
|
multiStageQueryDart: false,
|
||||||
coordinator: true,
|
coordinator: true,
|
||||||
overlord: true,
|
overlord: true,
|
||||||
});
|
});
|
||||||
Capabilities.COORDINATOR_OVERLORD = new Capabilities({
|
Capabilities.COORDINATOR_OVERLORD = new Capabilities({
|
||||||
queryType: 'none',
|
queryType: 'none',
|
||||||
multiStageQueryTask: false,
|
multiStageQueryTask: false,
|
||||||
|
multiStageQueryDart: false,
|
||||||
coordinator: true,
|
coordinator: true,
|
||||||
overlord: true,
|
overlord: true,
|
||||||
});
|
});
|
||||||
Capabilities.COORDINATOR = new Capabilities({
|
Capabilities.COORDINATOR = new Capabilities({
|
||||||
queryType: 'none',
|
queryType: 'none',
|
||||||
multiStageQueryTask: false,
|
multiStageQueryTask: false,
|
||||||
|
multiStageQueryDart: false,
|
||||||
coordinator: true,
|
coordinator: true,
|
||||||
overlord: false,
|
overlord: false,
|
||||||
});
|
});
|
||||||
Capabilities.OVERLORD = new Capabilities({
|
Capabilities.OVERLORD = new Capabilities({
|
||||||
queryType: 'none',
|
queryType: 'none',
|
||||||
multiStageQueryTask: false,
|
multiStageQueryTask: false,
|
||||||
|
multiStageQueryDart: false,
|
||||||
coordinator: false,
|
coordinator: false,
|
||||||
overlord: true,
|
overlord: true,
|
||||||
});
|
});
|
||||||
Capabilities.NO_PROXY = new Capabilities({
|
Capabilities.NO_PROXY = new Capabilities({
|
||||||
queryType: 'nativeAndSql',
|
queryType: 'nativeAndSql',
|
||||||
multiStageQueryTask: true,
|
multiStageQueryTask: true,
|
||||||
|
multiStageQueryDart: false,
|
||||||
coordinator: false,
|
coordinator: false,
|
||||||
overlord: false,
|
overlord: false,
|
||||||
});
|
});
|
||||||
|
|
|
@ -342,6 +342,19 @@ export async function queryDruidSql<T = any>(
|
||||||
return sqlResultResp.data;
|
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 {
|
export interface QueryExplanation {
|
||||||
query: any;
|
query: any;
|
||||||
signature: { name: string; type: string }[];
|
signature: { name: string; type: string }[];
|
||||||
|
|
|
@ -53,6 +53,7 @@ export const LocalStorageKeys = {
|
||||||
WORKBENCH_PANE_SIZE: 'workbench-pane-size' as const,
|
WORKBENCH_PANE_SIZE: 'workbench-pane-size' as const,
|
||||||
WORKBENCH_HISTORY: 'workbench-history' as const,
|
WORKBENCH_HISTORY: 'workbench-history' as const,
|
||||||
WORKBENCH_TASK_PANEL: 'workbench-task-panel' 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,
|
SQL_DATA_LOADER_CONTENT: 'sql-data-loader-content' as const,
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ exports[`HomeView matches snapshot (coordinator) 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": true,
|
"coordinator": true,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": false,
|
||||||
"multiStageQueryTask": false,
|
"multiStageQueryTask": false,
|
||||||
"overlord": false,
|
"overlord": false,
|
||||||
"queryType": "none",
|
"queryType": "none",
|
||||||
|
@ -21,6 +22,7 @@ exports[`HomeView matches snapshot (coordinator) 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": true,
|
"coordinator": true,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": false,
|
||||||
"multiStageQueryTask": false,
|
"multiStageQueryTask": false,
|
||||||
"overlord": false,
|
"overlord": false,
|
||||||
"queryType": "none",
|
"queryType": "none",
|
||||||
|
@ -32,6 +34,7 @@ exports[`HomeView matches snapshot (coordinator) 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": true,
|
"coordinator": true,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": false,
|
||||||
"multiStageQueryTask": false,
|
"multiStageQueryTask": false,
|
||||||
"overlord": false,
|
"overlord": false,
|
||||||
"queryType": "none",
|
"queryType": "none",
|
||||||
|
@ -44,6 +47,7 @@ exports[`HomeView matches snapshot (coordinator) 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": true,
|
"coordinator": true,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": false,
|
||||||
"multiStageQueryTask": false,
|
"multiStageQueryTask": false,
|
||||||
"overlord": false,
|
"overlord": false,
|
||||||
"queryType": "none",
|
"queryType": "none",
|
||||||
|
@ -55,6 +59,7 @@ exports[`HomeView matches snapshot (coordinator) 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": true,
|
"coordinator": true,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": false,
|
||||||
"multiStageQueryTask": false,
|
"multiStageQueryTask": false,
|
||||||
"overlord": false,
|
"overlord": false,
|
||||||
"queryType": "none",
|
"queryType": "none",
|
||||||
|
@ -73,6 +78,7 @@ exports[`HomeView matches snapshot (full) 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": true,
|
"coordinator": true,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": true,
|
||||||
"multiStageQueryTask": true,
|
"multiStageQueryTask": true,
|
||||||
"overlord": true,
|
"overlord": true,
|
||||||
"queryType": "nativeAndSql",
|
"queryType": "nativeAndSql",
|
||||||
|
@ -85,6 +91,7 @@ exports[`HomeView matches snapshot (full) 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": true,
|
"coordinator": true,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": true,
|
||||||
"multiStageQueryTask": true,
|
"multiStageQueryTask": true,
|
||||||
"overlord": true,
|
"overlord": true,
|
||||||
"queryType": "nativeAndSql",
|
"queryType": "nativeAndSql",
|
||||||
|
@ -96,6 +103,7 @@ exports[`HomeView matches snapshot (full) 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": true,
|
"coordinator": true,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": true,
|
||||||
"multiStageQueryTask": true,
|
"multiStageQueryTask": true,
|
||||||
"overlord": true,
|
"overlord": true,
|
||||||
"queryType": "nativeAndSql",
|
"queryType": "nativeAndSql",
|
||||||
|
@ -109,6 +117,7 @@ exports[`HomeView matches snapshot (full) 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": true,
|
"coordinator": true,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": true,
|
||||||
"multiStageQueryTask": true,
|
"multiStageQueryTask": true,
|
||||||
"overlord": true,
|
"overlord": true,
|
||||||
"queryType": "nativeAndSql",
|
"queryType": "nativeAndSql",
|
||||||
|
@ -120,6 +129,7 @@ exports[`HomeView matches snapshot (full) 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": true,
|
"coordinator": true,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": true,
|
||||||
"multiStageQueryTask": true,
|
"multiStageQueryTask": true,
|
||||||
"overlord": true,
|
"overlord": true,
|
||||||
"queryType": "nativeAndSql",
|
"queryType": "nativeAndSql",
|
||||||
|
@ -132,6 +142,7 @@ exports[`HomeView matches snapshot (full) 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": true,
|
"coordinator": true,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": true,
|
||||||
"multiStageQueryTask": true,
|
"multiStageQueryTask": true,
|
||||||
"overlord": true,
|
"overlord": true,
|
||||||
"queryType": "nativeAndSql",
|
"queryType": "nativeAndSql",
|
||||||
|
@ -143,6 +154,7 @@ exports[`HomeView matches snapshot (full) 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": true,
|
"coordinator": true,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": true,
|
||||||
"multiStageQueryTask": true,
|
"multiStageQueryTask": true,
|
||||||
"overlord": true,
|
"overlord": true,
|
||||||
"queryType": "nativeAndSql",
|
"queryType": "nativeAndSql",
|
||||||
|
@ -161,6 +173,7 @@ exports[`HomeView matches snapshot (overlord) 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": false,
|
"coordinator": false,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": false,
|
||||||
"multiStageQueryTask": false,
|
"multiStageQueryTask": false,
|
||||||
"overlord": true,
|
"overlord": true,
|
||||||
"queryType": "none",
|
"queryType": "none",
|
||||||
|
@ -173,6 +186,7 @@ exports[`HomeView matches snapshot (overlord) 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": false,
|
"coordinator": false,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": false,
|
||||||
"multiStageQueryTask": false,
|
"multiStageQueryTask": false,
|
||||||
"overlord": true,
|
"overlord": true,
|
||||||
"queryType": "none",
|
"queryType": "none",
|
||||||
|
@ -184,6 +198,7 @@ exports[`HomeView matches snapshot (overlord) 1`] = `
|
||||||
Capabilities {
|
Capabilities {
|
||||||
"coordinator": false,
|
"coordinator": false,
|
||||||
"maxTaskSlots": undefined,
|
"maxTaskSlots": undefined,
|
||||||
|
"multiStageQueryDart": false,
|
||||||
"multiStageQueryTask": false,
|
"multiStageQueryTask": false,
|
||||||
"overlord": true,
|
"overlord": true,
|
||||||
"queryType": "none",
|
"queryType": "none",
|
||||||
|
|
|
@ -688,10 +688,10 @@ export class ColumnTree extends React.PureComponent<ColumnTreeProps, ColumnTreeS
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { columnMetadataLoading } = this.props;
|
const { columnMetadata, columnMetadataLoading } = this.props;
|
||||||
const { currentSchemaSubtree, searchString } = this.state;
|
const { currentSchemaSubtree, searchString } = this.state;
|
||||||
|
|
||||||
if (columnMetadataLoading) {
|
if (columnMetadataLoading && !columnMetadata) {
|
||||||
return (
|
return (
|
||||||
<div className="column-tree">
|
<div className="column-tree">
|
||||||
<Loader />
|
<Loader />
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
);
|
||||||
|
});
|
|
@ -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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
);
|
||||||
|
});
|
|
@ -134,12 +134,12 @@ exports[`ExecutionStagesPane matches snapshot 1`] = `
|
||||||
<span
|
<span
|
||||||
className="cpu-label"
|
className="cpu-label"
|
||||||
>
|
>
|
||||||
counter
|
Counter
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className="cpu-counter"
|
className="cpu-counter"
|
||||||
>
|
>
|
||||||
wall time
|
Wall time
|
||||||
</span>
|
</span>
|
||||||
</i>
|
</i>
|
||||||
</React.Fragment>,
|
</React.Fragment>,
|
||||||
|
@ -147,7 +147,7 @@ exports[`ExecutionStagesPane matches snapshot 1`] = `
|
||||||
"className": "padded",
|
"className": "padded",
|
||||||
"id": "cpu",
|
"id": "cpu",
|
||||||
"show": false,
|
"show": false,
|
||||||
"width": 220,
|
"width": 240,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Header": <React.Fragment>
|
"Header": <React.Fragment>
|
||||||
|
|
|
@ -129,7 +129,7 @@
|
||||||
|
|
||||||
.cpu-label {
|
.cpu-label {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 120px;
|
width: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cpu-counter {
|
.cpu-counter {
|
||||||
|
|
|
@ -263,8 +263,8 @@ export const ExecutionStagesPane = React.memo(function ExecutionStagesPane(
|
||||||
Header: twoLines(
|
Header: twoLines(
|
||||||
'CPU utilization',
|
'CPU utilization',
|
||||||
<i>
|
<i>
|
||||||
<span className="cpu-label">counter</span>
|
<span className="cpu-label">Counter</span>
|
||||||
<span className="cpu-counter">wall time</span>
|
<span className="cpu-counter">Wall time</span>
|
||||||
</i>,
|
</i>,
|
||||||
),
|
),
|
||||||
id: 'cpu',
|
id: 'cpu',
|
||||||
|
@ -863,14 +863,14 @@ ${title} uncompressed size: ${formatBytesCompact(
|
||||||
Header: twoLines(
|
Header: twoLines(
|
||||||
'CPU utilization',
|
'CPU utilization',
|
||||||
<i>
|
<i>
|
||||||
<span className="cpu-label">counter</span>
|
<span className="cpu-label">Counter</span>
|
||||||
<span className="cpu-counter">wall time</span>
|
<span className="cpu-counter">Wall time</span>
|
||||||
</i>,
|
</i>,
|
||||||
),
|
),
|
||||||
id: 'cpu',
|
id: 'cpu',
|
||||||
accessor: () => null,
|
accessor: () => null,
|
||||||
className: 'padded',
|
className: 'padded',
|
||||||
width: 220,
|
width: 240,
|
||||||
show: stages.hasCounter('cpu'),
|
show: stages.hasCounter('cpu'),
|
||||||
Cell({ original }) {
|
Cell({ original }) {
|
||||||
const cpuTotals = stages.getCpuTotalsForStage(original);
|
const cpuTotals = stages.getCpuTotalsForStage(original);
|
||||||
|
|
|
@ -96,7 +96,7 @@ export const ExecutionSummaryPanel = React.memo(function ExecutionSummaryPanel(
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!execution) return;
|
if (!execution) return;
|
||||||
if (oneOf(execution.engine, 'sql-msq-task')) {
|
if (oneOf(execution.engine, 'sql-msq-task', 'sql-msq-dart')) {
|
||||||
onExecutionDetail();
|
onExecutionDetail();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -45,6 +45,7 @@ import {
|
||||||
getDruidErrorMessage,
|
getDruidErrorMessage,
|
||||||
nonEmptyArray,
|
nonEmptyArray,
|
||||||
queryDruidSql,
|
queryDruidSql,
|
||||||
|
queryDruidSqlDart,
|
||||||
} from '../../../utils';
|
} from '../../../utils';
|
||||||
|
|
||||||
import './explain-dialog.scss';
|
import './explain-dialog.scss';
|
||||||
|
@ -108,6 +109,10 @@ export const ExplainDialog = React.memo(function ExplainDialog(props: ExplainDia
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'sql-msq-dart':
|
||||||
|
result = await queryDruidSqlDart(payload);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Explain not supported for engine ${engine}`);
|
throw new Error(`Explain not supported for engine ${engine}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { Code, Intent } from '@blueprintjs/core';
|
import { Code, Intent } from '@blueprintjs/core';
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
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 axios from 'axios';
|
||||||
import type { JSX } from 'react';
|
import type { JSX } from 'react';
|
||||||
import React, { useCallback, useEffect, useRef, useState } 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 { WorkbenchRunningPromises } from '../../../singletons/workbench-running-promises';
|
||||||
import type { ColumnMetadata, QueryAction, QuerySlice, RowColumn } from '../../../utils';
|
import type { ColumnMetadata, QueryAction, QuerySlice, RowColumn } from '../../../utils';
|
||||||
import {
|
import {
|
||||||
|
deepGet,
|
||||||
DruidError,
|
DruidError,
|
||||||
findAllSqlQueriesInText,
|
findAllSqlQueriesInText,
|
||||||
localStorageGet,
|
localStorageGet,
|
||||||
|
@ -271,6 +272,67 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
|
||||||
|
|
||||||
return execution;
|
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)) {
|
} else if (WorkbenchRunningPromises.isWorkbenchRunningPromise(q)) {
|
||||||
return await q.executionPromise;
|
return await q.executionPromise;
|
||||||
|
@ -463,13 +525,7 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{execution &&
|
{execution &&
|
||||||
(execution.result ? (
|
(execution.error ? (
|
||||||
<ResultTablePane
|
|
||||||
runeMode={execution.engine === 'native'}
|
|
||||||
queryResult={execution.result}
|
|
||||||
onQueryAction={handleQueryAction}
|
|
||||||
/>
|
|
||||||
) : execution.error ? (
|
|
||||||
<div className="error-container">
|
<div className="error-container">
|
||||||
<ExecutionErrorPane execution={execution} />
|
<ExecutionErrorPane execution={execution} />
|
||||||
{execution.stages && (
|
{execution.stages && (
|
||||||
|
@ -481,6 +537,12 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
) : execution.result ? (
|
||||||
|
<ResultTablePane
|
||||||
|
runeMode={execution.engine === 'native'}
|
||||||
|
queryResult={execution.result}
|
||||||
|
onQueryAction={handleQueryAction}
|
||||||
|
/>
|
||||||
) : execution.isSuccessfulIngest() ? (
|
) : execution.isSuccessfulIngest() ? (
|
||||||
<IngestSuccessPane
|
<IngestSuccessPane
|
||||||
execution={execution}
|
execution={execution}
|
||||||
|
|
|
@ -225,6 +225,7 @@ LIMIT 100`,
|
||||||
className={'status-icon ' + w.taskStatus.toLowerCase()}
|
className={'status-icon ' + w.taskStatus.toLowerCase()}
|
||||||
icon={icon}
|
icon={icon}
|
||||||
style={{ color }}
|
style={{ color }}
|
||||||
|
data-tooltip={`Task status: ${w.taskStatus}`}
|
||||||
/>
|
/>
|
||||||
<div className="timing">
|
<div className="timing">
|
||||||
{prettyFormatIsoDate(w.createdTime) +
|
{prettyFormatIsoDate(w.createdTime) +
|
||||||
|
|
|
@ -46,7 +46,7 @@ exports[`RunPanel matches snapshot on msq (auto) query 1`] = `
|
||||||
<span
|
<span
|
||||||
class="bp5-button-text"
|
class="bp5-button-text"
|
||||||
>
|
>
|
||||||
Engine: SQL MSQ-task
|
Engine: SQL (task)
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
@ -150,7 +150,7 @@ exports[`RunPanel matches snapshot on native (auto) query 1`] = `
|
||||||
<span
|
<span
|
||||||
class="bp5-button-text"
|
class="bp5-button-text"
|
||||||
>
|
>
|
||||||
Engine: Auto (SQL native)
|
Engine: Auto [SQL (native)]
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
|
|
@ -103,13 +103,16 @@ const DEFAULT_ENGINES_LABEL_FN = (engine: DruidEngine | undefined) => {
|
||||||
if (!engine) return { text: 'Auto' };
|
if (!engine) return { text: 'Auto' };
|
||||||
switch (engine) {
|
switch (engine) {
|
||||||
case 'native':
|
case 'native':
|
||||||
return { text: 'Native' };
|
return { text: 'JSON (native)' };
|
||||||
|
|
||||||
case 'sql-native':
|
case 'sql-native':
|
||||||
return { text: 'SQL native' };
|
return { text: 'SQL (native)' };
|
||||||
|
|
||||||
case 'sql-msq-task':
|
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:
|
default:
|
||||||
return { text: engine };
|
return { text: engine };
|
||||||
|
@ -121,8 +124,6 @@ const SELECT_DESTINATION_LABEL: Record<SelectDestination, string> = {
|
||||||
durableStorage: 'Durable storage',
|
durableStorage: 'Durable storage',
|
||||||
};
|
};
|
||||||
|
|
||||||
const EXPERIMENTAL_ICON = <Icon icon={IconNames.WARNING_SIGN} title="Experimental" />;
|
|
||||||
|
|
||||||
export type EnginesMenuOption =
|
export type EnginesMenuOption =
|
||||||
| 'edit-query-context'
|
| 'edit-query-context'
|
||||||
| 'define-parameters'
|
| 'define-parameters'
|
||||||
|
@ -135,7 +136,6 @@ export type EnginesMenuOption =
|
||||||
| 'finalize-aggregations'
|
| 'finalize-aggregations'
|
||||||
| 'group-by-enable-multi-value-unnesting'
|
| 'group-by-enable-multi-value-unnesting'
|
||||||
| 'durable-shuffle-storage'
|
| 'durable-shuffle-storage'
|
||||||
| 'include-all-counters'
|
|
||||||
| 'use-cache'
|
| 'use-cache'
|
||||||
| 'approximate-top-n'
|
| 'approximate-top-n'
|
||||||
| 'limit-inline-results';
|
| 'limit-inline-results';
|
||||||
|
@ -158,21 +158,24 @@ function optionVisible(
|
||||||
case 'finalize-aggregations':
|
case 'finalize-aggregations':
|
||||||
case 'group-by-enable-multi-value-unnesting':
|
case 'group-by-enable-multi-value-unnesting':
|
||||||
case 'durable-shuffle-storage':
|
case 'durable-shuffle-storage':
|
||||||
case 'include-all-counters':
|
|
||||||
case 'join-algorithm':
|
|
||||||
return engine === 'sql-msq-task';
|
return engine === 'sql-msq-task';
|
||||||
|
|
||||||
|
case 'join-algorithm':
|
||||||
|
return engine === 'sql-msq-task' || engine === 'sql-msq-dart';
|
||||||
|
|
||||||
case 'timezone':
|
case 'timezone':
|
||||||
case 'approximate-count-distinct':
|
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':
|
case 'use-cache':
|
||||||
return engine === 'native' || engine === 'sql-native';
|
return engine === 'native' || engine === 'sql-native';
|
||||||
|
|
||||||
case 'approximate-top-n':
|
case 'approximate-top-n':
|
||||||
case 'limit-inline-results':
|
|
||||||
return engine === 'sql-native';
|
return engine === 'sql-native';
|
||||||
|
|
||||||
|
case 'limit-inline-results':
|
||||||
|
return engine === 'sql-native' || engine === 'sql-msq-dart';
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.warn(`Unknown option: ${option}`);
|
console.warn(`Unknown option: ${option}`);
|
||||||
return false;
|
return false;
|
||||||
|
@ -251,16 +254,6 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
queryContext,
|
queryContext,
|
||||||
defaultQueryContext,
|
defaultQueryContext,
|
||||||
);
|
);
|
||||||
const useConcurrentLocks = getQueryContextKey(
|
|
||||||
'useConcurrentLocks',
|
|
||||||
queryContext,
|
|
||||||
defaultQueryContext,
|
|
||||||
);
|
|
||||||
const forceSegmentSortByTime = getQueryContextKey(
|
|
||||||
'forceSegmentSortByTime',
|
|
||||||
queryContext,
|
|
||||||
defaultQueryContext,
|
|
||||||
);
|
|
||||||
const finalizeAggregations = queryContext.finalizeAggregations;
|
const finalizeAggregations = queryContext.finalizeAggregations;
|
||||||
const waitUntilSegmentsLoad = queryContext.waitUntilSegmentsLoad;
|
const waitUntilSegmentsLoad = queryContext.waitUntilSegmentsLoad;
|
||||||
const groupByEnableMultiValueUnnesting = queryContext.groupByEnableMultiValueUnnesting;
|
const groupByEnableMultiValueUnnesting = queryContext.groupByEnableMultiValueUnnesting;
|
||||||
|
@ -279,11 +272,6 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
queryContext,
|
queryContext,
|
||||||
defaultQueryContext,
|
defaultQueryContext,
|
||||||
);
|
);
|
||||||
const includeAllCounters = getQueryContextKey(
|
|
||||||
'includeAllCounters',
|
|
||||||
queryContext,
|
|
||||||
defaultQueryContext,
|
|
||||||
);
|
|
||||||
|
|
||||||
const indexSpec: IndexSpec | undefined = deepGet(queryContext, 'indexSpec');
|
const indexSpec: IndexSpec | undefined = deepGet(queryContext, 'indexSpec');
|
||||||
|
|
||||||
|
@ -385,7 +373,7 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
<Menu>
|
<Menu>
|
||||||
{queryEngines.length > 1 && (
|
{queryEngines.length > 1 && (
|
||||||
<>
|
<>
|
||||||
<MenuDivider title="Select engine" />
|
<MenuDivider title="Select language and engine" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key="auto"
|
key="auto"
|
||||||
icon={tickIcon(queryEngine === undefined)}
|
icon={tickIcon(queryEngine === undefined)}
|
||||||
|
@ -469,81 +457,33 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
/>
|
/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
{show('approximate-count-distinct') && (
|
||||||
{show('insert-replace-specific-context') && (
|
<MenuBoolean
|
||||||
<MenuItem icon={IconNames.BRING_DATA} text="INSERT / REPLACE specific context">
|
icon={IconNames.ROCKET_SLANT}
|
||||||
<MenuBoolean
|
text="Approximate COUNT(DISTINCT)"
|
||||||
text="Force segment sort by time"
|
value={useApproximateCountDistinct}
|
||||||
value={forceSegmentSortByTime}
|
onValueChange={useApproximateCountDistinct =>
|
||||||
onValueChange={forceSegmentSortByTime =>
|
changeQueryContext({
|
||||||
changeQueryContext({
|
...queryContext,
|
||||||
...queryContext,
|
useApproximateCountDistinct,
|
||||||
forceSegmentSortByTime,
|
})
|
||||||
})
|
}
|
||||||
}
|
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
|
||||||
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('max-parse-exceptions') && (
|
{show('approximate-top-n') && (
|
||||||
<MenuItem
|
<MenuBoolean
|
||||||
icon={IconNames.ERROR}
|
icon={IconNames.HORIZONTAL_BAR_CHART_DESC}
|
||||||
text="Max parse exceptions"
|
text="Approximate TopN"
|
||||||
label={String(maxParseExceptions)}
|
value={useApproximateTopN}
|
||||||
>
|
onValueChange={useApproximateTopN =>
|
||||||
{[0, 1, 5, 10, 1000, 10000, -1].map(v => (
|
changeQueryContext({
|
||||||
<MenuItem
|
...queryContext,
|
||||||
key={String(v)}
|
useApproximateTopN,
|
||||||
icon={tickIcon(v === maxParseExceptions)}
|
})
|
||||||
text={v === -1 ? '∞ (-1)' : String(v)}
|
}
|
||||||
onClick={() =>
|
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
|
||||||
changeQueryContext({ ...queryContext, maxParseExceptions: v })
|
/>
|
||||||
}
|
|
||||||
shouldDismissPopover={false}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
)}
|
||||||
{show('join-algorithm') && (
|
{show('join-algorithm') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
@ -566,6 +506,125 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
</MenuItem>
|
</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') && (
|
{show('select-destination') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.MANUALLY_ENTERED_DATA}
|
icon={IconNames.MANUALLY_ENTERED_DATA}
|
||||||
|
@ -602,119 +661,6 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
/>
|
/>
|
||||||
</MenuItem>
|
</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>
|
</Menu>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -722,7 +668,7 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
text={`Engine: ${
|
text={`Engine: ${
|
||||||
queryEngine
|
queryEngine
|
||||||
? enginesLabelFn(queryEngine).text
|
? enginesLabelFn(queryEngine).text
|
||||||
: `${autoEngineLabel.text} (${enginesLabelFn(effectiveEngine).text})`
|
: `${autoEngineLabel.text} [${enginesLabelFn(effectiveEngine).text}]`
|
||||||
}`}
|
}`}
|
||||||
rightIcon={IconNames.CARET_DOWN}
|
rightIcon={IconNames.CARET_DOWN}
|
||||||
intent={intent}
|
intent={intent}
|
||||||
|
|
|
@ -45,7 +45,7 @@ $recent-query-task-panel-width: 250px;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
|
|
||||||
.recent-query-task-panel,
|
.recent-query-task-panel,
|
||||||
.current-viper-panel {
|
.current-dart-panel {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import classNames from 'classnames';
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { MenuCheckbox } from '../../components';
|
||||||
import { SpecDialog, StringInputDialog } from '../../dialogs';
|
import { SpecDialog, StringInputDialog } from '../../dialogs';
|
||||||
import type {
|
import type {
|
||||||
CapacityInfo,
|
CapacityInfo,
|
||||||
|
@ -69,6 +70,7 @@ import {
|
||||||
|
|
||||||
import { ColumnTree } from './column-tree/column-tree';
|
import { ColumnTree } from './column-tree/column-tree';
|
||||||
import { ConnectExternalDataDialog } from './connect-external-data-dialog/connect-external-data-dialog';
|
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 { getDemoQueries } from './demo-queries';
|
||||||
import { ExecutionDetailsDialog } from './execution-details-dialog/execution-details-dialog';
|
import { ExecutionDetailsDialog } from './execution-details-dialog/execution-details-dialog';
|
||||||
import type { ExecutionDetailsTab } from './execution-details-pane/execution-details-pane';
|
import type { ExecutionDetailsTab } from './execution-details-pane/execution-details-pane';
|
||||||
|
@ -148,6 +150,7 @@ export interface WorkbenchViewState {
|
||||||
renamingTab?: TabEntry;
|
renamingTab?: TabEntry;
|
||||||
|
|
||||||
showRecentQueryTaskPanel: boolean;
|
showRecentQueryTaskPanel: boolean;
|
||||||
|
showCurrentDartPanel: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, WorkbenchViewState> {
|
export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, WorkbenchViewState> {
|
||||||
|
@ -166,6 +169,11 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb
|
||||||
hasSqlTask && localStorageGetJson(LocalStorageKeys.WORKBENCH_TASK_PANEL),
|
hasSqlTask && localStorageGetJson(LocalStorageKeys.WORKBENCH_TASK_PANEL),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const showCurrentDartPanel = Boolean(
|
||||||
|
queryEngines.includes('sql-msq-dart') &&
|
||||||
|
localStorageGetJson(LocalStorageKeys.WORKBENCH_DART_PANEL),
|
||||||
|
);
|
||||||
|
|
||||||
const tabEntries =
|
const tabEntries =
|
||||||
Array.isArray(possibleTabEntries) && possibleTabEntries.length
|
Array.isArray(possibleTabEntries) && possibleTabEntries.length
|
||||||
? possibleTabEntries.map(q => ({ ...q, query: new WorkbenchQuery(q.query) }))
|
? possibleTabEntries.map(q => ({ ...q, query: new WorkbenchQuery(q.query) }))
|
||||||
|
@ -198,6 +206,7 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb
|
||||||
taskIdSubmitDialogOpen: false,
|
taskIdSubmitDialogOpen: false,
|
||||||
|
|
||||||
showRecentQueryTaskPanel,
|
showRecentQueryTaskPanel,
|
||||||
|
showCurrentDartPanel,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,6 +273,11 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb
|
||||||
localStorageSetJson(LocalStorageKeys.WORKBENCH_TASK_PANEL, false);
|
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) => {
|
private readonly handleDetailsWithId = (id: string, initTab?: ExecutionDetailsTab) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
details: { id, initTab },
|
details: { id, initTab },
|
||||||
|
@ -656,7 +670,7 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb
|
||||||
if (!queryEngines.includes('sql-msq-task')) return;
|
if (!queryEngines.includes('sql-msq-task')) return;
|
||||||
if (hideToolbar) return;
|
if (hideToolbar) return;
|
||||||
|
|
||||||
const { showRecentQueryTaskPanel } = this.state;
|
const { showRecentQueryTaskPanel, showCurrentDartPanel } = this.state;
|
||||||
return (
|
return (
|
||||||
<ButtonGroup className="toolbar">
|
<ButtonGroup className="toolbar">
|
||||||
<Button
|
<Button
|
||||||
|
@ -669,16 +683,35 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb
|
||||||
}}
|
}}
|
||||||
minimal
|
minimal
|
||||||
/>
|
/>
|
||||||
<Button
|
<Popover
|
||||||
icon={IconNames.DRAWER_RIGHT}
|
position="bottom-right"
|
||||||
minimal
|
content={
|
||||||
data-tooltip="Open recent query task panel"
|
<Menu>
|
||||||
onClick={() => {
|
<MenuCheckbox
|
||||||
const n = !showRecentQueryTaskPanel;
|
text="Recent query task panel"
|
||||||
this.setState({ showRecentQueryTaskPanel: n });
|
checked={showRecentQueryTaskPanel}
|
||||||
localStorageSetJson(LocalStorageKeys.WORKBENCH_TASK_PANEL, n);
|
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>
|
</ButtonGroup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -744,7 +777,9 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb
|
||||||
runMoreMenu={
|
runMoreMenu={
|
||||||
<Menu>
|
<Menu>
|
||||||
{!hiddenMoreMenuItems.includes('explain') &&
|
{!hiddenMoreMenuItems.includes('explain') &&
|
||||||
(effectiveEngine === 'sql-native' || effectiveEngine === 'sql-msq-task') && (
|
(effectiveEngine === 'sql-native' ||
|
||||||
|
effectiveEngine === 'sql-msq-task' ||
|
||||||
|
effectiveEngine === 'sql-msq-dart') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.CLEAN}
|
icon={IconNames.CLEAN}
|
||||||
text="Explain SQL query"
|
text="Explain SQL query"
|
||||||
|
@ -861,7 +896,7 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { columnMetadataState, showRecentQueryTaskPanel } = this.state;
|
const { columnMetadataState, showRecentQueryTaskPanel, showCurrentDartPanel } = this.state;
|
||||||
const query = this.getCurrentQuery();
|
const query = this.getCurrentQuery();
|
||||||
|
|
||||||
let defaultSchema: string | undefined;
|
let defaultSchema: string | undefined;
|
||||||
|
@ -872,7 +907,7 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb
|
||||||
defaultTables = parsedQuery.getUsedTableNames();
|
defaultTables = parsedQuery.getUsedTableNames();
|
||||||
}
|
}
|
||||||
|
|
||||||
const showRightPanel = showRecentQueryTaskPanel;
|
const showRightPanel = showRecentQueryTaskPanel || showCurrentDartPanel;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames('workbench-view app-view', {
|
className={classNames('workbench-view app-view', {
|
||||||
|
@ -883,8 +918,8 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb
|
||||||
{!columnMetadataState.isError() && (
|
{!columnMetadataState.isError() && (
|
||||||
<ColumnTree
|
<ColumnTree
|
||||||
getParsedQuery={this.getParsedQuery}
|
getParsedQuery={this.getParsedQuery}
|
||||||
|
columnMetadata={columnMetadataState.getSomeData()}
|
||||||
columnMetadataLoading={columnMetadataState.loading}
|
columnMetadataLoading={columnMetadataState.loading}
|
||||||
columnMetadata={columnMetadataState.data}
|
|
||||||
onQueryChange={this.handleSqlQueryChange}
|
onQueryChange={this.handleSqlQueryChange}
|
||||||
defaultSchema={defaultSchema ? defaultSchema : 'druid'}
|
defaultSchema={defaultSchema ? defaultSchema : 'druid'}
|
||||||
defaultTables={defaultTables}
|
defaultTables={defaultTables}
|
||||||
|
@ -903,6 +938,9 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb
|
||||||
onNewTab={this.handleNewTab}
|
onNewTab={this.handleNewTab}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{showCurrentDartPanel && (
|
||||||
|
<CurrentDartPanel onClose={this.handleCurrentDartPanelClose} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{this.renderExecutionDetailsDialog()}
|
{this.renderExecutionDetailsDialog()}
|
||||||
|
|
Loading…
Reference in New Issue