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]


Reply via email to