diff --git a/package-lock.json b/package-lock.json
index c485bd6f4..862dea0f4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -50,6 +50,7 @@
"tslib": "^2.6.3",
"url": "^0.11.3",
"use-query-params": "^2.2.1",
+ "uuid": "^10.0.0",
"web-vitals": "^1.1.2",
"ydb-ui-components": "^4.2.0",
"zod": "^3.23.8"
@@ -73,6 +74,7 @@
"@types/react-dom": "^18.3.0",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
+ "@types/uuid": "^10.0.0",
"copyfiles": "^2.4.1",
"http-proxy-middleware": "^2.0.6",
"husky": "^9.0.11",
@@ -6295,6 +6297,13 @@
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
},
+ "node_modules/@types/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/ws": {
"version": "8.5.4",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz",
@@ -25893,6 +25902,19 @@
"node": ">= 0.4.0"
}
},
+ "node_modules/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/v8-to-istanbul": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz",
diff --git a/package.json b/package.json
index e9e873b28..03c025a19 100644
--- a/package.json
+++ b/package.json
@@ -21,9 +21,11 @@
"@gravity-ui/navigation": "^2.16.0",
"@gravity-ui/paranoid": "^2.0.1",
"@gravity-ui/react-data-table": "^2.1.1",
+ "@gravity-ui/table": "^0.5.0",
"@gravity-ui/uikit": "^6.20.1",
"@gravity-ui/websql-autocomplete": "^9.1.0",
"@reduxjs/toolkit": "^2.2.3",
+ "@tanstack/react-table": "^8.19.3",
"axios": "^1.7.2",
"axios-retry": "^4.4.0",
"colord": "^2.9.3",
@@ -50,11 +52,10 @@
"tslib": "^2.6.3",
"url": "^0.11.3",
"use-query-params": "^2.2.1",
+ "uuid": "^10.0.0",
"web-vitals": "^1.1.2",
"ydb-ui-components": "^4.2.0",
- "zod": "^3.23.8",
- "@gravity-ui/table": "^0.5.0",
- "@tanstack/react-table": "^8.19.3"
+ "zod": "^3.23.8"
},
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
@@ -144,6 +145,7 @@
"@types/react-dom": "^18.3.0",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
+ "@types/uuid": "^10.0.0",
"copyfiles": "^2.4.1",
"http-proxy-middleware": "^2.0.6",
"husky": "^9.0.11",
diff --git a/src/components/ElapsedTime/ElapsedTime.scss b/src/components/ElapsedTime/ElapsedTime.scss
new file mode 100644
index 000000000..01da8c07b
--- /dev/null
+++ b/src/components/ElapsedTime/ElapsedTime.scss
@@ -0,0 +1,3 @@
+.ydb-query-elapsed-time {
+ visibility: visible;
+}
diff --git a/src/components/ElapsedTime/ElapsedTime.tsx b/src/components/ElapsedTime/ElapsedTime.tsx
new file mode 100644
index 000000000..42c7cf48c
--- /dev/null
+++ b/src/components/ElapsedTime/ElapsedTime.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+
+import {duration} from '@gravity-ui/date-utils';
+import {Label} from '@gravity-ui/uikit';
+
+import {HOUR_IN_SECONDS, SECOND_IN_MS, cn} from '../../lib';
+
+const b = cn('ydb-query-elapsed-time');
+
+interface ElapsedTimeProps {
+ className?: string;
+}
+
+export default function ElapsedTime({className}: ElapsedTimeProps) {
+ const [, reRender] = React.useState({});
+ const [startTime] = React.useState(Date.now());
+ const elapsedTime = Date.now() - startTime;
+
+ React.useEffect(() => {
+ const timerId = setInterval(() => {
+ reRender({});
+ }, SECOND_IN_MS);
+ return () => {
+ clearInterval(timerId);
+ };
+ }, []);
+
+ const elapsedTimeFormatted =
+ elapsedTime > HOUR_IN_SECONDS * SECOND_IN_MS
+ ? duration(elapsedTime).format('hh:mm:ss')
+ : duration(elapsedTime).format('mm:ss');
+
+ return ;
+}
diff --git a/src/components/QueryExecutionStatus/QueryExecutionStatus.tsx b/src/components/QueryExecutionStatus/QueryExecutionStatus.tsx
index 7471c5f34..1a34d60d4 100644
--- a/src/components/QueryExecutionStatus/QueryExecutionStatus.tsx
+++ b/src/components/QueryExecutionStatus/QueryExecutionStatus.tsx
@@ -1,10 +1,17 @@
import React from 'react';
-import {CircleCheck, CircleInfo, CircleQuestionFill, CircleXmark} from '@gravity-ui/icons';
-import {Icon, Tooltip} from '@gravity-ui/uikit';
+import {
+ CircleCheck,
+ CircleInfo,
+ CircleQuestionFill,
+ CircleStop,
+ CircleXmark,
+} from '@gravity-ui/icons';
+import {Icon, Spin, Tooltip} from '@gravity-ui/uikit';
import {isAxiosError} from 'axios';
import i18n from '../../containers/Tenant/Query/i18n';
+import {isQueryCancelledError} from '../../containers/Tenant/Query/utils/isQueryCancelledError';
import {cn} from '../../utils/cn';
import {useChangedQuerySettings} from '../../utils/hooks/useChangedQuerySettings';
import QuerySettingsDescription from '../QuerySettingsDescription/QuerySettingsDescription';
@@ -16,6 +23,7 @@ const b = cn('kv-query-execution-status');
interface QueryExecutionStatusProps {
className?: string;
error?: unknown;
+ loading?: boolean;
}
const QuerySettingsIndicator = () => {
@@ -40,13 +48,19 @@ const QuerySettingsIndicator = () => {
);
};
-export const QueryExecutionStatus = ({className, error}: QueryExecutionStatusProps) => {
+export const QueryExecutionStatus = ({className, error, loading}: QueryExecutionStatusProps) => {
let icon: React.ReactNode;
let label: string;
- if (isAxiosError(error) && error.code === 'ECONNABORTED') {
+ if (loading) {
+ icon = ;
+ label = 'Running';
+ } else if (isAxiosError(error) && error.code === 'ECONNABORTED') {
icon = ;
label = 'Connection aborted';
+ } else if (isQueryCancelledError(error)) {
+ icon = ;
+ label = 'Stopped';
} else {
const hasError = Boolean(error);
icon = (
@@ -62,7 +76,7 @@ export const QueryExecutionStatus = ({className, error}: QueryExecutionStatusPro
{icon}
{label}
-
+ {isQueryCancelledError(error) || loading ? null : }
);
};
diff --git a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss b/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss
index 225f25974..52ec4bdb1 100644
--- a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss
+++ b/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss
@@ -48,14 +48,17 @@
&__controls-right {
display: flex;
+ align-items: center;
gap: 12px;
height: 100%;
}
+
&__controls-left {
display: flex;
gap: 4px;
}
+
&__inspector {
overflow: auto;
@@ -71,4 +74,14 @@
width: 100%;
height: 100%;
}
+
+ &__elapsed-label {
+ margin-left: var(--g-spacing-3);
+ }
+
+ &__stop-button {
+ &_error {
+ @include query-buttons-animations();
+ }
+ }
}
diff --git a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx b/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx
index 72ab9bc72..883c4ba3f 100644
--- a/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx
+++ b/src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx
@@ -1,18 +1,21 @@
import React from 'react';
+import {StopFill} from '@gravity-ui/icons';
import type {ControlGroupOption} from '@gravity-ui/uikit';
-import {RadioButton, Tabs} from '@gravity-ui/uikit';
+import {Button, Icon, RadioButton, Tabs} from '@gravity-ui/uikit';
import JSONTree from 'react-json-inspector';
import {ClipboardButton} from '../../../../components/ClipboardButton';
import Divider from '../../../../components/Divider/Divider';
+import ElapsedTime from '../../../../components/ElapsedTime/ElapsedTime';
import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton';
import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
import {YDBGraph} from '../../../../components/Graph/Graph';
+import {LoaderWrapper} from '../../../../components/LoaderWrapper/LoaderWrapper';
import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus';
import {QueryResultTable} from '../../../../components/QueryResultTable/QueryResultTable';
import {disableFullscreen} from '../../../../store/reducers/fullscreen';
-import type {ColumnType, KeyValueRow} from '../../../../types/api/query';
+import type {ColumnType, KeyValueRow, TKqpStatsQuery} from '../../../../types/api/query';
import type {ValueOf} from '../../../../types/common';
import type {IQueryResult} from '../../../../types/store/query';
import {getArray} from '../../../../utils';
@@ -26,6 +29,7 @@ import {ResultIssues} from '../Issues/Issues';
import {QueryDuration} from '../QueryDuration/QueryDuration';
import {QuerySettingsBanner} from '../QuerySettingsBanner/QuerySettingsBanner';
import {getPreparedResult} from '../utils/getPreparedResult';
+import {isQueryCancelledError} from '../utils/isQueryCancelledError';
import i18n from './i18n';
import {getPlan} from './utils';
@@ -46,25 +50,33 @@ type SectionID = ValueOf;
interface ExecuteResultProps {
data: IQueryResult | undefined;
error: unknown;
+ cancelError: unknown;
isResultsCollapsed?: boolean;
onCollapseResults: VoidFunction;
onExpandResults: VoidFunction;
+ onStopButtonClick: VoidFunction;
theme?: string;
+ loading?: boolean;
+ cancelQueryLoading?: boolean;
}
export function ExecuteResult({
data,
error,
+ cancelError,
isResultsCollapsed,
onCollapseResults,
onExpandResults,
+ onStopButtonClick,
theme,
+ loading,
+ cancelQueryLoading,
}: ExecuteResultProps) {
const [selectedResultSet, setSelectedResultSet] = React.useState(0);
const [activeSection, setActiveSection] = React.useState(resultOptionsIds.result);
const dispatch = useTypedDispatch();
- const stats = data?.stats;
+ const stats: TKqpStatsQuery | undefined = data?.stats;
const resultsSetsCount = data?.resultSets?.length;
const isMulti = resultsSetsCount && resultsSetsCount > 0;
const currentResult = isMulti ? data?.resultSets?.[selectedResultSet].result : data?.result;
@@ -93,8 +105,8 @@ export function ExecuteResult({
};
}, [dispatch]);
- const onSelectSection = (value: string) => {
- setActiveSection(value as SectionID);
+ const onSelectSection = (value: SectionID) => {
+ setActiveSection(value);
};
const renderResultTable = (
@@ -207,7 +219,7 @@ export function ExecuteResult({
};
const renderResultSection = () => {
- if (error) {
+ if (error && !isQueryCancelledError(error)) {
return renderIssues();
}
if (activeSection === resultOptionsIds.result) {
@@ -230,18 +242,38 @@ export function ExecuteResult({
-
- {stats && !error && (
+
+
+ {!error && !loading && (
-
-
-
+ {stats?.DurationUs !== undefined && (
+
+ )}
+ {resultOptions && activeSection && (
+
+
+
+
+ )}
)}
+ {loading ? (
+
+
+
+
+ ) : null}
{renderClipboardButton()}
@@ -254,8 +286,10 @@ export function ExecuteResult({
/>
-
- {renderResultSection()}
+ {loading || isQueryCancelledError(error) ? null : }
+
+ {renderResultSection()}
+
);
}
diff --git a/src/containers/Tenant/Query/ExecuteResult/i18n/en.json b/src/containers/Tenant/Query/ExecuteResult/i18n/en.json
index 009c16459..35989f129 100644
--- a/src/containers/Tenant/Query/ExecuteResult/i18n/en.json
+++ b/src/containers/Tenant/Query/ExecuteResult/i18n/en.json
@@ -3,6 +3,7 @@
"action.result": "Result",
"action.stats": "Stats",
"action.schema": "Schema",
+ "action.stop": "Stop",
"action.explain-plan": "Explain Plan",
"action.copy": "Copy {{activeSection}}"
}
diff --git a/src/containers/Tenant/Query/ExplainResult/ExplainResult.scss b/src/containers/Tenant/Query/ExplainResult/ExplainResult.scss
index a7b37db4d..375ced444 100644
--- a/src/containers/Tenant/Query/ExplainResult/ExplainResult.scss
+++ b/src/containers/Tenant/Query/ExplainResult/ExplainResult.scss
@@ -1,3 +1,5 @@
+@import '../../../../styles/mixins.scss';
+
.ydb-query-explain-result {
&__result {
display: flex;
@@ -24,22 +26,27 @@
border-bottom: 1px solid var(--g-color-line-generic);
background-color: var(--g-color-base-background);
}
+
&__controls-right {
display: flex;
+ align-items: center;
gap: 12px;
height: 100%;
}
+
&__controls-left {
display: flex;
gap: 4px;
}
- &__loader {
- display: flex;
- justify-content: center;
- align-items: center;
- width: 100%;
- margin-top: 20px;
+ &__elapsed-label {
+ margin-left: var(--g-spacing-3);
+ }
+
+ &__stop-button {
+ &_error {
+ @include query-buttons-animations();
+ }
}
}
diff --git a/src/containers/Tenant/Query/ExplainResult/ExplainResult.tsx b/src/containers/Tenant/Query/ExplainResult/ExplainResult.tsx
index 143a9e048..fd02e3a50 100644
--- a/src/containers/Tenant/Query/ExplainResult/ExplainResult.tsx
+++ b/src/containers/Tenant/Query/ExplainResult/ExplainResult.tsx
@@ -1,8 +1,11 @@
import React from 'react';
-import {ClipboardButton, RadioButton} from '@gravity-ui/uikit';
+import {StopFill} from '@gravity-ui/icons';
+import {Button, Icon, RadioButton} from '@gravity-ui/uikit';
+import {ClipboardButton} from '../../../../components/ClipboardButton';
import Divider from '../../../../components/Divider/Divider';
+import ElapsedTime from '../../../../components/ElapsedTime/ElapsedTime';
import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton';
import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
import {LoaderWrapper} from '../../../../components/LoaderWrapper/LoaderWrapper';
@@ -15,7 +18,9 @@ import {getStringifiedData} from '../../../../utils/dataFormatters/dataFormatter
import {useTypedDispatch} from '../../../../utils/hooks';
import {parseQueryErrorToString} from '../../../../utils/query';
import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers';
+import {QueryDuration} from '../QueryDuration/QueryDuration';
import {QuerySettingsBanner} from '../QuerySettingsBanner/QuerySettingsBanner';
+import {isQueryCancelledError} from '../utils/isQueryCancelledError';
import {Ast} from './components/Ast/Ast';
import {Graph} from './components/Graph/Graph';
@@ -55,14 +60,17 @@ const explainOptions = [
interface ExplainResultProps {
theme: string;
- explain?: PreparedExplainResponse['plan'];
+ explain?: PreparedExplainResponse['plan'] & {DurationUs?: number};
simplifiedPlan?: PreparedExplainResponse['simplifiedPlan'];
ast?: string;
loading?: boolean;
+ cancelQueryLoading?: boolean;
isResultsCollapsed?: boolean;
error: unknown;
+ cancelError: unknown;
onCollapseResults: VoidFunction;
onExpandResults: VoidFunction;
+ onStopButtonClick: VoidFunction;
}
export function ExplainResult({
@@ -70,9 +78,12 @@ export function ExplainResult({
ast,
theme,
error,
+ cancelError,
loading,
+ cancelQueryLoading,
onCollapseResults,
onExpandResults,
+ onStopButtonClick,
isResultsCollapsed,
simplifiedPlan,
}: ExplainResultProps) {
@@ -99,6 +110,10 @@ export function ExplainResult({
};
const renderContent = () => {
+ if (isQueryCancelledError(error)) {
+ return null;
+ }
+
if (error) {
return {parseQueryErrorToString(error)}
;
}
@@ -148,49 +163,63 @@ export function ExplainResult({
};
const statsToCopy = getStatsToCopy();
-
const copyText = getStringifiedData(statsToCopy);
return (
- {!loading && (
-
-
-
- {!error && (
-
-
- {
- startTransition(() => setActiveOption(tabId));
- }}
- />
-
+
+
+
+ {!error && !loading && (
+
+ {explain?.DurationUs !== undefined && (
+
)}
-
-
- {copyText && (
-
+
+ {
+ startTransition(() => setActiveOption(tabId));
+ }}
/>
- )}
-
-
-
-
- )}
+
+
+ )}
+ {loading ? (
+
+
+
+
+ ) : null}
+
+
+ {copyText && (
+
+ )}
+
+
+
-
+ {loading || isQueryCancelledError(error) ? null : }
{renderContent()}
diff --git a/src/containers/Tenant/Query/ExplainResult/i18n/en.json b/src/containers/Tenant/Query/ExplainResult/i18n/en.json
index 15bd21a2e..1befd1a5d 100644
--- a/src/containers/Tenant/Query/ExplainResult/i18n/en.json
+++ b/src/containers/Tenant/Query/ExplainResult/i18n/en.json
@@ -5,5 +5,6 @@
"action.explain-plan": "Explain Plan",
"action.json": "JSON",
"action.ast": "AST",
- "action.copy": "Copy {{activeOption}}"
+ "action.copy": "Copy {{activeOption}}",
+ "action.stop": "Stop"
}
diff --git a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx
index cce539394..c4d0d92f7 100644
--- a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx
+++ b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx
@@ -4,10 +4,12 @@ import {isEqual} from 'lodash';
import throttle from 'lodash/throttle';
import type Monaco from 'monaco-editor';
import {connect} from 'react-redux';
+import {v4 as uuidv4} from 'uuid';
import {MonacoEditor} from '../../../../components/MonacoEditor/MonacoEditor';
import SplitPane from '../../../../components/SplitPane';
import type {RootState} from '../../../../store';
+import {cancelQueryApi} from '../../../../store/reducers/cancelQuery';
import {
executeQueryApi,
goToNextQuery,
@@ -113,6 +115,7 @@ function QueryEditor(props: QueryEditorProps) {
const [sendExecuteQuery, executeQueryResult] = executeQueryApi.useExecuteQueryMutation();
const [sendExplainQuery, explainQueryResult] = explainQueryApi.useExplainQueryMutation();
+ const [sendCancelQuery, cancelQueryResult] = cancelQueryApi.useCancelQueryMutation();
React.useEffect(() => {
if (savedPath !== tenantName) {
@@ -157,17 +160,20 @@ function QueryEditor(props: QueryEditorProps) {
resetBanner();
setLastQueryExecutionSettings(querySettings);
}
-
+ const queryId = uuidv4();
setResultType(RESULT_TYPES.EXECUTE);
+
sendExecuteQuery({
query,
database: tenantName,
querySettings,
schema,
enableTracingLevel,
+ queryId,
});
setIsResultLoaded(true);
props.setShowPreview(false);
+ cancelQueryResult.reset();
// Don't save partial queries in history
if (!text) {
@@ -194,18 +200,34 @@ function QueryEditor(props: QueryEditorProps) {
setLastQueryExecutionSettings(querySettings);
}
+ const queryId = uuidv4();
setResultType(RESULT_TYPES.EXPLAIN);
+
sendExplainQuery({
query: input,
database: tenantName,
querySettings,
enableTracingLevel,
+ queryId,
});
+
setIsResultLoaded(true);
props.setShowPreview(false);
+ cancelQueryResult.reset();
+
dispatchResultVisibilityState(PaneVisibilityActionTypes.triggerExpand);
});
+ const currentQueryId = executeQueryResult.isLoading
+ ? executeQueryResult.originalArgs?.queryId
+ : explainQueryResult.originalArgs?.queryId;
+
+ const handleStopButtonClick = React.useCallback(() => {
+ if (currentQueryId) {
+ sendCancelQuery({queryId: currentQueryId, database: tenantName});
+ }
+ }, [currentQueryId, sendCancelQuery, tenantName]);
+
const handleSendQuery = useEventHandler(() => {
if (lastUsedQueryAction === QUERY_ACTIONS.explain) {
handleGetExplainQueryClick();
@@ -314,9 +336,8 @@ function QueryEditor(props: QueryEditorProps) {
@@ -357,12 +378,17 @@ function QueryEditor(props: QueryEditorProps) {
void;
- onCollapseResultHandler: () => void;
+ onExpandResultHandler: VoidFunction;
+ onCollapseResultHandler: VoidFunction;
+ onStopButtonClick: VoidFunction;
type?: EPathType;
theme: string;
resultType: ValueOf | undefined;
@@ -415,12 +446,16 @@ interface ResultProps {
function Result({
executeQueryData,
executeQueryError,
+ cancelQueryError,
explainQueryData,
explainQueryError,
explainQueryLoading,
+ executeResultLoading,
+ cancelQueryLoading,
resultVisibilityState,
onExpandResultHandler,
onCollapseResultHandler,
+ onStopButtonClick,
type,
theme,
resultType,
@@ -433,20 +468,20 @@ function Result({
}
if (resultType === RESULT_TYPES.EXECUTE) {
- if (executeQueryData || executeQueryError) {
- return (
-
- );
- }
-
- return null;
+ return (
+
+ );
}
if (resultType === RESULT_TYPES.EXPLAIN) {
@@ -455,14 +490,17 @@ function Result({
return (
);
}
diff --git a/src/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx b/src/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx
index a8146465d..97ae967a8 100644
--- a/src/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx
+++ b/src/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx
@@ -54,23 +54,23 @@ const SettingsButton = ({onClick, runIsLoading}: SettingsButtonProps) => {
};
interface QueryEditorControlsProps {
- handleSendExecuteClick: () => void;
- onSettingsButtonClick: () => void;
- runIsLoading: boolean;
- handleGetExplainQueryClick: () => void;
- explainIsLoading: boolean;
+ isLoading: boolean;
disabled: boolean;
highlightedAction: QueryAction;
+
+ handleGetExplainQueryClick: () => void;
+ handleSendExecuteClick: () => void;
+ onSettingsButtonClick: () => void;
}
export const QueryEditorControls = ({
+ disabled,
+ isLoading,
+ highlightedAction,
+
handleSendExecuteClick,
onSettingsButtonClick,
- runIsLoading,
handleGetExplainQueryClick,
- explainIsLoading,
- disabled,
- highlightedAction,
}: QueryEditorControlsProps) => {
const runView: ButtonView | undefined = highlightedAction === 'execute' ? 'action' : undefined;
const explainView: ButtonView | undefined =
@@ -90,7 +90,7 @@ export const QueryEditorControls = ({