ydb-embedded-ui 3.0.1 → 3.2.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 (55) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +2 -0
  3. package/dist/components/DateRange/DateRange.scss +11 -0
  4. package/dist/components/DateRange/DateRange.tsx +75 -0
  5. package/dist/components/DateRange/index.ts +1 -0
  6. package/dist/components/Illustration/Illustration.tsx +4 -11
  7. package/dist/components/InfoViewer/InfoViewer.scss +2 -0
  8. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +1 -1
  9. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +16 -0
  10. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +4 -5
  11. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +7 -7
  12. package/dist/containers/Tenant/Diagnostics/OverloadedShards/OverloadedShards.scss +27 -0
  13. package/dist/containers/Tenant/Diagnostics/{TopShards/TopShards.tsx → OverloadedShards/OverloadedShards.tsx} +75 -20
  14. package/dist/containers/Tenant/Diagnostics/OverloadedShards/i18n/en.json +4 -0
  15. package/dist/containers/Tenant/Diagnostics/OverloadedShards/i18n/index.ts +11 -0
  16. package/dist/containers/Tenant/Diagnostics/OverloadedShards/i18n/ru.json +4 -0
  17. package/dist/containers/Tenant/Diagnostics/OverloadedShards/index.ts +1 -0
  18. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +16 -19
  19. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +202 -0
  20. package/dist/containers/Tenant/Diagnostics/TopQueries/i18n/en.json +4 -0
  21. package/dist/containers/Tenant/Diagnostics/TopQueries/i18n/index.ts +11 -0
  22. package/dist/containers/Tenant/Diagnostics/TopQueries/i18n/ru.json +4 -0
  23. package/dist/containers/Tenant/Diagnostics/TopQueries/index.ts +1 -0
  24. package/dist/containers/UserSettings/UserSettings.tsx +1 -1
  25. package/dist/services/api.d.ts +7 -0
  26. package/dist/store/reducers/describe.ts +4 -1
  27. package/dist/store/reducers/executeTopQueries.ts +170 -0
  28. package/dist/store/reducers/settings.js +1 -1
  29. package/dist/store/reducers/shardsWorkload.ts +91 -25
  30. package/dist/store/reducers/storage.js +2 -0
  31. package/dist/store/reducers/{tablets.js → tablets.ts} +30 -17
  32. package/dist/store/state-url-mapping.js +16 -0
  33. package/dist/types/api/compute.ts +52 -0
  34. package/dist/types/api/consumer.ts +257 -0
  35. package/dist/types/api/enums.ts +2 -2
  36. package/dist/types/api/nodes.ts +5 -2
  37. package/dist/types/api/pdisk.ts +3 -0
  38. package/dist/types/api/schema.ts +1 -0
  39. package/dist/types/api/storage.ts +31 -28
  40. package/dist/types/api/tablet.ts +18 -2
  41. package/dist/types/api/tenant.ts +4 -1
  42. package/dist/types/api/topic.ts +157 -0
  43. package/dist/types/api/vdisk.ts +3 -0
  44. package/dist/types/store/executeTopQueries.ts +29 -0
  45. package/dist/types/store/schema.ts +3 -3
  46. package/dist/types/store/shardsWorkload.ts +11 -2
  47. package/dist/types/store/tablets.ts +42 -0
  48. package/dist/utils/getNodesColumns.js +8 -1
  49. package/dist/utils/query.ts +1 -1
  50. package/package.json +3 -3
  51. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.js +0 -188
  52. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.scss +0 -7
  53. package/dist/containers/Tenant/Diagnostics/TopShards/index.ts +0 -1
  54. package/dist/store/reducers/executeTopQueries.js +0 -66
  55. package/dist/types/api/consumers.ts +0 -3
@@ -0,0 +1,202 @@
1
+ import {useCallback, useEffect, useRef, useState} from 'react';
2
+ import {useDispatch} from 'react-redux';
3
+ import cn from 'bem-cn-lite';
4
+
5
+ import DataTable, {Column, Settings} from '@yandex-cloud/react-data-table';
6
+ import {Loader} from '@gravity-ui/uikit';
7
+
8
+ import {DateRange, DateRangeValues} from '../../../../components/DateRange';
9
+ import {Search} from '../../../../components/Search';
10
+ import TruncatedQuery from '../../../../components/TruncatedQuery/TruncatedQuery';
11
+
12
+ import {changeUserInput} from '../../../../store/reducers/executeQuery';
13
+ import {
14
+ fetchTopQueries,
15
+ setTopQueriesFilters,
16
+ setTopQueriesState,
17
+ } from '../../../../store/reducers/executeTopQueries';
18
+
19
+ import type {KeyValueRow} from '../../../../types/api/query';
20
+ import type {EPathType} from '../../../../types/api/schema';
21
+ import type {ITopQueriesFilters} from '../../../../types/store/executeTopQueries';
22
+ import type {IQueryResult} from '../../../../types/store/query';
23
+
24
+ import {DEFAULT_TABLE_SETTINGS, HOUR_IN_SECONDS} from '../../../../utils/constants';
25
+ import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
26
+ import {prepareQueryError} from '../../../../utils/query';
27
+
28
+ import {isColumnEntityType} from '../../utils/schema';
29
+ import {TenantGeneralTabsIds} from '../../TenantPages';
30
+
31
+ import i18n from './i18n';
32
+ import './TopQueries.scss';
33
+
34
+ const b = cn('kv-top-queries');
35
+
36
+ const TABLE_SETTINGS: Settings = {
37
+ ...DEFAULT_TABLE_SETTINGS,
38
+ dynamicRenderType: 'variable',
39
+ };
40
+
41
+ const MAX_QUERY_HEIGHT = 10;
42
+ const COLUMNS: Column<KeyValueRow>[] = [
43
+ {
44
+ name: 'CPUTimeUs',
45
+ width: 140,
46
+ sortAccessor: (row) => Number(row['CPUTimeUs']),
47
+ },
48
+ {
49
+ name: 'QueryText',
50
+ width: 500,
51
+ sortable: false,
52
+ render: ({value}) => <TruncatedQuery value={value} maxQueryHeight={MAX_QUERY_HEIGHT} />,
53
+ },
54
+ ];
55
+
56
+ interface TopQueriesProps {
57
+ path: string;
58
+ changeSchemaTab: (tab: TenantGeneralTabsIds) => void;
59
+ type?: EPathType;
60
+ }
61
+
62
+ export const TopQueries = ({path, type, changeSchemaTab}: TopQueriesProps) => {
63
+ const dispatch = useDispatch();
64
+
65
+ const {autorefresh} = useTypedSelector((state) => state.schema);
66
+
67
+ const {
68
+ loading,
69
+ wasLoaded,
70
+ error,
71
+ data: {result: data = undefined} = {},
72
+ filters: storeFilters,
73
+ } = useTypedSelector((state) => state.executeTopQueries);
74
+
75
+ const preventFetch = useRef(false);
76
+
77
+ // some filters sync between redux state and URL
78
+ // component state is for default values,
79
+ // default values are determined from the query response, and should not propagate to URL
80
+ const [filters, setFilters] = useState<ITopQueriesFilters>(storeFilters);
81
+
82
+ useEffect(() => {
83
+ dispatch(setTopQueriesFilters(filters));
84
+ }, [dispatch, filters]);
85
+
86
+ const setDefaultFiltersFromResponse = (responseData?: IQueryResult) => {
87
+ const intervalEnd = responseData?.result?.[0]?.IntervalEnd;
88
+
89
+ if (intervalEnd) {
90
+ const to = new Date(intervalEnd).getTime();
91
+ const from = new Date(to - HOUR_IN_SECONDS * 1000).getTime();
92
+
93
+ setFilters((currentFilters) => {
94
+ // request without filters returns the latest interval with data
95
+ // only in this case should update filters in ui
96
+ // also don't update if user already interacted with controls
97
+ const shouldUpdateFilters = !currentFilters.from && !currentFilters.to;
98
+
99
+ if (!shouldUpdateFilters) {
100
+ return currentFilters;
101
+ }
102
+
103
+ preventFetch.current = true;
104
+
105
+ return {...currentFilters, from, to};
106
+ });
107
+ }
108
+ };
109
+
110
+ useAutofetcher(
111
+ (isBackground) => {
112
+ if (preventFetch.current) {
113
+ preventFetch.current = false;
114
+ return;
115
+ }
116
+
117
+ if (!isBackground) {
118
+ dispatch(
119
+ setTopQueriesState({
120
+ wasLoaded: false,
121
+ data: undefined,
122
+ }),
123
+ );
124
+ }
125
+
126
+ // @ts-expect-error
127
+ // typed dispatch required, remove error expectation after adding it
128
+ dispatch(fetchTopQueries({database: path, filters})).then(
129
+ setDefaultFiltersFromResponse,
130
+ );
131
+ },
132
+ [dispatch, filters, path],
133
+ autorefresh,
134
+ );
135
+
136
+ const handleRowClick = useCallback(
137
+ (row) => {
138
+ const {QueryText: input} = row;
139
+
140
+ dispatch(changeUserInput({input}));
141
+ changeSchemaTab(TenantGeneralTabsIds.query);
142
+ },
143
+ [changeSchemaTab, dispatch],
144
+ );
145
+
146
+ const handleTextSearchUpdate = (text: string) => {
147
+ setFilters((currentFilters) => ({...currentFilters, text}));
148
+ };
149
+
150
+ const handleDateRangeChange = (value: DateRangeValues) => {
151
+ setFilters((currentFilters) => ({...currentFilters, ...value}));
152
+ };
153
+
154
+ const renderLoader = () => {
155
+ return (
156
+ <div className={b('loader')}>
157
+ <Loader size="m" />
158
+ </div>
159
+ );
160
+ };
161
+
162
+ const renderContent = () => {
163
+ if (loading && !wasLoaded) {
164
+ return renderLoader();
165
+ }
166
+
167
+ if (error && !error.isCancelled) {
168
+ return <div className="error">{prepareQueryError(error)}</div>;
169
+ }
170
+
171
+ if (!data || isColumnEntityType(type)) {
172
+ return i18n('no-data');
173
+ }
174
+
175
+ return (
176
+ <div className={b('result')}>
177
+ <DataTable
178
+ columns={COLUMNS}
179
+ data={data}
180
+ settings={TABLE_SETTINGS}
181
+ onRowClick={handleRowClick}
182
+ theme="yandex-cloud"
183
+ />
184
+ </div>
185
+ );
186
+ };
187
+
188
+ return (
189
+ <div className={b()}>
190
+ <div className={b('controls')}>
191
+ <Search
192
+ value={filters.text}
193
+ onChange={handleTextSearchUpdate}
194
+ placeholder={i18n('filter.text.placeholder')}
195
+ className={b('search')}
196
+ />
197
+ <DateRange from={filters.from} to={filters.to} onChange={handleDateRangeChange} />
198
+ </div>
199
+ {renderContent()}
200
+ </div>
201
+ );
202
+ };
@@ -0,0 +1,4 @@
1
+ {
2
+ "no-data": "No data",
3
+ "filter.text.placeholder": "Search by query text..."
4
+ }
@@ -0,0 +1,11 @@
1
+ import {i18n, Lang} from '../../../../../utils/i18n';
2
+
3
+ import en from './en.json';
4
+ import ru from './ru.json';
5
+
6
+ const COMPONENT = 'ydb-diagnostics-top-queries';
7
+
8
+ i18n.registerKeyset(Lang.En, COMPONENT, en);
9
+ i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
10
+
11
+ export default i18n.keyset(COMPONENT);
@@ -0,0 +1,4 @@
1
+ {
2
+ "no-data": "Нет данных",
3
+ "filter.text.placeholder": "Искать по тексту запроса..."
4
+ }
@@ -0,0 +1 @@
1
+ export * from './TopQueries';
@@ -67,7 +67,7 @@ const mapStateToProps = (state: any) => {
67
67
 
68
68
  return {
69
69
  theme,
70
- invertedDisks,
70
+ invertedDisks: JSON.parse(invertedDisks),
71
71
  };
72
72
  };
73
73
 
@@ -48,6 +48,13 @@ interface Window {
48
48
  getTenantInfo: (params: {
49
49
  path: string;
50
50
  }) => Promise<import('../types/api/tenant').TTenantInfo>;
51
+ getTabletsInfo: (params: {
52
+ nodes?: string[];
53
+ path?: string;
54
+ }) => Promise<import('../types/api/tablet').TEvTabletStateResponse>;
55
+ getHeatmapData: (params: {
56
+ path: string;
57
+ }) => Promise<import('../types/api/schema').TEvDescribeSchemeResult>;
51
58
  [method: string]: Function;
52
59
  };
53
60
  }
@@ -2,7 +2,6 @@ import {createSelector, Selector} from 'reselect';
2
2
  import {Reducer} from 'redux';
3
3
 
4
4
  import '../../services/api';
5
- import {IConsumer} from '../../types/api/consumers';
6
5
  import {
7
6
  IDescribeRootStateSlice,
8
7
  IDescribeState,
@@ -100,6 +99,10 @@ const selectConsumersNames = (state: IDescribeRootStateSlice, path?: string) =>
100
99
  ? state.describe.data[path]?.PathDescription?.PersQueueGroup?.PQTabletConfig?.ReadRules
101
100
  : undefined;
102
101
 
102
+ interface IConsumer {
103
+ name: string;
104
+ }
105
+
103
106
  export const selectConsumers: Selector<IDescribeRootStateSlice, IConsumer[], [string | undefined]> =
104
107
  createSelector(selectConsumersNames, (names = []) => names.map((name) => ({name})));
105
108
 
@@ -0,0 +1,170 @@
1
+ import type {AnyAction, Reducer} from 'redux';
2
+ import type {ThunkAction} from 'redux-thunk';
3
+
4
+ import '../../services/api';
5
+ import {
6
+ ITopQueriesAction,
7
+ ITopQueriesFilters,
8
+ ITopQueriesState,
9
+ } from '../../types/store/executeTopQueries';
10
+ import {IQueryResult} from '../../types/store/query';
11
+
12
+ import {parseQueryAPIExecuteResponse} from '../../utils/query';
13
+
14
+ import {createRequestActionTypes, createApiRequest} from '../utils';
15
+
16
+ import type {IRootState} from '.';
17
+
18
+ export const FETCH_TOP_QUERIES = createRequestActionTypes('top-queries', 'FETCH_TOP_QUERIES');
19
+ const SET_TOP_QUERIES_STATE = 'top-queries/SET_TOP_QUERIES_STATE';
20
+ const SET_TOP_QUERIES_FILTERS = 'top-queries/SET_TOP_QUERIES_FILTERS';
21
+
22
+ const initialState = {
23
+ loading: false,
24
+ wasLoaded: false,
25
+ filters: {},
26
+ };
27
+
28
+ const getMaxIntervalSubquery = (path: string) => `(
29
+ SELECT
30
+ MAX(IntervalEnd)
31
+ FROM \`${path}/.sys/top_queries_by_cpu_time_one_hour\`
32
+ )`;
33
+
34
+ function getFiltersConditions(path: string, filters?: ITopQueriesFilters) {
35
+ const conditions: string[] = [];
36
+
37
+ if (filters?.from && filters?.to && filters.from > filters.to) {
38
+ throw new Error('Invalid date range');
39
+ }
40
+
41
+ if (filters?.from) {
42
+ // matching `from` & `to` is an edge case
43
+ // other cases should not include the starting point, since intervals are stored using the ending time
44
+ const gt = filters.to === filters.from ? '>=' : '>';
45
+ conditions.push(`IntervalEnd ${gt} Timestamp('${new Date(filters.from).toISOString()}')`);
46
+ }
47
+
48
+ if (filters?.to) {
49
+ conditions.push(`IntervalEnd <= Timestamp('${new Date(filters.to).toISOString()}')`);
50
+ }
51
+
52
+ if (!filters?.from && !filters?.to) {
53
+ conditions.push(`IntervalEnd IN ${getMaxIntervalSubquery(path)}`);
54
+ }
55
+
56
+ if (filters?.text) {
57
+ conditions.push(`QueryText ILIKE '%${filters.text}%'`);
58
+ }
59
+
60
+ return conditions.join(' AND ');
61
+ }
62
+
63
+ const getQueryText = (path: string, filters?: ITopQueriesFilters) => {
64
+ const filterConditions = getFiltersConditions(path, filters);
65
+ return `
66
+ SELECT
67
+ CPUTime as CPUTimeUs,
68
+ QueryText,
69
+ IntervalEnd
70
+ FROM \`${path}/.sys/top_queries_by_cpu_time_one_hour\`
71
+ WHERE ${filterConditions || 'true'}
72
+ `;
73
+ };
74
+
75
+ const executeTopQueries: Reducer<ITopQueriesState, ITopQueriesAction> = (
76
+ state = initialState,
77
+ action,
78
+ ) => {
79
+ switch (action.type) {
80
+ case FETCH_TOP_QUERIES.REQUEST: {
81
+ return {
82
+ ...state,
83
+ loading: true,
84
+ error: undefined,
85
+ };
86
+ }
87
+ case FETCH_TOP_QUERIES.SUCCESS: {
88
+ return {
89
+ ...state,
90
+ data: action.data,
91
+ loading: false,
92
+ error: undefined,
93
+ wasLoaded: true,
94
+ };
95
+ }
96
+ // 401 Unauthorized error is handled by GenericAPI
97
+ case FETCH_TOP_QUERIES.FAILURE: {
98
+ return {
99
+ ...state,
100
+ error: action.error || 'Unauthorized',
101
+ loading: false,
102
+ };
103
+ }
104
+ case SET_TOP_QUERIES_STATE:
105
+ return {
106
+ ...state,
107
+ ...action.data,
108
+ };
109
+ case SET_TOP_QUERIES_FILTERS:
110
+ return {
111
+ ...state,
112
+ filters: {
113
+ ...state.filters,
114
+ ...action.filters,
115
+ },
116
+ };
117
+ default:
118
+ return state;
119
+ }
120
+ };
121
+
122
+ type FetchTopQueries = (params: {
123
+ database: string;
124
+ filters?: ITopQueriesFilters;
125
+ }) => ThunkAction<Promise<IQueryResult | undefined>, IRootState, unknown, AnyAction>;
126
+
127
+ export const fetchTopQueries: FetchTopQueries =
128
+ ({database, filters}) =>
129
+ async (dispatch, getState) => {
130
+ try {
131
+ return createApiRequest({
132
+ request: window.api.sendQuery(
133
+ {
134
+ schema: 'modern',
135
+ query: getQueryText(database, filters),
136
+ database,
137
+ action: 'execute-scan',
138
+ },
139
+ {
140
+ concurrentId: 'executeTopQueries',
141
+ },
142
+ ),
143
+ actions: FETCH_TOP_QUERIES,
144
+ dataHandler: parseQueryAPIExecuteResponse,
145
+ })(dispatch, getState);
146
+ } catch (error) {
147
+ dispatch({
148
+ type: FETCH_TOP_QUERIES.FAILURE,
149
+ error,
150
+ });
151
+
152
+ throw error;
153
+ }
154
+ };
155
+
156
+ export function setTopQueriesState(state: Partial<ITopQueriesState>) {
157
+ return {
158
+ type: SET_TOP_QUERIES_STATE,
159
+ data: state,
160
+ } as const;
161
+ }
162
+
163
+ export function setTopQueriesFilters(filters: Partial<ITopQueriesFilters>) {
164
+ return {
165
+ type: SET_TOP_QUERIES_FILTERS,
166
+ filters,
167
+ } as const;
168
+ }
169
+
170
+ export default executeTopQueries;
@@ -30,7 +30,7 @@ export const initialState = {
30
30
  ...defaultUserSettings,
31
31
  ...userSettings,
32
32
  [THEME_KEY]: readSavedSettingsValue(THEME_KEY, 'light'),
33
- [INVERTED_DISKS_KEY]: readSavedSettingsValue(INVERTED_DISKS_KEY) === 'true',
33
+ [INVERTED_DISKS_KEY]: readSavedSettingsValue(INVERTED_DISKS_KEY, 'false'),
34
34
  [SAVED_QUERIES_KEY]: readSavedSettingsValue(SAVED_QUERIES_KEY, '[]'),
35
35
  [TENANT_INITIAL_TAB_KEY]: readSavedSettingsValue(TENANT_INITIAL_TAB_KEY),
36
36
  [QUERY_INITIAL_RUN_ACTION_KEY]: readSavedSettingsValue(QUERY_INITIAL_RUN_ACTION_KEY),
@@ -1,18 +1,24 @@
1
1
  import type {Reducer} from 'redux';
2
2
 
3
3
  import '../../services/api';
4
- import type {IShardsWorkloadAction, IShardsWorkloadState} from '../../types/store/shardsWorkload';
4
+ import type {
5
+ IShardsWorkloadAction,
6
+ IShardsWorkloadFilters,
7
+ IShardsWorkloadState,
8
+ } from '../../types/store/shardsWorkload';
5
9
 
6
10
  import {parseQueryAPIExecuteResponse} from '../../utils/query';
7
11
 
8
12
  import {createRequestActionTypes, createApiRequest} from '../utils';
9
13
 
10
14
  export const SEND_SHARD_QUERY = createRequestActionTypes('query', 'SEND_SHARD_QUERY');
11
- const SET_SHARD_QUERY_OPTIONS = 'query/SET_SHARD_QUERY_OPTIONS';
15
+ const SET_SHARD_STATE = 'query/SET_SHARD_STATE';
16
+ const SET_SHARD_QUERY_FILTERS = 'shardsWorkload/SET_SHARD_QUERY_FILTERS';
12
17
 
13
18
  const initialState = {
14
19
  loading: false,
15
20
  wasLoaded: false,
21
+ filters: {},
16
22
  };
17
23
 
18
24
  export interface SortOrder {
@@ -24,22 +30,56 @@ function formatSortOrder({columnId, order}: SortOrder) {
24
30
  return `${columnId} ${order}`;
25
31
  }
26
32
 
27
- function createShardQuery(path: string, sortOrder?: SortOrder[], tenantName?: string) {
28
- const orderBy = sortOrder ? `ORDER BY ${sortOrder.map(formatSortOrder).join(', ')}` : '';
33
+ function getFiltersConditions(filters?: IShardsWorkloadFilters) {
34
+ const conditions: string[] = [];
35
+
36
+ if (filters?.from && filters?.to && filters.from > filters.to) {
37
+ throw new Error('Invalid date range');
38
+ }
29
39
 
40
+ if (filters?.from) {
41
+ // matching `from` & `to` is an edge case
42
+ // other cases should not include the starting point, since intervals are stored using the ending time
43
+ const gt = filters.to === filters.from ? '>=' : '>';
44
+ conditions.push(`IntervalEnd ${gt} Timestamp('${new Date(filters.from).toISOString()}')`);
45
+ }
46
+
47
+ if (filters?.to) {
48
+ conditions.push(`IntervalEnd <= Timestamp('${new Date(filters.to).toISOString()}')`);
49
+ }
50
+
51
+ return conditions.join(' AND ');
52
+ }
53
+
54
+ function createShardQuery(
55
+ path: string,
56
+ filters?: IShardsWorkloadFilters,
57
+ sortOrder?: SortOrder[],
58
+ tenantName?: string,
59
+ ) {
30
60
  const pathSelect = tenantName
31
61
  ? `CAST(SUBSTRING(CAST(Path AS String), ${tenantName.length}) AS Utf8) AS Path`
32
62
  : 'Path';
33
63
 
64
+ let where = `Path='${path}' OR Path LIKE '${path}/%'`;
65
+
66
+ const filterConditions = getFiltersConditions(filters);
67
+ if (filterConditions.length) {
68
+ where = `(${where}) AND ${filterConditions}`;
69
+ }
70
+
71
+ const orderBy = sortOrder ? `ORDER BY ${sortOrder.map(formatSortOrder).join(', ')}` : '';
72
+
34
73
  return `SELECT
35
74
  ${pathSelect},
36
75
  TabletId,
37
76
  CPUCores,
38
- DataSize
39
- FROM \`.sys/partition_stats\`
40
- WHERE
41
- Path='${path}'
42
- OR Path LIKE '${path}/%'
77
+ DataSize,
78
+ NodeId,
79
+ PeakTime,
80
+ InFlightTxCount
81
+ FROM \`.sys/top_partitions_one_hour\`
82
+ WHERE ${where}
43
83
  ${orderBy}
44
84
  LIMIT 20`;
45
85
  }
@@ -75,11 +115,19 @@ const shardsWorkload: Reducer<IShardsWorkloadState, IShardsWorkloadAction> = (
75
115
  loading: false,
76
116
  };
77
117
  }
78
- case SET_SHARD_QUERY_OPTIONS:
118
+ case SET_SHARD_STATE:
79
119
  return {
80
120
  ...state,
81
121
  ...action.data,
82
122
  };
123
+ case SET_SHARD_QUERY_FILTERS:
124
+ return {
125
+ ...state,
126
+ filters: {
127
+ ...state.filters,
128
+ ...action.filters,
129
+ },
130
+ };
83
131
  default:
84
132
  return state;
85
133
  }
@@ -89,28 +137,46 @@ interface SendShardQueryParams {
89
137
  database?: string;
90
138
  path?: string;
91
139
  sortOrder?: SortOrder[];
140
+ filters?: IShardsWorkloadFilters;
92
141
  }
93
142
 
94
- export const sendShardQuery = ({database, path = '', sortOrder}: SendShardQueryParams) => {
95
- return createApiRequest({
96
- request: window.api.sendQuery({
97
- schema: 'modern',
98
- query: createShardQuery(path, sortOrder, database),
99
- database,
100
- action: queryAction,
101
- }, {
102
- concurrentId: 'topShards',
103
- }),
104
- actions: SEND_SHARD_QUERY,
105
- dataHandler: parseQueryAPIExecuteResponse,
106
- });
143
+ export const sendShardQuery = ({database, path = '', sortOrder, filters}: SendShardQueryParams) => {
144
+ try {
145
+ return createApiRequest({
146
+ request: window.api.sendQuery(
147
+ {
148
+ schema: 'modern',
149
+ query: createShardQuery(path, filters, sortOrder, database),
150
+ database,
151
+ action: queryAction,
152
+ },
153
+ {
154
+ concurrentId: 'shardsWorkload',
155
+ },
156
+ ),
157
+ actions: SEND_SHARD_QUERY,
158
+ dataHandler: parseQueryAPIExecuteResponse,
159
+ });
160
+ } catch (error) {
161
+ return {
162
+ type: SEND_SHARD_QUERY.FAILURE,
163
+ error,
164
+ };
165
+ }
107
166
  };
108
167
 
109
- export function setShardQueryOptions(options: Partial<IShardsWorkloadState>) {
168
+ export function setShardsState(options: Partial<IShardsWorkloadState>) {
110
169
  return {
111
- type: SET_SHARD_QUERY_OPTIONS,
170
+ type: SET_SHARD_STATE,
112
171
  data: options,
113
172
  } as const;
114
173
  }
115
174
 
175
+ export function setShardsQueryFilters(filters: Partial<IShardsWorkloadFilters>) {
176
+ return {
177
+ type: SET_SHARD_QUERY_FILTERS,
178
+ filters,
179
+ } as const;
180
+ }
181
+
116
182
  export default shardsWorkload;
@@ -307,6 +307,8 @@ export const getFlatListStorageNodes = createSelector([getStorageNodes], (storag
307
307
  return {
308
308
  NodeId: node.NodeId,
309
309
  FQDN: systemState.Host,
310
+ DataCenter: systemState.DataCenter,
311
+ Rack: systemState.Rack,
310
312
  uptime: calcUptime(systemState.StartTime),
311
313
  StartTime: systemState.StartTime,
312
314
  PDisks: node.PDisks,