Skip to content

Commit

Permalink
feat(Query): enable queries with multiple resultsets (#595)
Browse files Browse the repository at this point in the history
  • Loading branch information
artemmufazalov authored Nov 27, 2023
1 parent 7f5a783 commit 2eedfb6
Show file tree
Hide file tree
Showing 22 changed files with 234 additions and 98 deletions.
3 changes: 2 additions & 1 deletion src/components/VirtualTable/TableChunk.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {useEffect, useRef, memo} from 'react';

import {getArray} from '../../utils';

import type {Column, Chunk, GetRowClassName} from './types';
import {LoadingTableRow, TableRow} from './TableRow';
import {getArray} from './utils';

// With original memo generic types are lost
const typedMemo: <T>(Component: T) => T = memo;
Expand Down
2 changes: 1 addition & 1 deletion src/components/VirtualTable/VirtualTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {useState, useReducer, useRef, useCallback, useEffect} from 'react';

import type {IResponseError} from '../../types/api/error';
import {getArray} from '../../utils';

import {TableWithControlsLayout} from '../TableWithControlsLayout/TableWithControlsLayout';
import {ResponseError} from '../Errors/ResponseError';
Expand Down Expand Up @@ -31,7 +32,6 @@ import {TableHead} from './TableHead';
import {TableChunk} from './TableChunk';
import {EmptyTableRow} from './TableRow';
import {useIntersectionObserver} from './useIntersectionObserver';
import {getArray} from './utils';
import i18n from './i18n';
import {b} from './shared';

Expand Down
15 changes: 5 additions & 10 deletions src/containers/Tenant/Diagnostics/Network/Network.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {changeFilter, ProblemFilterValues} from '../../../../store/reducers/sett
import {AutoFetcher} from '../../../../utils/autofetcher';
import {getDefaultNodePath} from '../../../Node/NodePages';

import {getConnectedNodesCount} from './utils';

import './Network.scss';

const b = cn('network');
Expand Down Expand Up @@ -145,11 +147,6 @@ class Network extends React.Component {
);
};

getConnectedNodesCount = (peers) => {
const res = peers?.reduce((acc, item) => (item.Connected ? acc + 1 : acc), 0);
return res;
};

renderNodes = (nodes, isRight) => {
const {showId, showRacks, clickedNode} = this.state;
let problemNodesCount = 0;
Expand All @@ -171,9 +168,7 @@ class Network extends React.Component {
let capacity, connected;
if (!isRight && nodeInfo?.Peers) {
capacity = Object.keys(nodeInfo?.Peers).length;
connected = this.getConnectedNodesCount(
nodeInfo?.Peers,
);
connected = getConnectedNodesCount(nodeInfo?.Peers);
}

if (
Expand Down Expand Up @@ -214,7 +209,7 @@ class Network extends React.Component {
let capacity, connected;
if (!isRight) {
capacity = nodeInfo?.Peers?.length;
connected = this.getConnectedNodesCount(nodeInfo?.Peers);
connected = getConnectedNodesCount(nodeInfo?.Peers);
}

if (
Expand All @@ -234,7 +229,7 @@ class Network extends React.Component {
capacity={nodeInfo?.Peers && nodeInfo?.Peers.length}
connected={
nodeInfo?.Peers &&
this.getConnectedNodesCount(nodeInfo?.Peers)
getConnectedNodesCount(nodeInfo?.Peers)
}
onMouseEnter={showTooltip}
onMouseLeave={hideTooltip}
Expand Down
6 changes: 6 additions & 0 deletions src/containers/Tenant/Diagnostics/Network/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type {TNetNodePeerInfo} from '../../../../types/api/netInfo';

// determine how many nodes have status Connected "true"
export const getConnectedNodesCount = (peers: TNetNodePeerInfo[] | undefined) => {
return peers?.reduce((acc, item) => (item.Connected ? acc + 1 : acc), 0);
};
18 changes: 13 additions & 5 deletions src/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,19 @@
& .data-table__table-wrapper {
padding-bottom: 0;
}
&_fullscreen {
width: 100%;
margin-top: 10px;
padding: 0 10px 10px;
}
}

&__result-fullscreen-wrapper {
display: flex;
flex-direction: column;

width: 100%;
margin-top: 10px;
padding: 0 10px 10px;
}

&__result-tabs {
padding-left: 10px;
}

&__error {
Expand Down
90 changes: 72 additions & 18 deletions src/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
import React, {type ReactNode, useEffect, useState} from 'react';
import React, {useEffect, useState} from 'react';
import {useDispatch} from 'react-redux';
import cn from 'bem-cn-lite';
import JSONTree from 'react-json-inspector';

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

import CopyToClipboard from '../../../../components/CopyToClipboard/CopyToClipboard';
import Divider from '../../../../components/Divider/Divider';
import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton';
import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus';
import {QueryResultTable} from '../../../../components/QueryResultTable/QueryResultTable';

import type {ValueOf} from '../../../../types/common';
import type {IQueryResult, QueryErrorResponse} from '../../../../types/store/query';
import type {ColumnType, KeyValueRow} from '../../../../types/api/query';
import {disableFullscreen} from '../../../../store/reducers/fullscreen';
import {prepareQueryError} from '../../../../utils/query';
import {useTypedSelector} from '../../../../utils/hooks';
import {getArray} from '../../../../utils';

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

import {ResultIssues} from '../Issues/Issues';
import {QueryDuration} from '../QueryDuration/QueryDuration';
import {getPreparedResult} from '../utils/getPreparedResult';

import './ExecuteResult.scss';

Expand All @@ -39,30 +43,35 @@ const resultOptions = [
];

interface ExecuteResultProps {
textResults: string;
result: ReactNode;
data: IQueryResult | undefined;
stats: IQueryResult['stats'] | undefined;
error: string | QueryErrorResponse | undefined;
copyDisabled?: boolean;
isResultsCollapsed?: boolean;
onCollapseResults: VoidFunction;
onExpandResults: VoidFunction;
}

export function ExecuteResult({
textResults,
result,
data,
stats,
error,
copyDisabled,
isResultsCollapsed,
onCollapseResults,
onExpandResults,
}: ExecuteResultProps) {
const [selectedResultSet, setSelectedResultSet] = useState(0);
const [activeSection, setActiveSection] = useState<SectionID>(resultOptionsIds.result);

const isFullscreen = useTypedSelector((state) => state.fullscreen);
const dispatch = useDispatch();

const resultsSetsCount = data?.resultSets?.length;
const isMulti = resultsSetsCount && resultsSetsCount > 0;
const currentResult = isMulti ? data?.resultSets?.[selectedResultSet].result : data?.result;
const currentColumns = isMulti ? data?.resultSets?.[selectedResultSet].columns : data?.columns;
const textResults = getPreparedResult(currentResult);
const copyDisabled = !textResults.length;

useEffect(() => {
return () => {
dispatch(disableFullscreen());
Expand All @@ -73,6 +82,37 @@ export function ExecuteResult({
setActiveSection(value as SectionID);
};

const renderResultTable = (
result: KeyValueRow[] | undefined,
columns: ColumnType[] | undefined,
) => {
return <QueryResultTable data={result} columns={columns} settings={{sortable: false}} />;
};

const renderContent = () => {
return (
<>
{isMulti && resultsSetsCount > 1 && (
<div>
<Tabs
className={b('result-tabs')}
size="l"
items={getArray(resultsSetsCount).map((item) => ({
id: String(item),
title: `Result #${item + 1}`,
}))}
activeTab={String(selectedResultSet)}
onSelectTab={(tabId) => setSelectedResultSet(Number(tabId))}
/>
</div>
)}
<div className={b('result')}>
{renderResultTable(currentResult, currentColumns)}
</div>
</>
);
};

const renderClipboardButton = () => {
return (
<CopyToClipboard
Expand Down Expand Up @@ -108,12 +148,14 @@ export function ExecuteResult({
};

const renderResult = () => {
const content = renderContent();

return (
<React.Fragment>
{result}
{content}
{isFullscreen && (
<Fullscreen>
<div className={b('result', {fullscreen: true})}>{result}</div>
<div className={b('result-fullscreen-wrapper')}>{content}</div>
</Fullscreen>
)}
</React.Fragment>
Expand All @@ -126,13 +168,15 @@ export function ExecuteResult({
}

if (typeof error === 'object' && error.data?.issues && Array.isArray(error.data.issues)) {
const content = <ResultIssues data={error.data} />;

return (
<React.Fragment>
<ResultIssues data={error.data} />
{content}
{isFullscreen && (
<Fullscreen>
<div className={b('result', {fullscreen: true})}>
<ResultIssues data={error.data} />
<div className={b('result-fullscreen-wrapper', b('result'))}>
{content}
</div>
</Fullscreen>
)}
Expand All @@ -145,6 +189,19 @@ export function ExecuteResult({
return <div className={b('error')}>{parsedError}</div>;
};

const renderResultSection = () => {
if (activeSection === resultOptionsIds.result && !error) {
return renderResult();
}

return (
<div className={b('result')}>
{activeSection === resultOptionsIds.stats && !error && renderStats()}
{renderIssues()}
</div>
);
};

return (
<React.Fragment>
<div className={b('controls')}>
Expand Down Expand Up @@ -174,11 +231,8 @@ export function ExecuteResult({
/>
</div>
</div>
<div className={b('result')}>
{activeSection === resultOptionsIds.result && !error && renderResult()}
{activeSection === resultOptionsIds.stats && !error && renderStats()}
{renderIssues()}
</div>

{renderResultSection()}
</React.Fragment>
);
}
3 changes: 2 additions & 1 deletion src/containers/Tenant/Query/ExplainResult/ExplainResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus'
import {explainVersions} from '../../../../store/reducers/explainQuery';
import {disableFullscreen} from '../../../../store/reducers/fullscreen';

import {renderExplainNode} from '../../../../utils';
import {LANGUAGE_S_EXPRESSION_ID} from '../../../../utils/monaco';

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

import {renderExplainNode} from './utils';

import './ExplainResult.scss';

const b = cn('ydb-query-explain-result');
Expand Down
6 changes: 6 additions & 0 deletions src/containers/Tenant/Query/ExplainResult/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type {GraphNode} from '@gravity-ui/paranoid';

export const renderExplainNode = (node: GraphNode): string => {
const parts = node.name.split('|');
return parts.length > 1 ? parts[1] : node.name;
};
35 changes: 11 additions & 24 deletions src/containers/Tenant/Query/QueryEditor/QueryEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import _ from 'lodash';
import MonacoEditor from 'react-monaco-editor';

import SplitPane from '../../../../components/SplitPane';
import {QueryResultTable} from '../../../../components/QueryResultTable';

import {
sendExecuteQuery,
Expand All @@ -26,6 +25,7 @@ import {
SAVED_QUERIES_KEY,
ENABLE_ADDITIONAL_QUERY_MODES,
LAST_USED_QUERY_ACTION_KEY,
QUERY_USE_MULTI_SCHEMA_KEY,
} from '../../../../utils/constants';
import {useSetting, useQueryModes} from '../../../../utils/hooks';
import {QUERY_ACTIONS, QUERY_MODES, isNewQueryMode} from '../../../../utils/query';
Expand All @@ -40,14 +40,8 @@ import {ExecuteResult} from '../ExecuteResult/ExecuteResult';
import {ExplainResult} from '../ExplainResult/ExplainResult';
import {QueryEditorControls} from '../QueryEditorControls/QueryEditorControls';

import {getPreparedResult} from '../utils/getPreparedResult';

import './QueryEditor.scss';

const TABLE_SETTINGS = {
sortable: false,
};

const EDITOR_OPTIONS = {
automaticLayout: true,
selectOnLineNumbers: true,
Expand Down Expand Up @@ -92,6 +86,7 @@ function QueryEditor(props) {
const [isResultLoaded, setIsResultLoaded] = useState(false);
const [queryMode, setQueryMode] = useQueryModes();
const [enableAdditionalQueryModes] = useSetting(ENABLE_ADDITIONAL_QUERY_MODES);
const [useMultiSchema] = useSetting(QUERY_USE_MULTI_SCHEMA_KEY);
const [lastUsedQueryAction, setLastUsedQueryAction] = useSetting(LAST_USED_QUERY_ACTION_KEY);

useEffect(() => {
Expand Down Expand Up @@ -256,9 +251,16 @@ function QueryEditor(props) {
setShowPreview,
} = props;

const schema = useMultiSchema ? 'multi' : 'modern';

setLastUsedQueryAction(QUERY_ACTIONS.execute);
setResultType(RESULT_TYPES.EXECUTE);
sendExecuteQuery({query: input, database: path, mode});
sendExecuteQuery({
query: input,
database: path,
mode,
schema,
});
setIsResultLoaded(true);
setShowPreview(false);

Expand Down Expand Up @@ -314,26 +316,11 @@ function QueryEditor(props) {
executeQuery: {data, error, stats},
} = props;

let content;
if (data) {
content = (
<QueryResultTable
data={data.result}
columns={data.columns}
settings={TABLE_SETTINGS}
/>
);
}
const textResults = getPreparedResult(data);
const disabled = !textResults.length || resultType !== RESULT_TYPES.EXECUTE;

return data || error ? (
<ExecuteResult
result={content}
data={data}
stats={stats}
error={error}
textResults={textResults}
copyDisabled={disabled}
isResultsCollapsed={resultVisibilityState.collapsed}
onExpandResults={onExpandResultHandler}
onCollapseResults={onCollapseResultHandler}
Expand Down
Loading

0 comments on commit 2eedfb6

Please sign in to comment.