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
This commit is contained in:
Sébastien 2024-09-10 20:34:49 +02:00 committed by GitHub
parent 2427972c10
commit 5de84253d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 429 additions and 305 deletions

View File

@ -327,7 +327,6 @@ export class ConsoleApplication extends React.PureComponent<
baseQueryContext={baseQueryContext}
serverQueryContext={serverQueryContext}
queryEngines={queryEngines}
allowExplain
goToTask={this.goToTasksWithTaskId}
getClusterCapacity={maybeGetClusterCapacity}
/>,

View File

@ -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>
}
/>

View File

@ -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)}
icon={tickIcon(taskAssigment === 'max')}
text={
<>
<strong>{capitalizeFirst(t)}</strong>: {TASK_ASSIGNMENT_DESCRIPTION[t]}
<strong>Max</strong>: uses the maximum possible tasks up to the specified limit.
</>
}
labelElement={TASK_ASSIGNMENT_LABEL_ELEMENT[t]}
shouldDismissPopover={false}
multiline
onClick={() => changeQueryContext({ ...queryContext, taskAssignment: t })}
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>
}

View File

@ -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

View File

@ -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 />
</>
)}
{!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') : ''}
label={
queryParameters ? pluralIfNeeded(queryParameters.length, 'parameter') : ''
}
/>
{effectiveEngine !== 'native' && (
)}
{effectiveEngine !== 'native' && !hiddenOptions.includes('timezone') && (
<MenuItem
icon={IconNames.GLOBE_NETWORK}
text="Timezone"
@ -393,7 +420,11 @@ 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}
@ -447,6 +478,8 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
}}
/>
</MenuItem>
)}
{!hiddenOptions.includes('max-parse-exceptions') && (
<MenuItem
icon={IconNames.ERROR}
text="Max parse exceptions"
@ -464,28 +497,8 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
/>
))}
</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}
/>
)}
{!hiddenOptions.includes('join-algorithm') && (
<MenuItem
icon={IconNames.INNER_JOIN}
text="Join algorithm"
@ -506,6 +519,9 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
/>
))}
</MenuItem>
)}
{!hiddenOptions.includes('select-destination') && (
<MenuItem
icon={IconNames.MANUALLY_ENTERED_DATA}
text="SELECT destination"
@ -540,49 +556,9 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
}}
/>
</MenuItem>
<MenuBoolean
icon={IconNames.CLOUD_TICK}
text="Durable shuffle storage"
value={durableShuffleStorage}
onValueChange={durableShuffleStorage =>
changeQueryContext({
...queryContext,
durableShuffleStorage,
})
}
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
/>
</>
) : (
<>
<MenuBoolean
icon={IconNames.DATA_CONNECTION}
text="Use cache"
value={useCache}
onValueChange={useCache =>
changeQueryContext({
...queryContext,
useCache,
populateCache: useCache,
})
}
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
/>
<MenuBoolean
icon={IconNames.HORIZONTAL_BAR_CHART_DESC}
text="Approximate TopN"
value={useApproximateTopN}
onValueChange={useApproximateTopN =>
changeQueryContext({
...queryContext,
useApproximateTopN,
})
}
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
/>
</>
)}
{effectiveEngine !== 'native' && (
{!hiddenOptions.includes('approximate-count-distinct') && (
<MenuBoolean
icon={IconNames.ROCKET_SLANT}
text="Approximate COUNT(DISTINCT)"
@ -596,7 +572,99 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
/>
)}
{effectiveEngine === 'sql-native' && (
{!hiddenOptions.includes('finalize-aggregations') && (
<MenuBoolean
icon={IconNames.TRANSLATE}
text="Finalize aggregations"
value={finalizeAggregations}
showUndefined
undefinedEffectiveValue={!ingestMode}
onValueChange={finalizeAggregations =>
changeQueryContext({ ...queryContext, finalizeAggregations })
}
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
/>
)}
{!hiddenOptions.includes('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}
/>
)}
{!hiddenOptions.includes('durable-shuffle-storage') && (
<MenuBoolean
icon={IconNames.CLOUD_TICK}
text="Durable shuffle storage"
value={durableShuffleStorage}
onValueChange={durableShuffleStorage =>
changeQueryContext({
...queryContext,
durableShuffleStorage,
})
}
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
/>
)}
</>
) : (
<>
{!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}
/>
)}
{!hiddenOptions.includes('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}
/>
)}
</>
)}
{effectiveEngine !== 'native' &&
effectiveEngine !== 'sql-msq-task' &&
!hiddenOptions.includes('approximate-count-distinct') && (
<MenuBoolean
icon={IconNames.ROCKET_SLANT}
text="Approximate COUNT(DISTINCT)"
value={useApproximateCountDistinct}
onValueChange={useApproximateCountDistinct =>
changeQueryContext({
...queryContext,
useApproximateCountDistinct,
})
}
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
/>
)}
{effectiveEngine === 'sql-native' &&
!hiddenOptions.includes('limit-inline-results') && (
<MenuCheckbox
checked={!query.unlimited}
intent={query.unlimited ? Intent.WARNING : undefined}
@ -630,6 +698,7 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
defaultQueryContext={defaultQueryContext}
menuHeader={maxTasksMenuHeader}
maxTasksLabelFn={maxTasksLabelFn}
maxTasksOptions={maxTasksOptions}
fullClusterCapacityLabelFn={fullClusterCapacityLabelFn}
/>
)}

View File

@ -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,31 +756,40 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb
)}
{queryEngines.includes('sql-msq-task') && (
<>
{!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 />
{!hiddenMoreMenuItems.includes('druid-sql-documentation') && (
<MenuItem
icon={IconNames.HELP}
text="DruidSQL documentation"
href={getLink('DOCS_SQL')}
target="_blank"
/>
{queryEngines.includes('sql-msq-task') && (
)}
{queryEngines.includes('sql-msq-task') &&
!hiddenMoreMenuItems.includes('load-demo-queries') && (
<MenuItem
icon={IconNames.ROCKET_SLANT}
text="Load demo queries"