mirror of https://github.com/apache/druid.git
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:
parent
2427972c10
commit
5de84253d8
|
@ -327,7 +327,6 @@ export class ConsoleApplication extends React.PureComponent<
|
||||||
baseQueryContext={baseQueryContext}
|
baseQueryContext={baseQueryContext}
|
||||||
serverQueryContext={serverQueryContext}
|
serverQueryContext={serverQueryContext}
|
||||||
queryEngines={queryEngines}
|
queryEngines={queryEngines}
|
||||||
allowExplain
|
|
||||||
goToTask={this.goToTasksWithTaskId}
|
goToTask={this.goToTasksWithTaskId}
|
||||||
getClusterCapacity={maybeGetClusterCapacity}
|
getClusterCapacity={maybeGetClusterCapacity}
|
||||||
/>,
|
/>,
|
||||||
|
|
|
@ -100,14 +100,13 @@ exports[`MaxTasksButton matches snapshot 1`] = `
|
||||||
multiline={true}
|
multiline={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
popoverProps={{}}
|
popoverProps={{}}
|
||||||
shouldDismissPopover={false}
|
shouldDismissPopover={true}
|
||||||
text={
|
text={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<strong>
|
<strong>
|
||||||
Max
|
Max
|
||||||
</strong>
|
</strong>
|
||||||
:
|
: uses the maximum possible tasks up to the specified limit.
|
||||||
uses the maximum possible tasks up to the specified limit.
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -115,24 +114,28 @@ exports[`MaxTasksButton matches snapshot 1`] = `
|
||||||
active={false}
|
active={false}
|
||||||
disabled={false}
|
disabled={false}
|
||||||
icon="blank"
|
icon="blank"
|
||||||
labelElement={
|
|
||||||
<Blueprint5.Button
|
|
||||||
icon="help"
|
|
||||||
minimal={true}
|
|
||||||
onClick={[Function]}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
multiline={true}
|
multiline={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
popoverProps={{}}
|
popoverProps={{}}
|
||||||
shouldDismissPopover={false}
|
shouldDismissPopover={true}
|
||||||
text={
|
text={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<strong>
|
<strong>
|
||||||
Auto
|
Auto
|
||||||
</strong>
|
</strong>
|
||||||
:
|
: uses the minimum number of tasks while
|
||||||
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.
|
|
||||||
|
<span
|
||||||
|
onClick={[Function]}
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"color": "#3eadf9",
|
||||||
|
"cursor": "pointer",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
staying within constraints.
|
||||||
|
</span>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -19,34 +19,17 @@
|
||||||
import type { ButtonProps } from '@blueprintjs/core';
|
import type { ButtonProps } from '@blueprintjs/core';
|
||||||
import { Button, Menu, MenuDivider, MenuItem, Popover, Position } from '@blueprintjs/core';
|
import { Button, Menu, MenuDivider, MenuItem, Popover, Position } from '@blueprintjs/core';
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import type { JSX, ReactNode } from 'react';
|
import type { JSX } from 'react';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { NumericInputDialog } from '../../../dialogs';
|
import { NumericInputDialog } from '../../../dialogs';
|
||||||
import type { QueryContext, TaskAssignment } from '../../../druid-models';
|
import type { QueryContext } from '../../../druid-models';
|
||||||
import { getQueryContextKey } from '../../../druid-models';
|
import { getQueryContextKey } from '../../../druid-models';
|
||||||
import { getLink } from '../../../links';
|
import { getLink } from '../../../links';
|
||||||
import { capitalizeFirst, deleteKeys, formatInteger, tickIcon } from '../../../utils';
|
import { capitalizeFirst, deleteKeys, formatInteger, tickIcon } from '../../../utils';
|
||||||
|
|
||||||
const MAX_NUM_TASK_OPTIONS = [2, 3, 4, 5, 7, 9, 11, 17, 33, 65, 129];
|
const DEFAULT_MAX_TASKS_OPTIONS = [2, 3, 4, 5, 7, 9, 11, 17, 33, 65, 129];
|
||||||
const TASK_ASSIGNMENT_OPTIONS: TaskAssignment[] = ['max', 'auto'];
|
const TASK_DOCUMENTATION_LINK = `${getLink('DOCS')}/multi-stage-query/reference#context-parameters`;
|
||||||
|
|
||||||
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_NUM_TASKS_LABEL_FN = (maxNum: number) => {
|
const DEFAULT_MAX_NUM_TASKS_LABEL_FN = (maxNum: number) => {
|
||||||
if (maxNum === 2) return { text: formatInteger(maxNum), label: '(1 controller + 1 worker)' };
|
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;
|
defaultQueryContext: QueryContext;
|
||||||
menuHeader?: JSX.Element;
|
menuHeader?: JSX.Element;
|
||||||
maxTasksLabelFn?: (maxNum: number) => { text: string; label?: string };
|
maxTasksLabelFn?: (maxNum: number) => { text: string; label?: string };
|
||||||
|
maxTasksOptions?: number[];
|
||||||
fullClusterCapacityLabelFn?: (clusterCapacity: number) => string;
|
fullClusterCapacityLabelFn?: (clusterCapacity: number) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +59,7 @@ export const MaxTasksButton = function MaxTasksButton(props: MaxTasksButtonProps
|
||||||
menuHeader,
|
menuHeader,
|
||||||
maxTasksLabelFn = DEFAULT_MAX_NUM_TASKS_LABEL_FN,
|
maxTasksLabelFn = DEFAULT_MAX_NUM_TASKS_LABEL_FN,
|
||||||
fullClusterCapacityLabelFn = DEFAULT_FULL_CLUSTER_CAPACITY_LABEL_FN,
|
fullClusterCapacityLabelFn = DEFAULT_FULL_CLUSTER_CAPACITY_LABEL_FN,
|
||||||
|
maxTasksOptions = DEFAULT_MAX_TASKS_OPTIONS,
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
const [customMaxNumTasksDialogOpen, setCustomMaxNumTasksDialogOpen] = useState(false);
|
const [customMaxNumTasksDialogOpen, setCustomMaxNumTasksDialogOpen] = useState(false);
|
||||||
|
@ -86,8 +71,8 @@ export const MaxTasksButton = function MaxTasksButton(props: MaxTasksButtonProps
|
||||||
typeof clusterCapacity === 'number' ? fullClusterCapacityLabelFn(clusterCapacity) : undefined;
|
typeof clusterCapacity === 'number' ? fullClusterCapacityLabelFn(clusterCapacity) : undefined;
|
||||||
|
|
||||||
const shownMaxNumTaskOptions = clusterCapacity
|
const shownMaxNumTaskOptions = clusterCapacity
|
||||||
? MAX_NUM_TASK_OPTIONS.filter(_ => _ <= clusterCapacity)
|
? maxTasksOptions.filter(_ => _ <= clusterCapacity)
|
||||||
: MAX_NUM_TASK_OPTIONS;
|
: maxTasksOptions;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -103,6 +88,7 @@ export const MaxTasksButton = function MaxTasksButton(props: MaxTasksButtonProps
|
||||||
icon={tickIcon(typeof maxNumTasks === 'undefined')}
|
icon={tickIcon(typeof maxNumTasks === 'undefined')}
|
||||||
text={fullClusterCapacity}
|
text={fullClusterCapacity}
|
||||||
onClick={() => changeQueryContext(deleteKeys(queryContext, ['maxNumTasks']))}
|
onClick={() => changeQueryContext(deleteKeys(queryContext, ['maxNumTasks']))}
|
||||||
|
shouldDismissPopover
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{shownMaxNumTaskOptions.map(m => {
|
{shownMaxNumTaskOptions.map(m => {
|
||||||
|
@ -115,6 +101,7 @@ export const MaxTasksButton = function MaxTasksButton(props: MaxTasksButtonProps
|
||||||
text={text}
|
text={text}
|
||||||
label={label}
|
label={label}
|
||||||
onClick={() => changeQueryContext({ ...queryContext, maxNumTasks: m })}
|
onClick={() => changeQueryContext({ ...queryContext, maxNumTasks: m })}
|
||||||
|
shouldDismissPopover
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -132,21 +119,39 @@ export const MaxTasksButton = function MaxTasksButton(props: MaxTasksButtonProps
|
||||||
label={capitalizeFirst(taskAssigment)}
|
label={capitalizeFirst(taskAssigment)}
|
||||||
submenuProps={{ style: { width: 300 } }}
|
submenuProps={{ style: { width: 300 } }}
|
||||||
>
|
>
|
||||||
{TASK_ASSIGNMENT_OPTIONS.map(t => (
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={String(t)}
|
icon={tickIcon(taskAssigment === 'max')}
|
||||||
icon={tickIcon(t === taskAssigment)}
|
|
||||||
text={
|
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
|
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>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,12 @@ const queryRunner = new QueryRunner({
|
||||||
export interface QueryTabProps
|
export interface QueryTabProps
|
||||||
extends Pick<
|
extends Pick<
|
||||||
RunPanelProps,
|
RunPanelProps,
|
||||||
'maxTasksMenuHeader' | 'enginesLabelFn' | 'maxTasksLabelFn' | 'fullClusterCapacityLabelFn'
|
| 'maxTasksMenuHeader'
|
||||||
|
| 'enginesLabelFn'
|
||||||
|
| 'maxTasksLabelFn'
|
||||||
|
| 'fullClusterCapacityLabelFn'
|
||||||
|
| 'maxTasksOptions'
|
||||||
|
| 'hiddenOptions'
|
||||||
> {
|
> {
|
||||||
query: WorkbenchQuery;
|
query: WorkbenchQuery;
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -110,7 +115,9 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
|
||||||
maxTasksMenuHeader,
|
maxTasksMenuHeader,
|
||||||
enginesLabelFn,
|
enginesLabelFn,
|
||||||
maxTasksLabelFn,
|
maxTasksLabelFn,
|
||||||
|
maxTasksOptions,
|
||||||
fullClusterCapacityLabelFn,
|
fullClusterCapacityLabelFn,
|
||||||
|
hiddenOptions,
|
||||||
} = props;
|
} = props;
|
||||||
const [alertElement, setAlertElement] = useState<JSX.Element | undefined>();
|
const [alertElement, setAlertElement] = useState<JSX.Element | undefined>();
|
||||||
|
|
||||||
|
@ -419,7 +426,9 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
|
||||||
maxTasksMenuHeader={maxTasksMenuHeader}
|
maxTasksMenuHeader={maxTasksMenuHeader}
|
||||||
enginesLabelFn={enginesLabelFn}
|
enginesLabelFn={enginesLabelFn}
|
||||||
maxTasksLabelFn={maxTasksLabelFn}
|
maxTasksLabelFn={maxTasksLabelFn}
|
||||||
|
maxTasksOptions={maxTasksOptions}
|
||||||
fullClusterCapacityLabelFn={fullClusterCapacityLabelFn}
|
fullClusterCapacityLabelFn={fullClusterCapacityLabelFn}
|
||||||
|
hiddenOptions={hiddenOptions}
|
||||||
/>
|
/>
|
||||||
{executionState.isLoading() && (
|
{executionState.isLoading() && (
|
||||||
<ExecutionTimerPanel
|
<ExecutionTimerPanel
|
||||||
|
|
|
@ -99,11 +99,6 @@ const SQL_JOIN_ALGORITHM_LABEL: Record<SqlJoinAlgorithm, string> = {
|
||||||
sortMerge: 'Sort merge',
|
sortMerge: 'Sort merge',
|
||||||
};
|
};
|
||||||
|
|
||||||
const SELECT_DESTINATION_LABEL: Record<SelectDestination, string> = {
|
|
||||||
taskReport: 'Task report',
|
|
||||||
durableStorage: 'Durable storage',
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_ENGINES_LABEL_FN = (engine: DruidEngine | undefined) => {
|
const DEFAULT_ENGINES_LABEL_FN = (engine: DruidEngine | undefined) => {
|
||||||
switch (engine) {
|
switch (engine) {
|
||||||
case 'native':
|
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" />;
|
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
|
export interface RunPanelProps
|
||||||
extends Pick<MaxTasksButtonProps, 'maxTasksLabelFn' | 'fullClusterCapacityLabelFn'> {
|
extends Pick<
|
||||||
|
MaxTasksButtonProps,
|
||||||
|
'maxTasksLabelFn' | 'fullClusterCapacityLabelFn' | 'maxTasksOptions'
|
||||||
|
> {
|
||||||
query: WorkbenchQuery;
|
query: WorkbenchQuery;
|
||||||
onQueryChange(query: WorkbenchQuery): void;
|
onQueryChange(query: WorkbenchQuery): void;
|
||||||
running: boolean;
|
running: boolean;
|
||||||
|
@ -134,6 +152,7 @@ export interface RunPanelProps
|
||||||
moreMenu?: JSX.Element;
|
moreMenu?: JSX.Element;
|
||||||
maxTasksMenuHeader?: JSX.Element;
|
maxTasksMenuHeader?: JSX.Element;
|
||||||
enginesLabelFn?: (engine: DruidEngine | undefined) => { text: string; label?: string };
|
enginesLabelFn?: (engine: DruidEngine | undefined) => { text: string; label?: string };
|
||||||
|
hiddenOptions?: EnginesMenuOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
|
@ -149,7 +168,9 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
maxTasksMenuHeader,
|
maxTasksMenuHeader,
|
||||||
enginesLabelFn = DEFAULT_ENGINES_LABEL_FN,
|
enginesLabelFn = DEFAULT_ENGINES_LABEL_FN,
|
||||||
maxTasksLabelFn,
|
maxTasksLabelFn,
|
||||||
|
maxTasksOptions,
|
||||||
fullClusterCapacityLabelFn,
|
fullClusterCapacityLabelFn,
|
||||||
|
hiddenOptions = [],
|
||||||
} = props;
|
} = props;
|
||||||
const [editContextDialogOpen, setEditContextDialogOpen] = useState(false);
|
const [editContextDialogOpen, setEditContextDialogOpen] = useState(false);
|
||||||
const [editParametersDialogOpen, setEditParametersDialogOpen] = useState(false);
|
const [editParametersDialogOpen, setEditParametersDialogOpen] = useState(false);
|
||||||
|
@ -340,19 +361,25 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{!hiddenOptions.includes('edit-query-context') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.PROPERTIES}
|
icon={IconNames.PROPERTIES}
|
||||||
text="Edit query context..."
|
text="Edit query context..."
|
||||||
onClick={() => setEditContextDialogOpen(true)}
|
onClick={() => setEditContextDialogOpen(true)}
|
||||||
label={pluralIfNeeded(numContextKeys, 'key')}
|
label={pluralIfNeeded(numContextKeys, 'key')}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
{!hiddenOptions.includes('define-parameters') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.HELP}
|
icon={IconNames.HELP}
|
||||||
text="Define parameters..."
|
text="Define parameters..."
|
||||||
onClick={() => setEditParametersDialogOpen(true)}
|
onClick={() => setEditParametersDialogOpen(true)}
|
||||||
label={queryParameters ? pluralIfNeeded(queryParameters.length, 'parameter') : ''}
|
label={
|
||||||
|
queryParameters ? pluralIfNeeded(queryParameters.length, 'parameter') : ''
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{effectiveEngine !== 'native' && (
|
)}
|
||||||
|
{effectiveEngine !== 'native' && !hiddenOptions.includes('timezone') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.GLOBE_NETWORK}
|
icon={IconNames.GLOBE_NETWORK}
|
||||||
text="Timezone"
|
text="Timezone"
|
||||||
|
@ -393,7 +420,11 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
)}
|
)}
|
||||||
{effectiveEngine === 'sql-msq-task' ? (
|
{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
|
<MenuBoolean
|
||||||
text="Force segment sort by time"
|
text="Force segment sort by time"
|
||||||
value={forceSegmentSortByTime}
|
value={forceSegmentSortByTime}
|
||||||
|
@ -447,6 +478,8 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{!hiddenOptions.includes('max-parse-exceptions') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.ERROR}
|
icon={IconNames.ERROR}
|
||||||
text="Max parse exceptions"
|
text="Max parse exceptions"
|
||||||
|
@ -464,28 +497,8 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuBoolean
|
)}
|
||||||
icon={IconNames.TRANSLATE}
|
{!hiddenOptions.includes('join-algorithm') && (
|
||||||
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
|
<MenuItem
|
||||||
icon={IconNames.INNER_JOIN}
|
icon={IconNames.INNER_JOIN}
|
||||||
text="Join algorithm"
|
text="Join algorithm"
|
||||||
|
@ -506,6 +519,9 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!hiddenOptions.includes('select-destination') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.MANUALLY_ENTERED_DATA}
|
icon={IconNames.MANUALLY_ENTERED_DATA}
|
||||||
text="SELECT destination"
|
text="SELECT destination"
|
||||||
|
@ -540,49 +556,9 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</MenuItem>
|
</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
|
<MenuBoolean
|
||||||
icon={IconNames.ROCKET_SLANT}
|
icon={IconNames.ROCKET_SLANT}
|
||||||
text="Approximate COUNT(DISTINCT)"
|
text="Approximate COUNT(DISTINCT)"
|
||||||
|
@ -596,7 +572,99 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
optionsText={ENABLE_DISABLE_OPTIONS_TEXT}
|
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
|
<MenuCheckbox
|
||||||
checked={!query.unlimited}
|
checked={!query.unlimited}
|
||||||
intent={query.unlimited ? Intent.WARNING : undefined}
|
intent={query.unlimited ? Intent.WARNING : undefined}
|
||||||
|
@ -630,6 +698,7 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
defaultQueryContext={defaultQueryContext}
|
defaultQueryContext={defaultQueryContext}
|
||||||
menuHeader={maxTasksMenuHeader}
|
menuHeader={maxTasksMenuHeader}
|
||||||
maxTasksLabelFn={maxTasksLabelFn}
|
maxTasksLabelFn={maxTasksLabelFn}
|
||||||
|
maxTasksOptions={maxTasksOptions}
|
||||||
fullClusterCapacityLabelFn={fullClusterCapacityLabelFn}
|
fullClusterCapacityLabelFn={fullClusterCapacityLabelFn}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -96,6 +96,16 @@ function externalDataTabId(tabId: string | undefined): boolean {
|
||||||
return String(tabId).startsWith('connect-external-data');
|
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
|
export interface WorkbenchViewProps
|
||||||
extends Pick<
|
extends Pick<
|
||||||
QueryTabProps,
|
QueryTabProps,
|
||||||
|
@ -110,10 +120,16 @@ export interface WorkbenchViewProps
|
||||||
mandatoryQueryContext?: QueryContext;
|
mandatoryQueryContext?: QueryContext;
|
||||||
serverQueryContext?: QueryContext;
|
serverQueryContext?: QueryContext;
|
||||||
queryEngines: DruidEngine[];
|
queryEngines: DruidEngine[];
|
||||||
allowExplain: boolean;
|
hiddenMoreMenuItems?: MoreMenuItem[] | ((engine: DruidEngine) => MoreMenuItem[]);
|
||||||
goToTask(taskId: string): void;
|
goToTask(taskId: string): void;
|
||||||
getClusterCapacity: (() => Promise<CapacityInfo | undefined>) | undefined;
|
getClusterCapacity: (() => Promise<CapacityInfo | undefined>) | undefined;
|
||||||
hideToolbar?: boolean;
|
hideToolbar?: boolean;
|
||||||
|
maxTasksOptions?:
|
||||||
|
| QueryTabProps['maxTasksOptions']
|
||||||
|
| ((engine: DruidEngine) => QueryTabProps['maxTasksOptions']);
|
||||||
|
hiddenOptions?:
|
||||||
|
| QueryTabProps['hiddenOptions']
|
||||||
|
| ((engine: DruidEngine) => QueryTabProps['hiddenOptions']);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkbenchViewState {
|
export interface WorkbenchViewState {
|
||||||
|
@ -663,18 +679,24 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb
|
||||||
baseQueryContext,
|
baseQueryContext,
|
||||||
serverQueryContext = DEFAULT_SERVER_QUERY_CONTEXT,
|
serverQueryContext = DEFAULT_SERVER_QUERY_CONTEXT,
|
||||||
queryEngines,
|
queryEngines,
|
||||||
allowExplain,
|
|
||||||
goToTask,
|
goToTask,
|
||||||
getClusterCapacity,
|
getClusterCapacity,
|
||||||
maxTasksMenuHeader,
|
maxTasksMenuHeader,
|
||||||
enginesLabelFn,
|
enginesLabelFn,
|
||||||
maxTasksLabelFn,
|
maxTasksLabelFn,
|
||||||
|
maxTasksOptions,
|
||||||
fullClusterCapacityLabelFn,
|
fullClusterCapacityLabelFn,
|
||||||
|
hiddenOptions,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { columnMetadataState } = this.state;
|
const { columnMetadataState } = this.state;
|
||||||
const currentTabEntry = this.getCurrentTabEntry();
|
const currentTabEntry = this.getCurrentTabEntry();
|
||||||
const effectiveEngine = currentTabEntry.query.getEffectiveEngine();
|
const effectiveEngine = currentTabEntry.query.getEffectiveEngine();
|
||||||
|
|
||||||
|
const hiddenMoreMenuItems =
|
||||||
|
typeof this.props.hiddenMoreMenuItems === 'function'
|
||||||
|
? this.props.hiddenMoreMenuItems(effectiveEngine)
|
||||||
|
: this.props.hiddenMoreMenuItems || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="center-panel">
|
<div className="center-panel">
|
||||||
<div className="tab-and-tool-bar">
|
<div className="tab-and-tool-bar">
|
||||||
|
@ -699,10 +721,18 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb
|
||||||
maxTasksMenuHeader={maxTasksMenuHeader}
|
maxTasksMenuHeader={maxTasksMenuHeader}
|
||||||
enginesLabelFn={enginesLabelFn}
|
enginesLabelFn={enginesLabelFn}
|
||||||
maxTasksLabelFn={maxTasksLabelFn}
|
maxTasksLabelFn={maxTasksLabelFn}
|
||||||
|
maxTasksOptions={
|
||||||
|
typeof maxTasksOptions === 'function'
|
||||||
|
? maxTasksOptions(effectiveEngine)
|
||||||
|
: maxTasksOptions
|
||||||
|
}
|
||||||
fullClusterCapacityLabelFn={fullClusterCapacityLabelFn}
|
fullClusterCapacityLabelFn={fullClusterCapacityLabelFn}
|
||||||
|
hiddenOptions={
|
||||||
|
typeof hiddenOptions === 'function' ? hiddenOptions(effectiveEngine) : hiddenOptions
|
||||||
|
}
|
||||||
runMoreMenu={
|
runMoreMenu={
|
||||||
<Menu>
|
<Menu>
|
||||||
{allowExplain &&
|
{!hiddenMoreMenuItems.includes('explain') &&
|
||||||
(effectiveEngine === 'sql-native' || effectiveEngine === 'sql-msq-task') && (
|
(effectiveEngine === 'sql-native' || effectiveEngine === 'sql-msq-task') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.CLEAN}
|
icon={IconNames.CLEAN}
|
||||||
|
@ -710,14 +740,14 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb
|
||||||
onClick={this.openExplainDialog}
|
onClick={this.openExplainDialog}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{effectiveEngine !== 'sql-msq-task' && (
|
{effectiveEngine !== 'sql-msq-task' && !hiddenMoreMenuItems.includes('history') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.HISTORY}
|
icon={IconNames.HISTORY}
|
||||||
text="Query history"
|
text="Query history"
|
||||||
onClick={this.openHistoryDialog}
|
onClick={this.openHistoryDialog}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{currentTabEntry.query.canPrettify() && (
|
{currentTabEntry.query.canPrettify() && !hiddenMoreMenuItems.includes('prettify') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.ALIGN_LEFT}
|
icon={IconNames.ALIGN_LEFT}
|
||||||
text="Prettify query"
|
text="Prettify query"
|
||||||
|
@ -726,31 +756,40 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb
|
||||||
)}
|
)}
|
||||||
{queryEngines.includes('sql-msq-task') && (
|
{queryEngines.includes('sql-msq-task') && (
|
||||||
<>
|
<>
|
||||||
|
{!hiddenMoreMenuItems.includes('convert-ingestion-to-sql') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.TEXT_HIGHLIGHT}
|
icon={IconNames.TEXT_HIGHLIGHT}
|
||||||
text="Convert ingestion spec to SQL"
|
text="Convert ingestion spec to SQL"
|
||||||
onClick={this.openSpecDialog}
|
onClick={this.openSpecDialog}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
{!hiddenMoreMenuItems.includes('attach-tab-from-task-id') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.DOCUMENT_OPEN}
|
icon={IconNames.DOCUMENT_OPEN}
|
||||||
text="Attach tab from task ID"
|
text="Attach tab from task ID"
|
||||||
onClick={this.openTaskIdSubmitDialog}
|
onClick={this.openTaskIdSubmitDialog}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
{!hiddenMoreMenuItems.includes('open-query-detail-archive') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.UNARCHIVE}
|
icon={IconNames.UNARCHIVE}
|
||||||
text="Open query detail archive"
|
text="Open query detail archive"
|
||||||
onClick={this.openExecutionSubmitDialog}
|
onClick={this.openExecutionSubmitDialog}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
|
{!hiddenMoreMenuItems.includes('druid-sql-documentation') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.HELP}
|
icon={IconNames.HELP}
|
||||||
text="DruidSQL documentation"
|
text="DruidSQL documentation"
|
||||||
href={getLink('DOCS_SQL')}
|
href={getLink('DOCS_SQL')}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
/>
|
/>
|
||||||
{queryEngines.includes('sql-msq-task') && (
|
)}
|
||||||
|
{queryEngines.includes('sql-msq-task') &&
|
||||||
|
!hiddenMoreMenuItems.includes('load-demo-queries') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={IconNames.ROCKET_SLANT}
|
icon={IconNames.ROCKET_SLANT}
|
||||||
text="Load demo queries"
|
text="Load demo queries"
|
||||||
|
|
Loading…
Reference in New Issue