This is an automated email from the ASF dual-hosted git repository.
vogievetsky pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new 5de84253d80 Web console query view improvements (#16991)
5de84253d80 is described below
commit 5de84253d806e48278c7020b775cdd5f64b2d0f4
Author: Sébastien <[email protected]>
AuthorDate: Tue Sep 10 20:34:49 2024 +0200
Web console query view improvements (#16991)
* Made maxNumTaskOptions configurable in the Query view
* Updated the copy for taskAssignment options
* Reordered options in engine menu for msq engine
* fixed snapshot
* maxNumTaskOptions -> maxTasksOptions
* added back select destination item
* fixed duplicate menu item
* snapshot
* Added the ability to hide certain engine menu options
* Added the ability to hide/show more menu items
* -> fn
* -> fn
---
web-console/src/console-application.tsx | 1 -
.../__snapshots__/max-tasks-button.spec.tsx.snap | 29 +-
.../max-tasks-button/max-tasks-button.tsx | 81 ++--
.../views/workbench-view/query-tab/query-tab.tsx | 11 +-
.../views/workbench-view/run-panel/run-panel.tsx | 473 ++++++++++++---------
.../src/views/workbench-view/workbench-view.tsx | 101 +++--
6 files changed, 410 insertions(+), 286 deletions(-)
diff --git a/web-console/src/console-application.tsx
b/web-console/src/console-application.tsx
index 36a0b8aa392..8a166b932e8 100644
--- a/web-console/src/console-application.tsx
+++ b/web-console/src/console-application.tsx
@@ -327,7 +327,6 @@ export class ConsoleApplication extends React.PureComponent<
baseQueryContext={baseQueryContext}
serverQueryContext={serverQueryContext}
queryEngines={queryEngines}
- allowExplain
goToTask={this.goToTasksWithTaskId}
getClusterCapacity={maybeGetClusterCapacity}
/>,
diff --git
a/web-console/src/views/workbench-view/max-tasks-button/__snapshots__/max-tasks-button.spec.tsx.snap
b/web-console/src/views/workbench-view/max-tasks-button/__snapshots__/max-tasks-button.spec.tsx.snap
index d4326fdf6dc..89f2fdf4143 100644
---
a/web-console/src/views/workbench-view/max-tasks-button/__snapshots__/max-tasks-button.spec.tsx.snap
+++
b/web-console/src/views/workbench-view/max-tasks-button/__snapshots__/max-tasks-button.spec.tsx.snap
@@ -100,14 +100,13 @@ exports[`MaxTasksButton matches snapshot 1`] = `
multiline={true}
onClick={[Function]}
popoverProps={{}}
- shouldDismissPopover={false}
+ shouldDismissPopover={true}
text={
<React.Fragment>
<strong>
Max
</strong>
- :
- uses the maximum possible tasks up to the specified limit.
+ : uses the maximum possible tasks up to the specified limit.
</React.Fragment>
}
/>
@@ -115,24 +114,28 @@ exports[`MaxTasksButton matches snapshot 1`] = `
active={false}
disabled={false}
icon="blank"
- labelElement={
- <Blueprint5.Button
- icon="help"
- minimal={true}
- onClick={[Function]}
- />
- }
multiline={true}
onClick={[Function]}
popoverProps={{}}
- shouldDismissPopover={false}
+ shouldDismissPopover={true}
text={
<React.Fragment>
<strong>
Auto
</strong>
- :
- maximizes the number of tasks while staying within 512 MiB or
10,000 files per task, unless more tasks are needed to stay under the max task
limit.
+ : uses the minimum number of tasks while
+
+ <span
+ onClick={[Function]}
+ style={
+ {
+ "color": "#3eadf9",
+ "cursor": "pointer",
+ }
+ }
+ >
+ staying within constraints.
+ </span>
</React.Fragment>
}
/>
diff --git
a/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.tsx
b/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.tsx
index 21ba60f6b8d..509e943d8b7 100644
--- a/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.tsx
+++ b/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.tsx
@@ -19,34 +19,17 @@
import type { ButtonProps } from '@blueprintjs/core';
import { Button, Menu, MenuDivider, MenuItem, Popover, Position } from
'@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
-import type { JSX, ReactNode } from 'react';
+import type { JSX } from 'react';
import React, { useState } from 'react';
import { NumericInputDialog } from '../../../dialogs';
-import type { QueryContext, TaskAssignment } from '../../../druid-models';
+import type { QueryContext } from '../../../druid-models';
import { getQueryContextKey } from '../../../druid-models';
import { getLink } from '../../../links';
import { capitalizeFirst, deleteKeys, formatInteger, tickIcon } from
'../../../utils';
-const MAX_NUM_TASK_OPTIONS = [2, 3, 4, 5, 7, 9, 11, 17, 33, 65, 129];
-const TASK_ASSIGNMENT_OPTIONS: TaskAssignment[] = ['max', 'auto'];
-
-const TASK_ASSIGNMENT_DESCRIPTION: Record<string, string> = {
- max: 'uses the maximum possible tasks up to the specified limit.',
- auto: 'maximizes the number of tasks while staying within 512 MiB or 10,000
files per task, unless more tasks are needed to stay under the max task limit.',
-};
-
-const TASK_ASSIGNMENT_LABEL_ELEMENT: Record<string, ReactNode> = {
- auto: (
- <Button
- icon={IconNames.HELP}
- minimal
- onClick={() =>
-
window.open(`${getLink('DOCS')}/multi-stage-query/reference#context-parameters`,
'_blank')
- }
- />
- ),
-};
+const DEFAULT_MAX_TASKS_OPTIONS = [2, 3, 4, 5, 7, 9, 11, 17, 33, 65, 129];
+const TASK_DOCUMENTATION_LINK =
`${getLink('DOCS')}/multi-stage-query/reference#context-parameters`;
const DEFAULT_MAX_NUM_TASKS_LABEL_FN = (maxNum: number) => {
if (maxNum === 2) return { text: formatInteger(maxNum), label: '(1
controller + 1 worker)' };
@@ -63,6 +46,7 @@ export interface MaxTasksButtonProps extends
Omit<ButtonProps, 'text' | 'rightIc
defaultQueryContext: QueryContext;
menuHeader?: JSX.Element;
maxTasksLabelFn?: (maxNum: number) => { text: string; label?: string };
+ maxTasksOptions?: number[];
fullClusterCapacityLabelFn?: (clusterCapacity: number) => string;
}
@@ -75,6 +59,7 @@ export const MaxTasksButton = function MaxTasksButton(props:
MaxTasksButtonProps
menuHeader,
maxTasksLabelFn = DEFAULT_MAX_NUM_TASKS_LABEL_FN,
fullClusterCapacityLabelFn = DEFAULT_FULL_CLUSTER_CAPACITY_LABEL_FN,
+ maxTasksOptions = DEFAULT_MAX_TASKS_OPTIONS,
...rest
} = props;
const [customMaxNumTasksDialogOpen, setCustomMaxNumTasksDialogOpen] =
useState(false);
@@ -86,8 +71,8 @@ export const MaxTasksButton = function MaxTasksButton(props:
MaxTasksButtonProps
typeof clusterCapacity === 'number' ?
fullClusterCapacityLabelFn(clusterCapacity) : undefined;
const shownMaxNumTaskOptions = clusterCapacity
- ? MAX_NUM_TASK_OPTIONS.filter(_ => _ <= clusterCapacity)
- : MAX_NUM_TASK_OPTIONS;
+ ? maxTasksOptions.filter(_ => _ <= clusterCapacity)
+ : maxTasksOptions;
return (
<>
@@ -103,6 +88,7 @@ export const MaxTasksButton = function MaxTasksButton(props:
MaxTasksButtonProps
icon={tickIcon(typeof maxNumTasks === 'undefined')}
text={fullClusterCapacity}
onClick={() => changeQueryContext(deleteKeys(queryContext,
['maxNumTasks']))}
+ shouldDismissPopover
/>
)}
{shownMaxNumTaskOptions.map(m => {
@@ -115,6 +101,7 @@ export const MaxTasksButton = function
MaxTasksButton(props: MaxTasksButtonProps
text={text}
label={label}
onClick={() => changeQueryContext({ ...queryContext,
maxNumTasks: m })}
+ shouldDismissPopover
/>
);
})}
@@ -132,21 +119,39 @@ export const MaxTasksButton = function
MaxTasksButton(props: MaxTasksButtonProps
label={capitalizeFirst(taskAssigment)}
submenuProps={{ style: { width: 300 } }}
>
- {TASK_ASSIGNMENT_OPTIONS.map(t => (
- <MenuItem
- key={String(t)}
- icon={tickIcon(t === taskAssigment)}
- text={
- <>
- <strong>{capitalizeFirst(t)}</strong>:
{TASK_ASSIGNMENT_DESCRIPTION[t]}
- </>
- }
- labelElement={TASK_ASSIGNMENT_LABEL_ELEMENT[t]}
- shouldDismissPopover={false}
- multiline
- onClick={() => changeQueryContext({ ...queryContext,
taskAssignment: t })}
- />
- ))}
+ <MenuItem
+ icon={tickIcon(taskAssigment === 'max')}
+ text={
+ <>
+ <strong>Max</strong>: uses the maximum possible tasks up
to the specified limit.
+ </>
+ }
+ multiline
+ onClick={() => changeQueryContext({ ...queryContext,
taskAssignment: 'max' })}
+ />
+
+ <MenuItem
+ icon={tickIcon(taskAssigment === 'auto')}
+ text={
+ <>
+ <strong>Auto</strong>: uses the minimum number of tasks
while{' '}
+ <span
+ style={{
+ color: '#3eadf9',
+ cursor: 'pointer',
+ }}
+ onClick={e => {
+ window.open(TASK_DOCUMENTATION_LINK, '_blank');
+ e.stopPropagation();
+ }}
+ >
+ staying within constraints.
+ </span>
+ </>
+ }
+ multiline
+ onClick={() => changeQueryContext({ ...queryContext,
taskAssignment: 'auto' })}
+ />
</MenuItem>
</Menu>
}
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 acdfa67fad2..02c36c5a24b 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
@@ -73,7 +73,12 @@ const queryRunner = new QueryRunner({
export interface QueryTabProps
extends Pick<
RunPanelProps,
- 'maxTasksMenuHeader' | 'enginesLabelFn' | 'maxTasksLabelFn' |
'fullClusterCapacityLabelFn'
+ | 'maxTasksMenuHeader'
+ | 'enginesLabelFn'
+ | 'maxTasksLabelFn'
+ | 'fullClusterCapacityLabelFn'
+ | 'maxTasksOptions'
+ | 'hiddenOptions'
> {
query: WorkbenchQuery;
id: string;
@@ -110,7 +115,9 @@ export const QueryTab = React.memo(function QueryTab(props:
QueryTabProps) {
maxTasksMenuHeader,
enginesLabelFn,
maxTasksLabelFn,
+ maxTasksOptions,
fullClusterCapacityLabelFn,
+ hiddenOptions,
} = props;
const [alertElement, setAlertElement] = useState<JSX.Element | undefined>();
@@ -419,7 +426,9 @@ export const QueryTab = React.memo(function QueryTab(props:
QueryTabProps) {
maxTasksMenuHeader={maxTasksMenuHeader}
enginesLabelFn={enginesLabelFn}
maxTasksLabelFn={maxTasksLabelFn}
+ maxTasksOptions={maxTasksOptions}
fullClusterCapacityLabelFn={fullClusterCapacityLabelFn}
+ hiddenOptions={hiddenOptions}
/>
{executionState.isLoading() && (
<ExecutionTimerPanel
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 13901388b4e..79f3fef039a 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
@@ -99,11 +99,6 @@ const SQL_JOIN_ALGORITHM_LABEL: Record<SqlJoinAlgorithm,
string> = {
sortMerge: 'Sort merge',
};
-const SELECT_DESTINATION_LABEL: Record<SelectDestination, string> = {
- taskReport: 'Task report',
- durableStorage: 'Durable storage',
-};
-
const DEFAULT_ENGINES_LABEL_FN = (engine: DruidEngine | undefined) => {
switch (engine) {
case 'native':
@@ -120,10 +115,33 @@ const DEFAULT_ENGINES_LABEL_FN = (engine: DruidEngine |
undefined) => {
}
};
+const SELECT_DESTINATION_LABEL: Record<SelectDestination, string> = {
+ taskReport: 'Task report',
+ durableStorage: 'Durable storage',
+};
+
const EXPERIMENTAL_ICON = <Icon icon={IconNames.WARNING_SIGN}
title="Experimental" />;
+type EnginesMenuOption =
+ | 'edit-query-context'
+ | 'define-parameters'
+ | 'timezone'
+ | 'insert-replace-specific-context'
+ | 'max-parse-exceptions'
+ | 'join-algorithm'
+ | 'select-destination'
+ | 'approximate-count-distinct'
+ | 'finalize-aggregations'
+ | 'group-by-enable-multi-value-unnesting'
+ | 'durable-shuffle-storage'
+ | 'use-cache'
+ | 'approximate-top-n'
+ | 'limit-inline-results';
export interface RunPanelProps
- extends Pick<MaxTasksButtonProps, 'maxTasksLabelFn' |
'fullClusterCapacityLabelFn'> {
+ extends Pick<
+ MaxTasksButtonProps,
+ 'maxTasksLabelFn' | 'fullClusterCapacityLabelFn' | 'maxTasksOptions'
+ > {
query: WorkbenchQuery;
onQueryChange(query: WorkbenchQuery): void;
running: boolean;
@@ -134,6 +152,7 @@ export interface RunPanelProps
moreMenu?: JSX.Element;
maxTasksMenuHeader?: JSX.Element;
enginesLabelFn?: (engine: DruidEngine | undefined) => { text: string;
label?: string };
+ hiddenOptions?: EnginesMenuOption[];
}
export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
@@ -149,7 +168,9 @@ export const RunPanel = React.memo(function RunPanel(props:
RunPanelProps) {
maxTasksMenuHeader,
enginesLabelFn = DEFAULT_ENGINES_LABEL_FN,
maxTasksLabelFn,
+ maxTasksOptions,
fullClusterCapacityLabelFn,
+ hiddenOptions = [],
} = props;
const [editContextDialogOpen, setEditContextDialogOpen] = useState(false);
const [editParametersDialogOpen, setEditParametersDialogOpen] =
useState(false);
@@ -340,19 +361,25 @@ export const RunPanel = React.memo(function
RunPanel(props: RunPanelProps) {
<MenuDivider />
</>
)}
- <MenuItem
- icon={IconNames.PROPERTIES}
- text="Edit query context..."
- onClick={() => setEditContextDialogOpen(true)}
- label={pluralIfNeeded(numContextKeys, 'key')}
- />
- <MenuItem
- icon={IconNames.HELP}
- text="Define parameters..."
- onClick={() => setEditParametersDialogOpen(true)}
- label={queryParameters ?
pluralIfNeeded(queryParameters.length, 'parameter') : ''}
- />
- {effectiveEngine !== 'native' && (
+ {!hiddenOptions.includes('edit-query-context') && (
+ <MenuItem
+ icon={IconNames.PROPERTIES}
+ text="Edit query context..."
+ onClick={() => setEditContextDialogOpen(true)}
+ label={pluralIfNeeded(numContextKeys, 'key')}
+ />
+ )}
+ {!hiddenOptions.includes('define-parameters') && (
+ <MenuItem
+ icon={IconNames.HELP}
+ text="Define parameters..."
+ onClick={() => setEditParametersDialogOpen(true)}
+ label={
+ queryParameters ? pluralIfNeeded(queryParameters.length,
'parameter') : ''
+ }
+ />
+ )}
+ {effectiveEngine !== 'native' &&
!hiddenOptions.includes('timezone') && (
<MenuItem
icon={IconNames.GLOBE_NETWORK}
text="Timezone"
@@ -393,222 +420,263 @@ export const RunPanel = React.memo(function
RunPanel(props: RunPanelProps) {
)}
{effectiveEngine === 'sql-msq-task' ? (
<>
- <MenuItem icon={IconNames.BRING_DATA} text="INSERT /
REPLACE specific context">
+
{!hiddenOptions.includes('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>
+ )}
+ {!hiddenOptions.includes('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>
+ )}
+ {!hiddenOptions.includes('join-algorithm') && (
+ <MenuItem
+ icon={IconNames.INNER_JOIN}
+ text="Join algorithm"
+ label={
+ SQL_JOIN_ALGORITHM_LABEL[sqlJoinAlgorithm as
SqlJoinAlgorithm] ??
+ sqlJoinAlgorithm
+ }
+ >
+ {(['broadcast', 'sortMerge'] as
SqlJoinAlgorithm[]).map(o => (
+ <MenuItem
+ key={o}
+ icon={tickIcon(sqlJoinAlgorithm === o)}
+ text={SQL_JOIN_ALGORITHM_LABEL[o]}
+ shouldDismissPopover={false}
+ onClick={() =>
+ changeQueryContext({ ...queryContext,
sqlJoinAlgorithm: o })
+ }
+ />
+ ))}
+ </MenuItem>
+ )}
+
+ {!hiddenOptions.includes('select-destination') && (
+ <MenuItem
+ icon={IconNames.MANUALLY_ENTERED_DATA}
+ text="SELECT destination"
+ label={
+ SELECT_DESTINATION_LABEL[selectDestination as
SelectDestination] ??
+ selectDestination
+ }
+ intent={intent}
+ >
+ {(['taskReport', 'durableStorage'] as
SelectDestination[]).map(o => (
+ <MenuItem
+ key={o}
+ icon={tickIcon(selectDestination === o)}
+ text={SELECT_DESTINATION_LABEL[o]}
+ shouldDismissPopover={false}
+ onClick={() =>
+ changeQueryContext({ ...queryContext,
selectDestination: o })
+ }
+ />
+ ))}
+ <MenuDivider />
+ <MenuCheckbox
+ checked={selectDestination === 'taskReport' ?
!query.unlimited : false}
+ intent={intent}
+ disabled={selectDestination !== 'taskReport'}
+ text="Limit SELECT results in taskReport"
+ labelElement={
+ query.unlimited ? <Icon
icon={IconNames.WARNING_SIGN} /> : undefined
+ }
+ onChange={() => {
+ onQueryChange(query.toggleUnlimited());
+ }}
+ />
+ </MenuItem>
+ )}
+
+ {!hiddenOptions.includes('approximate-count-distinct') && (
<MenuBoolean
- text="Force segment sort by time"
- value={forceSegmentSortByTime}
- onValueChange={forceSegmentSortByTime =>
+ icon={IconNames.ROCKET_SLANT}
+ text="Approximate COUNT(DISTINCT)"
+ value={useApproximateCountDistinct}
+ onValueChange={useApproximateCountDistinct =>
changeQueryContext({
...queryContext,
- forceSegmentSortByTime,
+ useApproximateCountDistinct,
})
}
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
- optionsLabelElement={{ false: EXPERIMENTAL_ICON }}
/>
+ )}
+
+ {!hiddenOptions.includes('finalize-aggregations') && (
<MenuBoolean
- text="Use concurrent locks"
- value={useConcurrentLocks}
- onValueChange={useConcurrentLocks =>
- changeQueryContext({
- ...queryContext,
- useConcurrentLocks,
- })
+ icon={IconNames.TRANSLATE}
+ text="Finalize aggregations"
+ value={finalizeAggregations}
+ showUndefined
+ undefinedEffectiveValue={!ingestMode}
+ onValueChange={finalizeAggregations =>
+ changeQueryContext({ ...queryContext,
finalizeAggregations })
}
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
- optionsLabelElement={{ true: EXPERIMENTAL_ICON }}
/>
+ )}
+
{!hiddenOptions.includes('group-by-enable-multi-value-unnesting') && (
<MenuBoolean
- text="Fail on empty insert"
- value={failOnEmptyInsert}
+ icon={IconNames.FORK}
+ text="GROUP BY multi-value unnesting"
+ value={groupByEnableMultiValueUnnesting}
showUndefined
- undefinedEffectiveValue={false}
- onValueChange={failOnEmptyInsert =>
- changeQueryContext({ ...queryContext,
failOnEmptyInsert })
+ undefinedEffectiveValue={!ingestMode}
+ onValueChange={groupByEnableMultiValueUnnesting =>
+ changeQueryContext({ ...queryContext,
groupByEnableMultiValueUnnesting })
}
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
/>
+ )}
+ {!hiddenOptions.includes('durable-shuffle-storage') && (
<MenuBoolean
- text="Wait until segments have loaded"
- value={waitUntilSegmentsLoad}
- showUndefined
- undefinedEffectiveValue={ingestMode}
- onValueChange={waitUntilSegmentsLoad =>
- changeQueryContext({ ...queryContext,
waitUntilSegmentsLoad })
+ icon={IconNames.CLOUD_TICK}
+ text="Durable shuffle storage"
+ value={durableShuffleStorage}
+ onValueChange={durableShuffleStorage =>
+ changeQueryContext({
+ ...queryContext,
+ durableShuffleStorage,
+ })
}
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
/>
- <MenuItem
- text="Edit index spec..."
- label={summarizeIndexSpec(indexSpec)}
- shouldDismissPopover={false}
- onClick={() => {
- setIndexSpecDialogSpec(indexSpec || {});
- }}
+ )}
+ </>
+ ) : (
+ <>
+ {!hiddenOptions.includes('use-cache') && (
+ <MenuBoolean
+ icon={IconNames.DATA_CONNECTION}
+ text="Use cache"
+ value={useCache}
+ onValueChange={useCache =>
+ changeQueryContext({
+ ...queryContext,
+ useCache,
+ populateCache: useCache,
+ })
+ }
+ optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
/>
- </MenuItem>
- <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>
- <MenuBoolean
- icon={IconNames.TRANSLATE}
- text="Finalize aggregations"
- value={finalizeAggregations}
- showUndefined
- undefinedEffectiveValue={!ingestMode}
- onValueChange={finalizeAggregations =>
- changeQueryContext({ ...queryContext,
finalizeAggregations })
- }
- optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
- />
- <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}
- />
- <MenuItem
- icon={IconNames.INNER_JOIN}
- text="Join algorithm"
- label={
- SQL_JOIN_ALGORITHM_LABEL[sqlJoinAlgorithm as
SqlJoinAlgorithm] ??
- sqlJoinAlgorithm
- }
- >
- {(['broadcast', 'sortMerge'] as
SqlJoinAlgorithm[]).map(o => (
- <MenuItem
- key={o}
- icon={tickIcon(sqlJoinAlgorithm === o)}
- text={SQL_JOIN_ALGORITHM_LABEL[o]}
- shouldDismissPopover={false}
- onClick={() =>
- changeQueryContext({ ...queryContext,
sqlJoinAlgorithm: o })
- }
- />
- ))}
- </MenuItem>
- <MenuItem
- icon={IconNames.MANUALLY_ENTERED_DATA}
- text="SELECT destination"
- label={
- SELECT_DESTINATION_LABEL[selectDestination as
SelectDestination] ??
- selectDestination
- }
- intent={intent}
- >
- {(['taskReport', 'durableStorage'] as
SelectDestination[]).map(o => (
- <MenuItem
- key={o}
- icon={tickIcon(selectDestination === o)}
- text={SELECT_DESTINATION_LABEL[o]}
- shouldDismissPopover={false}
- onClick={() =>
- changeQueryContext({ ...queryContext,
selectDestination: o })
- }
- />
- ))}
- <MenuDivider />
- <MenuCheckbox
- checked={selectDestination === 'taskReport' ?
!query.unlimited : false}
- intent={intent}
- disabled={selectDestination !== 'taskReport'}
- text="Limit SELECT results in taskReport"
- labelElement={
- query.unlimited ? <Icon
icon={IconNames.WARNING_SIGN} /> : undefined
+ )}
+ {!hiddenOptions.includes('approximate-top-n') && (
+ <MenuBoolean
+ icon={IconNames.HORIZONTAL_BAR_CHART_DESC}
+ text="Approximate TopN"
+ value={useApproximateTopN}
+ onValueChange={useApproximateTopN =>
+ changeQueryContext({
+ ...queryContext,
+ useApproximateTopN,
+ })
}
- onChange={() => {
- onQueryChange(query.toggleUnlimited());
- }}
+ optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
/>
- </MenuItem>
- <MenuBoolean
- icon={IconNames.CLOUD_TICK}
- text="Durable shuffle storage"
- value={durableShuffleStorage}
- onValueChange={durableShuffleStorage =>
- changeQueryContext({
- ...queryContext,
- durableShuffleStorage,
- })
- }
- optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
- />
+ )}
</>
- ) : (
- <>
+ )}
+ {effectiveEngine !== 'native' &&
+ effectiveEngine !== 'sql-msq-task' &&
+ !hiddenOptions.includes('approximate-count-distinct') && (
<MenuBoolean
- icon={IconNames.DATA_CONNECTION}
- text="Use cache"
- value={useCache}
- onValueChange={useCache =>
+ icon={IconNames.ROCKET_SLANT}
+ text="Approximate COUNT(DISTINCT)"
+ value={useApproximateCountDistinct}
+ onValueChange={useApproximateCountDistinct =>
changeQueryContext({
...queryContext,
- useCache,
- populateCache: useCache,
+ useApproximateCountDistinct,
})
}
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
/>
- <MenuBoolean
- icon={IconNames.HORIZONTAL_BAR_CHART_DESC}
- text="Approximate TopN"
- value={useApproximateTopN}
- onValueChange={useApproximateTopN =>
- changeQueryContext({
- ...queryContext,
- useApproximateTopN,
- })
+ )}
+ {effectiveEngine === 'sql-native' &&
+ !hiddenOptions.includes('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
}
- optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
+ onChange={() => {
+ onQueryChange(query.toggleUnlimited());
+ }}
/>
- </>
- )}
- {effectiveEngine !== 'native' && (
- <MenuBoolean
- icon={IconNames.ROCKET_SLANT}
- text="Approximate COUNT(DISTINCT)"
- value={useApproximateCountDistinct}
- onValueChange={useApproximateCountDistinct =>
- changeQueryContext({
- ...queryContext,
- useApproximateCountDistinct,
- })
- }
- optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
- />
- )}
- {effectiveEngine === 'sql-native' && (
- <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());
- }}
- />
- )}
+ )}
</Menu>
}
>
@@ -630,6 +698,7 @@ export const RunPanel = React.memo(function RunPanel(props:
RunPanelProps) {
defaultQueryContext={defaultQueryContext}
menuHeader={maxTasksMenuHeader}
maxTasksLabelFn={maxTasksLabelFn}
+ maxTasksOptions={maxTasksOptions}
fullClusterCapacityLabelFn={fullClusterCapacityLabelFn}
/>
)}
diff --git a/web-console/src/views/workbench-view/workbench-view.tsx
b/web-console/src/views/workbench-view/workbench-view.tsx
index 8da695c0be2..df6d75a285e 100644
--- a/web-console/src/views/workbench-view/workbench-view.tsx
+++ b/web-console/src/views/workbench-view/workbench-view.tsx
@@ -96,6 +96,16 @@ function externalDataTabId(tabId: string | undefined):
boolean {
return String(tabId).startsWith('connect-external-data');
}
+type MoreMenuItem =
+ | 'explain'
+ | 'history'
+ | 'prettify'
+ | 'convert-ingestion-to-sql'
+ | 'attach-tab-from-task-id'
+ | 'open-query-detail-archive'
+ | 'druid-sql-documentation'
+ | 'load-demo-queries';
+
export interface WorkbenchViewProps
extends Pick<
QueryTabProps,
@@ -110,10 +120,16 @@ export interface WorkbenchViewProps
mandatoryQueryContext?: QueryContext;
serverQueryContext?: QueryContext;
queryEngines: DruidEngine[];
- allowExplain: boolean;
+ hiddenMoreMenuItems?: MoreMenuItem[] | ((engine: DruidEngine) =>
MoreMenuItem[]);
goToTask(taskId: string): void;
getClusterCapacity: (() => Promise<CapacityInfo | undefined>) | undefined;
hideToolbar?: boolean;
+ maxTasksOptions?:
+ | QueryTabProps['maxTasksOptions']
+ | ((engine: DruidEngine) => QueryTabProps['maxTasksOptions']);
+ hiddenOptions?:
+ | QueryTabProps['hiddenOptions']
+ | ((engine: DruidEngine) => QueryTabProps['hiddenOptions']);
}
export interface WorkbenchViewState {
@@ -663,18 +679,24 @@ export class WorkbenchView extends
React.PureComponent<WorkbenchViewProps, Workb
baseQueryContext,
serverQueryContext = DEFAULT_SERVER_QUERY_CONTEXT,
queryEngines,
- allowExplain,
goToTask,
getClusterCapacity,
maxTasksMenuHeader,
enginesLabelFn,
maxTasksLabelFn,
+ maxTasksOptions,
fullClusterCapacityLabelFn,
+ hiddenOptions,
} = this.props;
const { columnMetadataState } = this.state;
const currentTabEntry = this.getCurrentTabEntry();
const effectiveEngine = currentTabEntry.query.getEffectiveEngine();
+ const hiddenMoreMenuItems =
+ typeof this.props.hiddenMoreMenuItems === 'function'
+ ? this.props.hiddenMoreMenuItems(effectiveEngine)
+ : this.props.hiddenMoreMenuItems || [];
+
return (
<div className="center-panel">
<div className="tab-and-tool-bar">
@@ -699,10 +721,18 @@ export class WorkbenchView extends
React.PureComponent<WorkbenchViewProps, Workb
maxTasksMenuHeader={maxTasksMenuHeader}
enginesLabelFn={enginesLabelFn}
maxTasksLabelFn={maxTasksLabelFn}
+ maxTasksOptions={
+ typeof maxTasksOptions === 'function'
+ ? maxTasksOptions(effectiveEngine)
+ : maxTasksOptions
+ }
fullClusterCapacityLabelFn={fullClusterCapacityLabelFn}
+ hiddenOptions={
+ typeof hiddenOptions === 'function' ?
hiddenOptions(effectiveEngine) : hiddenOptions
+ }
runMoreMenu={
<Menu>
- {allowExplain &&
+ {!hiddenMoreMenuItems.includes('explain') &&
(effectiveEngine === 'sql-native' || effectiveEngine ===
'sql-msq-task') && (
<MenuItem
icon={IconNames.CLEAN}
@@ -710,14 +740,14 @@ export class WorkbenchView extends
React.PureComponent<WorkbenchViewProps, Workb
onClick={this.openExplainDialog}
/>
)}
- {effectiveEngine !== 'sql-msq-task' && (
+ {effectiveEngine !== 'sql-msq-task' &&
!hiddenMoreMenuItems.includes('history') && (
<MenuItem
icon={IconNames.HISTORY}
text="Query history"
onClick={this.openHistoryDialog}
/>
)}
- {currentTabEntry.query.canPrettify() && (
+ {currentTabEntry.query.canPrettify() &&
!hiddenMoreMenuItems.includes('prettify') && (
<MenuItem
icon={IconNames.ALIGN_LEFT}
text="Prettify query"
@@ -726,38 +756,47 @@ export class WorkbenchView extends
React.PureComponent<WorkbenchViewProps, Workb
)}
{queryEngines.includes('sql-msq-task') && (
<>
- <MenuItem
- icon={IconNames.TEXT_HIGHLIGHT}
- text="Convert ingestion spec to SQL"
- onClick={this.openSpecDialog}
- />
- <MenuItem
- icon={IconNames.DOCUMENT_OPEN}
- text="Attach tab from task ID"
- onClick={this.openTaskIdSubmitDialog}
- />
- <MenuItem
- icon={IconNames.UNARCHIVE}
- text="Open query detail archive"
- onClick={this.openExecutionSubmitDialog}
- />
+ {!hiddenMoreMenuItems.includes('convert-ingestion-to-sql')
&& (
+ <MenuItem
+ icon={IconNames.TEXT_HIGHLIGHT}
+ text="Convert ingestion spec to SQL"
+ onClick={this.openSpecDialog}
+ />
+ )}
+ {!hiddenMoreMenuItems.includes('attach-tab-from-task-id') &&
(
+ <MenuItem
+ icon={IconNames.DOCUMENT_OPEN}
+ text="Attach tab from task ID"
+ onClick={this.openTaskIdSubmitDialog}
+ />
+ )}
+ {!hiddenMoreMenuItems.includes('open-query-detail-archive')
&& (
+ <MenuItem
+ icon={IconNames.UNARCHIVE}
+ text="Open query detail archive"
+ onClick={this.openExecutionSubmitDialog}
+ />
+ )}
</>
)}
<MenuDivider />
- <MenuItem
- icon={IconNames.HELP}
- text="DruidSQL documentation"
- href={getLink('DOCS_SQL')}
- target="_blank"
- />
- {queryEngines.includes('sql-msq-task') && (
+ {!hiddenMoreMenuItems.includes('druid-sql-documentation') && (
<MenuItem
- icon={IconNames.ROCKET_SLANT}
- text="Load demo queries"
- label="(replaces current tabs)"
- onClick={() => this.handleQueriesChange(getDemoQueries())}
+ icon={IconNames.HELP}
+ text="DruidSQL documentation"
+ href={getLink('DOCS_SQL')}
+ target="_blank"
/>
)}
+ {queryEngines.includes('sql-msq-task') &&
+ !hiddenMoreMenuItems.includes('load-demo-queries') && (
+ <MenuItem
+ icon={IconNames.ROCKET_SLANT}
+ text="Load demo queries"
+ label="(replaces current tabs)"
+ onClick={() => this.handleQueriesChange(getDemoQueries())}
+ />
+ )}
</Menu>
}
/>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]