ydb-embedded-ui 2.4.0 → 2.4.2

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.4.2](https://github.com/ydb-platform/ydb-embedded-ui/compare/v2.4.1...v2.4.2) (2022-11-09)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **QueryExplain:** apply all node types ([06d26de](https://github.com/ydb-platform/ydb-embedded-ui/commit/06d26def15496f8e2de00d941b39bf6a68382f14))
9
+
10
+ ## [2.4.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v2.4.0...v2.4.1) (2022-11-01)
11
+
12
+
13
+ ### Performance Improvements
14
+
15
+ * **SchemaTree:** batch preloaded data dispatch ([c9ac514](https://github.com/ydb-platform/ydb-embedded-ui/commit/c9ac514aabf5e9674aae95956604f47ba8a2d257))
16
+
3
17
  ## [2.4.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v2.3.0...v2.4.0) (2022-10-27)
4
18
 
5
19
 
@@ -1,11 +1,11 @@
1
1
  import {useEffect, useState} from 'react';
2
- import {useDispatch, useSelector} from 'react-redux';
2
+ import {useDispatch} from 'react-redux';
3
3
  import block from 'bem-cn-lite';
4
4
 
5
5
  import DataTable, {Column} from '@yandex-cloud/react-data-table';
6
6
 
7
7
  import {DEFAULT_TABLE_SETTINGS} from '../../../../utils/constants';
8
- import {useAutofetcher} from '../../../../utils/hooks';
8
+ import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
9
9
  import {Search} from '../../../../components/Search';
10
10
  import {getDescribe, selectConsumers} from '../../../../store/reducers/describe';
11
11
 
@@ -28,7 +28,7 @@ export const Consumers = ({path}: ConsumersProps) => {
28
28
 
29
29
  useAutofetcher(fetchData, [path]);
30
30
 
31
- const consumers = useSelector((state) => selectConsumers(state, path));
31
+ const consumers = useTypedSelector((state) => selectConsumers(state, path));
32
32
 
33
33
  const [consumersToRender, setConsumersToRender] = useState(consumers);
34
34
 
@@ -1,16 +1,16 @@
1
1
  import {useCallback} from 'react';
2
- import {useDispatch, useSelector} from 'react-redux';
2
+ import {useDispatch} from 'react-redux';
3
3
  import cn from 'bem-cn-lite';
4
4
 
5
5
  import {Loader} from '@gravity-ui/uikit';
6
6
 
7
7
  import {SelfCheckResult} from '../../../../types/api/healthcheck';
8
+ import {useTypedSelector, useAutofetcher} from '../../../../utils/hooks';
8
9
  import {
9
10
  getHealthcheckInfo,
10
11
  selectIssuesTreeById,
11
12
  selectIssuesTreesRoots,
12
13
  } from '../../../../store/reducers/healthcheckInfo';
13
- import {useAutofetcher} from '../../../../utils/hooks';
14
14
 
15
15
  import {Details} from './Details';
16
16
  import {Preview} from './Preview';
@@ -33,13 +33,15 @@ export const Healthcheck = (props: HealthcheckProps) => {
33
33
 
34
34
  const dispatch = useDispatch();
35
35
 
36
- const {data, loading, wasLoaded, error} = useSelector((state: any) => state.healthcheckInfo);
36
+ const {data, loading, wasLoaded, error} = useTypedSelector((state) => state.healthcheckInfo);
37
37
  const selfCheckResult = data?.self_check_result || SelfCheckResult.UNSPECIFIED;
38
38
 
39
- const issuesTreesRoots = useSelector(selectIssuesTreesRoots);
40
- const expandedIssueTree = useSelector((state) => selectIssuesTreeById(state, expandedIssueId));
39
+ const issuesTreesRoots = useTypedSelector(selectIssuesTreesRoots);
40
+ const expandedIssueTree = useTypedSelector((state) =>
41
+ selectIssuesTreeById(state, expandedIssueId),
42
+ );
41
43
 
42
- const {autorefresh} = useSelector((state: any) => state.schema);
44
+ const {autorefresh} = useTypedSelector((state) => state.schema);
43
45
 
44
46
  const fetchHealthcheck = useCallback(() => {
45
47
  dispatch(getHealthcheckInfo(tenant));
@@ -3,10 +3,10 @@ import {useDispatch} from 'react-redux';
3
3
 
4
4
  import {NavigationTree} from 'ydb-ui-components';
5
5
 
6
- import {setCurrentSchemaPath, getSchema, preloadSchema} from '../../../../store/reducers/schema';
6
+ import {setCurrentSchemaPath, getSchema, preloadSchemas} from '../../../../store/reducers/schema';
7
7
  import {getDescribe} from '../../../../store/reducers/describe';
8
8
  import {getSchemaAcl} from '../../../../store/reducers/schemaAcl';
9
- import type {EPathType} from '../../../../types/api/schema';
9
+ import type {EPathType, TEvDescribeSchemeResult} from '../../../../types/api/schema';
10
10
 
11
11
  import {mapPathTypeToNavigationTreeType} from '../../utils/schema';
12
12
  import {getActions} from '../../utils/schemaActions';
@@ -29,15 +29,15 @@ export function SchemaTree(props: SchemaTreeProps) {
29
29
  .then((data) => {
30
30
  const {PathDescription: {Children = []} = {}} = data;
31
31
 
32
- dispatch(preloadSchema(path, data));
32
+ const preloadedData: Record<string, TEvDescribeSchemeResult> = {
33
+ [path]: data
34
+ };
33
35
 
34
- return Children.map((childData) => {
36
+ const childItems = Children.map((childData) => {
35
37
  const {Name = '', PathType, PathSubType} = childData;
36
38
 
37
39
  // not full data, but it contains PathType, which ensures seamless switch between nodes
38
- dispatch(
39
- preloadSchema(`${path}/${Name}`, {PathDescription: {Self: childData}}),
40
- );
40
+ preloadedData[`${path}/${Name}`] = {PathDescription: {Self: childData}};
41
41
 
42
42
  return {
43
43
  name: Name,
@@ -47,6 +47,10 @@ export function SchemaTree(props: SchemaTreeProps) {
47
47
  expandable: true,
48
48
  };
49
49
  });
50
+
51
+ dispatch(preloadSchemas(preloadedData));
52
+
53
+ return childItems;
50
54
  });
51
55
 
52
56
  const handleActivePathUpdate = (activePath: string) => {
@@ -0,0 +1,27 @@
1
+ import url from 'url';
2
+
3
+ export const getUrlData = (href: string, singleClusterMode: boolean) => {
4
+ if (!singleClusterMode) {
5
+ const {backend, clusterName} = url.parse(href, true).query;
6
+ return {
7
+ basename: '/',
8
+ backend,
9
+ clusterName,
10
+ };
11
+ } else if (window.custom_backend) {
12
+ const {backend} = url.parse(href, true).query;
13
+ return {
14
+ basename: '/',
15
+ backend: backend || window.custom_backend,
16
+ };
17
+ } else {
18
+ const parsedPrefix = window.location.pathname.match(/.*(?=\/monitoring)/) || [];
19
+ const basenamePrefix = Boolean(parsedPrefix.length) && parsedPrefix[0];
20
+ const basename = [basenamePrefix, 'monitoring'].filter(Boolean).join('/');
21
+
22
+ return {
23
+ basename,
24
+ backend: basenamePrefix || '',
25
+ };
26
+ }
27
+ };
@@ -1,47 +1,16 @@
1
1
  import {createStore, applyMiddleware, compose} from 'redux';
2
2
  import thunkMiddleware from 'redux-thunk';
3
- import getLocationMiddleware from './state-url-mapping';
4
3
  import {createBrowserHistory} from 'history';
5
4
  import {listenForHistoryChange} from 'redux-location-state';
6
5
 
6
+ import {getUrlData} from './getUrlData';
7
+ import getLocationMiddleware from './state-url-mapping';
7
8
  import rootReducer from './reducers';
8
9
 
9
- import url from 'url';
10
-
11
- export const webVersion = window.web_version;
12
-
13
- export const customBackend = window.custom_backend;
14
-
15
- export const getUrlData = (href, singleClusterMode) => {
16
- if (!singleClusterMode) {
17
- const {backend, clusterName} = url.parse(href, true).query;
18
- return {
19
- basename: '/',
20
- backend,
21
- clusterName,
22
- };
23
- } else if (customBackend) {
24
- const {backend} = url.parse(href, true).query;
25
- return {
26
- basename: '/',
27
- backend: backend || window.custom_backend,
28
- };
29
- } else {
30
- const parsedPrefix = window.location.pathname.match(/.*(?=\/monitoring)/) || [];
31
- const basenamePrefix = Boolean(parsedPrefix.length) && parsedPrefix[0];
32
- const basename = [basenamePrefix, 'monitoring'].filter(Boolean).join('/');
33
-
34
- return {
35
- basename,
36
- backend: basenamePrefix || '',
37
- };
38
- }
39
- };
10
+ export let backend, basename, clusterName;
40
11
 
41
12
  const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
42
13
 
43
- export let backend, basename, clusterName;
44
-
45
14
  function _configureStore(aRootReducer, history, singleClusterMode) {
46
15
  const {locationMiddleware, reducersWithLocation} = getLocationMiddleware(history, aRootReducer);
47
16
  const middlewares = applyMiddleware(thunkMiddleware, locationMiddleware);
@@ -51,7 +20,7 @@ function _configureStore(aRootReducer, history, singleClusterMode) {
51
20
  });
52
21
  }
53
22
 
54
- export default function configureStore(aRootReducer = rootReducer, singleClusterMode = true) {
23
+ function configureStore(aRootReducer = rootReducer, singleClusterMode = true) {
55
24
  ({backend, basename, clusterName} = getUrlData(window.location.href, singleClusterMode));
56
25
  const history = createBrowserHistory({basename});
57
26
 
@@ -59,3 +28,10 @@ export default function configureStore(aRootReducer = rootReducer, singleCluster
59
28
  listenForHistoryChange(store, history);
60
29
  return {history, store};
61
30
  }
31
+
32
+ export const webVersion = window.web_version;
33
+ export const customBackend = window.custom_backend;
34
+
35
+ export * from "./reducers"
36
+
37
+ export default configureStore;
@@ -1,16 +1,25 @@
1
- import {createSelector} from 'reselect';
1
+ import {createSelector, Selector} from 'reselect';
2
+ import {Reducer} from 'redux';
2
3
 
3
4
  import '../../services/api';
4
5
  import {TEvDescribeSchemeResult} from '../../types/api/schema';
5
6
  import {IConsumer} from '../../types/api/consumers';
7
+ import {IDescribeRootStateSlice, IDescribeState} from '../../types/store/describe';
8
+ import {IResponseError} from '../../types/api/error';
6
9
  import {createRequestActionTypes, createApiRequest, ApiRequestAction} from '../utils';
7
10
 
8
11
  const FETCH_DESCRIBE = createRequestActionTypes('describe', 'FETCH_DESCRIBE');
9
12
 
10
- const describe = (
11
- state = {loading: false, wasLoaded: false, data: {}},
12
- action: ApiRequestAction<typeof FETCH_DESCRIBE, TEvDescribeSchemeResult, unknown>,
13
- ) => {
13
+ const initialState = {
14
+ loading: false,
15
+ wasLoaded: false,
16
+ data: {},
17
+ };
18
+
19
+ const describe: Reducer<
20
+ IDescribeState,
21
+ ApiRequestAction<typeof FETCH_DESCRIBE, TEvDescribeSchemeResult, IResponseError>
22
+ > = (state = initialState, action) => {
14
23
  switch (action.type) {
15
24
  case FETCH_DESCRIBE.REQUEST: {
16
25
  return {
@@ -50,16 +59,21 @@ const describe = (
50
59
  };
51
60
 
52
61
  // Consumers selectors
53
- const selectConsumersNames = (state: any, path: string): string[] | undefined =>
54
- state.describe.data[path]?.PathDescription?.PersQueueGroup?.PQTabletConfig?.ReadRules;
62
+ const selectConsumersNames: Selector<IDescribeRootStateSlice, string[] | undefined, [string]> = (
63
+ state,
64
+ path,
65
+ ) => state.describe.data[path]?.PathDescription?.PersQueueGroup?.PQTabletConfig?.ReadRules;
55
66
 
56
- export const selectConsumers = createSelector(selectConsumersNames, (names = []): IConsumer[] => {
57
- const consumers = names.map((name) => {
58
- return {name};
59
- });
67
+ export const selectConsumers: Selector<IDescribeRootStateSlice, IConsumer[]> = createSelector(
68
+ selectConsumersNames,
69
+ (names = []) => {
70
+ const consumers = names.map((name) => {
71
+ return {name};
72
+ });
60
73
 
61
- return consumers;
62
- });
74
+ return consumers;
75
+ },
76
+ );
63
77
 
64
78
  export function getDescribe({path}: {path: string}) {
65
79
  return createApiRequest({
@@ -1,10 +1,13 @@
1
+ import {Reducer} from 'redux';
2
+
1
3
  const SET_HEADER = 'SET_HEADER';
2
4
 
5
+ type IHeaderAction = ReturnType<typeof setHeader>;
3
6
  export type HeaderItemType = {text: string; link?: string};
4
7
 
5
8
  const initialState: HeaderItemType[] = [];
6
9
 
7
- const header = function (state = initialState, action: ReturnType<typeof setHeader>) {
10
+ const header: Reducer<HeaderItemType[], IHeaderAction> = function (state = initialState, action) {
8
11
  switch (action.type) {
9
12
  case SET_HEADER:
10
13
  return action.data;
@@ -2,10 +2,16 @@ import _flow from 'lodash/fp/flow';
2
2
  import _sortBy from 'lodash/fp/sortBy';
3
3
  import _uniqBy from 'lodash/fp/uniqBy';
4
4
  import _omit from 'lodash/omit';
5
- import {createSelector} from 'reselect';
6
-
7
- import {IIssuesTree} from '../../types/store/healthcheck';
5
+ import {createSelector, Selector} from 'reselect';
6
+ import {Reducer} from 'redux';
7
+
8
+ import {
9
+ IHealthcheckInfoState,
10
+ IHealthcheckInfoRootStateSlice,
11
+ IIssuesTree,
12
+ } from '../../types/store/healthcheck';
8
13
  import {HealthCheckAPIResponse, IssueLog, StatusFlag} from '../../types/api/healthcheck';
14
+ import {IResponseError} from '../../types/api/error';
9
15
 
10
16
  import '../../services/api';
11
17
  import {createRequestActionTypes, createApiRequest, ApiRequestAction} from '../utils';
@@ -14,10 +20,10 @@ const FETCH_HEALTHCHECK = createRequestActionTypes('cluster', 'FETCH_HEALTHCHECK
14
20
 
15
21
  const initialState = {loading: false, wasLoaded: false};
16
22
 
17
- const healthcheckInfo = function (
18
- state = initialState,
19
- action: ApiRequestAction<typeof FETCH_HEALTHCHECK, HealthCheckAPIResponse, unknown>,
20
- ) {
23
+ const healthcheckInfo: Reducer<
24
+ IHealthcheckInfoState,
25
+ ApiRequestAction<typeof FETCH_HEALTHCHECK, HealthCheckAPIResponse, IResponseError>
26
+ > = function (state = initialState, action) {
21
27
  switch (action.type) {
22
28
  case FETCH_HEALTHCHECK.REQUEST: {
23
29
  return {
@@ -60,7 +66,7 @@ const getReasonsForIssue = ({issue, data}: {issue: IssueLog; data: IssueLog[]})
60
66
  return data.filter((item) => issue.reason && issue.reason.indexOf(item.id) !== -1);
61
67
  };
62
68
 
63
- const getRoots = (data: IssueLog[]) => {
69
+ const getRoots = (data: IssueLog[]): IssueLog[] => {
64
70
  let roots = data.filter((item) => {
65
71
  return !data.find((issue) => issue.reason && issue.reason.indexOf(item.id) !== -1);
66
72
  });
@@ -95,22 +101,23 @@ const getInvertedConsequencesTree = ({
95
101
  : [];
96
102
  };
97
103
 
98
- const getIssuesLog = (state: any): IssueLog[] | undefined => state.healthcheckInfo.data?.issue_log;
104
+ const getIssuesLog = (state: IHealthcheckInfoRootStateSlice) =>
105
+ state.healthcheckInfo.data?.issue_log;
99
106
 
100
- export const selectIssuesTreesRoots = createSelector(getIssuesLog, (issues = []) =>
101
- getRoots(issues),
102
- );
107
+ export const selectIssuesTreesRoots: Selector<IHealthcheckInfoRootStateSlice, IssueLog[]> =
108
+ createSelector(getIssuesLog, (issues = []) => getRoots(issues));
103
109
 
104
- export const selectIssuesTrees = createSelector(
105
- [getIssuesLog, selectIssuesTreesRoots],
106
- (data = [], roots = []) => {
110
+ export const selectIssuesTrees: Selector<IHealthcheckInfoRootStateSlice, IIssuesTree[]> =
111
+ createSelector([getIssuesLog, selectIssuesTreesRoots], (data = [], roots = []) => {
107
112
  return getInvertedConsequencesTree({data, roots});
108
- },
109
- );
113
+ });
110
114
 
111
- export const selectIssuesTreeById = createSelector(
112
- [selectIssuesTrees, (_: any, id: string | undefined) => id],
113
- (issuesTrees = [], id) => issuesTrees.find((issuesTree: IIssuesTree) => issuesTree.id === id),
115
+ export const selectIssuesTreeById: Selector<
116
+ IHealthcheckInfoRootStateSlice,
117
+ IIssuesTree | undefined,
118
+ [string | undefined]
119
+ > = createSelector([selectIssuesTrees, (_, id: string | undefined) => id], (issuesTrees = [], id) =>
120
+ issuesTrees.find((issuesTree) => issuesTree.id === id),
114
121
  );
115
122
 
116
123
  export function getHealthcheckInfo(database: string) {
@@ -34,10 +34,7 @@ import authentication from './authentication';
34
34
  import header from './header';
35
35
  import saveQuery from './saveQuery';
36
36
  import fullscreen from './fullscreen';
37
-
38
- function singleClusterMode(state = true) {
39
- return state;
40
- }
37
+ import singleClusterMode from './singleClusterMode';
41
38
 
42
39
  export const rootReducer = {
43
40
  singleClusterMode,
@@ -77,6 +74,11 @@ export const rootReducer = {
77
74
  fullscreen,
78
75
  };
79
76
 
80
- export default combineReducers({
77
+ const combinedReducer = combineReducers({
81
78
  ...rootReducer,
82
79
  });
80
+
81
+ export type IRootReducer = typeof combinedReducer;
82
+ export type IRootState = ReturnType<IRootReducer>;
83
+
84
+ export default combinedReducer;
@@ -1,6 +1,6 @@
1
1
  import '../../services/api';
2
2
 
3
- import type {ErrorRepsonse, ExecuteActions} from '../../types/api/query';
3
+ import type {ErrorResponse, ExecuteActions} from '../../types/api/query';
4
4
  import type {IQueryResult} from '../../types/store/query';
5
5
  import {parseQueryAPIExecuteResponse} from '../../utils/query';
6
6
 
@@ -16,7 +16,7 @@ const initialState = {
16
16
 
17
17
  const preview = (
18
18
  state = initialState,
19
- action: ApiRequestAction<typeof SEND_QUERY, IQueryResult, ErrorRepsonse> | ReturnType<typeof setQueryOptions>,
19
+ action: ApiRequestAction<typeof SEND_QUERY, IQueryResult, ErrorResponse> | ReturnType<typeof setQueryOptions>,
20
20
  ) => {
21
21
  switch (action.type) {
22
22
  case SEND_QUERY.REQUEST: {
@@ -1,12 +1,14 @@
1
+ import {Reducer} from 'redux';
2
+
1
3
  const SET_QUERY_NAME_TO_EDIT = 'SET_QUERY_NAME_TO_EDIT';
2
4
  const CLEAR_QUERY_NAME_TO_EDIT = 'CLEAR_QUERY_NAME_TO_EDIT';
3
5
 
6
+ type IAction = ReturnType<typeof setQueryNameToEdit> | ReturnType<typeof clearQueryNameToEdit>;
7
+ type ISaveQueryState = string | null;
8
+
4
9
  const initialState = null;
5
10
 
6
- const saveQuery = function (
7
- state = initialState,
8
- action: ReturnType<typeof setQueryNameToEdit> | ReturnType<typeof clearQueryNameToEdit>,
9
- ) {
11
+ const saveQuery: Reducer<ISaveQueryState, IAction> = function (state = initialState, action) {
10
12
  switch (action.type) {
11
13
  case SET_QUERY_NAME_TO_EDIT:
12
14
  return action.data;
@@ -2,7 +2,7 @@ import {createRequestActionTypes, createApiRequest} from '../utils';
2
2
  import '../../services/api';
3
3
 
4
4
  const FETCH_SCHEMA = createRequestActionTypes('schema', 'FETCH_SCHEMA');
5
- const PRELOAD_SCHEMA = 'schema/PRELOAD_SCHEMA';
5
+ const PRELOAD_SCHEMAS = 'schema/PRELOAD_SCHEMAS';
6
6
  const SET_SCHEMA = 'schema/SET_SCHEMA';
7
7
  const SET_SHOW_PREVIEW = 'schema/SET_SHOW_PREVIEW';
8
8
  const ENABLE_AUTOREFRESH = 'schema/ENABLE_AUTOREFRESH';
@@ -54,16 +54,13 @@ const schema = (state = initialState, action) => {
54
54
  loading: false,
55
55
  };
56
56
  }
57
- case PRELOAD_SCHEMA: {
58
- if (state.data[action.path]) {
59
- return state;
60
- }
61
-
57
+ case PRELOAD_SCHEMAS: {
62
58
  return {
63
59
  ...state,
64
60
  data: {
61
+ // we don't want to overwrite existing paths
62
+ ...action.data,
65
63
  ...state.data,
66
- [action.path]: action.data,
67
64
  },
68
65
  };
69
66
  }
@@ -133,11 +130,11 @@ export function setShowPreview(value) {
133
130
  };
134
131
  }
135
132
 
136
- // only stores the passed data if the path doesn't exist yet
137
- export function preloadSchema(path, data) {
133
+ // only stores data for paths that are not in the store yet
134
+ // existing paths are ignored
135
+ export function preloadSchemas(data) {
138
136
  return {
139
- type: PRELOAD_SCHEMA,
140
- path,
137
+ type: PRELOAD_SCHEMAS,
141
138
  data,
142
139
  };
143
140
  }
@@ -0,0 +1,5 @@
1
+ function singleClusterMode(state = true) {
2
+ return state;
3
+ }
4
+
5
+ export default singleClusterMode;
@@ -0,0 +1,4 @@
1
+ export interface IResponseError {
2
+ status: number;
3
+ statusText: string;
4
+ }
@@ -8,16 +8,16 @@ export interface CommonFields {
8
8
  ast?: AST;
9
9
  plan?: Plan;
10
10
  stats?: Stats;
11
- };
11
+ }
12
12
 
13
13
  interface DeprecatedCommonFields {
14
14
  stats?: Stats;
15
15
  }
16
16
 
17
- export interface ErrorRepsonse {
17
+ export interface ErrorResponse {
18
18
  error?: any;
19
19
  issues?: any;
20
- };
20
+ }
21
21
 
22
22
  export type ExecuteActions = 'execute-script' | 'execute' | 'execute-scan' | undefined;
23
23
  export type ExplainActions = 'explain' | 'explain-ast';
@@ -34,7 +34,7 @@ type CellValue = string | number | null | undefined;
34
34
 
35
35
  export type KeyValueRow<T = CellValue> = {
36
36
  [key: string]: T;
37
- }
37
+ };
38
38
 
39
39
  export type ArrayRow<T = CellValue> = Array<T>;
40
40
 
@@ -63,17 +63,15 @@ export type ExecuteYdbResponse = {
63
63
  result: KeyValueRow[];
64
64
  } & CommonFields;
65
65
 
66
- type ExecuteResponse<Schema extends Schemas> =
66
+ type ExecuteResponse<Schema extends Schemas> =
67
67
  | CommonFields // result can be undefined for queries like `insert into`
68
- | (
69
- Schema extends 'modern'
70
- ? ExecuteModernResponse
71
- : Schema extends 'ydb'
72
- ? ExecuteYdbResponse
73
- : Schema extends 'classic' | undefined
74
- ? ExecuteClassicResponse
75
- : unknown
76
- );
68
+ | (Schema extends 'modern'
69
+ ? ExecuteModernResponse
70
+ : Schema extends 'ydb'
71
+ ? ExecuteYdbResponse
72
+ : Schema extends 'classic' | undefined
73
+ ? ExecuteClassicResponse
74
+ : unknown);
77
75
 
78
76
  // deprecated response from older versions, backward compatibility
79
77
 
@@ -92,8 +90,9 @@ export type DeprecatedExecuteResponseDeep = {
92
90
  // can be undefined for queries like `insert into`
93
91
  export type DeprecatedExecuteResponsePlain = DeprecatedExecuteResponseValue | undefined;
94
92
 
95
- export type DeprecatedExecuteResponse = DeprecatedExecuteResponseDeep | DeprecatedExecuteResponsePlain;
96
-
93
+ export type DeprecatedExecuteResponse =
94
+ | DeprecatedExecuteResponseDeep
95
+ | DeprecatedExecuteResponsePlain;
97
96
 
98
97
  // ==== EXPLAIN ====
99
98
 
@@ -103,14 +102,12 @@ type ExplainResponse = CommonFields;
103
102
 
104
103
  // deprecated response from older versions, backward compatibility
105
104
 
106
- type DeprecatedExplainResponse<Action extends ExplainActions> = (
105
+ type DeprecatedExplainResponse<Action extends ExplainActions> =
107
106
  Action extends 'explain-ast'
108
107
  ? ({result: {ast: AST}} & Required<DeprecatedCommonFields>) | {ast: AST}
109
108
  : Action extends 'explain'
110
109
  ? ({result: Plan} & Required<DeprecatedCommonFields>) | Plan
111
- : unknown
112
- );
113
-
110
+ : unknown;
114
111
 
115
112
  // ==== COMBINED API RESPONSE ====
116
113
 
@@ -124,15 +121,14 @@ export type QueryAPIExplainResponse<Action extends ExplainActions> =
124
121
  | DeprecatedExplainResponse<Action>
125
122
  | null;
126
123
 
127
- export type QueryAPIResponse<Action extends Actions, Schema extends Schemas = undefined> = (
124
+ export type QueryAPIResponse<Action extends Actions, Schema extends Schemas = undefined> =
128
125
  Action extends ExecuteActions
129
126
  ? QueryAPIExecuteResponse<Schema>
130
127
  : Action extends ExplainActions
131
128
  ? QueryAPIExplainResponse<Action>
132
- : unknown
133
- );
129
+ : unknown;
134
130
 
135
- export type AnyExecuteResponse =
131
+ export type AnyExecuteResponse =
136
132
  | ExecuteModernResponse
137
133
  | ExecuteClassicResponse
138
134
  | ExecuteYdbResponse
@@ -146,13 +142,11 @@ export type DeepExecuteResponse =
146
142
  | ExecuteYdbResponse
147
143
  | DeprecatedExecuteResponseDeep;
148
144
 
149
- export type AnyExplainResponse =
145
+ export type AnyExplainResponse =
150
146
  | ExplainResponse
151
147
  | CommonFields
152
148
  | DeprecatedExplainResponse<'explain'>
153
149
  | DeprecatedExplainResponse<'explain-ast'>
154
150
  | null;
155
151
 
156
- export type AnyResponse =
157
- | AnyExecuteResponse
158
- | AnyExplainResponse;
152
+ export type AnyResponse = AnyExecuteResponse | AnyExplainResponse;
@@ -0,0 +1,14 @@
1
+ import {IResponseError} from '../api/error';
2
+ import {TEvDescribeSchemeResult} from '../api/schema';
3
+
4
+ export interface IDescribeState {
5
+ loading: boolean;
6
+ wasLoaded: boolean;
7
+ data: Record<string, TEvDescribeSchemeResult>;
8
+ currentDescribe?: TEvDescribeSchemeResult;
9
+ error?: IResponseError;
10
+ }
11
+
12
+ export interface IDescribeRootStateSlice {
13
+ describe: IDescribeState;
14
+ }
@@ -1,3 +1,4 @@
1
+ import {IResponseError} from '../api/error';
1
2
  import type {HealthCheckAPIResponse, IssueLog} from '../api/healthcheck';
2
3
 
3
4
  export interface IIssuesTree extends IssueLog {
@@ -5,3 +6,14 @@ export interface IIssuesTree extends IssueLog {
5
6
  }
6
7
 
7
8
  export type IHealthCheck = HealthCheckAPIResponse;
9
+
10
+ export interface IHealthcheckInfoState {
11
+ loading: boolean;
12
+ wasLoaded: boolean;
13
+ data?: HealthCheckAPIResponse;
14
+ error?: IResponseError;
15
+ }
16
+
17
+ export interface IHealthcheckInfoRootStateSlice {
18
+ healthcheckInfo: IHealthcheckInfoState;
19
+ }
@@ -30,4 +30,11 @@ interface Window {
30
30
  Ya?: {
31
31
  Rum?: RumCounter;
32
32
  };
33
+
34
+ // eslint-disable-next-line
35
+ web_version?: boolean;
36
+ // eslint-disable-next-line
37
+ custom_backend?: string;
38
+
39
+ __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof import('redux').compose;
33
40
  }
@@ -1 +1,2 @@
1
1
  export * from './useAutofetcher';
2
+ export * from './useTypedSelector';
@@ -0,0 +1,5 @@
1
+ import {TypedUseSelectorHook, useSelector} from 'react-redux';
2
+
3
+ import {IRootState} from '../../store';
4
+
5
+ export const useTypedSelector: TypedUseSelectorHook<IRootState> = useSelector;
@@ -4,6 +4,7 @@ import {
4
4
  TopologyNodeDataStats,
5
5
  TopologyNodeDataStatsSection,
6
6
  ExplainPlanNodeData,
7
+ TopologyNodeDataStatsItem,
7
8
  } from '@yandex-cloud/paranoid';
8
9
 
9
10
  interface PlanOperator {
@@ -25,6 +26,8 @@ export interface RootPlan {
25
26
  Plan: Plan;
26
27
  }
27
28
 
29
+ const CONNECTION_NODE_META_FIELDS = new Set(['PlanNodeId', 'PlanNodeType', 'Node Type', 'Plans']);
30
+
28
31
  function prepareStats(plan: Plan) {
29
32
  const stats: TopologyNodeDataStats[] = [];
30
33
 
@@ -52,21 +55,52 @@ function prepareStats(plan: Plan) {
52
55
  });
53
56
  }
54
57
 
58
+ if (plan.PlanNodeType === 'Connection') {
59
+ const attrStats: TopologyNodeDataStatsItem[] = [];
60
+
61
+ for (const [key, value] of Object.entries(plan)) {
62
+ if (CONNECTION_NODE_META_FIELDS.has(key)) {
63
+ continue;
64
+ }
65
+
66
+ attrStats.push({name: key, value: String(value)});
67
+ }
68
+
69
+ if (attrStats.length > 0) {
70
+ stats.push({
71
+ group: 'Attributes',
72
+ stats: attrStats,
73
+ });
74
+ }
75
+ }
76
+
55
77
  return stats;
56
78
  }
57
79
 
80
+ function getNodeType(plan: Plan) {
81
+ switch (plan.PlanNodeType) {
82
+ case 'Connection':
83
+ return 'connection';
84
+ case 'ResultSet':
85
+ return 'result';
86
+ case 'Query':
87
+ return 'query';
88
+ default:
89
+ return 'stage';
90
+ }
91
+ }
92
+
58
93
  export function preparePlan(plan: Plan) {
59
94
  const nodes: GraphNode[] = [];
60
95
  const links: Link[] = [];
61
96
 
62
- function parsePlans(plans: Plan[] = [], from: string, curDepth: number) {
63
- const depth = curDepth + 1;
97
+ function parsePlans(plans: Plan[] = [], from: string) {
64
98
  plans.forEach((p) => {
65
99
  const node: GraphNode<ExplainPlanNodeData> = {
66
100
  name: String(p.PlanNodeId),
67
101
  data: {
68
102
  id: p.PlanNodeId,
69
- type: p.PlanNodeType === 'Connection' ? 'connection' : 'stage',
103
+ type: getNodeType(p),
70
104
  name: p['Node Type'],
71
105
  operators: p.Operators?.map((o) => o.Name),
72
106
  stats: prepareStats(p),
@@ -75,7 +109,7 @@ export function preparePlan(plan: Plan) {
75
109
  };
76
110
  nodes.push(node);
77
111
  links.push({from, to: node.name});
78
- parsePlans(p.Plans, node.name, depth);
112
+ parsePlans(p.Plans, node.name);
79
113
  });
80
114
  }
81
115
 
@@ -84,12 +118,13 @@ export function preparePlan(plan: Plan) {
84
118
  name: String(rootPlan.PlanNodeId),
85
119
  data: {
86
120
  id: rootPlan.PlanNodeId,
87
- type: 'stage',
121
+ type: getNodeType(rootPlan),
88
122
  name: rootPlan['Node Type'],
89
123
  },
90
124
  };
91
125
  nodes.push(rootNode);
92
- parsePlans(rootPlan.Plans, rootNode.name, 0);
126
+ parsePlans(rootPlan.Plans, rootNode.name);
127
+
93
128
  return {
94
129
  nodes,
95
130
  links,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -33,7 +33,7 @@
33
33
  "redux": "4.0.1",
34
34
  "redux-location-state": "2.6.0",
35
35
  "redux-thunk": "2.3.0",
36
- "reselect": "4.0.0",
36
+ "reselect": "4.1.6",
37
37
  "sass": "1.32.8",
38
38
  "web-vitals": "1.1.2",
39
39
  "ydb-ui-components": "3.0.1"