ydb-embedded-ui 3.0.1 → 3.2.0

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