mirror of https://github.com/apache/druid.git
Web console: fix query timer issues (#16235)
* fix timer issues * wording
This commit is contained in:
parent
7759f25095
commit
9658e1ad7f
|
@ -23,24 +23,24 @@ interface EditorState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AceEditorStateCache {
|
export class AceEditorStateCache {
|
||||||
static states: Record<string, EditorState> = {};
|
static states = new Map<string, EditorState>();
|
||||||
|
|
||||||
static saveState(id: string, editor: Ace.Editor): void {
|
static saveState(id: string, editor: Ace.Editor): void {
|
||||||
const session = editor.getSession();
|
const session = editor.getSession();
|
||||||
const undoManager: any = session.getUndoManager();
|
const undoManager: any = session.getUndoManager();
|
||||||
AceEditorStateCache.states[id] = {
|
AceEditorStateCache.states.set(id, {
|
||||||
undoManager,
|
undoManager,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static applyState(id: string, editor: Ace.Editor): void {
|
static applyState(id: string, editor: Ace.Editor): void {
|
||||||
const state = AceEditorStateCache.states[id];
|
const state = AceEditorStateCache.states.get(id);
|
||||||
if (!state) return;
|
if (!state) return;
|
||||||
const session = editor.getSession();
|
const session = editor.getSession();
|
||||||
session.setUndoManager(state.undoManager);
|
session.setUndoManager(state.undoManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
static deleteState(id: string): void {
|
static deleteState(id: string): void {
|
||||||
delete AceEditorStateCache.states[id];
|
AceEditorStateCache.states.delete(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,17 +20,17 @@ import type { Execution } from '../druid-models';
|
||||||
import type { DruidError, QueryState } from '../utils';
|
import type { DruidError, QueryState } from '../utils';
|
||||||
|
|
||||||
export class ExecutionStateCache {
|
export class ExecutionStateCache {
|
||||||
private static readonly cache: Record<string, QueryState<Execution, DruidError, Execution>> = {};
|
private static readonly cache = new Map<string, QueryState<Execution, DruidError, Execution>>();
|
||||||
|
|
||||||
static storeState(id: string, report: QueryState<Execution, DruidError, Execution>): void {
|
static storeState(id: string, report: QueryState<Execution, DruidError, Execution>): void {
|
||||||
ExecutionStateCache.cache[id] = report;
|
ExecutionStateCache.cache.set(id, report);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getState(id: string): QueryState<Execution, DruidError, Execution> | undefined {
|
static getState(id: string): QueryState<Execution, DruidError, Execution> | undefined {
|
||||||
return ExecutionStateCache.cache[id];
|
return ExecutionStateCache.cache.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
static deleteState(id: string): void {
|
static deleteState(id: string): void {
|
||||||
delete ExecutionStateCache.cache[id];
|
ExecutionStateCache.cache.delete(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,24 +21,25 @@ import type { QueryResult } from '@druid-toolkit/query';
|
||||||
export interface WorkbenchRunningPromise {
|
export interface WorkbenchRunningPromise {
|
||||||
promise: Promise<QueryResult>;
|
promise: Promise<QueryResult>;
|
||||||
prefixLines: number;
|
prefixLines: number;
|
||||||
|
startTime: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorkbenchRunningPromises {
|
export class WorkbenchRunningPromises {
|
||||||
private static readonly promises: Record<string, WorkbenchRunningPromise> = {};
|
private static readonly promises = new Map<string, WorkbenchRunningPromise>();
|
||||||
|
|
||||||
static isWorkbenchRunningPromise(x: any): x is WorkbenchRunningPromise {
|
static isWorkbenchRunningPromise(x: any): x is WorkbenchRunningPromise {
|
||||||
return Boolean(x.promise);
|
return Boolean(x.promise);
|
||||||
}
|
}
|
||||||
|
|
||||||
static storePromise(id: string, promise: WorkbenchRunningPromise): void {
|
static storePromise(id: string, promise: WorkbenchRunningPromise): void {
|
||||||
WorkbenchRunningPromises.promises[id] = promise;
|
WorkbenchRunningPromises.promises.set(id, promise);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getPromise(id: string): WorkbenchRunningPromise | undefined {
|
static getPromise(id: string): WorkbenchRunningPromise | undefined {
|
||||||
return WorkbenchRunningPromises.promises[id];
|
return WorkbenchRunningPromises.promises.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
static deletePromise(id: string): void {
|
static deletePromise(id: string): void {
|
||||||
delete WorkbenchRunningPromises.promises[id];
|
WorkbenchRunningPromises.promises.delete(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -267,6 +267,7 @@ export class DruidError extends Error {
|
||||||
public startRowColumn?: RowColumn;
|
public startRowColumn?: RowColumn;
|
||||||
public endRowColumn?: RowColumn;
|
public endRowColumn?: RowColumn;
|
||||||
public suggestion?: QuerySuggestion;
|
public suggestion?: QuerySuggestion;
|
||||||
|
public queryDuration?: number;
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
public error?: string;
|
public error?: string;
|
||||||
|
|
|
@ -36,6 +36,7 @@ import './execution-summary-panel.scss';
|
||||||
|
|
||||||
export interface ExecutionSummaryPanelProps {
|
export interface ExecutionSummaryPanelProps {
|
||||||
execution: Execution | undefined;
|
execution: Execution | undefined;
|
||||||
|
queryErrorDuration: number | undefined;
|
||||||
onExecutionDetail(): void;
|
onExecutionDetail(): void;
|
||||||
onReset?: () => void;
|
onReset?: () => void;
|
||||||
}
|
}
|
||||||
|
@ -43,12 +44,22 @@ export interface ExecutionSummaryPanelProps {
|
||||||
export const ExecutionSummaryPanel = React.memo(function ExecutionSummaryPanel(
|
export const ExecutionSummaryPanel = React.memo(function ExecutionSummaryPanel(
|
||||||
props: ExecutionSummaryPanelProps,
|
props: ExecutionSummaryPanelProps,
|
||||||
) {
|
) {
|
||||||
const { execution, onExecutionDetail, onReset } = props;
|
const { execution, queryErrorDuration, onExecutionDetail, onReset } = props;
|
||||||
const [showDestinationPages, setShowDestinationPages] = useState(false);
|
const [showDestinationPages, setShowDestinationPages] = useState(false);
|
||||||
const queryResult = execution?.result;
|
const queryResult = execution?.result;
|
||||||
|
|
||||||
const buttons: JSX.Element[] = [];
|
const buttons: JSX.Element[] = [];
|
||||||
|
|
||||||
|
if (typeof queryErrorDuration === 'number') {
|
||||||
|
buttons.push(
|
||||||
|
<Button
|
||||||
|
key="timing"
|
||||||
|
minimal
|
||||||
|
text={`Error after ${formatDurationHybrid(queryErrorDuration)}`}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (queryResult) {
|
if (queryResult) {
|
||||||
const wrapQueryLimit = queryResult.getSqlOuterLimit();
|
const wrapQueryLimit = queryResult.getSqlOuterLimit();
|
||||||
let resultCount: string;
|
let resultCount: string;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`AnchoredQueryTimer matches snapshot 1`] = `
|
exports[`AnchoredQueryTimer matches snapshot with execution 1`] = `
|
||||||
<div
|
<div
|
||||||
class="bp4-button-group execution-timer-panel"
|
class="bp4-button-group execution-timer-panel"
|
||||||
>
|
>
|
||||||
|
@ -57,3 +57,61 @@ exports[`AnchoredQueryTimer matches snapshot 1`] = `
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`AnchoredQueryTimer matches snapshot with startTime 1`] = `
|
||||||
|
<div
|
||||||
|
class="bp4-button-group execution-timer-panel"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="bp4-button bp4-minimal timer"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="bp4-icon bp4-icon-stopwatch"
|
||||||
|
icon="stopwatch"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
data-icon="stopwatch"
|
||||||
|
height="16"
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
width="16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M9 2v1.083A6.002 6.002 0 018 15 6 6 0 017 3.083V2H6a1 1 0 110-2h4a1 1 0 010 2H9zM8 5a4 4 0 104 4H8V5z"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="bp4-button-text"
|
||||||
|
>
|
||||||
|
1.00s
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="bp4-button bp4-minimal"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="bp4-icon bp4-icon-cross"
|
||||||
|
icon="cross"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
data-icon="cross"
|
||||||
|
height="16"
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
width="16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M9.41 8l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L8 6.59l-3.29-3.3a1.003 1.003 0 00-1.42 1.42L6.59 8 3.3 11.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71L8 9.41l3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L9.41 8z"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
|
@ -37,13 +37,21 @@ describe('AnchoredQueryTimer', () => {
|
||||||
jest.restoreAllMocks();
|
jest.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot with execution', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<ExecutionTimerPanel
|
<ExecutionTimerPanel
|
||||||
execution={new Execution({ engine: 'sql-msq-task', id: 'xxx', startTime: new Date(start) })}
|
execution={new Execution({ engine: 'sql-msq-task', id: 'xxx', startTime: new Date(start) })}
|
||||||
|
startTime={undefined}
|
||||||
onCancel={() => {}}
|
onCancel={() => {}}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('matches snapshot with startTime', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<ExecutionTimerPanel execution={undefined} startTime={new Date(start)} onCancel={() => {}} />,
|
||||||
|
);
|
||||||
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,15 +29,16 @@ import './execution-timer-panel.scss';
|
||||||
|
|
||||||
export interface ExecutionTimerPanelProps {
|
export interface ExecutionTimerPanelProps {
|
||||||
execution: Execution | undefined;
|
execution: Execution | undefined;
|
||||||
|
startTime: Date | undefined;
|
||||||
onCancel(): void;
|
onCancel(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExecutionTimerPanel = React.memo(function ExecutionTimerPanel(
|
export const ExecutionTimerPanel = React.memo(function ExecutionTimerPanel(
|
||||||
props: ExecutionTimerPanelProps,
|
props: ExecutionTimerPanelProps,
|
||||||
) {
|
) {
|
||||||
const { execution, onCancel } = props;
|
const { execution, startTime, onCancel } = props;
|
||||||
const [showCancelConfirm, setShowCancelConfirm] = useState(false);
|
const [showCancelConfirm, setShowCancelConfirm] = useState(false);
|
||||||
const [mountTime] = useState(Date.now());
|
const [mountTime] = useState(startTime?.valueOf() ?? Date.now());
|
||||||
const [currentTime, setCurrentTime] = useState(Date.now());
|
const [currentTime, setCurrentTime] = useState(Date.now());
|
||||||
|
|
||||||
useInterval(() => {
|
useInterval(() => {
|
||||||
|
|
|
@ -170,16 +170,16 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
|
||||||
|
|
||||||
const queryInputRef = useRef<FlexibleQueryInput | null>(null);
|
const queryInputRef = useRef<FlexibleQueryInput | null>(null);
|
||||||
|
|
||||||
|
const cachedExecutionState = ExecutionStateCache.getState(id);
|
||||||
|
const currentRunningPromise = WorkbenchRunningPromises.getPromise(id);
|
||||||
const [executionState, queryManager] = useQueryManager<
|
const [executionState, queryManager] = useQueryManager<
|
||||||
WorkbenchQuery | WorkbenchRunningPromise | LastExecution,
|
WorkbenchQuery | WorkbenchRunningPromise | LastExecution,
|
||||||
Execution,
|
Execution,
|
||||||
Execution,
|
Execution,
|
||||||
DruidError
|
DruidError
|
||||||
>({
|
>({
|
||||||
initQuery: ExecutionStateCache.getState(id)
|
initQuery: cachedExecutionState ? undefined : currentRunningPromise || query.getLastExecution(),
|
||||||
? undefined
|
initState: cachedExecutionState,
|
||||||
: WorkbenchRunningPromises.getPromise(id) || query.getLastExecution(),
|
|
||||||
initState: ExecutionStateCache.getState(id),
|
|
||||||
processQuery: async (q, cancelToken) => {
|
processQuery: async (q, cancelToken) => {
|
||||||
if (q instanceof WorkbenchQuery) {
|
if (q instanceof WorkbenchQuery) {
|
||||||
ExecutionStateCache.deleteState(id);
|
ExecutionStateCache.deleteState(id);
|
||||||
|
@ -214,6 +214,7 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
|
||||||
|
|
||||||
onQueryChange(props.query.changeLastExecution(undefined));
|
onQueryChange(props.query.changeLastExecution(undefined));
|
||||||
|
|
||||||
|
const startTime = new Date();
|
||||||
let result: QueryResult;
|
let result: QueryResult;
|
||||||
try {
|
try {
|
||||||
const resultPromise = queryRunner.runQuery({
|
const resultPromise = queryRunner.runQuery({
|
||||||
|
@ -223,13 +224,19 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
|
||||||
nativeQueryCancelFnRef.current = cancelFn;
|
nativeQueryCancelFnRef.current = cancelFn;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
WorkbenchRunningPromises.storePromise(id, { promise: resultPromise, prefixLines });
|
WorkbenchRunningPromises.storePromise(id, {
|
||||||
|
promise: resultPromise,
|
||||||
|
prefixLines,
|
||||||
|
startTime,
|
||||||
|
});
|
||||||
|
|
||||||
result = await resultPromise;
|
result = await resultPromise;
|
||||||
nativeQueryCancelFnRef.current = undefined;
|
nativeQueryCancelFnRef.current = undefined;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
nativeQueryCancelFnRef.current = undefined;
|
nativeQueryCancelFnRef.current = undefined;
|
||||||
throw new DruidError(e, prefixLines);
|
const druidError = new DruidError(e, prefixLines);
|
||||||
|
druidError.queryDuration = Date.now() - startTime.valueOf();
|
||||||
|
throw druidError;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Execution.fromResult(engine, result);
|
return Execution.fromResult(engine, result);
|
||||||
|
@ -240,11 +247,9 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
|
||||||
try {
|
try {
|
||||||
result = await q.promise;
|
result = await q.promise;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
WorkbenchRunningPromises.deletePromise(id);
|
|
||||||
throw new DruidError(e, q.prefixLines);
|
throw new DruidError(e, q.prefixLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkbenchRunningPromises.deletePromise(id);
|
|
||||||
return Execution.fromResult('sql-native', result);
|
return Execution.fromResult('sql-native', result);
|
||||||
} else {
|
} else {
|
||||||
switch (q.engine) {
|
switch (q.engine) {
|
||||||
|
@ -265,9 +270,9 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!executionState.data) return;
|
if (!executionState.data && !executionState.error) return;
|
||||||
|
WorkbenchRunningPromises.deletePromise(id);
|
||||||
ExecutionStateCache.storeState(id, executionState);
|
ExecutionStateCache.storeState(id, executionState);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [executionState.data, executionState.error]);
|
}, [executionState.data, executionState.error]);
|
||||||
|
|
||||||
const incrementWorkVersion = useStore(
|
const incrementWorkVersion = useStore(
|
||||||
|
@ -397,12 +402,14 @@ export const QueryTab = React.memo(function QueryTab(props: QueryTabProps) {
|
||||||
{executionState.isLoading() && (
|
{executionState.isLoading() && (
|
||||||
<ExecutionTimerPanel
|
<ExecutionTimerPanel
|
||||||
execution={executionState.intermediate}
|
execution={executionState.intermediate}
|
||||||
|
startTime={currentRunningPromise?.startTime}
|
||||||
onCancel={() => queryManager.cancelCurrent()}
|
onCancel={() => queryManager.cancelCurrent()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(execution || executionState.error) && (
|
{(execution || executionState.error) && (
|
||||||
<ExecutionSummaryPanel
|
<ExecutionSummaryPanel
|
||||||
execution={execution}
|
execution={execution}
|
||||||
|
queryErrorDuration={executionState.error?.queryDuration}
|
||||||
onExecutionDetail={() => onDetails(statsTaskId!)}
|
onExecutionDetail={() => onDetails(statsTaskId!)}
|
||||||
onReset={() => {
|
onReset={() => {
|
||||||
queryManager.reset();
|
queryManager.reset();
|
||||||
|
|
Loading…
Reference in New Issue