Skip to content

Commit 624dccd

Browse files
committed
test
1 parent 4db34be commit 624dccd

File tree

21 files changed

+839
-140
lines changed

21 files changed

+839
-140
lines changed

src/components/QueryResultTable/QueryResultTable.tsx

+12-8
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import React from 'react';
33
import DataTable from '@gravity-ui/react-data-table';
44
import type {Column, Settings} from '@gravity-ui/react-data-table';
55

6+
import {INDEX_COLUMN} from '../../store/reducers/query/query';
67
import type {ColumnType, KeyValueRow} from '../../types/api/query';
78
import {cn} from '../../utils/cn';
89
import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
9-
import {getColumnType, prepareQueryResponse} from '../../utils/query';
10+
import {getColumnType} from '../../utils/query';
1011
import {isNumeric} from '../../utils/utils';
1112
import type {ResizeableDataTableProps} from '../ResizeableDataTable/ResizeableDataTable';
1213
import {ResizeableDataTable} from '../ResizeableDataTable/ResizeableDataTable';
@@ -21,7 +22,6 @@ const TABLE_SETTINGS: Settings = {
2122
...DEFAULT_TABLE_SETTINGS,
2223
stripedRows: true,
2324
sortable: false,
24-
displayIndices: true,
2525
};
2626

2727
export const b = cn('ydb-query-result-table');
@@ -49,8 +49,8 @@ const prepareTypedColumns = (columns: ColumnType[], data?: KeyValueRow[]) => {
4949
});
5050
};
5151

52-
const prepareGenericColumns = (data: KeyValueRow[]) => {
53-
if (!data.length) {
52+
const prepareGenericColumns = (data?: KeyValueRow[]) => {
53+
if (!data?.length) {
5454
return [];
5555
}
5656

@@ -80,28 +80,32 @@ interface QueryResultTableProps
8080
}
8181

8282
export const QueryResultTable = (props: QueryResultTableProps) => {
83-
const {columns: rawColumns, data: rawData, ...restProps} = props;
83+
const {columns: rawColumns, data, ...restProps} = props;
8484

85-
const data = React.useMemo(() => prepareQueryResponse(rawData), [rawData]);
8685
const columns = React.useMemo(() => {
8786
return rawColumns ? prepareTypedColumns(rawColumns, data) : prepareGenericColumns(data);
8887
}, [data, rawColumns]);
8988

9089
// empty data is expected to be be an empty array
9190
// undefined data is not rendered at all
92-
if (!Array.isArray(rawData)) {
91+
if (!Array.isArray(data)) {
9392
return null;
9493
}
9594

9695
if (!columns.length) {
9796
return <div className={b('message')}>{i18n('empty')}</div>;
9897
}
9998

99+
const settings = {
100+
...TABLE_SETTINGS,
101+
displayIndices: columns.filter(({name}) => INDEX_COLUMN.name === name).length === 0,
102+
};
103+
100104
return (
101105
<ResizeableDataTable
102106
data={data}
103107
columns={columns}
104-
settings={TABLE_SETTINGS}
108+
settings={settings}
105109
// prevent accessing row.id in case it is present but is not the PK (i.e. may repeat)
106110
rowKey={getRowIndex}
107111
visibleRowIndex={getVisibleRowIndex}

src/components/QueryResultTable/utils/getColumnWidth.ts

+6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
import {INDEX_COLUMN} from '../../../store/reducers/query/query';
12
import type {KeyValueRow} from '../../../types/api/query';
23

34
export const MAX_COLUMN_WIDTH = 600;
45
export const HEADER_PADDING = 20;
6+
export const INDEX_COLUMN_WIDTH = 80;
57

68
export const getColumnWidth = ({data, name}: {data?: KeyValueRow[]; name: string}) => {
79
let maxColumnContentLength = name.length;
810

11+
if (INDEX_COLUMN.name === name) {
12+
return INDEX_COLUMN_WIDTH;
13+
}
14+
915
if (data) {
1016
for (const row of data) {
1117
const cellLength = row[name] ? String(row[name]).length : 0;

src/containers/Tenant/Query/CancelQueryButton/CancelQueryButton.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@ const b = cn('cancel-query-button');
1414
interface CancelQueryButtonProps {
1515
queryId: string;
1616
tenantName: string;
17+
onClick?: VoidFunction;
1718
}
1819

19-
export function CancelQueryButton({queryId, tenantName}: CancelQueryButtonProps) {
20+
export function CancelQueryButton({queryId, tenantName, onClick}: CancelQueryButtonProps) {
2021
const [sendCancelQuery, cancelQueryResponse] = cancelQueryApi.useCancelQueryMutation();
2122

2223
const onStopButtonClick = React.useCallback(() => {
2324
sendCancelQuery({queryId, database: tenantName});
24-
}, [queryId, sendCancelQuery, tenantName]);
25+
onClick?.();
26+
}, [onClick, queryId, sendCancelQuery, tenantName]);
2527

2628
return (
2729
<Button

src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx

+38-10
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import {isEqual} from 'lodash';
44
import {v4 as uuidv4} from 'uuid';
55

66
import SplitPane from '../../../../components/SplitPane';
7-
import {useTracingLevelOptionAvailable} from '../../../../store/reducers/capabilities/hooks';
7+
import {
8+
useStreamingAvailable,
9+
useTracingLevelOptionAvailable,
10+
} from '../../../../store/reducers/capabilities/hooks';
811
import {
912
queryApi,
1013
saveQueryToHistory,
@@ -86,8 +89,12 @@ export default function QueryEditor(props: QueryEditorProps) {
8689
LAST_USED_QUERY_ACTION_KEY,
8790
);
8891
const [lastExecutedQueryText, setLastExecutedQueryText] = React.useState<string>('');
92+
const isStreamingSupported = useStreamingAvailable();
8993

9094
const [sendQuery] = queryApi.useUseSendQueryMutation();
95+
const [streamQuery] = queryApi.useUseStreamQueryMutation();
96+
97+
const runningQueryRef = React.useRef<{abort: VoidFunction} | null>(null);
9198

9299
React.useEffect(() => {
93100
if (savedPath !== tenantName) {
@@ -121,14 +128,25 @@ export default function QueryEditor(props: QueryEditorProps) {
121128
}
122129
const queryId = uuidv4();
123130

124-
sendQuery({
125-
actionType: 'execute',
126-
query: text,
127-
database: tenantName,
128-
querySettings,
129-
enableTracingLevel,
130-
queryId,
131-
});
131+
if (isStreamingSupported) {
132+
runningQueryRef.current = streamQuery({
133+
actionType: 'execute',
134+
query: text,
135+
database: tenantName,
136+
querySettings,
137+
enableTracingLevel,
138+
queryId,
139+
});
140+
} else {
141+
runningQueryRef.current = sendQuery({
142+
actionType: 'execute',
143+
query: text,
144+
database: tenantName,
145+
querySettings,
146+
enableTracingLevel,
147+
queryId,
148+
});
149+
}
132150

133151
dispatch(setShowPreview(false));
134152

@@ -155,7 +173,7 @@ export default function QueryEditor(props: QueryEditorProps) {
155173

156174
const queryId = uuidv4();
157175

158-
sendQuery({
176+
runningQueryRef.current = sendQuery({
159177
actionType: 'explain',
160178
query: text,
161179
database: tenantName,
@@ -169,6 +187,12 @@ export default function QueryEditor(props: QueryEditorProps) {
169187
dispatchResultVisibilityState(PaneVisibilityActionTypes.triggerExpand);
170188
});
171189

190+
const handleCancelRunningQuery = React.useCallback(() => {
191+
if (runningQueryRef.current) {
192+
runningQueryRef.current.abort();
193+
}
194+
}, []);
195+
172196
const onCollapseResultHandler = () => {
173197
dispatchResultVisibilityState(PaneVisibilityActionTypes.triggerCollapse);
174198
};
@@ -233,6 +257,7 @@ export default function QueryEditor(props: QueryEditorProps) {
233257
path={path}
234258
showPreview={showPreview}
235259
queryText={lastExecutedQueryText}
260+
onCancelRunningQuery={handleCancelRunningQuery}
236261
/>
237262
</div>
238263
</SplitPane>
@@ -252,6 +277,7 @@ interface ResultProps {
252277
path: string;
253278
showPreview?: boolean;
254279
queryText: string;
280+
onCancelRunningQuery: VoidFunction;
255281
}
256282
function Result({
257283
resultVisibilityState,
@@ -264,6 +290,7 @@ function Result({
264290
path,
265291
showPreview,
266292
queryText,
293+
onCancelRunningQuery,
267294
}: ResultProps) {
268295
if (showPreview) {
269296
return <Preview database={tenantName} path={path} type={type} />;
@@ -280,6 +307,7 @@ function Result({
280307
onExpandResults={onExpandResultHandler}
281308
onCollapseResults={onCollapseResultHandler}
282309
queryText={queryText}
310+
onCancelRunningQuery={onCancelRunningQuery}
283311
/>
284312
);
285313
}

src/containers/Tenant/Query/QueryResult/QueryResultViewer.tsx

+20-7
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,10 @@ interface ExecuteResultProps {
8181
isResultsCollapsed?: boolean;
8282
theme?: string;
8383
tenantName: string;
84+
queryText?: string;
85+
onCancelRunningQuery?: VoidFunction;
8486
onCollapseResults: VoidFunction;
8587
onExpandResults: VoidFunction;
86-
queryText?: string;
8788
}
8889

8990
export function QueryResultViewer({
@@ -93,6 +94,7 @@ export function QueryResultViewer({
9394
theme,
9495
tenantName,
9596
queryText,
97+
onCancelRunningQuery,
9698
onCollapseResults,
9799
onExpandResults,
98100
}: ExecuteResultProps) {
@@ -107,7 +109,7 @@ export function QueryResultViewer({
107109
});
108110
const [useShowPlanToSvg] = useSetting<boolean>(USE_SHOW_PLAN_SVG_KEY);
109111

110-
const {error, isLoading, queryId, data = {}} = result;
112+
const {error, isLoading, queryId, data = {}, speedMetrics} = result;
111113
const {preparedPlan, simplifiedPlan, stats, resultSets, ast} = data;
112114

113115
React.useEffect(() => {
@@ -168,6 +170,10 @@ export function QueryResultViewer({
168170
};
169171

170172
const renderClipboardButton = () => {
173+
if (isLoading) {
174+
return null;
175+
}
176+
171177
const statsToCopy = getStatsToCopy();
172178
const copyText = getStringifiedData(statsToCopy);
173179
if (!copyText) {
@@ -213,19 +219,22 @@ export function QueryResultViewer({
213219
};
214220

215221
const renderResultSection = () => {
216-
if (error) {
217-
return <QueryResultError error={error} />;
218-
}
219222
if (activeSection === RESULT_OPTIONS_IDS.result) {
220223
return (
221224
<ResultSetsViewer
225+
rowsPerSecond={speedMetrics?.rowsPerSecond}
222226
resultSets={resultSets}
223227
selectedResultSet={selectedResultSet}
228+
errorHeader={<QueryResultError error={error} />}
224229
setSelectedResultSet={setSelectedResultSet}
225230
/>
226231
);
227232
}
228233

234+
if (error) {
235+
return <QueryResultError error={error} />;
236+
}
237+
229238
if (activeSection === RESULT_OPTIONS_IDS.schema) {
230239
if (!preparedPlan?.nodes?.length) {
231240
return renderStubMessage();
@@ -284,7 +293,11 @@ export function QueryResultViewer({
284293
{isLoading ? (
285294
<React.Fragment>
286295
<ElapsedTime className={b('elapsed-time')} />
287-
<CancelQueryButton queryId={queryId} tenantName={tenantName} />
296+
<CancelQueryButton
297+
queryId={queryId}
298+
tenantName={tenantName}
299+
onClick={onCancelRunningQuery}
300+
/>
288301
</React.Fragment>
289302
) : null}
290303
{data?.traceId && isExecute ? (
@@ -317,7 +330,7 @@ export function QueryResultViewer({
317330
{renderRightControls()}
318331
</div>
319332
{isLoading || isQueryCancelledError(error) ? null : <QuerySettingsBanner />}
320-
<LoaderWrapper loading={isLoading}>
333+
<LoaderWrapper loading={isLoading && !data.resultSets}>
321334
<Fullscreen className={b('result')}>{renderResultSection()}</Fullscreen>
322335
</LoaderWrapper>
323336
</React.Fragment>

src/containers/Tenant/Query/QueryResult/components/QueryResultError/QueryResultError.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const b = cn('ydb-query-result-error ');
1010
export function QueryResultError({error}: {error: unknown}) {
1111
const parsedError = parseQueryError(error);
1212

13-
// "Stopped" message is displayd in QueryExecutionStatus
13+
// "Stopped" message is displayed in QueryExecutionStatus
1414
// There is no need to display "Query is cancelled" message too
1515
if (!parsedError || isQueryCancelledError(error)) {
1616
return null;

src/containers/Tenant/Query/QueryResult/components/ResultSetsViewer/ResultSetsViewer.tsx

+20-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {Tabs, Text} from '@gravity-ui/uikit';
2+
import {flatten} from 'lodash';
23

34
import {QueryResultTable} from '../../../../../../components/QueryResultTable';
45
import type {ParsedResultSet} from '../../../../../../types/store/query';
@@ -11,16 +12,16 @@ import './ResultSetsViewer.scss';
1112
const b = cn('ydb-query-result-sets-viewer');
1213

1314
interface ResultSetsViewerProps {
15+
rowsPerSecond?: number;
1416
resultSets?: ParsedResultSet[];
1517
selectedResultSet: number;
18+
errorHeader?: React.ReactNode;
1619
setSelectedResultSet: (resultSet: number) => void;
1720
}
1821

19-
export function ResultSetsViewer({
20-
resultSets,
21-
selectedResultSet,
22-
setSelectedResultSet,
23-
}: ResultSetsViewerProps) {
22+
export function ResultSetsViewer(props: ResultSetsViewerProps) {
23+
const {selectedResultSet, setSelectedResultSet, resultSets} = props;
24+
2425
const resultsSetsCount = resultSets?.length || 0;
2526
const currentResult = resultSets?.[selectedResultSet];
2627

@@ -53,12 +54,19 @@ export function ResultSetsViewer({
5354
<Text variant="subheader-3">
5455
{currentResult?.truncated ? i18n('title.truncated') : i18n('title.result')}
5556
</Text>
56-
{currentResult?.result ? (
57+
{currentResult?.resultChunks ? (
5758
<Text
5859
color="secondary"
5960
variant="body-2"
6061
className={b('row-count')}
61-
>{`(${currentResult?.result.length})`}</Text>
62+
>{`(${currentResult?.totalCount})`}</Text>
63+
) : null}
64+
{props.rowsPerSecond ? (
65+
<Text
66+
color="secondary"
67+
variant="body-2"
68+
className={b('row-count')}
69+
>{`(${props.rowsPerSecond.toFixed(0)} rows/s)`}</Text>
6270
) : null}
6371
</div>
6472
);
@@ -67,10 +75,14 @@ export function ResultSetsViewer({
6775
return (
6876
<div className={b('result-wrapper')}>
6977
{renderTabs()}
78+
{props.errorHeader ? props.errorHeader : null}
7079
{currentResult ? (
7180
<div className={b('result')}>
7281
{renderResultHeadWithCount()}
73-
<QueryResultTable data={currentResult.result} columns={currentResult.columns} />
82+
<QueryResultTable
83+
data={flatten(currentResult.resultChunks || [])}
84+
columns={currentResult.columns}
85+
/>
7486
</div>
7587
) : null}
7688
</div>

src/containers/Tenant/Query/QueryResult/components/TraceButton/TraceButton.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ interface TraceUrlButtonProps {
1313
isTraceReady?: true;
1414
}
1515

16-
export function TraceButton({traceId, isTraceReady}: TraceUrlButtonProps) {
16+
export const TraceButton = React.memo(function TraceButton({
17+
traceId,
18+
isTraceReady,
19+
}: TraceUrlButtonProps) {
1720
const {traceCheck, traceView} = useClusterBaseInfo();
1821

1922
const checkTraceUrl = traceCheck?.url ? replaceParams(traceCheck.url, {traceId}) : '';
@@ -47,4 +50,4 @@ export function TraceButton({traceId, isTraceReady}: TraceUrlButtonProps) {
4750
</Button.Icon>
4851
</Button>
4952
);
50-
}
53+
});

0 commit comments

Comments
 (0)