ydb-embedded-ui 2.4.4 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/components/InfoViewer/InfoViewer.scss +3 -3
  3. package/dist/components/InfoViewer/formatters/schema.ts +2 -1
  4. package/dist/components/InfoViewer/schemaInfo/CDCStreamInfo.tsx +23 -22
  5. package/dist/components/InfoViewer/schemaInfo/PersQueueGroupInfo.tsx +8 -13
  6. package/dist/components/InfoViewer/schemaInfo/TableIndexInfo.tsx +2 -12
  7. package/dist/components/InfoViewer/schemaOverview/CDCStreamOverview.tsx +3 -16
  8. package/dist/components/InfoViewer/schemaOverview/PersQueueGroupOverview.tsx +8 -13
  9. package/dist/components/InfoViewer/utils.ts +6 -6
  10. package/dist/components/Loader/Loader.scss +6 -3
  11. package/dist/components/Loader/Loader.tsx +7 -5
  12. package/dist/components/Loader/index.ts +1 -0
  13. package/dist/components/UptimeFIlter/UptimeFilter.tsx +21 -0
  14. package/dist/components/UptimeFIlter/index.ts +1 -0
  15. package/dist/containers/Node/Node.scss +1 -0
  16. package/dist/containers/Node/Node.tsx +3 -8
  17. package/dist/containers/Node/NodeStructure/NodeStructure.scss +0 -6
  18. package/dist/containers/Node/NodeStructure/NodeStructure.tsx +1 -1
  19. package/dist/containers/Nodes/Nodes.js +22 -10
  20. package/dist/{components → containers}/NodesViewer/NodesViewer.js +49 -62
  21. package/dist/{components → containers}/NodesViewer/NodesViewer.scss +0 -0
  22. package/dist/containers/Storage/Pdisk/Pdisk.scss +1 -1
  23. package/dist/containers/Storage/Pdisk/Pdisk.tsx +3 -9
  24. package/dist/containers/Storage/Pdisk/__tests__/colors.tsx +1 -1
  25. package/dist/containers/Storage/Storage.js +46 -11
  26. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +39 -32
  27. package/dist/containers/Storage/StorageNodes/StorageNodes.scss +2 -2
  28. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +35 -17
  29. package/dist/containers/Storage/StorageNodes/i18n/en.json +6 -4
  30. package/dist/containers/Storage/StorageNodes/i18n/ru.json +6 -4
  31. package/dist/containers/Storage/UsageFilter/UsageFilter.scss +10 -5
  32. package/dist/containers/Tenant/Acl/Acl.js +1 -7
  33. package/dist/containers/Tenant/Diagnostics/Compute/Compute.js +22 -14
  34. package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.tsx +52 -10
  35. package/dist/containers/Tenant/Diagnostics/Describe/Describe.scss +0 -8
  36. package/dist/containers/Tenant/Diagnostics/Describe/Describe.tsx +42 -15
  37. package/dist/containers/Tenant/Diagnostics/Diagnostics.scss +0 -7
  38. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +19 -15
  39. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +1 -1
  40. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.tsx +13 -5
  41. package/dist/containers/Tenant/Diagnostics/Network/Network.js +17 -4
  42. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +50 -16
  43. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +16 -2
  44. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.js +1 -0
  45. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.js +21 -13
  46. package/dist/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx +1 -5
  47. package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.js +4 -4
  48. package/dist/containers/Tenant/QueryEditor/SaveQuery/SaveQuery.js +1 -4
  49. package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.js +23 -28
  50. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +2 -2
  51. package/dist/containers/Tenant/TenantPages.tsx +1 -1
  52. package/dist/containers/Tenant/utils/schema.ts +84 -0
  53. package/dist/containers/Tenant/utils/schemaActions.ts +9 -20
  54. package/dist/services/api.d.ts +17 -11
  55. package/dist/store/reducers/clusterNodes.js +29 -10
  56. package/dist/store/reducers/describe.ts +56 -14
  57. package/dist/store/reducers/healthcheckInfo.ts +23 -8
  58. package/dist/store/reducers/network.js +22 -1
  59. package/dist/store/reducers/nodes.js +37 -3
  60. package/dist/store/reducers/schema.ts +229 -0
  61. package/dist/store/reducers/storage.js +59 -5
  62. package/dist/types/api/enums.ts +10 -0
  63. package/dist/types/api/nodes.ts +96 -0
  64. package/dist/types/api/pdisk.ts +48 -0
  65. package/dist/types/api/schema.ts +148 -9
  66. package/dist/types/api/storage.ts +3 -173
  67. package/dist/types/api/tablet.ts +97 -0
  68. package/dist/types/api/vdisk.ts +120 -0
  69. package/dist/types/store/describe.ts +8 -2
  70. package/dist/types/store/healthcheck.ts +12 -0
  71. package/dist/types/store/schema.ts +52 -0
  72. package/dist/utils/index.js +6 -2
  73. package/dist/utils/nodes.ts +9 -0
  74. package/dist/utils/pdisk.ts +1 -1
  75. package/dist/utils/storage.ts +1 -1
  76. package/package.json +1 -1
  77. package/dist/store/reducers/schema.js +0 -148
@@ -1,5 +1,5 @@
1
- import {ReactNode, useMemo} from 'react';
2
- import {useDispatch, useSelector} from 'react-redux';
1
+ import {ReactNode, useCallback, useMemo} from 'react';
2
+ import {shallowEqual, useDispatch, useSelector} from 'react-redux';
3
3
  import cn from 'bem-cn-lite';
4
4
 
5
5
  import {Loader} from '@gravity-ui/uikit';
@@ -13,15 +13,24 @@ import {
13
13
  } from '../../../../components/InfoViewer/schemaInfo';
14
14
 
15
15
  import {EPathType, TColumnTableDescription} from '../../../../types/api/schema';
16
- import {isColumnEntityType, isTableType} from '../../utils/schema';
16
+ import {
17
+ isEntityWithMergedImplementation,
18
+ isColumnEntityType,
19
+ isTableType,
20
+ } from '../../utils/schema';
17
21
  //@ts-ignore
18
- import {getSchema, resetLoadingState} from '../../../../store/reducers/schema';
22
+ import {
23
+ getSchema,
24
+ getSchemaBatched,
25
+ resetLoadingState,
26
+ selectSchemaMergedChildrenPaths,
27
+ } from '../../../../store/reducers/schema';
19
28
  //@ts-ignore
20
29
  import {
21
30
  getOlapStats,
22
31
  resetLoadingState as resetOlapLoadingState,
23
32
  } from '../../../../store/reducers/olapStats';
24
- import {useAutofetcher} from '../../../../utils/hooks';
33
+ import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
25
34
 
26
35
  import './Overview.scss';
27
36
 
@@ -60,9 +69,7 @@ interface OverviewProps {
60
69
 
61
70
  const b = cn('kv-tenant-overview');
62
71
 
63
- function Overview(props: OverviewProps) {
64
- const {tenantName, type} = props;
65
-
72
+ function Overview({type, tenantName, className}: OverviewProps) {
66
73
  const dispatch = useDispatch();
67
74
 
68
75
  const {
@@ -78,14 +85,31 @@ function Overview(props: OverviewProps) {
78
85
 
79
86
  const loading = schemaLoading || olapStatsLoading;
80
87
 
81
- useAutofetcher(
82
- (isBackground) => {
88
+ const isEntityWithMergedImpl = isEntityWithMergedImplementation(type);
89
+
90
+ // There is a circular dependency here. Fetch data depends on children paths
91
+ // When data in store updated on fetch request,
92
+ // new object is set there, so source children array is updated
93
+ // This updates selector, the selector returns a new array, and data is fetched again
94
+ // To prevent it, shallowEqual, which compares array content, was added
95
+ const mergedChildrenPaths = useTypedSelector(
96
+ (state) => selectSchemaMergedChildrenPaths(state, currentSchemaPath, type),
97
+ shallowEqual,
98
+ );
99
+
100
+ const fetchData = useCallback(
101
+ (isBackground: boolean) => {
83
102
  if (!isBackground) {
84
103
  dispatch(resetLoadingState());
85
104
  }
86
105
 
87
106
  const schemaPath = currentSchemaPath || tenantName;
88
- dispatch(getSchema({path: schemaPath}));
107
+
108
+ if (!isEntityWithMergedImpl) {
109
+ dispatch(getSchema({path: schemaPath}));
110
+ } else if (mergedChildrenPaths) {
111
+ dispatch(getSchemaBatched([schemaPath, ...mergedChildrenPaths]));
112
+ }
89
113
 
90
114
  if (isTableType(type) && isColumnEntityType(type)) {
91
115
  if (!isBackground) {
@@ -94,10 +118,18 @@ function Overview(props: OverviewProps) {
94
118
  dispatch(getOlapStats({path: schemaPath}));
95
119
  }
96
120
  },
97
- [currentSchemaPath, dispatch, tenantName, type],
98
- autorefresh,
121
+ [
122
+ tenantName,
123
+ currentSchemaPath,
124
+ type,
125
+ isEntityWithMergedImpl,
126
+ mergedChildrenPaths,
127
+ dispatch,
128
+ ],
99
129
  );
100
130
 
131
+ useAutofetcher(fetchData, [fetchData], autorefresh);
132
+
101
133
  const tableSchema =
102
134
  currentItem?.PathDescription?.Table || currentItem?.PathDescription?.ColumnTableDescription;
103
135
 
@@ -128,7 +160,9 @@ function Overview(props: OverviewProps) {
128
160
  [EPathType.EPathTypeExtSubDomain]: undefined,
129
161
  [EPathType.EPathTypeColumnStore]: undefined,
130
162
  [EPathType.EPathTypeColumnTable]: undefined,
131
- [EPathType.EPathTypeCdcStream]: () => <CDCStreamInfo data={schemaData} />,
163
+ [EPathType.EPathTypeCdcStream]: () => (
164
+ <CDCStreamInfo data={schemaData} childrenPaths={mergedChildrenPaths} />
165
+ ),
132
166
  [EPathType.EPathTypePersQueueGroup]: () => <PersQueueGroupInfo data={schemaData} />,
133
167
  };
134
168
 
@@ -139,10 +173,10 @@ function Overview(props: OverviewProps) {
139
173
  );
140
174
  };
141
175
 
142
- return loading && !wasLoaded ? (
176
+ return (loading && !wasLoaded) || (isEntityWithMergedImpl && !mergedChildrenPaths) ? (
143
177
  renderLoader()
144
178
  ) : (
145
- <div className={props.className}>{renderContent()}</div>
179
+ <div className={className}>{renderContent()}</div>
146
180
  );
147
181
  }
148
182
 
@@ -53,6 +53,7 @@ class TenantOverview extends React.Component {
53
53
 
54
54
  componentDidMount() {
55
55
  const {tenantName, autorefresh, getTenantInfo} = this.props;
56
+ getTenantInfo({path: tenantName});
56
57
  this.autofetcher = new AutoFetcher();
57
58
  if (autorefresh) {
58
59
  this.autofetcher.start();
@@ -62,12 +63,25 @@ class TenantOverview extends React.Component {
62
63
 
63
64
  componentDidUpdate(prevProps) {
64
65
  const {autorefresh, tenantName, getTenantInfo} = this.props;
65
- if (autorefresh && !prevProps.autorefresh) {
66
- getTenantInfo({path: tenantName});
66
+
67
+ const restartAutorefresh = () => {
67
68
  this.autofetcher.stop();
68
69
  this.autofetcher.start();
69
70
  this.autofetcher.fetch(() => getTenantInfo({path: tenantName}));
71
+ };
72
+
73
+ if (prevProps.tenantName !== this.props.tenantName) {
74
+ getTenantInfo({path: tenantName});
75
+ if (autorefresh) {
76
+ restartAutorefresh();
77
+ }
70
78
  }
79
+
80
+ if (autorefresh && !prevProps.autorefresh) {
81
+ getTenantInfo({path: tenantName});
82
+ restartAutorefresh();
83
+ }
84
+
71
85
  if (!autorefresh && prevProps.autorefresh) {
72
86
  this.autofetcher.stop();
73
87
  }
@@ -90,6 +90,7 @@ class TopQueries extends React.Component {
90
90
  wasLoaded: false,
91
91
  data: undefined,
92
92
  });
93
+ this.getTopQueries();
93
94
  }
94
95
  }
95
96
 
@@ -47,17 +47,23 @@ function prepareDateSizeValue(value) {
47
47
  }
48
48
 
49
49
  function stringToDataTableSortOrder(value) {
50
- return value && value.split(',').map((columnId) => ({
51
- columnId,
52
- order: DataTable.DESCENDING,
53
- }));
50
+ return (
51
+ value &&
52
+ value.split(',').map((columnId) => ({
53
+ columnId,
54
+ order: DataTable.DESCENDING,
55
+ }))
56
+ );
54
57
  }
55
58
 
56
59
  function stringToQuerySortOrder(value) {
57
- return value && value.split(',').map((columnId) => ({
58
- columnId,
59
- order: 'DESC',
60
- }));
60
+ return (
61
+ value &&
62
+ value.split(',').map((columnId) => ({
63
+ columnId,
64
+ order: 'DESC',
65
+ }))
66
+ );
61
67
  }
62
68
 
63
69
  function dataTableToStringSortOrder(value = []) {
@@ -85,11 +91,13 @@ function TopShards({
85
91
 
86
92
  if (autorefresh) {
87
93
  autofetcher.start();
88
- autofetcher.fetch(() => sendShardQuery({
89
- database: path,
90
- path: currentSchemaPath,
91
- sortOrder: stringToQuerySortOrder(sortOrder),
92
- }));
94
+ autofetcher.fetch(() =>
95
+ sendShardQuery({
96
+ database: path,
97
+ path: currentSchemaPath,
98
+ sortOrder: stringToQuerySortOrder(sortOrder),
99
+ }),
100
+ );
93
101
  }
94
102
 
95
103
  return () => {
@@ -54,11 +54,7 @@ function ObjectGeneral(props: ObjectGeneralProps) {
54
54
  if (!tenantName) {
55
55
  return null;
56
56
  }
57
- return (
58
- <div className={b()}>
59
- {renderTabContent()}
60
- </div>
61
- );
57
+ return <div className={b()}>{renderTabContent()}</div>;
62
58
  };
63
59
 
64
60
  return renderContent();
@@ -1,4 +1,4 @@
1
- import React, {useCallback, useEffect, useRef, useState} from 'react';
1
+ import React, {useEffect, useRef, useState} from 'react';
2
2
  import cn from 'bem-cn-lite';
3
3
  import MonacoEditor from 'react-monaco-editor';
4
4
  import {Loader, RadioButton} from '@gravity-ui/uikit';
@@ -58,7 +58,7 @@ function GraphRoot(props) {
58
58
  updateComponentTheme(theme);
59
59
  }, [theme]);
60
60
 
61
- const render = useCallback(() => {
61
+ const render = () => {
62
62
  if (version === explainVersions.v2) {
63
63
  paranoid.current = getTopology('graphRoot', data, opts, shapes);
64
64
  paranoid.current.render();
@@ -66,7 +66,7 @@ function GraphRoot(props) {
66
66
  paranoid.current = getCompactTopology('graphRoot', data, opts);
67
67
  paranoid.current.renderCompactTopology();
68
68
  }
69
- }, [data, opts, shapes, version]);
69
+ };
70
70
 
71
71
  useEffect(() => {
72
72
  render();
@@ -86,7 +86,7 @@ function GraphRoot(props) {
86
86
  graphRoot.innerHTML = '';
87
87
 
88
88
  render();
89
- }, [componentTheme, render]);
89
+ }, [componentTheme]);
90
90
 
91
91
  useEffect(() => {
92
92
  paranoid.current?.updateData?.(props.data);
@@ -73,10 +73,7 @@ function SaveQuery({savedQueries, onSaveQuery, saveButtonDisabled}) {
73
73
  </div>
74
74
  )}
75
75
  <div className={b('dialog-row')}>
76
- <label
77
- htmlFor="queryName"
78
- className={b('field-title', 'required')}
79
- >
76
+ <label htmlFor="queryName" className={b('field-title', 'required')}>
80
77
  Query name
81
78
  </label>
82
79
  <div className={b('control-wrapper')}>
@@ -44,9 +44,10 @@ const formatTableStatsItem = createInfoFormatter({
44
44
  defaultValueFormatter: formatNumber,
45
45
  });
46
46
 
47
- const formatTableStats = (fields) => Object.entries(fields)
48
- .map(([label, value]) => formatTableStatsItem(label, value))
49
- .filter(({value}) => Boolean(value));
47
+ const formatTableStats = (fields) =>
48
+ Object.entries(fields)
49
+ .map(([label, value]) => formatTableStatsItem(label, value))
50
+ .filter(({value}) => Boolean(value));
50
51
 
51
52
  class SchemaInfoViewer extends React.Component {
52
53
  static propTypes = {
@@ -60,17 +61,18 @@ class SchemaInfoViewer extends React.Component {
60
61
 
61
62
  return (
62
63
  <div className={b('item')}>
63
- <InfoViewer
64
- title={title}
65
- info={itemData}
66
- />
64
+ <InfoViewer title={title} info={itemData} />
67
65
  </div>
68
66
  );
69
67
  }
70
68
 
71
69
  renderContent(data) {
72
70
  const {PathDescription = {}} = data;
73
- const {TableStats = {}, TabletMetrics = {}, Table: {PartitionConfig = {}} = {}} = PathDescription;
71
+ const {
72
+ TableStats = {},
73
+ TabletMetrics = {},
74
+ Table: {PartitionConfig = {}} = {},
75
+ } = PathDescription;
74
76
  const {
75
77
  PartCount,
76
78
  RowCount,
@@ -127,33 +129,30 @@ class SchemaInfoViewer extends React.Component {
127
129
  ];
128
130
 
129
131
  const tabletMetricsInfo = Object.keys(TabletMetrics).map((key) =>
130
- formatTabletMetricsItem(key, TabletMetrics[key])
132
+ formatTabletMetricsItem(key, TabletMetrics[key]),
131
133
  );
132
134
 
133
135
  const partitionConfigInfo = [];
134
136
 
135
137
  if (Array.isArray(FollowerGroups) && FollowerGroups.length > 0) {
136
- partitionConfigInfo.push(...Object.keys(FollowerGroups[0]).map((key) =>
137
- formatFollowerGroupItem(key, FollowerGroups[0][key])
138
- ));
139
- } else if (FollowerCount !== undefined) {
140
138
  partitionConfigInfo.push(
141
- formatPartitionConfigItem('FollowerCount', FollowerCount)
139
+ ...Object.keys(FollowerGroups[0]).map((key) =>
140
+ formatFollowerGroupItem(key, FollowerGroups[0][key]),
141
+ ),
142
142
  );
143
+ } else if (FollowerCount !== undefined) {
144
+ partitionConfigInfo.push(formatPartitionConfigItem('FollowerCount', FollowerCount));
143
145
  } else if (CrossDataCenterFollowerCount !== undefined) {
144
146
  partitionConfigInfo.push(
145
- formatPartitionConfigItem('CrossDataCenterFollowerCount', CrossDataCenterFollowerCount)
147
+ formatPartitionConfigItem(
148
+ 'CrossDataCenterFollowerCount',
149
+ CrossDataCenterFollowerCount,
150
+ ),
146
151
  );
147
152
  }
148
153
 
149
- if ([
150
- tabletMetricsInfo,
151
- partitionConfigInfo,
152
- tableStatsInfo.flat(),
153
- ].flat().length === 0) {
154
- return (
155
- <div className={b('item')}>Empty</div>
156
- );
154
+ if ([tabletMetricsInfo, partitionConfigInfo, tableStatsInfo.flat()].flat().length === 0) {
155
+ return <div className={b('item')}>Empty</div>;
157
156
  }
158
157
 
159
158
  return (
@@ -179,11 +178,7 @@ class SchemaInfoViewer extends React.Component {
179
178
  const {data} = this.props;
180
179
 
181
180
  if (data) {
182
- return (
183
- <div className={b()}>
184
- {this.renderContent(data)}
185
- </div>
186
- );
181
+ return <div className={b()}>{this.renderContent(data)}</div>;
187
182
  } else {
188
183
  return <div className="error">no schema data</div>;
189
184
  }
@@ -6,7 +6,7 @@ import {NavigationTree} from 'ydb-ui-components';
6
6
  import {setCurrentSchemaPath, preloadSchemas} from '../../../../store/reducers/schema';
7
7
  import type {EPathType, TEvDescribeSchemeResult} from '../../../../types/api/schema';
8
8
 
9
- import {mapPathTypeToNavigationTreeType} from '../../utils/schema';
9
+ import {isChildlessPathType, mapPathTypeToNavigationTreeType} from '../../utils/schema';
10
10
  import {getActions} from '../../utils/schemaActions';
11
11
 
12
12
  interface SchemaTreeProps {
@@ -42,7 +42,7 @@ export function SchemaTree(props: SchemaTreeProps) {
42
42
  type: mapPathTypeToNavigationTreeType(PathType, PathSubType),
43
43
  // FIXME: should only be explicitly set to true for tables with indexes
44
44
  // at the moment of writing there is no property to determine this, fix later
45
- expandable: true,
45
+ expandable: !isChildlessPathType(PathType, PathSubType),
46
46
  };
47
47
  });
48
48
 
@@ -14,7 +14,7 @@ export enum TenantInfoTabsIds {
14
14
  export enum TenantTabsGroups {
15
15
  info = 'info',
16
16
  general = 'general',
17
- generalTab = 'generalTab'
17
+ generalTab = 'generalTab',
18
18
  }
19
19
 
20
20
  export const TENANT_GENERAL_TABS = [
@@ -88,3 +88,87 @@ const pathTypeToIsColumn: Record<EPathType, boolean> = {
88
88
  };
89
89
 
90
90
  export const isColumnEntityType = (type?: EPathType) => (type && pathTypeToIsColumn[type]) ?? false;
91
+
92
+ // ====================
93
+
94
+ const pathTypeToIsDatabase: Record<EPathType, boolean> = {
95
+ [EPathType.EPathTypeSubDomain]: true,
96
+ [EPathType.EPathTypeExtSubDomain]: true,
97
+
98
+ [EPathType.EPathTypeInvalid]: false,
99
+ [EPathType.EPathTypeDir]: false,
100
+ [EPathType.EPathTypeColumnStore]: false,
101
+ [EPathType.EPathTypeColumnTable]: false,
102
+ [EPathType.EPathTypeTable]: false,
103
+ [EPathType.EPathTypeTableIndex]: false,
104
+ [EPathType.EPathTypeCdcStream]: false,
105
+ [EPathType.EPathTypePersQueueGroup]: false,
106
+ };
107
+
108
+ export const isDatabaseEntityType = (type?: EPathType) =>
109
+ (type && pathTypeToIsDatabase[type]) ?? false;
110
+
111
+ // ====================
112
+
113
+ const pathTypeToIsCdcStream: Record<EPathType, boolean> = {
114
+ [EPathType.EPathTypeCdcStream]: true,
115
+
116
+ [EPathType.EPathTypeInvalid]: false,
117
+ [EPathType.EPathTypeColumnStore]: false,
118
+ [EPathType.EPathTypeColumnTable]: false,
119
+ [EPathType.EPathTypeDir]: false,
120
+ [EPathType.EPathTypeTable]: false,
121
+ [EPathType.EPathTypeSubDomain]: false,
122
+ [EPathType.EPathTypeTableIndex]: false,
123
+ [EPathType.EPathTypeExtSubDomain]: false,
124
+ [EPathType.EPathTypePersQueueGroup]: false,
125
+ };
126
+
127
+ export const isCdcStreamEntityType = (type?: EPathType) =>
128
+ (type && pathTypeToIsCdcStream[type]) ?? false;
129
+
130
+ // ====================
131
+
132
+ const pathTypeToEntityWithMergedImplementation: Record<EPathType, boolean> = {
133
+ [EPathType.EPathTypeCdcStream]: true,
134
+
135
+ [EPathType.EPathTypePersQueueGroup]: false,
136
+ [EPathType.EPathTypeInvalid]: false,
137
+ [EPathType.EPathTypeColumnStore]: false,
138
+ [EPathType.EPathTypeColumnTable]: false,
139
+ [EPathType.EPathTypeDir]: false,
140
+ [EPathType.EPathTypeTable]: false,
141
+ [EPathType.EPathTypeSubDomain]: false,
142
+ [EPathType.EPathTypeTableIndex]: false,
143
+ [EPathType.EPathTypeExtSubDomain]: false,
144
+ };
145
+
146
+ export const isEntityWithMergedImplementation = (type?: EPathType) =>
147
+ (type && pathTypeToEntityWithMergedImplementation[type]) ?? false;
148
+
149
+ // ====================
150
+
151
+ const pathSubTypeToChildless: Record<EPathSubType, boolean> = {
152
+ [EPathSubType.EPathSubTypeSyncIndexImplTable]: true,
153
+ [EPathSubType.EPathSubTypeAsyncIndexImplTable]: true,
154
+
155
+ [EPathSubType.EPathSubTypeStreamImpl]: false,
156
+ [EPathSubType.EPathSubTypeEmpty]: false,
157
+ };
158
+
159
+ const pathTypeToChildless: Record<EPathType, boolean> = {
160
+ [EPathType.EPathTypeCdcStream]: true,
161
+ [EPathType.EPathTypePersQueueGroup]: true,
162
+
163
+ [EPathType.EPathTypeInvalid]: false,
164
+ [EPathType.EPathTypeColumnStore]: false,
165
+ [EPathType.EPathTypeColumnTable]: false,
166
+ [EPathType.EPathTypeDir]: false,
167
+ [EPathType.EPathTypeTable]: false,
168
+ [EPathType.EPathTypeSubDomain]: false,
169
+ [EPathType.EPathTypeTableIndex]: false,
170
+ [EPathType.EPathTypeExtSubDomain]: false,
171
+ };
172
+
173
+ export const isChildlessPathType = (type?: EPathType, subType?: EPathSubType) =>
174
+ ((subType && pathSubTypeToChildless[subType]) || (type && pathTypeToChildless[type])) ?? false;
@@ -37,9 +37,9 @@ const bindActions = (
37
37
  ) => {
38
38
  const inputQuery = (tmpl: (path: string) => string) => () => {
39
39
  dispatch(changeUserInput({input: tmpl(path)}));
40
- dispatch(setTopLevelTab(TenantGeneralTabsIds.query))
40
+ dispatch(setTopLevelTab(TenantGeneralTabsIds.query));
41
41
  setActivePath(path);
42
- }
42
+ };
43
43
 
44
44
  return {
45
45
  createTable: inputQuery(createTableTemplate),
@@ -66,7 +66,7 @@ const bindActions = (
66
66
  },
67
67
  openPreview: () => {
68
68
  dispatch(setShowPreview(true));
69
- dispatch(setTopLevelTab(TenantGeneralTabsIds.query))
69
+ dispatch(setTopLevelTab(TenantGeneralTabsIds.query));
70
70
  setActivePath(path);
71
71
  },
72
72
  };
@@ -74,27 +74,18 @@ const bindActions = (
74
74
 
75
75
  type ActionsSet = ReturnType<Required<NavigationTreeProps>['getActions']>;
76
76
 
77
- export const getActions = (
78
- dispatch: Dispatch<any>,
79
- setActivePath: (path: string) => void,
80
- ) =>
77
+ export const getActions =
78
+ (dispatch: Dispatch<any>, setActivePath: (path: string) => void) =>
81
79
  (path: string, type: NavigationTreeNodeType) => {
82
80
  const actions = bindActions(path, dispatch, setActivePath);
83
81
  const copyItem = {text: 'Copy path', action: actions.copyPath};
84
82
 
85
83
  const DIR_SET: ActionsSet = [
86
- [
87
- copyItem,
88
- ],
89
- [
90
- {text: 'Create table...', action: actions.createTable},
91
- ],
84
+ [copyItem],
85
+ [{text: 'Create table...', action: actions.createTable}],
92
86
  ];
93
87
  const TABLE_SET: ActionsSet = [
94
- [
95
- {text: 'Open preview', action: actions.openPreview},
96
- copyItem,
97
- ],
88
+ [{text: 'Open preview', action: actions.openPreview}, copyItem],
98
89
  [
99
90
  {text: 'Alter table...', action: actions.alterTable},
100
91
  {text: 'Select query...', action: actions.selectQuery},
@@ -102,9 +93,7 @@ export const getActions = (
102
93
  ],
103
94
  ];
104
95
 
105
- const JUST_COPY: ActionsSet = [
106
- copyItem,
107
- ];
96
+ const JUST_COPY: ActionsSet = [copyItem];
108
97
 
109
98
  const EMPTY_SET: ActionsSet = [];
110
99
 
@@ -8,25 +8,29 @@ interface Window {
8
8
  params: {path: string},
9
9
  axiosOptions?: AxiosOptions,
10
10
  ) => Promise<import('../types/api/schema').TEvDescribeSchemeResult>;
11
+ getDescribe: (
12
+ params: {path: string},
13
+ axiosOptions?: AxiosOptions,
14
+ ) => Promise<import('../types/api/schema').TEvDescribeSchemeResult>;
11
15
  getStorageInfo: (
12
16
  params: {
13
- tenant: string,
14
- filter: string,
15
- nodeId: string,
16
- type: 'Groups' | 'Nodes',
17
+ tenant: string;
18
+ filter: string;
19
+ nodeId: string;
20
+ type: 'Groups' | 'Nodes';
17
21
  },
18
22
  axiosOptions?: AxiosOptions,
19
23
  ) => Promise<import('../types/api/storage').TStorageInfo>;
20
24
  sendQuery: <
21
25
  Action extends import('../types/api/query').Actions,
22
- Schema extends import('../types/api/query').Schemas = undefined
26
+ Schema extends import('../types/api/query').Schemas = undefined,
23
27
  >(
24
28
  params: {
25
- query?: string,
26
- database?: string,
27
- action?: Action,
28
- stats?: string,
29
- schema?: Schema,
29
+ query?: string;
30
+ database?: string;
31
+ action?: Action;
32
+ stats?: string;
33
+ schema?: Schema;
30
34
  },
31
35
  axiosOptions?: AxiosOptions,
32
36
  ) => Promise<import('../types/api/query').QueryAPIResponse<Action, Schema>>;
@@ -38,7 +42,9 @@ interface Window {
38
42
  query: string,
39
43
  database: string,
40
44
  ) => Promise<import('../types/api/query').QueryAPIExplainResponse<'explain-ast'>>;
41
- getHealthcheckInfo: (database: string) => Promise<import('../types/api/healthcheck').HealthCheckAPIResponse>,
45
+ getHealthcheckInfo: (
46
+ database: string,
47
+ ) => Promise<import('../types/api/healthcheck').HealthCheckAPIResponse>;
42
48
  [method: string]: Function;
43
49
  };
44
50
  }
@@ -1,7 +1,11 @@
1
- import {createRequestActionTypes, createApiRequest} from '../utils';
2
1
  import {createSelector} from 'reselect';
2
+
3
3
  import '../../services/api';
4
4
  import {ALL} from '../../utils/constants';
5
+ import {createRequestActionTypes, createApiRequest} from '../utils';
6
+
7
+ import {filterByUptime} from './storage';
8
+ import {getNodesUptimeFilter} from './nodes';
5
9
 
6
10
  const FETCH_NODES_LIST = createRequestActionTypes('tenants', 'FETCH_NODES_LIST');
7
11
 
@@ -47,23 +51,38 @@ export function getNodesList() {
47
51
  });
48
52
  }
49
53
 
54
+ const filterByProblemsStatus = (nodes = [], problemFilter) => {
55
+ if (problemFilter === ALL) {
56
+ return nodes;
57
+ }
58
+
59
+ return nodes.filter(({Overall}) => {
60
+ return Overall && Overall !== 'Green';
61
+ });
62
+ };
63
+
64
+ export const filterNodesByStatusAndUptime = (nodes = [], problemFilter, uptimeFilter) => {
65
+ let result = filterByProblemsStatus(nodes, problemFilter);
66
+ result = filterByUptime(result, uptimeFilter);
67
+
68
+ return result;
69
+ };
70
+
50
71
  export const getFilteredNodes = createSelector(
51
- (state) => state.nodes.data?.Tenants,
52
- (state) => state.settings.problemFilter,
53
- (tenants, filter) => {
72
+ [
73
+ (state) => state.nodes.data?.Tenants,
74
+ (state) => state.settings.problemFilter,
75
+ getNodesUptimeFilter,
76
+ ],
77
+ (tenants, problemFilter, uptimeFilter) => {
54
78
  const nodes = tenants?.reduce((acc, item) => {
55
79
  if (Array.isArray(item.Nodes)) {
56
80
  return [...acc, ...item.Nodes.map((node) => ({...node, TenantName: item.Name}))];
57
81
  }
58
82
  return acc;
59
83
  }, []);
60
- if (filter === ALL) {
61
- return nodes;
62
- }
63
84
 
64
- return nodes?.filter(({Overall}) => {
65
- return Overall && Overall !== 'Green';
66
- });
85
+ return filterNodesByStatusAndUptime(nodes, problemFilter, uptimeFilter);
67
86
  },
68
87
  );
69
88