ydb-embedded-ui 4.21.1 → 4.23.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ };