ydb-embedded-ui 3.4.5 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/components/InfoViewer/formatters/table.ts +6 -0
  3. package/dist/components/TruncatedQuery/TruncatedQuery.js +1 -1
  4. package/dist/components/TruncatedQuery/TruncatedQuery.scss +7 -3
  5. package/dist/containers/Node/{NodePages.js → NodePages.ts} +1 -1
  6. package/dist/containers/Tablet/TabletControls/TabletControls.tsx +2 -2
  7. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +11 -43
  8. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.tsx +19 -17
  9. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo.ts +192 -37
  10. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +20 -14
  11. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +49 -12
  12. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +37 -18
  13. package/dist/containers/Tenant/QueryEditor/QueriesHistory/QueriesHistory.tsx +3 -3
  14. package/dist/containers/Tenant/QueryEditor/QueryDuration/QueryDuration.scss +8 -0
  15. package/dist/containers/Tenant/QueryEditor/QueryDuration/QueryDuration.tsx +21 -0
  16. package/dist/containers/Tenant/QueryEditor/QueryEditor.js +58 -82
  17. package/dist/containers/Tenant/QueryEditor/QueryEditor.scss +0 -33
  18. package/dist/containers/Tenant/QueryEditor/QueryEditorControls/OldQueryEditorControls.tsx +83 -0
  19. package/dist/containers/Tenant/QueryEditor/QueryEditorControls/QueryEditorControls.scss +57 -0
  20. package/dist/containers/Tenant/QueryEditor/QueryEditorControls/QueryEditorControls.tsx +84 -0
  21. package/dist/containers/Tenant/QueryEditor/QueryEditorControls/shared.ts +23 -0
  22. package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.js +12 -23
  23. package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.js +4 -6
  24. package/dist/containers/Tenant/QueryEditor/i18n/en.json +3 -0
  25. package/dist/containers/Tenant/QueryEditor/i18n/index.ts +11 -0
  26. package/dist/containers/Tenant/QueryEditor/i18n/ru.json +3 -0
  27. package/dist/containers/UserSettings/UserSettings.tsx +30 -1
  28. package/dist/routes.ts +1 -1
  29. package/dist/services/api.d.ts +4 -3
  30. package/dist/services/api.js +5 -5
  31. package/dist/store/reducers/{executeQuery.js → executeQuery.ts} +48 -43
  32. package/dist/store/reducers/executeTopQueries.ts +5 -1
  33. package/dist/store/reducers/{explainQuery.js → explainQuery.ts} +44 -59
  34. package/dist/store/reducers/{olapStats.js → olapStats.ts} +8 -18
  35. package/dist/store/reducers/settings.js +19 -4
  36. package/dist/store/reducers/storage.js +5 -7
  37. package/dist/types/api/error.ts +14 -0
  38. package/dist/types/api/query.ts +227 -115
  39. package/dist/types/api/schema.ts +523 -3
  40. package/dist/types/common.ts +1 -0
  41. package/dist/types/store/executeQuery.ts +38 -0
  42. package/dist/types/store/explainQuery.ts +38 -0
  43. package/dist/types/store/olapStats.ts +14 -0
  44. package/dist/types/store/query.ts +23 -3
  45. package/dist/utils/constants.ts +2 -1
  46. package/dist/utils/error.ts +25 -0
  47. package/dist/utils/index.js +0 -49
  48. package/dist/utils/prepareQueryExplain.ts +7 -24
  49. package/dist/utils/query.test.ts +148 -213
  50. package/dist/utils/query.ts +68 -90
  51. package/dist/utils/timeParsers/formatDuration.ts +30 -12
  52. package/dist/utils/timeParsers/i18n/en.json +9 -5
  53. package/dist/utils/timeParsers/i18n/ru.json +9 -5
  54. package/dist/utils/timeParsers/parsers.ts +9 -0
  55. package/dist/utils/utils.js +1 -2
  56. package/package.json +1 -1
@@ -1,35 +1,40 @@
1
- import {createRequestActionTypes, createApiRequest} from '../utils';
2
- import '../../services/api';
1
+ import type {Reducer} from 'redux';
2
+
3
+ import type {ExecuteActions} from '../../types/api/query';
4
+ import type {
5
+ ExecuteQueryAction,
6
+ ExecuteQueryState,
7
+ MonacoHotKeyAction,
8
+ } from '../../types/store/executeQuery';
9
+ import type {QueryRequestParams, QueryModes} from '../../types/store/query';
3
10
  import {getValueFromLS, parseJson} from '../../utils/utils';
4
- import {QUERIES_HISTORY_KEY, QUERY_INITIAL_RUN_ACTION_KEY} from '../../utils/constants';
11
+ import {QUERIES_HISTORY_KEY} from '../../utils/constants';
5
12
  import {parseQueryAPIExecuteResponse} from '../../utils/query';
6
- import {readSavedSettingsValue} from './settings';
13
+ import {parseQueryError} from '../../utils/error';
14
+ import '../../services/api';
15
+
16
+ import {createRequestActionTypes, createApiRequest} from '../utils';
7
17
 
8
18
  const MAXIMUM_QUERIES_IN_HISTORY = 20;
9
19
 
10
- const SEND_QUERY = createRequestActionTypes('query', 'SEND_QUERY');
20
+ export const SEND_QUERY = createRequestActionTypes('query', 'SEND_QUERY');
21
+
11
22
  const CHANGE_USER_INPUT = 'query/CHANGE_USER_INPUT';
12
23
  const SAVE_QUERY_TO_HISTORY = 'query/SAVE_QUERY_TO_HISTORY';
13
24
  const GO_TO_PREVIOUS_QUERY = 'query/GO_TO_PREVIOUS_QUERY';
14
25
  const GO_TO_NEXT_QUERY = 'query/GO_TO_NEXT_QUERY';
15
- const SELECT_RUN_ACTION = 'query/SELECT_RUN_ACTION';
16
26
  const MONACO_HOT_KEY = 'query/MONACO_HOT_KEY';
17
27
 
18
- const queriesHistoryInitial = parseJson(getValueFromLS(QUERIES_HISTORY_KEY, '[]'));
28
+ const queriesHistoryInitial: string[] = parseJson(getValueFromLS(QUERIES_HISTORY_KEY, '[]'));
19
29
 
20
30
  const sliceLimit = queriesHistoryInitial.length - MAXIMUM_QUERIES_IN_HISTORY;
21
31
 
22
- export const RUN_ACTIONS_VALUES = {
23
- script: 'execute-script',
24
- scan: 'execute-scan',
25
- };
26
-
27
32
  export const MONACO_HOT_KEY_ACTIONS = {
28
33
  sendQuery: 'sendQuery',
29
34
  goPrev: 'goPrev',
30
35
  goNext: 'goNext',
31
36
  getExplain: 'getExplain',
32
- };
37
+ } as const;
33
38
 
34
39
  const initialState = {
35
40
  loading: false,
@@ -41,11 +46,13 @@ const initialState = {
41
46
  ? MAXIMUM_QUERIES_IN_HISTORY - 1
42
47
  : queriesHistoryInitial.length - 1,
43
48
  },
44
- runAction: readSavedSettingsValue(QUERY_INITIAL_RUN_ACTION_KEY, RUN_ACTIONS_VALUES.script),
45
49
  monacoHotKey: null,
46
50
  };
47
51
 
48
- const executeQuery = (state = initialState, action) => {
52
+ const executeQuery: Reducer<ExecuteQueryState, ExecuteQueryAction> = (
53
+ state = initialState,
54
+ action,
55
+ ) => {
49
56
  switch (action.type) {
50
57
  case SEND_QUERY.REQUEST: {
51
58
  return {
@@ -64,22 +71,14 @@ const executeQuery = (state = initialState, action) => {
64
71
  error: undefined,
65
72
  };
66
73
  }
67
- // 401 Unauthorized error is handled by GenericAPI
68
74
  case SEND_QUERY.FAILURE: {
69
75
  return {
70
76
  ...state,
71
- error: action.error || 'Unauthorized',
77
+ error: parseQueryError(action.error),
72
78
  loading: false,
73
79
  };
74
80
  }
75
81
 
76
- case SELECT_RUN_ACTION: {
77
- return {
78
- ...state,
79
- runAction: action.data,
80
- };
81
- }
82
-
83
82
  case CHANGE_USER_INPUT: {
84
83
  return {
85
84
  ...state,
@@ -141,51 +140,57 @@ const executeQuery = (state = initialState, action) => {
141
140
  }
142
141
  };
143
142
 
144
- export const sendQuery = ({query, database, action}) => {
143
+ interface SendQueryParams extends QueryRequestParams {
144
+ mode?: QueryModes;
145
+ }
146
+
147
+ export const sendExecuteQuery = ({query, database, mode}: SendQueryParams) => {
148
+ const action: ExecuteActions = mode ? `execute-${mode}` : 'execute';
149
+
145
150
  return createApiRequest({
146
- request: window.api.sendQuery({schema: 'modern', query, database, action, stats: 'profile'}),
151
+ request: window.api.sendQuery({
152
+ schema: 'modern',
153
+ query,
154
+ database,
155
+ action,
156
+ stats: 'profile',
157
+ }),
147
158
  actions: SEND_QUERY,
148
159
  dataHandler: parseQueryAPIExecuteResponse,
149
160
  });
150
161
  };
151
162
 
152
- export const saveQueryToHistory = (query) => {
163
+ export const saveQueryToHistory = (query: string) => {
153
164
  return {
154
165
  type: SAVE_QUERY_TO_HISTORY,
155
166
  data: query,
156
- };
157
- };
158
-
159
- export const selectRunAction = (value) => {
160
- return {
161
- type: SELECT_RUN_ACTION,
162
- data: value,
163
- };
167
+ } as const;
164
168
  };
165
169
 
166
170
  export const goToPreviousQuery = () => {
167
171
  return {
168
172
  type: GO_TO_PREVIOUS_QUERY,
169
- };
173
+ } as const;
170
174
  };
171
175
 
172
176
  export const goToNextQuery = () => {
173
177
  return {
174
178
  type: GO_TO_NEXT_QUERY,
175
- };
179
+ } as const;
176
180
  };
177
181
 
178
- export const changeUserInput = ({input}) => {
179
- return (dispatch) => {
180
- dispatch({type: CHANGE_USER_INPUT, data: {input}});
181
- };
182
+ export const changeUserInput = ({input}: {input: string}) => {
183
+ return {
184
+ type: CHANGE_USER_INPUT,
185
+ data: {input},
186
+ } as const;
182
187
  };
183
188
 
184
- export const setMonacoHotKey = (value) => {
189
+ export const setMonacoHotKey = (value: MonacoHotKeyAction) => {
185
190
  return {
186
191
  type: MONACO_HOT_KEY,
187
192
  data: value,
188
- };
193
+ } as const;
189
194
  };
190
195
 
191
196
  export default executeQuery;
@@ -66,7 +66,11 @@ const getQueryText = (path: string, filters?: ITopQueriesFilters) => {
66
66
  SELECT
67
67
  CPUTime as CPUTimeUs,
68
68
  QueryText,
69
- IntervalEnd
69
+ IntervalEnd,
70
+ EndTime,
71
+ ReadRows,
72
+ ReadBytes,
73
+ UserSID
70
74
  FROM \`${path}/.sys/top_queries_by_cpu_time_one_hour\`
71
75
  WHERE ${filterConditions || 'true'}
72
76
  `;
@@ -1,21 +1,33 @@
1
+ import type {Reducer} from 'redux';
2
+ import type {ExplainPlanNodeData, GraphNode, Link} from '@gravity-ui/paranoid';
1
3
  import _ from 'lodash';
2
4
 
3
5
  import '../../services/api';
6
+ import type {ExplainActions} from '../../types/api/query';
7
+ import type {
8
+ ExplainQueryAction,
9
+ ExplainQueryState,
10
+ PreparedExplainResponse,
11
+ } from '../../types/store/explainQuery';
12
+ import type {QueryRequestParams, QueryModes} from '../../types/store/query';
4
13
 
5
- import {getExplainNodeId, getMetaForExplainNode} from '../../utils';
6
14
  import {preparePlan} from '../../utils/prepareQueryExplain';
7
- import {parseQueryAPIExplainResponse} from '../../utils/query';
15
+ import {parseQueryAPIExplainResponse, parseQueryExplainPlan} from '../../utils/query';
16
+ import {parseQueryError} from '../../utils/error';
8
17
 
9
18
  import {createRequestActionTypes, createApiRequest} from '../utils';
10
19
 
11
- const GET_EXPLAIN_QUERY = createRequestActionTypes('query', 'GET_EXPLAIN_QUERY');
12
- const GET_EXPLAIN_QUERY_AST = createRequestActionTypes('query', 'GET_EXPLAIN_QUERY_AST');
20
+ export const GET_EXPLAIN_QUERY = createRequestActionTypes('query', 'GET_EXPLAIN_QUERY');
21
+ export const GET_EXPLAIN_QUERY_AST = createRequestActionTypes('query', 'GET_EXPLAIN_QUERY_AST');
13
22
 
14
23
  const initialState = {
15
24
  loading: false,
16
25
  };
17
26
 
18
- const explainQuery = (state = initialState, action) => {
27
+ const explainQuery: Reducer<ExplainQueryState, ExplainQueryAction> = (
28
+ state = initialState,
29
+ action,
30
+ ) => {
19
31
  switch (action.type) {
20
32
  case GET_EXPLAIN_QUERY.REQUEST: {
21
33
  return {
@@ -36,11 +48,10 @@ const explainQuery = (state = initialState, action) => {
36
48
  error: undefined,
37
49
  };
38
50
  }
39
- // 401 Unauthorized error is handled by GenericAPI
40
51
  case GET_EXPLAIN_QUERY.FAILURE: {
41
52
  return {
42
53
  ...state,
43
- error: action.error || 'Unauthorized',
54
+ error: parseQueryError(action.error),
44
55
  loading: false,
45
56
  };
46
57
  }
@@ -63,7 +74,7 @@ const explainQuery = (state = initialState, action) => {
63
74
  case GET_EXPLAIN_QUERY_AST.FAILURE: {
64
75
  return {
65
76
  ...state,
66
- errorAst: action.error || 'Unauthorized',
77
+ errorAst: parseQueryError(action.error),
67
78
  loadingAst: false,
68
79
  };
69
80
  }
@@ -73,7 +84,7 @@ const explainQuery = (state = initialState, action) => {
73
84
  }
74
85
  };
75
86
 
76
- export const getExplainQueryAst = ({query, database}) => {
87
+ export const getExplainQueryAst = ({query, database}: QueryRequestParams) => {
77
88
  return createApiRequest({
78
89
  request: window.api.getExplainQueryAst(query, database),
79
90
  actions: GET_EXPLAIN_QUERY_AST,
@@ -82,74 +93,48 @@ export const getExplainQueryAst = ({query, database}) => {
82
93
  };
83
94
 
84
95
  export const explainVersions = {
85
- v1: '0.1',
86
96
  v2: '0.2',
87
97
  };
88
98
 
89
99
  const supportedExplainQueryVersions = Object.values(explainVersions);
90
100
 
91
- export const getExplainQuery = ({query, database}) => {
101
+ interface ExplainQueryParams extends QueryRequestParams {
102
+ mode?: QueryModes;
103
+ }
104
+
105
+ export const getExplainQuery = ({query, database, mode}: ExplainQueryParams) => {
106
+ const action: ExplainActions = mode ? `explain-${mode}` : 'explain';
107
+
92
108
  return createApiRequest({
93
- request: window.api.getExplainQuery(query, database),
109
+ request: window.api.getExplainQuery(query, database, action),
94
110
  actions: GET_EXPLAIN_QUERY,
95
- dataHandler: (response) => {
96
- const {plan: result, ast} = parseQueryAPIExplainResponse(response);
111
+ dataHandler: (response): PreparedExplainResponse => {
112
+ const {plan: rawPlan, ast} = parseQueryAPIExplainResponse(response);
97
113
 
98
- if (!result) {
114
+ if (!rawPlan) {
99
115
  return {ast};
100
116
  }
101
117
 
102
- let links = [];
103
- let nodes = [];
104
- const {tables, meta, Plan} = result;
118
+ const {tables, meta, Plan} = parseQueryExplainPlan(rawPlan);
105
119
 
106
120
  if (supportedExplainQueryVersions.indexOf(meta.version) === -1) {
121
+ // Do not prepare plan for not supported versions
107
122
  return {
108
- pristine: result,
109
- version: meta.version,
123
+ plan: {
124
+ pristine: rawPlan,
125
+ version: meta.version,
126
+ },
127
+ ast,
110
128
  };
111
129
  }
112
- if (meta.version === explainVersions.v2) {
130
+
131
+ let links: Link[] = [];
132
+ let nodes: GraphNode<ExplainPlanNodeData>[] = [];
133
+
134
+ if (Plan) {
113
135
  const preparedPlan = preparePlan(Plan);
114
136
  links = preparedPlan.links;
115
137
  nodes = preparedPlan.nodes;
116
- } else {
117
- _.forEach(tables, (table) => {
118
- nodes.push({
119
- name: table.name,
120
- });
121
-
122
- const tableTypes = {};
123
-
124
- const {reads = [], writes = []} = table;
125
- let prevNodeId = table.name;
126
-
127
- _.forEach([...reads, ...writes], (node) => {
128
- if (tableTypes[node.type]) {
129
- tableTypes[node.type] = tableTypes[node.type] + 1;
130
- } else {
131
- tableTypes[node.type] = 1;
132
- }
133
-
134
- const nodeId = getExplainNodeId(
135
- table.name,
136
- node.type,
137
- tableTypes[node.type],
138
- );
139
-
140
- links.push({
141
- from: prevNodeId,
142
- to: nodeId,
143
- });
144
- nodes.push({
145
- name: nodeId,
146
- meta: getMetaForExplainNode(node),
147
- id: nodeId,
148
- });
149
-
150
- prevNodeId = nodeId;
151
- });
152
- });
153
138
  }
154
139
 
155
140
  return {
@@ -158,7 +143,7 @@ export const getExplainQuery = ({query, database}) => {
158
143
  nodes,
159
144
  tables,
160
145
  version: meta.version,
161
- pristine: result,
146
+ pristine: rawPlan,
162
147
  },
163
148
  ast,
164
149
  };
@@ -1,11 +1,13 @@
1
- import '../../services/api';
1
+ import type {Reducer} from 'redux';
2
+
3
+ import type {OlapStatsAction, OlapStatsState} from '../../types/store/olapStats';
2
4
 
5
+ import '../../services/api';
3
6
  import {parseQueryAPIExecuteResponse} from '../../utils/query';
4
7
 
5
8
  import {createRequestActionTypes, createApiRequest} from '../utils';
6
9
 
7
- const FETCH_OLAP_STATS = createRequestActionTypes('query', 'SEND_OLAP_STATS_QUERY');
8
- const SET_OLAP_STATS_OPTIONS = createRequestActionTypes('query', 'SET_OLAP_STATS_OPTIONS');
10
+ export const FETCH_OLAP_STATS = createRequestActionTypes('query', 'SEND_OLAP_STATS_QUERY');
9
11
  const RESET_LOADING_STATE = 'olapStats/RESET_LOADING_STATE';
10
12
 
11
13
  const initialState = {
@@ -13,13 +15,13 @@ const initialState = {
13
15
  wasLoaded: false,
14
16
  };
15
17
 
16
- function createOlatStatsQuery(path) {
18
+ function createOlatStatsQuery(path: string) {
17
19
  return `SELECT * FROM \`${path}/.sys/primary_index_stats\``;
18
20
  }
19
21
 
20
22
  const queryAction = 'execute-scan';
21
23
 
22
- const olapStats = (state = initialState, action) => {
24
+ const olapStats: Reducer<OlapStatsState, OlapStatsAction> = (state = initialState, action) => {
23
25
  switch (action.type) {
24
26
  case FETCH_OLAP_STATS.REQUEST: {
25
27
  return {
@@ -44,11 +46,6 @@ const olapStats = (state = initialState, action) => {
44
46
  loading: false,
45
47
  };
46
48
  }
47
- case SET_OLAP_STATS_OPTIONS:
48
- return {
49
- ...state,
50
- ...action.data,
51
- };
52
49
  case RESET_LOADING_STATE: {
53
50
  return {
54
51
  ...state,
@@ -73,17 +70,10 @@ export const getOlapStats = ({path = ''}) => {
73
70
  });
74
71
  };
75
72
 
76
- export function setOlapStatsOptions(options) {
77
- return {
78
- type: SET_OLAP_STATS_OPTIONS,
79
- data: options,
80
- };
81
- }
82
-
83
73
  export function resetLoadingState() {
84
74
  return {
85
75
  type: RESET_LOADING_STATE,
86
- };
76
+ } as const;
87
77
  }
88
78
 
89
79
  export default olapStats;
@@ -3,14 +3,16 @@ import {
3
3
  SAVED_QUERIES_KEY,
4
4
  THEME_KEY,
5
5
  TENANT_INITIAL_TAB_KEY,
6
- QUERY_INITIAL_RUN_ACTION_KEY,
7
6
  INVERTED_DISKS_KEY,
8
7
  ASIDE_HEADER_COMPACT_KEY,
9
8
  USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
10
9
  PARTITIONS_SELECTED_COLUMNS_KEY,
10
+ QUERY_INITIAL_MODE_KEY,
11
+ ENABLE_QUERY_MODES_FOR_EXPLAIN,
11
12
  } from '../../utils/constants';
12
13
  import '../../services/api';
13
- import {getValueFromLS} from '../../utils/utils';
14
+ import {getValueFromLS, parseJson} from '../../utils/utils';
15
+ import {QueryModes} from '../../types/store/query';
14
16
 
15
17
  const CHANGE_PROBLEM_FILTER = 'settings/CHANGE_PROBLEM_FILTER';
16
18
  const SET_SETTING_VALUE = 'settings/SET_VALUE';
@@ -38,15 +40,19 @@ export const initialState = {
38
40
  problemFilter: ALL,
39
41
  userSettings: {
40
42
  ...userSettings,
41
- [THEME_KEY]: readSavedSettingsValue(THEME_KEY, 'light'),
43
+ [THEME_KEY]: readSavedSettingsValue(THEME_KEY, 'system'),
42
44
  [INVERTED_DISKS_KEY]: readSavedSettingsValue(INVERTED_DISKS_KEY, 'false'),
43
45
  [USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY]: readSavedSettingsValue(
44
46
  USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
45
47
  'false',
46
48
  ),
49
+ [ENABLE_QUERY_MODES_FOR_EXPLAIN]: readSavedSettingsValue(
50
+ ENABLE_QUERY_MODES_FOR_EXPLAIN,
51
+ 'false',
52
+ ),
47
53
  [SAVED_QUERIES_KEY]: readSavedSettingsValue(SAVED_QUERIES_KEY, '[]'),
48
54
  [TENANT_INITIAL_TAB_KEY]: readSavedSettingsValue(TENANT_INITIAL_TAB_KEY),
49
- [QUERY_INITIAL_RUN_ACTION_KEY]: readSavedSettingsValue(QUERY_INITIAL_RUN_ACTION_KEY),
55
+ [QUERY_INITIAL_MODE_KEY]: readSavedSettingsValue(QUERY_INITIAL_MODE_KEY, QueryModes.script),
50
56
  [ASIDE_HEADER_COMPACT_KEY]: readSavedSettingsValue(
51
57
  ASIDE_HEADER_COMPACT_KEY,
52
58
  legacyAsideNavCompactState || 'true',
@@ -97,6 +103,15 @@ export const getSettingValue = (state, name) => {
97
103
  return state.settings.userSettings[name];
98
104
  };
99
105
 
106
+ /**
107
+ * Returns parsed settings value.
108
+ * If value cannot be parsed, returns initially stored string
109
+ */
110
+ export const getParsedSettingValue = (state, name) => {
111
+ const value = state.settings.userSettings[name];
112
+ return parseJson(value);
113
+ };
114
+
100
115
  export const changeFilter = (filter) => {
101
116
  return {
102
117
  type: CHANGE_PROBLEM_FILTER,
@@ -243,13 +243,11 @@ export const getFlatListStorageGroups = createSelector([getStoragePools], (stora
243
243
  const limitSizeBytes = _.reduce(
244
244
  group.VDisks,
245
245
  (acc, vDisk) => {
246
- return (
247
- acc +
248
- (Number(vDisk.AvailableSize) ||
249
- Number(vDisk.PDisk?.AvailableSize) ||
250
- 0) +
251
- (Number(vDisk.AllocatedSize) || 0)
252
- );
246
+ const {AvailableSize, AllocatedSize, PDisk} = vDisk;
247
+ const available = (AvailableSize ?? PDisk?.AvailableSize) || 0;
248
+ const allocated = AllocatedSize || 0;
249
+
250
+ return acc + Number(available) + Number(allocated);
253
251
  },
254
252
  0,
255
253
  );
@@ -4,3 +4,17 @@ export interface IResponseError {
4
4
  statusText?: string;
5
5
  isCancelled?: boolean;
6
6
  }
7
+
8
+ // Error on offline backend or requests blocked by CORS
9
+ export interface NetworkError {
10
+ code?: unknown;
11
+ columnNumber?: unknown;
12
+ config?: Record<string, unknown>;
13
+ description?: unknown;
14
+ fileName?: unknown;
15
+ lineNumber?: unknown;
16
+ message?: 'Network Error';
17
+ name?: string;
18
+ number?: unknown;
19
+ stack?: string;
20
+ }