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 9658e1ad7fb Web console: fix query timer issues (#16235)
9658e1ad7fb is described below
commit 9658e1ad7fb413a53834cebf6b51e1af04ec74d5
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Thu Apr 4 13:13:31 2024 -0700
Web console: fix query timer issues (#16235)
* fix timer issues
* wording
---
.../src/singletons/ace-editor-state-cache.ts | 10 ++--
.../src/singletons/execution-state-cache.ts | 8 +--
.../src/singletons/workbench-running-promises.ts | 9 ++--
web-console/src/utils/druid-query.ts | 1 +
.../execution-summary-panel.tsx | 13 ++++-
.../execution-timer-panel.spec.tsx.snap | 60 +++++++++++++++++++++-
.../execution-timer-panel.spec.tsx | 10 +++-
.../execution-timer-panel.tsx | 5 +-
.../views/workbench-view/query-tab/query-tab.tsx | 27 ++++++----
9 files changed, 115 insertions(+), 28 deletions(-)
diff --git a/web-console/src/singletons/ace-editor-state-cache.ts
b/web-console/src/singletons/ace-editor-state-cache.ts
index 9e1b73aaba8..62a440ad52d 100644
--- a/web-console/src/singletons/ace-editor-state-cache.ts
+++ b/web-console/src/singletons/ace-editor-state-cache.ts
@@ -23,24 +23,24 @@ interface EditorState {
}
export class AceEditorStateCache {
- static states: Record<string, EditorState> = {};
+ static states = new Map<string, EditorState>();
static saveState(id: string, editor: Ace.Editor): void {
const session = editor.getSession();
const undoManager: any = session.getUndoManager();
- AceEditorStateCache.states[id] = {
+ AceEditorStateCache.states.set(id, {
undoManager,
- };
+ });
}
static applyState(id: string, editor: Ace.Editor): void {
- const state = AceEditorStateCache.states[id];
+ const state = AceEditorStateCache.states.get(id);
if (!state) return;
const session = editor.getSession();
session.setUndoManager(state.undoManager);
}
static deleteState(id: string): void {
- delete AceEditorStateCache.states[id];
+ AceEditorStateCache.states.delete(id);
}
}
diff --git a/web-console/src/singletons/execution-state-cache.ts
b/web-console/src/singletons/execution-state-cache.ts
index ef1b2d60aeb..83773ede86d 100644
--- a/web-console/src/singletons/execution-state-cache.ts
+++ b/web-console/src/singletons/execution-state-cache.ts
@@ -20,17 +20,17 @@ import type { Execution } from '../druid-models';
import type { DruidError, QueryState } from '../utils';
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 {
- ExecutionStateCache.cache[id] = report;
+ ExecutionStateCache.cache.set(id, report);
}
static getState(id: string): QueryState<Execution, DruidError, Execution> |
undefined {
- return ExecutionStateCache.cache[id];
+ return ExecutionStateCache.cache.get(id);
}
static deleteState(id: string): void {
- delete ExecutionStateCache.cache[id];
+ ExecutionStateCache.cache.delete(id);
}
}
diff --git a/web-console/src/singletons/workbench-running-promises.ts
b/web-console/src/singletons/workbench-running-promises.ts
index 5a14ec136b7..996070537d3 100644
--- a/web-console/src/singletons/workbench-running-promises.ts
+++ b/web-console/src/singletons/workbench-running-promises.ts
@@ -21,24 +21,25 @@ import type { QueryResult } from '@druid-toolkit/query';
export interface WorkbenchRunningPromise {
promise: Promise<QueryResult>;
prefixLines: number;
+ startTime: Date;
}
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 {
return Boolean(x.promise);
}
static storePromise(id: string, promise: WorkbenchRunningPromise): void {
- WorkbenchRunningPromises.promises[id] = promise;
+ WorkbenchRunningPromises.promises.set(id, promise);
}
static getPromise(id: string): WorkbenchRunningPromise | undefined {
- return WorkbenchRunningPromises.promises[id];
+ return WorkbenchRunningPromises.promises.get(id);
}
static deletePromise(id: string): void {
- delete WorkbenchRunningPromises.promises[id];
+ WorkbenchRunningPromises.promises.delete(id);
}
}
diff --git a/web-console/src/utils/druid-query.ts
b/web-console/src/utils/druid-query.ts
index 6040950d110..c94bfca3d1c 100644
--- a/web-console/src/utils/druid-query.ts
+++ b/web-console/src/utils/druid-query.ts
@@ -267,6 +267,7 @@ export class DruidError extends Error {
public startRowColumn?: RowColumn;
public endRowColumn?: RowColumn;
public suggestion?: QuerySuggestion;
+ public queryDuration?: number;
// Deprecated
public error?: string;
diff --git
a/web-console/src/views/workbench-view/execution-summary-panel/execution-summary-panel.tsx
b/web-console/src/views/workbench-view/execution-summary-panel/execution-summary-panel.tsx
index 720711500b6..75999b5ee15 100644
---
a/web-console/src/views/workbench-view/execution-summary-panel/execution-summary-panel.tsx
+++
b/web-console/src/views/workbench-view/execution-summary-panel/execution-summary-panel.tsx
@@ -36,6 +36,7 @@ import './execution-summary-panel.scss';
export interface ExecutionSummaryPanelProps {
execution: Execution | undefined;
+ queryErrorDuration: number | undefined;
onExecutionDetail(): void;
onReset?: () => void;
}
@@ -43,12 +44,22 @@ export interface ExecutionSummaryPanelProps {
export const ExecutionSummaryPanel = React.memo(function ExecutionSummaryPanel(
props: ExecutionSummaryPanelProps,
) {
- const { execution, onExecutionDetail, onReset } = props;
+ const { execution, queryErrorDuration, onExecutionDetail, onReset } = props;
const [showDestinationPages, setShowDestinationPages] = useState(false);
const queryResult = execution?.result;
const buttons: JSX.Element[] = [];
+ if (typeof queryErrorDuration === 'number') {
+ buttons.push(
+ <Button
+ key="timing"
+ minimal
+ text={`Error after ${formatDurationHybrid(queryErrorDuration)}`}
+ />,
+ );
+ }
+
if (queryResult) {
const wrapQueryLimit = queryResult.getSqlOuterLimit();
let resultCount: string;
diff --git
a/web-console/src/views/workbench-view/execution-timer-panel/__snapshots__/execution-timer-panel.spec.tsx.snap
b/web-console/src/views/workbench-view/execution-timer-panel/__snapshots__/execution-timer-panel.spec.tsx.snap
index bb0be1741d9..e4af6c079fc 100644
---
a/web-console/src/views/workbench-view/execution-timer-panel/__snapshots__/execution-timer-panel.spec.tsx.snap
+++
b/web-console/src/views/workbench-view/execution-timer-panel/__snapshots__/execution-timer-panel.spec.tsx.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`AnchoredQueryTimer matches snapshot 1`] = `
+exports[`AnchoredQueryTimer matches snapshot with execution 1`] = `
<div
class="bp4-button-group execution-timer-panel"
>
@@ -57,3 +57,61 @@ exports[`AnchoredQueryTimer matches snapshot 1`] = `
</button>
</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>
+`;
diff --git
a/web-console/src/views/workbench-view/execution-timer-panel/execution-timer-panel.spec.tsx
b/web-console/src/views/workbench-view/execution-timer-panel/execution-timer-panel.spec.tsx
index 7e1013e2cde..deb3edb317e 100644
---
a/web-console/src/views/workbench-view/execution-timer-panel/execution-timer-panel.spec.tsx
+++
b/web-console/src/views/workbench-view/execution-timer-panel/execution-timer-panel.spec.tsx
@@ -37,13 +37,21 @@ describe('AnchoredQueryTimer', () => {
jest.restoreAllMocks();
});
- it('matches snapshot', () => {
+ it('matches snapshot with execution', () => {
const { container } = render(
<ExecutionTimerPanel
execution={new Execution({ engine: 'sql-msq-task', id: 'xxx',
startTime: new Date(start) })}
+ startTime={undefined}
onCancel={() => {}}
/>,
);
expect(container.firstChild).toMatchSnapshot();
});
+
+ it('matches snapshot with startTime', () => {
+ const { container } = render(
+ <ExecutionTimerPanel execution={undefined} startTime={new Date(start)}
onCancel={() => {}} />,
+ );
+ expect(container.firstChild).toMatchSnapshot();
+ });
});
diff --git
a/web-console/src/views/workbench-view/execution-timer-panel/execution-timer-panel.tsx
b/web-console/src/views/workbench-view/execution-timer-panel/execution-timer-panel.tsx
index 3202240ee2a..ea09913d584 100644
---
a/web-console/src/views/workbench-view/execution-timer-panel/execution-timer-panel.tsx
+++
b/web-console/src/views/workbench-view/execution-timer-panel/execution-timer-panel.tsx
@@ -29,15 +29,16 @@ import './execution-timer-panel.scss';
export interface ExecutionTimerPanelProps {
execution: Execution | undefined;
+ startTime: Date | undefined;
onCancel(): void;
}
export const ExecutionTimerPanel = React.memo(function ExecutionTimerPanel(
props: ExecutionTimerPanelProps,
) {
- const { execution, onCancel } = props;
+ const { execution, startTime, onCancel } = props;
const [showCancelConfirm, setShowCancelConfirm] = useState(false);
- const [mountTime] = useState(Date.now());
+ const [mountTime] = useState(startTime?.valueOf() ?? Date.now());
const [currentTime, setCurrentTime] = useState(Date.now());
useInterval(() => {
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 bd3a7dc769c..69234b58454 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
@@ -170,16 +170,16 @@ export const QueryTab = React.memo(function
QueryTab(props: QueryTabProps) {
const queryInputRef = useRef<FlexibleQueryInput | null>(null);
+ const cachedExecutionState = ExecutionStateCache.getState(id);
+ const currentRunningPromise = WorkbenchRunningPromises.getPromise(id);
const [executionState, queryManager] = useQueryManager<
WorkbenchQuery | WorkbenchRunningPromise | LastExecution,
Execution,
Execution,
DruidError
>({
- initQuery: ExecutionStateCache.getState(id)
- ? undefined
- : WorkbenchRunningPromises.getPromise(id) || query.getLastExecution(),
- initState: ExecutionStateCache.getState(id),
+ initQuery: cachedExecutionState ? undefined : currentRunningPromise ||
query.getLastExecution(),
+ initState: cachedExecutionState,
processQuery: async (q, cancelToken) => {
if (q instanceof WorkbenchQuery) {
ExecutionStateCache.deleteState(id);
@@ -214,6 +214,7 @@ export const QueryTab = React.memo(function QueryTab(props:
QueryTabProps) {
onQueryChange(props.query.changeLastExecution(undefined));
+ const startTime = new Date();
let result: QueryResult;
try {
const resultPromise = queryRunner.runQuery({
@@ -223,13 +224,19 @@ export const QueryTab = React.memo(function
QueryTab(props: QueryTabProps) {
nativeQueryCancelFnRef.current = cancelFn;
}),
});
- WorkbenchRunningPromises.storePromise(id, { promise:
resultPromise, prefixLines });
+ WorkbenchRunningPromises.storePromise(id, {
+ promise: resultPromise,
+ prefixLines,
+ startTime,
+ });
result = await resultPromise;
nativeQueryCancelFnRef.current = undefined;
} catch (e) {
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);
@@ -240,11 +247,9 @@ export const QueryTab = React.memo(function
QueryTab(props: QueryTabProps) {
try {
result = await q.promise;
} catch (e) {
- WorkbenchRunningPromises.deletePromise(id);
throw new DruidError(e, q.prefixLines);
}
- WorkbenchRunningPromises.deletePromise(id);
return Execution.fromResult('sql-native', result);
} else {
switch (q.engine) {
@@ -265,9 +270,9 @@ export const QueryTab = React.memo(function QueryTab(props:
QueryTabProps) {
});
useEffect(() => {
- if (!executionState.data) return;
+ if (!executionState.data && !executionState.error) return;
+ WorkbenchRunningPromises.deletePromise(id);
ExecutionStateCache.storeState(id, executionState);
- // eslint-disable-next-line react-hooks/exhaustive-deps
}, [executionState.data, executionState.error]);
const incrementWorkVersion = useStore(
@@ -397,12 +402,14 @@ export const QueryTab = React.memo(function
QueryTab(props: QueryTabProps) {
{executionState.isLoading() && (
<ExecutionTimerPanel
execution={executionState.intermediate}
+ startTime={currentRunningPromise?.startTime}
onCancel={() => queryManager.cancelCurrent()}
/>
)}
{(execution || executionState.error) && (
<ExecutionSummaryPanel
execution={execution}
+ queryErrorDuration={executionState.error?.queryDuration}
onExecutionDetail={() => onDetails(statsTaskId!)}
onReset={() => {
queryManager.reset();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]