Skip to content

Commit 2eedfb6

Browse files
feat(Query): enable queries with multiple resultsets (#595)
1 parent 7f5a783 commit 2eedfb6

File tree

22 files changed

+234
-98
lines changed

22 files changed

+234
-98
lines changed

src/components/VirtualTable/TableChunk.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import {useEffect, useRef, memo} from 'react';
22

3+
import {getArray} from '../../utils';
4+
35
import type {Column, Chunk, GetRowClassName} from './types';
46
import {LoadingTableRow, TableRow} from './TableRow';
5-
import {getArray} from './utils';
67

78
// With original memo generic types are lost
89
const typedMemo: <T>(Component: T) => T = memo;

src/components/VirtualTable/VirtualTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {useState, useReducer, useRef, useCallback, useEffect} from 'react';
22

33
import type {IResponseError} from '../../types/api/error';
4+
import {getArray} from '../../utils';
45

56
import {TableWithControlsLayout} from '../TableWithControlsLayout/TableWithControlsLayout';
67
import {ResponseError} from '../Errors/ResponseError';
@@ -31,7 +32,6 @@ import {TableHead} from './TableHead';
3132
import {TableChunk} from './TableChunk';
3233
import {EmptyTableRow} from './TableRow';
3334
import {useIntersectionObserver} from './useIntersectionObserver';
34-
import {getArray} from './utils';
3535
import i18n from './i18n';
3636
import {b} from './shared';
3737

src/containers/Tenant/Diagnostics/Network/Network.js

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {changeFilter, ProblemFilterValues} from '../../../../store/reducers/sett
1818
import {AutoFetcher} from '../../../../utils/autofetcher';
1919
import {getDefaultNodePath} from '../../../Node/NodePages';
2020

21+
import {getConnectedNodesCount} from './utils';
22+
2123
import './Network.scss';
2224

2325
const b = cn('network');
@@ -145,11 +147,6 @@ class Network extends React.Component {
145147
);
146148
};
147149

148-
getConnectedNodesCount = (peers) => {
149-
const res = peers?.reduce((acc, item) => (item.Connected ? acc + 1 : acc), 0);
150-
return res;
151-
};
152-
153150
renderNodes = (nodes, isRight) => {
154151
const {showId, showRacks, clickedNode} = this.state;
155152
let problemNodesCount = 0;
@@ -171,9 +168,7 @@ class Network extends React.Component {
171168
let capacity, connected;
172169
if (!isRight && nodeInfo?.Peers) {
173170
capacity = Object.keys(nodeInfo?.Peers).length;
174-
connected = this.getConnectedNodesCount(
175-
nodeInfo?.Peers,
176-
);
171+
connected = getConnectedNodesCount(nodeInfo?.Peers);
177172
}
178173

179174
if (
@@ -214,7 +209,7 @@ class Network extends React.Component {
214209
let capacity, connected;
215210
if (!isRight) {
216211
capacity = nodeInfo?.Peers?.length;
217-
connected = this.getConnectedNodesCount(nodeInfo?.Peers);
212+
connected = getConnectedNodesCount(nodeInfo?.Peers);
218213
}
219214

220215
if (
@@ -234,7 +229,7 @@ class Network extends React.Component {
234229
capacity={nodeInfo?.Peers && nodeInfo?.Peers.length}
235230
connected={
236231
nodeInfo?.Peers &&
237-
this.getConnectedNodesCount(nodeInfo?.Peers)
232+
getConnectedNodesCount(nodeInfo?.Peers)
238233
}
239234
onMouseEnter={showTooltip}
240235
onMouseLeave={hideTooltip}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type {TNetNodePeerInfo} from '../../../../types/api/netInfo';
2+
3+
// determine how many nodes have status Connected "true"
4+
export const getConnectedNodesCount = (peers: TNetNodePeerInfo[] | undefined) => {
5+
return peers?.reduce((acc, item) => (item.Connected ? acc + 1 : acc), 0);
6+
};

src/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,19 @@
1313
& .data-table__table-wrapper {
1414
padding-bottom: 0;
1515
}
16-
&_fullscreen {
17-
width: 100%;
18-
margin-top: 10px;
19-
padding: 0 10px 10px;
20-
}
16+
}
17+
18+
&__result-fullscreen-wrapper {
19+
display: flex;
20+
flex-direction: column;
21+
22+
width: 100%;
23+
margin-top: 10px;
24+
padding: 0 10px 10px;
25+
}
26+
27+
&__result-tabs {
28+
padding-left: 10px;
2129
}
2230

2331
&__error {

src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx

Lines changed: 72 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
1-
import React, {type ReactNode, useEffect, useState} from 'react';
1+
import React, {useEffect, useState} from 'react';
22
import {useDispatch} from 'react-redux';
33
import cn from 'bem-cn-lite';
44
import JSONTree from 'react-json-inspector';
55

6-
import {RadioButton} from '@gravity-ui/uikit';
6+
import {RadioButton, Tabs} from '@gravity-ui/uikit';
77

88
import CopyToClipboard from '../../../../components/CopyToClipboard/CopyToClipboard';
99
import Divider from '../../../../components/Divider/Divider';
1010
import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton';
1111
import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
1212
import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus';
13+
import {QueryResultTable} from '../../../../components/QueryResultTable/QueryResultTable';
1314

1415
import type {ValueOf} from '../../../../types/common';
1516
import type {IQueryResult, QueryErrorResponse} from '../../../../types/store/query';
17+
import type {ColumnType, KeyValueRow} from '../../../../types/api/query';
1618
import {disableFullscreen} from '../../../../store/reducers/fullscreen';
1719
import {prepareQueryError} from '../../../../utils/query';
1820
import {useTypedSelector} from '../../../../utils/hooks';
21+
import {getArray} from '../../../../utils';
1922

2023
import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers';
2124

2225
import {ResultIssues} from '../Issues/Issues';
2326
import {QueryDuration} from '../QueryDuration/QueryDuration';
27+
import {getPreparedResult} from '../utils/getPreparedResult';
2428

2529
import './ExecuteResult.scss';
2630

@@ -39,30 +43,35 @@ const resultOptions = [
3943
];
4044

4145
interface ExecuteResultProps {
42-
textResults: string;
43-
result: ReactNode;
46+
data: IQueryResult | undefined;
4447
stats: IQueryResult['stats'] | undefined;
4548
error: string | QueryErrorResponse | undefined;
46-
copyDisabled?: boolean;
4749
isResultsCollapsed?: boolean;
4850
onCollapseResults: VoidFunction;
4951
onExpandResults: VoidFunction;
5052
}
5153

5254
export function ExecuteResult({
53-
textResults,
54-
result,
55+
data,
5556
stats,
5657
error,
57-
copyDisabled,
5858
isResultsCollapsed,
5959
onCollapseResults,
6060
onExpandResults,
6161
}: ExecuteResultProps) {
62+
const [selectedResultSet, setSelectedResultSet] = useState(0);
6263
const [activeSection, setActiveSection] = useState<SectionID>(resultOptionsIds.result);
64+
6365
const isFullscreen = useTypedSelector((state) => state.fullscreen);
6466
const dispatch = useDispatch();
6567

68+
const resultsSetsCount = data?.resultSets?.length;
69+
const isMulti = resultsSetsCount && resultsSetsCount > 0;
70+
const currentResult = isMulti ? data?.resultSets?.[selectedResultSet].result : data?.result;
71+
const currentColumns = isMulti ? data?.resultSets?.[selectedResultSet].columns : data?.columns;
72+
const textResults = getPreparedResult(currentResult);
73+
const copyDisabled = !textResults.length;
74+
6675
useEffect(() => {
6776
return () => {
6877
dispatch(disableFullscreen());
@@ -73,6 +82,37 @@ export function ExecuteResult({
7382
setActiveSection(value as SectionID);
7483
};
7584

85+
const renderResultTable = (
86+
result: KeyValueRow[] | undefined,
87+
columns: ColumnType[] | undefined,
88+
) => {
89+
return <QueryResultTable data={result} columns={columns} settings={{sortable: false}} />;
90+
};
91+
92+
const renderContent = () => {
93+
return (
94+
<>
95+
{isMulti && resultsSetsCount > 1 && (
96+
<div>
97+
<Tabs
98+
className={b('result-tabs')}
99+
size="l"
100+
items={getArray(resultsSetsCount).map((item) => ({
101+
id: String(item),
102+
title: `Result #${item + 1}`,
103+
}))}
104+
activeTab={String(selectedResultSet)}
105+
onSelectTab={(tabId) => setSelectedResultSet(Number(tabId))}
106+
/>
107+
</div>
108+
)}
109+
<div className={b('result')}>
110+
{renderResultTable(currentResult, currentColumns)}
111+
</div>
112+
</>
113+
);
114+
};
115+
76116
const renderClipboardButton = () => {
77117
return (
78118
<CopyToClipboard
@@ -108,12 +148,14 @@ export function ExecuteResult({
108148
};
109149

110150
const renderResult = () => {
151+
const content = renderContent();
152+
111153
return (
112154
<React.Fragment>
113-
{result}
155+
{content}
114156
{isFullscreen && (
115157
<Fullscreen>
116-
<div className={b('result', {fullscreen: true})}>{result}</div>
158+
<div className={b('result-fullscreen-wrapper')}>{content}</div>
117159
</Fullscreen>
118160
)}
119161
</React.Fragment>
@@ -126,13 +168,15 @@ export function ExecuteResult({
126168
}
127169

128170
if (typeof error === 'object' && error.data?.issues && Array.isArray(error.data.issues)) {
171+
const content = <ResultIssues data={error.data} />;
172+
129173
return (
130174
<React.Fragment>
131-
<ResultIssues data={error.data} />
175+
{content}
132176
{isFullscreen && (
133177
<Fullscreen>
134-
<div className={b('result', {fullscreen: true})}>
135-
<ResultIssues data={error.data} />
178+
<div className={b('result-fullscreen-wrapper', b('result'))}>
179+
{content}
136180
</div>
137181
</Fullscreen>
138182
)}
@@ -145,6 +189,19 @@ export function ExecuteResult({
145189
return <div className={b('error')}>{parsedError}</div>;
146190
};
147191

192+
const renderResultSection = () => {
193+
if (activeSection === resultOptionsIds.result && !error) {
194+
return renderResult();
195+
}
196+
197+
return (
198+
<div className={b('result')}>
199+
{activeSection === resultOptionsIds.stats && !error && renderStats()}
200+
{renderIssues()}
201+
</div>
202+
);
203+
};
204+
148205
return (
149206
<React.Fragment>
150207
<div className={b('controls')}>
@@ -174,11 +231,8 @@ export function ExecuteResult({
174231
/>
175232
</div>
176233
</div>
177-
<div className={b('result')}>
178-
{activeSection === resultOptionsIds.result && !error && renderResult()}
179-
{activeSection === resultOptionsIds.stats && !error && renderStats()}
180-
{renderIssues()}
181-
</div>
234+
235+
{renderResultSection()}
182236
</React.Fragment>
183237
);
184238
}

src/containers/Tenant/Query/ExplainResult/ExplainResult.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus'
1616
import {explainVersions} from '../../../../store/reducers/explainQuery';
1717
import {disableFullscreen} from '../../../../store/reducers/fullscreen';
1818

19-
import {renderExplainNode} from '../../../../utils';
2019
import {LANGUAGE_S_EXPRESSION_ID} from '../../../../utils/monaco';
2120

2221
import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers';
2322

23+
import {renderExplainNode} from './utils';
24+
2425
import './ExplainResult.scss';
2526

2627
const b = cn('ydb-query-explain-result');
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type {GraphNode} from '@gravity-ui/paranoid';
2+
3+
export const renderExplainNode = (node: GraphNode): string => {
4+
const parts = node.name.split('|');
5+
return parts.length > 1 ? parts[1] : node.name;
6+
};

src/containers/Tenant/Query/QueryEditor/QueryEditor.js

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import _ from 'lodash';
66
import MonacoEditor from 'react-monaco-editor';
77

88
import SplitPane from '../../../../components/SplitPane';
9-
import {QueryResultTable} from '../../../../components/QueryResultTable';
109

1110
import {
1211
sendExecuteQuery,
@@ -26,6 +25,7 @@ import {
2625
SAVED_QUERIES_KEY,
2726
ENABLE_ADDITIONAL_QUERY_MODES,
2827
LAST_USED_QUERY_ACTION_KEY,
28+
QUERY_USE_MULTI_SCHEMA_KEY,
2929
} from '../../../../utils/constants';
3030
import {useSetting, useQueryModes} from '../../../../utils/hooks';
3131
import {QUERY_ACTIONS, QUERY_MODES, isNewQueryMode} from '../../../../utils/query';
@@ -40,14 +40,8 @@ import {ExecuteResult} from '../ExecuteResult/ExecuteResult';
4040
import {ExplainResult} from '../ExplainResult/ExplainResult';
4141
import {QueryEditorControls} from '../QueryEditorControls/QueryEditorControls';
4242

43-
import {getPreparedResult} from '../utils/getPreparedResult';
44-
4543
import './QueryEditor.scss';
4644

47-
const TABLE_SETTINGS = {
48-
sortable: false,
49-
};
50-
5145
const EDITOR_OPTIONS = {
5246
automaticLayout: true,
5347
selectOnLineNumbers: true,
@@ -92,6 +86,7 @@ function QueryEditor(props) {
9286
const [isResultLoaded, setIsResultLoaded] = useState(false);
9387
const [queryMode, setQueryMode] = useQueryModes();
9488
const [enableAdditionalQueryModes] = useSetting(ENABLE_ADDITIONAL_QUERY_MODES);
89+
const [useMultiSchema] = useSetting(QUERY_USE_MULTI_SCHEMA_KEY);
9590
const [lastUsedQueryAction, setLastUsedQueryAction] = useSetting(LAST_USED_QUERY_ACTION_KEY);
9691

9792
useEffect(() => {
@@ -256,9 +251,16 @@ function QueryEditor(props) {
256251
setShowPreview,
257252
} = props;
258253

254+
const schema = useMultiSchema ? 'multi' : 'modern';
255+
259256
setLastUsedQueryAction(QUERY_ACTIONS.execute);
260257
setResultType(RESULT_TYPES.EXECUTE);
261-
sendExecuteQuery({query: input, database: path, mode});
258+
sendExecuteQuery({
259+
query: input,
260+
database: path,
261+
mode,
262+
schema,
263+
});
262264
setIsResultLoaded(true);
263265
setShowPreview(false);
264266

@@ -314,26 +316,11 @@ function QueryEditor(props) {
314316
executeQuery: {data, error, stats},
315317
} = props;
316318

317-
let content;
318-
if (data) {
319-
content = (
320-
<QueryResultTable
321-
data={data.result}
322-
columns={data.columns}
323-
settings={TABLE_SETTINGS}
324-
/>
325-
);
326-
}
327-
const textResults = getPreparedResult(data);
328-
const disabled = !textResults.length || resultType !== RESULT_TYPES.EXECUTE;
329-
330319
return data || error ? (
331320
<ExecuteResult
332-
result={content}
321+
data={data}
333322
stats={stats}
334323
error={error}
335-
textResults={textResults}
336-
copyDisabled={disabled}
337324
isResultsCollapsed={resultVisibilityState.collapsed}
338325
onExpandResults={onExpandResultHandler}
339326
onCollapseResults={onCollapseResultHandler}

0 commit comments

Comments
 (0)