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 e286be94278 Exposes hooks to customize the workbench-view (#16749) e286be94278 is described below commit e286be9427850263990062612c384527e27bc2f4 Author: Sébastien <sebast...@imply.io> AuthorDate: Fri Jul 19 17:53:34 2024 +0200 Exposes hooks to customize the workbench-view (#16749) * Exposes hooks to customize the workbench-view * addressed PR feedback * naming * auto -> formatInteger(maxNum) --- .../max-tasks-button/max-tasks-button.tsx | 39 ++++++++--- .../views/workbench-view/query-tab/query-tab.tsx | 11 +++- .../views/workbench-view/run-panel/run-panel.tsx | 76 ++++++++++++++++------ .../src/views/workbench-view/workbench-view.tsx | 11 +++- 4 files changed, 106 insertions(+), 31 deletions(-) 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 c5e107c8a7a..ea239bc3a26 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 @@ -39,14 +39,28 @@ const TASK_ASSIGNMENT_DESCRIPTION: Record<string, string> = { auto: `Use as few tasks as possible without exceeding 512 MiB or 10,000 files per task, unless exceeding these limits is necessary to stay within 'maxNumTasks'. When calculating the size of files, the weighted size is used, which considers the file format and compression format used if any. When file sizes cannot be determined through directory listing (for example: http), behaves the same as 'max'.`, }; +const DEFAULT_MAX_NUM_LABEL_FN = (maxNum: number) => { + if (maxNum === 2) return { text: formatInteger(maxNum), label: '(1 controller + 1 worker)' }; + return { text: formatInteger(maxNum), label: `(1 controller + max ${maxNum - 1} workers)` }; +}; + export interface MaxTasksButtonProps extends Omit<ButtonProps, 'text' | 'rightIcon'> { clusterCapacity: number | undefined; queryContext: QueryContext; changeQueryContext(queryContext: QueryContext): void; + menuHeader?: JSX.Element; + maxNumLabelFn?: (maxNum: number) => { text: string; label?: string }; } export const MaxTasksButton = function MaxTasksButton(props: MaxTasksButtonProps) { - const { clusterCapacity, queryContext, changeQueryContext, ...rest } = props; + const { + clusterCapacity, + queryContext, + changeQueryContext, + menuHeader, + maxNumLabelFn = DEFAULT_MAX_NUM_LABEL_FN, + ...rest + } = props; const [customMaxNumTasksDialogOpen, setCustomMaxNumTasksDialogOpen] = useState(false); const maxNumTasks = getMaxNumTasks(queryContext); @@ -68,6 +82,7 @@ export const MaxTasksButton = function MaxTasksButton(props: MaxTasksButtonProps position={Position.BOTTOM_LEFT} content={ <Menu> + {menuHeader} <MenuDivider title="Maximum number of tasks to launch" /> {Boolean(fullClusterCapacity) && ( <MenuItem @@ -76,15 +91,19 @@ export const MaxTasksButton = function MaxTasksButton(props: MaxTasksButtonProps onClick={() => changeQueryContext(changeMaxNumTasks(queryContext, undefined))} /> )} - {shownMaxNumTaskOptions.map(m => ( - <MenuItem - key={String(m)} - icon={tickIcon(m === maxNumTasks)} - text={formatInteger(m)} - label={`(1 controller + ${m === 2 ? '1 worker' : `max ${m - 1} workers`})`} - onClick={() => changeQueryContext(changeMaxNumTasks(queryContext, m))} - /> - ))} + {shownMaxNumTaskOptions.map(m => { + const { text, label } = maxNumLabelFn(m); + + return ( + <MenuItem + key={String(m)} + icon={tickIcon(m === maxNumTasks)} + text={text} + label={label} + onClick={() => changeQueryContext(changeMaxNumTasks(queryContext, m))} + /> + ); + })} <MenuItem icon={tickIcon( typeof maxNumTasks === 'number' && !shownMaxNumTaskOptions.includes(maxNumTasks), 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 1c7aebdff69..cf863a14387 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 @@ -21,7 +21,7 @@ import { IconNames } from '@blueprintjs/icons'; import type { QueryResult } from '@druid-toolkit/query'; import { QueryRunner, SqlQuery } from '@druid-toolkit/query'; import axios from 'axios'; -import type { JSX } from 'react'; +import type { ComponentProps, JSX } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import SplitterLayout from 'react-splitter-layout'; import { useStore } from 'zustand'; @@ -82,6 +82,9 @@ export interface QueryTabProps { clusterCapacity: number | undefined; goToTask(taskId: string): void; getClusterCapacity: (() => Promise<CapacityInfo | undefined>) | undefined; + maxTaskMenuHeader?: JSX.Element; + enginesLabelFn?: ComponentProps<typeof RunPanel>['enginesLabelFn']; + maxTaskLabelFn?: ComponentProps<typeof RunPanel>['maxTaskLabelFn']; } export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) { @@ -98,6 +101,9 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) { clusterCapacity, goToTask, getClusterCapacity, + maxTaskMenuHeader, + enginesLabelFn, + maxTaskLabelFn, } = props; const [alertElement, setAlertElement] = useState<JSX.Element | undefined>(); @@ -399,6 +405,9 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) { queryEngines={queryEngines} clusterCapacity={clusterCapacity} moreMenu={runMoreMenu} + maxTaskMenuHeader={maxTaskMenuHeader} + enginesLabelFn={enginesLabelFn} + maxTaskLabelFn={maxTaskLabelFn} /> {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 b58c05bea35..f8235379612 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 @@ -30,7 +30,7 @@ import { useHotkeys, } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import type { JSX } from 'react'; +import type { ComponentProps, JSX } from 'react'; import React, { useCallback, useMemo, useState } from 'react'; import { MenuCheckbox, MenuTristate } from '../../../components'; @@ -111,6 +111,14 @@ const ARRAY_INGEST_MODE_DESCRIPTION: Record<ArrayIngestMode, JSX.Element> = { ), }; +const DEFAULT_ENGINES_LABEL_FN = (engine: DruidEngine | undefined) => { + if (!engine) return { text: 'auto' }; + return { + text: engine, + label: engine === 'sql-msq-task' ? 'multi-stage-query' : undefined, + }; +}; + export interface RunPanelProps { query: WorkbenchQuery; onQueryChange(query: WorkbenchQuery): void; @@ -120,11 +128,25 @@ export interface RunPanelProps { queryEngines: DruidEngine[]; clusterCapacity: number | undefined; moreMenu?: JSX.Element; + maxTaskMenuHeader?: JSX.Element; + enginesLabelFn?: (engine: DruidEngine | undefined) => { text: string; label?: string }; + maxTaskLabelFn?: ComponentProps<typeof MaxTasksButton>['maxNumLabelFn']; } export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) { - const { query, onQueryChange, onRun, moreMenu, running, small, queryEngines, clusterCapacity } = - props; + const { + query, + onQueryChange, + onRun, + moreMenu, + running, + small, + queryEngines, + clusterCapacity, + maxTaskMenuHeader, + maxTaskLabelFn, + enginesLabelFn = DEFAULT_ENGINES_LABEL_FN, + } = props; const [editContextDialogOpen, setEditContextDialogOpen] = useState(false); const [editParametersDialogOpen, setEditParametersDialogOpen] = useState(false); const [customTimezoneDialogOpen, setCustomTimezoneDialogOpen] = useState(false); @@ -186,25 +208,11 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) { useHotkeys(hotkeys); const queryEngine = query.engine; - function renderQueryEngineMenuItem(e: DruidEngine | undefined) { - return ( - <MenuItem - key={String(e)} - icon={tickIcon(e === queryEngine)} - text={typeof e === 'undefined' ? 'auto' : e} - label={e === 'sql-msq-task' ? 'multi-stage-query' : undefined} - onClick={() => onQueryChange(query.changeEngine(e))} - shouldDismissPopover={false} - /> - ); - } function changeQueryContext(queryContext: QueryContext) { onQueryChange(query.changeQueryContext(queryContext)); } - const availableEngines = ([undefined] as (DruidEngine | undefined)[]).concat(queryEngines); - function offsetOptions(): JSX.Element[] { const items: JSX.Element[] = []; @@ -231,6 +239,9 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) { const intent = overloadWarning ? Intent.WARNING : undefined; const effectiveEngine = query.getEffectiveEngine(); + + const autoEngineLabel = enginesLabelFn(undefined); + return ( <div className="run-panel"> <Button @@ -262,7 +273,29 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) { {queryEngines.length > 1 && ( <> <MenuDivider title="Select engine" /> - {availableEngines.map(renderQueryEngineMenuItem)} + <MenuItem + key="auto" + icon={tickIcon(queryEngine === undefined)} + text={autoEngineLabel.text} + label={autoEngineLabel.label} + onClick={() => onQueryChange(query.changeEngine(undefined))} + shouldDismissPopover={false} + /> + {queryEngines.map(engine => { + const { text, label } = enginesLabelFn(engine); + + return ( + <MenuItem + key={String(engine)} + icon={tickIcon(engine === queryEngine)} + text={text} + label={label} + onClick={() => onQueryChange(query.changeEngine(engine))} + shouldDismissPopover={false} + /> + ); + })} + <MenuDivider /> </> )} @@ -485,7 +518,10 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) { } > <Button - text={`Engine: ${queryEngine || `auto (${effectiveEngine})`}`} + text={`Engine: ${ + (enginesLabelFn ? enginesLabelFn(queryEngine).text : queryEngine) || + `auto (${enginesLabelFn ? enginesLabelFn(effectiveEngine) : effectiveEngine})` + }`} rightIcon={IconNames.CARET_DOWN} intent={intent} /> @@ -495,6 +531,8 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) { clusterCapacity={clusterCapacity} queryContext={queryContext} changeQueryContext={changeQueryContext} + menuHeader={maxTaskMenuHeader} + maxNumLabelFn={maxTaskLabelFn} /> )} {ingestMode && ( diff --git a/web-console/src/views/workbench-view/workbench-view.tsx b/web-console/src/views/workbench-view/workbench-view.tsx index 6f93ff6c2a4..0fcaa5a1da9 100644 --- a/web-console/src/views/workbench-view/workbench-view.tsx +++ b/web-console/src/views/workbench-view/workbench-view.tsx @@ -30,7 +30,7 @@ import type { SqlQuery } from '@druid-toolkit/query'; import { SqlExpression } from '@druid-toolkit/query'; import classNames from 'classnames'; import copy from 'copy-to-clipboard'; -import React from 'react'; +import React, { ComponentProps } from 'react'; import { SpecDialog, StringInputDialog } from '../../dialogs'; import type { @@ -101,6 +101,9 @@ export interface WorkbenchViewProps { allowExplain: boolean; goToTask(taskId: string): void; getClusterCapacity: (() => Promise<CapacityInfo | undefined>) | undefined; + maxTaskMenuHeader?: JSX.Element; + enginesLabelFn?: ComponentProps<typeof QueryTab>['enginesLabelFn']; + maxTaskLabelFn?: ComponentProps<typeof QueryTab>['maxTaskLabelFn']; } export interface WorkbenchViewState { @@ -649,6 +652,9 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb allowExplain, goToTask, getClusterCapacity, + maxTaskMenuHeader, + enginesLabelFn, + maxTaskLabelFn, } = this.props; const { columnMetadataState } = this.state; const currentTabEntry = this.getCurrentTabEntry(); @@ -673,6 +679,9 @@ export class WorkbenchView extends React.PureComponent<WorkbenchViewProps, Workb clusterCapacity={capabilities.getMaxTaskSlots()} goToTask={goToTask} getClusterCapacity={getClusterCapacity} + maxTaskMenuHeader={maxTaskMenuHeader} + enginesLabelFn={enginesLabelFn} + maxTaskLabelFn={maxTaskLabelFn} runMoreMenu={ <Menu> {allowExplain && --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@druid.apache.org For additional commands, e-mail: commits-h...@druid.apache.org