Web console: add durable storage selector (#14669)

This commit is contained in:
Vadim Ogievetsky 2023-07-30 22:33:24 -07:00 committed by GitHub
parent 8232c03667
commit 9e1650e327
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 83 additions and 38 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) }}>&#x25cf;&nbsp;</span>
{status}
{errorMsg && errorMsg !== CANCELED_ERROR_MSG && (
<a
onClick={() => this.setState({ alertErrorMsg: errorMsg })}
title={errorMsg}
>
&nbsp;?
</a>
{errorMsg && !TASK_CANCELED_ERROR_MESSAGES.includes(errorMsg) && (
<a onClick={() => this.setState({ alertErrorMsg: errorMsg })}>&nbsp;?</a>
)}
</span>
</TableFilterableCell>

View File

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

View File

@ -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' && (