Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: stop running query #1096

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/components/QueryExecutionStatus/QueryExecutionStatus.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
gap: 4px;

color: var(--g-color-text-complementary);

&__result-status-icon {
color: var(--g-color-text-positive);
&_error {
color: var(--g-color-text-danger);
}
}

&__elapsed-label {
margin-left: var(--g-spacing-3);
}
}
41 changes: 38 additions & 3 deletions src/components/QueryExecutionStatus/QueryExecutionStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import React from 'react';

import {CircleCheck, CircleQuestionFill, CircleXmark} from '@gravity-ui/icons';
import {Icon} from '@gravity-ui/uikit';
import {Icon, Label, Spin} from '@gravity-ui/uikit';
import {isAxiosError} from 'axios';

import {MS_IN_SECOND} from '../../lib';
import {cn} from '../../utils/cn';
import {configuredNumeral} from '../../utils/numeral';

import './QueryExecutionStatus.scss';

Expand All @@ -11,13 +15,39 @@ const b = cn('kv-query-execution-status');
interface QueryExecutionStatusProps {
className?: string;
error?: unknown;
loading?: boolean;
}

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') {
const [startTime, setStartTime] = React.useState<number>(0);
const [elapsedTime, setElapsedTime] = React.useState<number>(0);

const intervalRef = React.useRef<number>();

React.useEffect(() => {
if (loading) {
setStartTime(Date.now());
} else {
clearInterval(intervalRef.current);
setElapsedTime(0);
}
}, [loading]);

React.useEffect(() => {
intervalRef.current = window.setInterval(() => {
setElapsedTime(Math.floor((Date.now() - startTime) / MS_IN_SECOND));
}, MS_IN_SECOND);

return () => window.clearInterval(intervalRef.current);
}, [startTime]);

if (loading) {
icon = <Spin size="xs" />;
label = 'Running';
} else if (isAxiosError(error) && error.code === 'ECONNABORTED') {
icon = <Icon data={CircleQuestionFill} />;
label = 'Connection aborted';
} else {
Expand All @@ -35,6 +65,11 @@ export const QueryExecutionStatus = ({className, error}: QueryExecutionStatusPro
<div className={b(null, className)}>
{icon}
{label}
{loading ? (
<Label className={b('elapsed-label')}>
{configuredNumeral(elapsedTime).format('00:00')}
</Label>
) : null}
</div>
);
};
26 changes: 0 additions & 26 deletions src/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,32 +30,6 @@
padding: 15px 10px;
}

&__controls {
position: sticky;
z-index: 2;
top: 0;

display: flex;
justify-content: space-between;
align-items: center;

height: 53px;
padding: 12px 20px;

border-bottom: 1px solid var(--g-color-line-generic);
background-color: var(--g-color-base-background);
}

&__controls-right {
display: flex;
gap: 12px;

height: 100%;
}
&__controls-left {
display: flex;
gap: 4px;
}
&__inspector {
padding: 15px 10px;
@include json-tree-styles();
Expand Down
75 changes: 24 additions & 51 deletions src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
import React from 'react';

import type {ControlGroupOption} from '@gravity-ui/uikit';
import {RadioButton, Tabs} from '@gravity-ui/uikit';
import {Tabs} from '@gravity-ui/uikit';
import JSONTree from 'react-json-inspector';

import {ClipboardButton} from '../../../../components/ClipboardButton';
import Divider from '../../../../components/Divider/Divider';
import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton';
import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
import {YDBGraph} from '../../../../components/Graph/Graph';
import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus';
import {LoaderWrapper} from '../../../../components/LoaderWrapper/LoaderWrapper';
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';
import {cn} from '../../../../utils/cn';
import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
import {parseQueryError} from '../../../../utils/query';
import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers';
import {ResultIssues} from '../Issues/Issues';
import {QueryDuration} from '../QueryDuration/QueryDuration';
import {ResultControls} from '../ResultControls/ResultControls';
import {getPreparedResult} from '../utils/getPreparedResult';

import {getPlan} from './utils';
Expand All @@ -45,6 +41,7 @@ interface ExecuteResultProps {
onCollapseResults: VoidFunction;
onExpandResults: VoidFunction;
theme?: string;
loading?: boolean;
}

export function ExecuteResult({
Expand All @@ -54,14 +51,15 @@ export function ExecuteResult({
onCollapseResults,
onExpandResults,
theme,
loading,
}: ExecuteResultProps) {
const [selectedResultSet, setSelectedResultSet] = React.useState(0);
const [activeSection, setActiveSection] = React.useState<SectionID>(resultOptionsIds.result);

const isFullscreen = useTypedSelector((state) => state.fullscreen);
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;
Expand All @@ -86,8 +84,8 @@ export function ExecuteResult({
};
}, [dispatch]);

const onSelectSection = (value: string) => {
setActiveSection(value as SectionID);
const onSelectSection = (value: SectionID) => {
setActiveSection(value);
};

const renderResultTable = (
Expand Down Expand Up @@ -121,17 +119,6 @@ export function ExecuteResult({
);
};

const renderClipboardButton = () => {
return (
<ClipboardButton
text={textResults}
view="flat-secondary"
title="Copy results"
disabled={copyDisabled}
/>
);
};

const renderStats = () => {
const content = (
<JSONTree
Expand Down Expand Up @@ -235,35 +222,21 @@ export function ExecuteResult({

return (
<React.Fragment>
<div className={b('controls')}>
<div className={b('controls-right')}>
<QueryExecutionStatus error={error} />

{stats && !error && (
<React.Fragment>
<QueryDuration duration={stats?.DurationUs} />
<Divider />
<RadioButton
options={resultOptions}
value={activeSection}
onUpdate={onSelectSection}
/>
</React.Fragment>
)}
</div>
<div className={b('controls-left')}>
{renderClipboardButton()}
<EnableFullscreenButton />
<PaneVisibilityToggleButtons
onCollapse={onCollapseResults}
onExpand={onExpandResults}
isCollapsed={isResultsCollapsed}
initialDirection="bottom"
/>
</div>
</div>

{renderResultSection()}
<ResultControls<SectionID>
error={error}
stats={stats ? {DurationUs: stats.DurationUs} : undefined}
activeSection={activeSection}
onSelectSection={onSelectSection}
sectionOptions={resultOptions}
clipboardText={textResults}
isClipboardDisabled={copyDisabled}
isResultsCollapsed={isResultsCollapsed}
onCollapseResults={onCollapseResults}
onExpandResults={onExpandResults}
isFullscreenDisabled={Boolean(error)}
loading={loading}
/>
<LoaderWrapper loading={loading}>{renderResultSection()}</LoaderWrapper>
</React.Fragment>
);
}
34 changes: 1 addition & 33 deletions src/containers/Tenant/Query/ExplainResult/ExplainResult.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,8 @@
flex-grow: 1;
flex-direction: column;
}

&__text-message {
padding: 15px 20px;
}
&__controls {
position: sticky;
z-index: 2;
top: 0;

display: flex;
justify-content: space-between;
align-items: center;

height: 53px;
padding: 12px 20px;

border-bottom: 1px solid var(--g-color-line-generic);
background-color: var(--g-color-base-background);
}
&__controls-right {
display: flex;
gap: 12px;

height: 100%;
}
&__controls-left {
display: flex;
gap: 4px;
}
&__loader {
display: flex;
justify-content: center;
align-items: center;

width: 100%;
margin-top: 20px;
}
}
50 changes: 14 additions & 36 deletions src/containers/Tenant/Query/ExplainResult/ExplainResult.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import React from 'react';

import {RadioButton} from '@gravity-ui/uikit';

import Divider from '../../../../components/Divider/Divider';
import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton';
import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
import {LoaderWrapper} from '../../../../components/LoaderWrapper/LoaderWrapper';
import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus';
import type {PreparedExplainResponse} from '../../../../store/reducers/explainQuery/types';
import {disableFullscreen} from '../../../../store/reducers/fullscreen';
import type {ValueOf} from '../../../../types/common';
import {cn} from '../../../../utils/cn';
import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
import {parseQueryErrorToString} from '../../../../utils/query';
import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers';
import {ResultControls} from '../ResultControls/ResultControls';

import {Ast} from './components/Ast/Ast';
import {Graph} from './components/Graph/Graph';
Expand Down Expand Up @@ -135,36 +130,19 @@ export function ExplainResult({

return (
<React.Fragment>
<div className={b('controls')}>
{!loading && (
<React.Fragment>
<div className={b('controls-right')}>
<QueryExecutionStatus error={error} />
{!error && (
<React.Fragment>
<Divider />
<RadioButton
options={explainOptions}
value={activeOption}
onUpdate={(tabId) => {
startTransition(() => setActiveOption(tabId));
}}
/>
</React.Fragment>
)}
</div>
<div className={b('controls-left')}>
<EnableFullscreenButton disabled={Boolean(error)} />
<PaneVisibilityToggleButtons
onCollapse={onCollapseResults}
onExpand={onExpandResults}
isCollapsed={isResultsCollapsed}
initialDirection="bottom"
/>
</div>
</React.Fragment>
)}
</div>
<ResultControls<QueryExplainTab>
error={error}
activeSection={activeOption}
onSelectSection={(tabId) => {
startTransition(() => setActiveOption(tabId));
}}
sectionOptions={explainOptions}
isResultsCollapsed={isResultsCollapsed}
onCollapseResults={onCollapseResults}
onExpandResults={onExpandResults}
isFullscreenDisabled={Boolean(error)}
loading={loading}
/>
<LoaderWrapper loading={loading || isPending}>
{/* this is a hack: only one Graph component may be in DOM because of it's canvas id */}
{activeOption === EXPLAIN_OPTIONS_IDS.schema && isFullscreen ? null : (
Expand Down
Loading
Loading