ydb-embedded-ui 4.21.1 → 4.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/components/ProgressViewer/ProgressViewer.tsx +1 -1
  3. package/dist/components/VirtualTable/TableChunk.tsx +2 -1
  4. package/dist/components/VirtualTable/VirtualTable.tsx +1 -1
  5. package/dist/containers/Cluster/Cluster.tsx +2 -0
  6. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.scss +14 -5
  7. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.tsx +104 -24
  8. package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.tsx +1 -1
  9. package/dist/containers/Cluster/i18n/en.json +16 -0
  10. package/dist/containers/Cluster/i18n/index.ts +11 -0
  11. package/dist/containers/Cluster/i18n/ru.json +16 -0
  12. package/dist/containers/Node/NodeStructure/Pdisk.tsx +4 -1
  13. package/dist/containers/Nodes/getNodesColumns.tsx +57 -13
  14. package/dist/containers/Tenant/Diagnostics/Network/Network.js +5 -10
  15. package/dist/containers/Tenant/Diagnostics/Network/utils.ts +6 -0
  16. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByCpu.tsx +18 -3
  17. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopNodesByLoad.tsx +18 -3
  18. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx +17 -1
  19. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx +20 -1
  20. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantMemory/TopNodesByMemory.tsx +18 -3
  21. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverviewTableLayout.tsx +2 -1
  22. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopGroups.tsx +19 -2
  23. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx +8 -1
  24. package/dist/containers/Tenant/Diagnostics/TenantOverview/getSectionTitle.tsx +28 -0
  25. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +17 -1
  26. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +17 -1
  27. package/dist/containers/Tenant/Query/ExecuteResult/ExecuteResult.scss +13 -5
  28. package/dist/containers/Tenant/Query/ExecuteResult/ExecuteResult.tsx +72 -18
  29. package/dist/containers/Tenant/Query/ExplainResult/ExplainResult.js +2 -1
  30. package/dist/containers/Tenant/Query/ExplainResult/utils.ts +6 -0
  31. package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +11 -24
  32. package/dist/containers/Tenant/Query/utils/getPreparedResult.ts +4 -5
  33. package/dist/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx +19 -11
  34. package/dist/containers/UserSettings/i18n/en.json +3 -0
  35. package/dist/containers/UserSettings/i18n/ru.json +3 -0
  36. package/dist/containers/UserSettings/settings.ts +7 -0
  37. package/dist/store/reducers/cluster/__test__/parseGroupsStatsQueryResponse.test.ts +121 -0
  38. package/dist/store/reducers/cluster/cluster.ts +46 -2
  39. package/dist/store/reducers/cluster/types.ts +29 -4
  40. package/dist/store/reducers/cluster/utils.ts +88 -0
  41. package/dist/store/reducers/executeQuery.ts +4 -3
  42. package/dist/store/reducers/nodes/types.ts +11 -1
  43. package/dist/store/reducers/nodes/utils.ts +6 -0
  44. package/dist/store/reducers/settings/settings.ts +2 -0
  45. package/dist/types/api/cluster.ts +3 -0
  46. package/dist/types/api/netInfo.ts +1 -1
  47. package/dist/types/api/nodes.ts +24 -0
  48. package/dist/types/api/query.ts +23 -8
  49. package/dist/types/store/query.ts +6 -0
  50. package/dist/utils/constants.ts +3 -0
  51. package/dist/utils/developerUI/__test__/developerUI.test.ts +50 -0
  52. package/dist/utils/developerUI/developerUI.ts +42 -0
  53. package/dist/utils/diagnostics.ts +1 -0
  54. package/dist/utils/hooks/index.ts +1 -0
  55. package/dist/utils/hooks/useSearchQuery.ts +9 -0
  56. package/dist/utils/query.ts +60 -12
  57. package/package.json +1 -1
  58. package/dist/utils/developerUI.ts +0 -32
  59. package/dist/utils/index.js +0 -9
  60. /package/dist/{components/VirtualTable/utils.ts → utils/index.ts} +0 -0
@@ -1,16 +1,20 @@
1
1
  import {useDispatch} from 'react-redux';
2
2
  import {useCallback} from 'react';
3
3
 
4
- import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
4
+ import {useAutofetcher, useSearchQuery, useTypedSelector} from '../../../../../utils/hooks';
5
5
  import {
6
6
  getTopNodesByLoad,
7
7
  selectTopNodesByLoad,
8
8
  setDataWasNotLoaded,
9
9
  } from '../../../../../store/reducers/tenantOverview/topNodesByLoad/topNodesByLoad';
10
+ import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
10
11
  import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
11
12
  import {getTopNodesByLoadColumns} from '../../../../Nodes/getNodesColumns';
12
- import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
13
13
 
14
+ import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
15
+
16
+ import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
17
+ import {getSectionTitle} from '../getSectionTitle';
14
18
  import i18n from '../i18n';
15
19
 
16
20
  interface TopNodesByLoadProps {
@@ -21,6 +25,8 @@ interface TopNodesByLoadProps {
21
25
  export function TopNodesByLoad({path, additionalNodesProps}: TopNodesByLoadProps) {
22
26
  const dispatch = useDispatch();
23
27
 
28
+ const query = useSearchQuery();
29
+
24
30
  const {wasLoaded, loading, error} = useTypedSelector((state) => state.topNodesByLoad);
25
31
  const {autorefresh} = useTypedSelector((state) => state.schema);
26
32
  const topNodes = useTypedSelector(selectTopNodesByLoad);
@@ -39,11 +45,20 @@ export function TopNodesByLoad({path, additionalNodesProps}: TopNodesByLoadProps
39
45
 
40
46
  useAutofetcher(fetchNodes, [fetchNodes], autorefresh);
41
47
 
48
+ const title = getSectionTitle({
49
+ entity: i18n('nodes'),
50
+ postfix: i18n('by-load'),
51
+ link: getTenantPath({
52
+ ...query,
53
+ [TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.nodes,
54
+ }),
55
+ });
56
+
42
57
  return (
43
58
  <TenantOverviewTableLayout
44
59
  data={topNodes || []}
45
60
  columns={columns}
46
- title="Top nodes by load"
61
+ title={title}
47
62
  loading={loading}
48
63
  wasLoaded={wasLoaded}
49
64
  error={error}
@@ -3,6 +3,7 @@ import {useHistory, useLocation} from 'react-router';
3
3
  import {useCallback} from 'react';
4
4
 
5
5
  import {
6
+ TENANT_DIAGNOSTICS_TABS_IDS,
6
7
  TENANT_PAGE,
7
8
  TENANT_PAGES_IDS,
8
9
  TENANT_QUERY_TABS_ID,
@@ -14,9 +15,13 @@ import {
14
15
  import {changeUserInput} from '../../../../../store/reducers/executeQuery';
15
16
  import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
16
17
  import {parseQuery} from '../../../../../routes';
18
+
17
19
  import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
18
20
  import {getTenantOverviewTopQueriesColumns} from '../../TopQueries/getTopQueriesColumns';
21
+
19
22
  import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
23
+ import {getSectionTitle} from '../getSectionTitle';
24
+ import i18n from '../i18n';
20
25
 
21
26
  interface TopQueriesProps {
22
27
  path: string;
@@ -27,6 +32,8 @@ export function TopQueries({path}: TopQueriesProps) {
27
32
  const location = useLocation();
28
33
  const history = useHistory();
29
34
 
35
+ const query = parseQuery(location);
36
+
30
37
  const {autorefresh} = useTypedSelector((state) => state.schema);
31
38
 
32
39
  const {
@@ -68,12 +75,21 @@ export function TopQueries({path}: TopQueriesProps) {
68
75
  [dispatch, history, location],
69
76
  );
70
77
 
78
+ const title = getSectionTitle({
79
+ entity: i18n('queries'),
80
+ postfix: i18n('by-cpu-time'),
81
+ link: getTenantPath({
82
+ ...query,
83
+ [TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.topQueries,
84
+ }),
85
+ });
86
+
71
87
  return (
72
88
  <TenantOverviewTableLayout
73
89
  data={data || []}
74
90
  columns={columns}
75
91
  onRowClick={handleRowClick}
76
- title="Top queries by cpu time"
92
+ title={title}
77
93
  loading={loading}
78
94
  wasLoaded={wasLoaded}
79
95
  error={error}
@@ -2,12 +2,20 @@ import {useDispatch} from 'react-redux';
2
2
  import {useLocation} from 'react-router';
3
3
 
4
4
  import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
5
+ import {parseQuery} from '../../../../../routes';
6
+
5
7
  import {
6
8
  sendTenantOverviewTopShardsQuery,
7
9
  setDataWasNotLoaded,
8
10
  } from '../../../../../store/reducers/tenantOverview/topShards/tenantOverviewTopShards';
11
+ import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
9
12
  import {getTopShardsColumns} from '../../TopShards/getTopShardsColumns';
13
+
14
+ import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
15
+
10
16
  import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
17
+ import {getSectionTitle} from '../getSectionTitle';
18
+ import i18n from '../i18n';
11
19
 
12
20
  interface TopShardsProps {
13
21
  path: string;
@@ -17,6 +25,8 @@ export const TopShards = ({path}: TopShardsProps) => {
17
25
  const dispatch = useDispatch();
18
26
  const location = useLocation();
19
27
 
28
+ const query = parseQuery(location);
29
+
20
30
  const {autorefresh, currentSchemaPath} = useTypedSelector((state) => state.schema);
21
31
 
22
32
  const {
@@ -39,11 +49,20 @@ export const TopShards = ({path}: TopShardsProps) => {
39
49
 
40
50
  const columns = getTopShardsColumns(path, location);
41
51
 
52
+ const title = getSectionTitle({
53
+ entity: i18n('shards'),
54
+ postfix: i18n('by-cpu-usage'),
55
+ link: getTenantPath({
56
+ ...query,
57
+ [TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.topShards,
58
+ }),
59
+ });
60
+
42
61
  return (
43
62
  <TenantOverviewTableLayout
44
63
  data={data || []}
45
64
  columns={columns}
46
- title="Top shards by cpu usage"
65
+ title={title}
47
66
  loading={loading}
48
67
  wasLoaded={wasLoaded}
49
68
  error={error}
@@ -1,16 +1,20 @@
1
1
  import {useDispatch} from 'react-redux';
2
2
  import {useCallback} from 'react';
3
3
 
4
- import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
4
+ import {useAutofetcher, useTypedSelector, useSearchQuery} from '../../../../../utils/hooks';
5
5
  import {
6
6
  getTopNodesByMemory,
7
7
  selectTopNodesByMemory,
8
8
  setDataWasNotLoaded,
9
9
  } from '../../../../../store/reducers/tenantOverview/topNodesByMemory/topNodesByMemory';
10
+ import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
10
11
  import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
11
12
  import {getTopNodesByMemoryColumns} from '../../../../Nodes/getNodesColumns';
12
- import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
13
13
 
14
+ import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
15
+
16
+ import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
17
+ import {getSectionTitle} from '../getSectionTitle';
14
18
  import i18n from '../i18n';
15
19
 
16
20
  interface TopNodesByMemoryProps {
@@ -21,6 +25,8 @@ interface TopNodesByMemoryProps {
21
25
  export function TopNodesByMemory({path, additionalNodesProps}: TopNodesByMemoryProps) {
22
26
  const dispatch = useDispatch();
23
27
 
28
+ const query = useSearchQuery();
29
+
24
30
  const {wasLoaded, loading, error} = useTypedSelector((state) => state.topNodesByMemory);
25
31
  const {autorefresh} = useTypedSelector((state) => state.schema);
26
32
  const topNodes = useTypedSelector(selectTopNodesByMemory);
@@ -41,11 +47,20 @@ export function TopNodesByMemory({path, additionalNodesProps}: TopNodesByMemoryP
41
47
 
42
48
  useAutofetcher(fetchNodes, [fetchNodes], autorefresh);
43
49
 
50
+ const title = getSectionTitle({
51
+ entity: i18n('nodes'),
52
+ postfix: i18n('by-memory'),
53
+ link: getTenantPath({
54
+ ...query,
55
+ [TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.nodes,
56
+ }),
57
+ });
58
+
44
59
  return (
45
60
  <TenantOverviewTableLayout
46
61
  data={topNodes || []}
47
62
  columns={columns}
48
- title="Top nodes by memory"
63
+ title={title}
49
64
  loading={loading}
50
65
  wasLoaded={wasLoaded}
51
66
  error={error}
@@ -1,3 +1,4 @@
1
+ import type {ReactNode} from 'react';
1
2
  import cn from 'bem-cn-lite';
2
3
 
3
4
  import DataTable from '@gravity-ui/react-data-table';
@@ -14,7 +15,7 @@ import {ResponseError} from '../../../../components/Errors/ResponseError';
14
15
  const b = cn('tenant-overview');
15
16
 
16
17
  interface TenantOverviewTableLayoutProps<T> extends Omit<DataTableProps<T>, 'theme'> {
17
- title: string;
18
+ title: ReactNode;
18
19
  loading?: boolean;
19
20
  wasLoaded?: boolean;
20
21
  error?: IResponseError;
@@ -1,14 +1,20 @@
1
1
  import {useCallback} from 'react';
2
2
  import {useDispatch} from 'react-redux';
3
3
 
4
- import {useAutofetcher, useTypedSelector} from '../../../../../utils/hooks';
4
+ import {useAutofetcher, useSearchQuery, useTypedSelector} from '../../../../../utils/hooks';
5
5
  import {
6
6
  setDataWasNotLoaded,
7
7
  getTopStorageGroups,
8
8
  selectTopStorageGroups,
9
9
  } from '../../../../../store/reducers/tenantOverview/topStorageGroups/topStorageGroups';
10
+ import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
10
11
  import {getStorageTopGroupsColumns} from '../../../../Storage/StorageGroups/getStorageGroupsColumns';
12
+
13
+ import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
14
+
11
15
  import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
16
+ import {getSectionTitle} from '../getSectionTitle';
17
+ import i18n from '../i18n';
12
18
 
13
19
  interface TopGroupsProps {
14
20
  tenant?: string;
@@ -17,6 +23,8 @@ interface TopGroupsProps {
17
23
  export function TopGroups({tenant}: TopGroupsProps) {
18
24
  const dispatch = useDispatch();
19
25
 
26
+ const query = useSearchQuery();
27
+
20
28
  const {autorefresh} = useTypedSelector((state) => state.schema);
21
29
  const {loading, wasLoaded, error} = useTypedSelector((state) => state.topStorageGroups);
22
30
  const topGroups = useTypedSelector(selectTopStorageGroups);
@@ -36,11 +44,20 @@ export function TopGroups({tenant}: TopGroupsProps) {
36
44
 
37
45
  useAutofetcher(fetchData, [fetchData], autorefresh);
38
46
 
47
+ const title = getSectionTitle({
48
+ entity: i18n('groups'),
49
+ postfix: i18n('by-usage'),
50
+ link: getTenantPath({
51
+ ...query,
52
+ [TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.storage,
53
+ }),
54
+ });
55
+
39
56
  return (
40
57
  <TenantOverviewTableLayout
41
58
  data={topGroups || []}
42
59
  columns={columns}
43
- title="Top groups by usage"
60
+ title={title}
44
61
  loading={loading}
45
62
  wasLoaded={wasLoaded}
46
63
  error={error}
@@ -12,7 +12,10 @@ import type {KeyValueRow} from '../../../../../types/api/query';
12
12
  import {formatBytes, getSizeWithSignificantDigits} from '../../../../../utils/bytesParsers';
13
13
  import {LinkToSchemaObject} from '../../../../../components/LinkToSchemaObject/LinkToSchemaObject';
14
14
  import {CellWithPopover} from '../../../../../components/CellWithPopover/CellWithPopover';
15
+
15
16
  import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
17
+ import {getSectionTitle} from '../getSectionTitle';
18
+ import i18n from '../i18n';
16
19
 
17
20
  import '../TenantOverview.scss';
18
21
 
@@ -72,12 +75,16 @@ export function TopTables({path}: TopTablesProps) {
72
75
  ) : null,
73
76
  },
74
77
  ];
78
+ const title = getSectionTitle({
79
+ entity: i18n('tables'),
80
+ postfix: i18n('by-size'),
81
+ });
75
82
 
76
83
  return (
77
84
  <TenantOverviewTableLayout
78
85
  data={data || []}
79
86
  columns={columns}
80
- title="Top tables by size"
87
+ title={title}
81
88
  loading={loading}
82
89
  wasLoaded={wasLoaded}
83
90
  error={error}
@@ -0,0 +1,28 @@
1
+ import {InternalLink} from '../../../../components/InternalLink/InternalLink';
2
+
3
+ import i18n from './i18n';
4
+
5
+ interface GetSectionTitleParams {
6
+ entity: string;
7
+ postfix: string;
8
+ prefix?: string;
9
+ link?: string;
10
+ }
11
+
12
+ // Titles are formed by the principle "Top entities by parameter"
13
+ export const getSectionTitle = ({
14
+ prefix = i18n('top'),
15
+ entity,
16
+ postfix,
17
+ link,
18
+ }: GetSectionTitleParams) => {
19
+ if (link) {
20
+ return (
21
+ <>
22
+ {prefix} <InternalLink to={link}>{entity}</InternalLink> {postfix}
23
+ </>
24
+ );
25
+ }
26
+
27
+ return `${prefix} ${entity} ${postfix}`;
28
+ };
@@ -7,5 +7,21 @@
7
7
  "title.pools": "Pools",
8
8
  "title.metrics": "Metrics",
9
9
 
10
- "top-groups.empty-data": "No such groups"
10
+ "top-groups.empty-data": "No such groups",
11
+
12
+ "top": "Top",
13
+
14
+ "nodes": "nodes",
15
+ "shards": "shards",
16
+ "groups": "groups",
17
+ "queries": "queries",
18
+ "tables": "tables",
19
+
20
+ "by-pools-usage": "by pools usage",
21
+ "by-cpu-time": "by cpu time",
22
+ "by-cpu-usage": "by cpu usage",
23
+ "by-load": "by load",
24
+ "by-memory": "by memory",
25
+ "by-usage": "by usage",
26
+ "by-size": "by size"
11
27
  }
@@ -7,5 +7,21 @@
7
7
  "title.pools": "Пулы",
8
8
  "title.metrics": "Метрики",
9
9
 
10
- "top-groups.empty-data": "Нет групп"
10
+ "top-groups.empty-data": "Нет групп",
11
+
12
+ "top": "Топ",
13
+
14
+ "nodes": "узлов",
15
+ "shards": "шардов",
16
+ "groups": "групп",
17
+ "queries": "запросов",
18
+ "tables": "таблиц",
19
+
20
+ "by-pools-usage": "по использованию пулов",
21
+ "by-cpu-time": "по времени cpu",
22
+ "by-cpu-usage": "по использованию cpu",
23
+ "by-load": "по нагрузке",
24
+ "by-memory": "по памяти",
25
+ "by-usage": "по потреблению",
26
+ "by-size": "по размеру"
11
27
  }
@@ -13,11 +13,19 @@
13
13
  & .data-table__table-wrapper {
14
14
  padding-bottom: 0;
15
15
  }
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;
21
29
  }
22
30
 
23
31
  &__error {
@@ -1,26 +1,30 @@
1
- import React, {type ReactNode, useEffect, useState} from 'react';
1
+ import React, {useEffect, useState} from 'react';
2
2
  import {useDispatch} from 'react-redux';
3
3
  import cn from 'bem-cn-lite';
4
4
  import JSONTree from 'react-json-inspector';
5
5
 
6
- import {RadioButton} from '@gravity-ui/uikit';
6
+ import {RadioButton, Tabs} from '@gravity-ui/uikit';
7
7
 
8
8
  import CopyToClipboard from '../../../../components/CopyToClipboard/CopyToClipboard';
9
9
  import Divider from '../../../../components/Divider/Divider';
10
10
  import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton';
11
11
  import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
12
12
  import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus';
13
+ import {QueryResultTable} from '../../../../components/QueryResultTable/QueryResultTable';
13
14
 
14
15
  import type {ValueOf} from '../../../../types/common';
15
16
  import type {IQueryResult, QueryErrorResponse} from '../../../../types/store/query';
17
+ import type {ColumnType, KeyValueRow} from '../../../../types/api/query';
16
18
  import {disableFullscreen} from '../../../../store/reducers/fullscreen';
17
19
  import {prepareQueryError} from '../../../../utils/query';
18
20
  import {useTypedSelector} from '../../../../utils/hooks';
21
+ import {getArray} from '../../../../utils';
19
22
 
20
23
  import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers';
21
24
 
22
25
  import {ResultIssues} from '../Issues/Issues';
23
26
  import {QueryDuration} from '../QueryDuration/QueryDuration';
27
+ import {getPreparedResult} from '../utils/getPreparedResult';
24
28
 
25
29
  import './ExecuteResult.scss';
26
30
 
@@ -39,30 +43,35 @@ const resultOptions = [
39
43
  ];
40
44
 
41
45
  interface ExecuteResultProps {
42
- textResults: string;
43
- result: ReactNode;
46
+ data: IQueryResult | undefined;
44
47
  stats: IQueryResult['stats'] | undefined;
45
48
  error: string | QueryErrorResponse | undefined;
46
- copyDisabled?: boolean;
47
49
  isResultsCollapsed?: boolean;
48
50
  onCollapseResults: VoidFunction;
49
51
  onExpandResults: VoidFunction;
50
52
  }
51
53
 
52
54
  export function ExecuteResult({
53
- textResults,
54
- result,
55
+ data,
55
56
  stats,
56
57
  error,
57
- copyDisabled,
58
58
  isResultsCollapsed,
59
59
  onCollapseResults,
60
60
  onExpandResults,
61
61
  }: ExecuteResultProps) {
62
+ const [selectedResultSet, setSelectedResultSet] = useState(0);
62
63
  const [activeSection, setActiveSection] = useState<SectionID>(resultOptionsIds.result);
64
+
63
65
  const isFullscreen = useTypedSelector((state) => state.fullscreen);
64
66
  const dispatch = useDispatch();
65
67
 
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
+
66
75
  useEffect(() => {
67
76
  return () => {
68
77
  dispatch(disableFullscreen());
@@ -73,6 +82,37 @@ export function ExecuteResult({
73
82
  setActiveSection(value as SectionID);
74
83
  };
75
84
 
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
+
76
116
  const renderClipboardButton = () => {
77
117
  return (
78
118
  <CopyToClipboard
@@ -108,12 +148,14 @@ export function ExecuteResult({
108
148
  };
109
149
 
110
150
  const renderResult = () => {
151
+ const content = renderContent();
152
+
111
153
  return (
112
154
  <React.Fragment>
113
- {result}
155
+ {content}
114
156
  {isFullscreen && (
115
157
  <Fullscreen>
116
- <div className={b('result', {fullscreen: true})}>{result}</div>
158
+ <div className={b('result-fullscreen-wrapper')}>{content}</div>
117
159
  </Fullscreen>
118
160
  )}
119
161
  </React.Fragment>
@@ -126,13 +168,15 @@ export function ExecuteResult({
126
168
  }
127
169
 
128
170
  if (typeof error === 'object' && error.data?.issues && Array.isArray(error.data.issues)) {
171
+ const content = <ResultIssues data={error.data} />;
172
+
129
173
  return (
130
174
  <React.Fragment>
131
- <ResultIssues data={error.data} />
175
+ {content}
132
176
  {isFullscreen && (
133
177
  <Fullscreen>
134
- <div className={b('result', {fullscreen: true})}>
135
- <ResultIssues data={error.data} />
178
+ <div className={b('result-fullscreen-wrapper', b('result'))}>
179
+ {content}
136
180
  </div>
137
181
  </Fullscreen>
138
182
  )}
@@ -145,6 +189,19 @@ export function ExecuteResult({
145
189
  return <div className={b('error')}>{parsedError}</div>;
146
190
  };
147
191
 
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
+
148
205
  return (
149
206
  <React.Fragment>
150
207
  <div className={b('controls')}>
@@ -174,11 +231,8 @@ export function ExecuteResult({
174
231
  />
175
232
  </div>
176
233
  </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()}
182
236
  </React.Fragment>
183
237
  );
184
238
  }
@@ -16,11 +16,12 @@ import {QueryExecutionStatus} from '../../../../components/QueryExecutionStatus'
16
16
  import {explainVersions} from '../../../../store/reducers/explainQuery';
17
17
  import {disableFullscreen} from '../../../../store/reducers/fullscreen';
18
18
 
19
- import {renderExplainNode} from '../../../../utils';
20
19
  import {LANGUAGE_S_EXPRESSION_ID} from '../../../../utils/monaco';
21
20
 
22
21
  import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers';
23
22
 
23
+ import {renderExplainNode} from './utils';
24
+
24
25
  import './ExplainResult.scss';
25
26
 
26
27
  const b = cn('ydb-query-explain-result');
@@ -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
+ };