mirror of https://github.com/apache/druid.git
Web console: add durable storage selector (#14669)
This commit is contained in:
parent
8232c03667
commit
9e1650e327
|
@ -18,13 +18,14 @@
|
|||
|
||||
import type { MenuItemProps } from '@blueprintjs/core';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
import { checkedCircleIcon } from '../../utils';
|
||||
|
||||
export interface MenuCheckboxProps extends Omit<MenuItemProps, 'icon' | 'onClick'> {
|
||||
checked: boolean;
|
||||
checked: boolean | undefined;
|
||||
onChange: () => void;
|
||||
}
|
||||
|
||||
|
@ -34,7 +35,7 @@ export function MenuCheckbox(props: MenuCheckboxProps) {
|
|||
return (
|
||||
<MenuItem
|
||||
className={classNames('menu-checkbox', className)}
|
||||
icon={checkedCircleIcon(checked)}
|
||||
icon={typeof checked === 'boolean' ? checkedCircleIcon(checked) : IconNames.BLANK}
|
||||
onClick={onChange}
|
||||
shouldDismissPopover={shouldDismissPopover ?? false}
|
||||
{...rest}
|
||||
|
|
|
@ -28,6 +28,7 @@ export interface QueryContext {
|
|||
// Multi-stage query
|
||||
maxNumTasks?: number;
|
||||
finalizeAggregations?: boolean;
|
||||
selectDestination?: string;
|
||||
durableShuffleStorage?: boolean;
|
||||
maxParseExceptions?: number;
|
||||
groupByEnableMultiValueUnnesting?: boolean;
|
||||
|
@ -161,7 +162,7 @@ export function changeFinalizeAggregations(
|
|||
: deepDelete(context, 'finalizeAggregations');
|
||||
}
|
||||
|
||||
// finalizeAggregations
|
||||
// groupByEnableMultiValueUnnesting
|
||||
|
||||
export function getGroupByEnableMultiValueUnnesting(context: QueryContext): boolean | undefined {
|
||||
const { groupByEnableMultiValueUnnesting } = context;
|
||||
|
|
|
@ -16,11 +16,20 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { C } from '@druid-toolkit/query';
|
||||
|
||||
import type { StageDefinition } from '../stages/stages';
|
||||
|
||||
export type TaskStatus = 'WAITING' | 'PENDING' | 'RUNNING' | 'FAILED' | 'SUCCESS';
|
||||
export type TaskStatusWithCanceled = TaskStatus | 'CANCELED';
|
||||
|
||||
export const TASK_CANCELED_ERROR_MESSAGES: string[] = [
|
||||
'Shutdown request from user',
|
||||
'Canceled: Query canceled by user or by task shutdown.',
|
||||
];
|
||||
|
||||
export const TASK_CANCELED_PREDICATE = C('error_msg').in(TASK_CANCELED_ERROR_MESSAGES);
|
||||
|
||||
export interface TaskStatusResponse {
|
||||
task: string;
|
||||
status: {
|
||||
|
|
|
@ -600,7 +600,7 @@ export class WorkbenchQuery {
|
|||
}
|
||||
|
||||
const ingestQuery = this.isIngestQuery();
|
||||
if (!unlimited && !ingestQuery) {
|
||||
if (!unlimited && !ingestQuery && queryContext.selectDestination !== 'durableStorage') {
|
||||
apiQuery.context ||= {};
|
||||
apiQuery.context.sqlOuterLimit = 1001;
|
||||
}
|
||||
|
|
|
@ -259,7 +259,7 @@ export class DruidError extends Error {
|
|||
public position?: RowColumn;
|
||||
public suggestion?: QuerySuggestion;
|
||||
|
||||
// Depricated
|
||||
// Deprecated
|
||||
public error?: string;
|
||||
public errorClass?: string;
|
||||
public host?: string;
|
||||
|
|
|
@ -36,6 +36,7 @@ import {
|
|||
} from '../../components';
|
||||
import { AlertDialog, AsyncActionDialog, SpecDialog, TaskTableActionDialog } from '../../dialogs';
|
||||
import type { QueryWithContext } from '../../druid-models';
|
||||
import { TASK_CANCELED_ERROR_MESSAGES, TASK_CANCELED_PREDICATE } from '../../druid-models';
|
||||
import type { Capabilities } from '../../helpers';
|
||||
import { SMALL_TABLE_PAGE_SIZE, SMALL_TABLE_PAGE_SIZE_OPTIONS } from '../../react-table';
|
||||
import { Api, AppToaster } from '../../singletons';
|
||||
|
@ -66,8 +67,6 @@ const taskTableColumns: string[] = [
|
|||
ACTION_COLUMN_LABEL,
|
||||
];
|
||||
|
||||
const CANCELED_ERROR_MSG = 'Shutdown request from user';
|
||||
|
||||
interface TaskQueryResultRow {
|
||||
task_id: string;
|
||||
group_id: string;
|
||||
|
@ -137,7 +136,7 @@ export class TasksView extends React.PureComponent<TasksViewProps, TasksViewStat
|
|||
|
||||
static TASK_SQL = `WITH tasks AS (SELECT
|
||||
"task_id", "group_id", "type", "datasource", "created_time", "location", "duration", "error_msg",
|
||||
CASE WHEN "error_msg" = '${CANCELED_ERROR_MSG}' THEN 'CANCELED' WHEN "status" = 'RUNNING' THEN "runner_status" ELSE "status" END AS "status"
|
||||
CASE WHEN ${TASK_CANCELED_PREDICATE} THEN 'CANCELED' WHEN "status" = 'RUNNING' THEN "runner_status" ELSE "status" END AS "status"
|
||||
FROM sys.tasks
|
||||
)
|
||||
SELECT "task_id", "group_id", "type", "datasource", "created_time", "location", "duration", "error_msg", "status"
|
||||
|
@ -406,16 +405,11 @@ ORDER BY
|
|||
filters={filters}
|
||||
onFiltersChange={onFiltersChange}
|
||||
>
|
||||
<span>
|
||||
<span title={errorMsg}>
|
||||
<span style={{ color: statusToColor(status) }}>● </span>
|
||||
{status}
|
||||
{errorMsg && errorMsg !== CANCELED_ERROR_MSG && (
|
||||
<a
|
||||
onClick={() => this.setState({ alertErrorMsg: errorMsg })}
|
||||
title={errorMsg}
|
||||
>
|
||||
?
|
||||
</a>
|
||||
{errorMsg && !TASK_CANCELED_ERROR_MESSAGES.includes(errorMsg) && (
|
||||
<a onClick={() => this.setState({ alertErrorMsg: errorMsg })}> ?</a>
|
||||
)}
|
||||
</span>
|
||||
</TableFilterableCell>
|
||||
|
|
|
@ -28,7 +28,7 @@ import { useStore } from 'zustand';
|
|||
|
||||
import { Loader } from '../../../components';
|
||||
import type { TaskStatusWithCanceled } from '../../../druid-models';
|
||||
import { Execution, WorkbenchQuery } from '../../../druid-models';
|
||||
import { Execution, TASK_CANCELED_PREDICATE, WorkbenchQuery } from '../../../druid-models';
|
||||
import { cancelTaskExecution, getTaskExecution } from '../../../helpers';
|
||||
import { useClock, useInterval, useQueryManager } from '../../../hooks';
|
||||
import { AppToaster } from '../../../singletons';
|
||||
|
@ -105,7 +105,7 @@ export const RecentQueryTaskPanel = React.memo(function RecentQueryTaskPanel(
|
|||
processQuery: async _ => {
|
||||
return await queryDruidSql<RecentQueryEntry>({
|
||||
query: `SELECT
|
||||
CASE WHEN "error_msg" = 'Shutdown request from user' THEN 'CANCELED' ELSE "status" END AS "taskStatus",
|
||||
CASE WHEN ${TASK_CANCELED_PREDICATE} THEN 'CANCELED' ELSE "status" END AS "taskStatus",
|
||||
"task_id" AS "taskId",
|
||||
"datasource",
|
||||
"created_time" AS "createdTime",
|
||||
|
|
|
@ -109,6 +109,7 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
|||
const finalizeAggregations = getFinalizeAggregations(queryContext);
|
||||
const groupByEnableMultiValueUnnesting = getGroupByEnableMultiValueUnnesting(queryContext);
|
||||
const sqlJoinAlgorithm = queryContext.sqlJoinAlgorithm ?? 'broadcast';
|
||||
const selectDestination = queryContext.selectDestination ?? 'taskReport';
|
||||
const durableShuffleStorage = getDurableShuffleStorage(queryContext);
|
||||
const indexSpec: IndexSpec | undefined = deepGet(queryContext, 'indexSpec');
|
||||
const useApproximateCountDistinct = getUseApproximateCountDistinct(queryContext);
|
||||
|
@ -189,6 +190,12 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
|||
return items;
|
||||
}
|
||||
|
||||
const overloadWarning =
|
||||
query.unlimited &&
|
||||
(queryEngine === 'sql-native' ||
|
||||
(queryEngine === 'sql-msq-task' && selectDestination === 'taskReport'));
|
||||
const intent = overloadWarning ? Intent.WARNING : undefined;
|
||||
|
||||
const effectiveEngine = query.getEffectiveEngine();
|
||||
return (
|
||||
<div className="run-panel">
|
||||
|
@ -322,14 +329,36 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
|||
))}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={IconNames.TH_DERIVED}
|
||||
text="Edit index spec"
|
||||
label={summarizeIndexSpec(indexSpec)}
|
||||
shouldDismissPopover={false}
|
||||
onClick={() => {
|
||||
setIndexSpecDialogSpec(indexSpec || {});
|
||||
}}
|
||||
/>
|
||||
icon={IconNames.MANUALLY_ENTERED_DATA}
|
||||
text="SELECT destination"
|
||||
label={selectDestination}
|
||||
intent={intent}
|
||||
>
|
||||
{['taskReport', 'durableStorage'].map(o => (
|
||||
<MenuItem
|
||||
key={o}
|
||||
icon={tickIcon(selectDestination === o)}
|
||||
text={o}
|
||||
shouldDismissPopover={false}
|
||||
onClick={() =>
|
||||
changeQueryContext(deepSet(queryContext, 'selectDestination', o))
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<MenuDivider />
|
||||
<MenuCheckbox
|
||||
checked={selectDestination === 'taskReport' ? !query.unlimited : undefined}
|
||||
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>
|
||||
<MenuCheckbox
|
||||
checked={durableShuffleStorage}
|
||||
text="Durable shuffle storage"
|
||||
|
@ -339,6 +368,15 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
|||
)
|
||||
}
|
||||
/>
|
||||
<MenuItem
|
||||
icon={IconNames.TH_DERIVED}
|
||||
text="Edit index spec"
|
||||
label={summarizeIndexSpec(indexSpec)}
|
||||
shouldDismissPopover={false}
|
||||
onClick={() => {
|
||||
setIndexSpecDialogSpec(indexSpec || {});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
@ -372,24 +410,26 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
|||
}
|
||||
/>
|
||||
)}
|
||||
<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());
|
||||
}}
|
||||
/>
|
||||
{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>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
text={`Engine: ${queryEngine || `auto (${effectiveEngine})`}`}
|
||||
rightIcon={IconNames.CARET_DOWN}
|
||||
intent={query.unlimited ? Intent.WARNING : undefined}
|
||||
intent={intent}
|
||||
/>
|
||||
</Popover2>
|
||||
{effectiveEngine === 'sql-msq-task' && (
|
||||
|
|
Loading…
Reference in New Issue