ydb-embedded-ui 22.2.0 → 22.3.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 (116) hide show
  1. package/dist/components/Graph/GravityGraph.js +9 -2
  2. package/dist/components/Graph/GravityGraph.js.map +1 -1
  3. package/dist/components/Graph/utils.d.ts +2 -1
  4. package/dist/components/Graph/utils.js +7 -0
  5. package/dist/components/Graph/utils.js.map +1 -1
  6. package/dist/components/PaginatedTable/rowLookup.js +30 -9
  7. package/dist/components/PaginatedTable/rowLookup.js.map +1 -1
  8. package/dist/components/TableKeyboardNavigation/TableKeyboardNavigation.js +5 -1
  9. package/dist/components/TableKeyboardNavigation/TableKeyboardNavigation.js.map +1 -1
  10. package/dist/containers/Authentication/Authentication.js +29 -30
  11. package/dist/containers/Authentication/Authentication.js.map +1 -1
  12. package/dist/containers/Authentication/utils.d.ts +2 -2
  13. package/dist/containers/Authentication/utils.js +1 -1
  14. package/dist/containers/Authentication/utils.js.map +1 -1
  15. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.js +5 -1
  16. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.js.map +1 -1
  17. package/dist/containers/Tenant/Diagnostics/Overview/Overview.js +7 -1
  18. package/dist/containers/Tenant/Diagnostics/Overview/Overview.js.map +1 -1
  19. package/dist/containers/Tenant/Diagnostics/Overview/ResourcePoolInfo/ResourcePoolInfo.scss +24 -0
  20. package/dist/containers/Tenant/Diagnostics/Overview/ResourcePoolInfo/ResourcePoolUsage.d.ts +8 -0
  21. package/dist/containers/Tenant/Diagnostics/Overview/ResourcePoolInfo/ResourcePoolUsage.js +34 -0
  22. package/dist/containers/Tenant/Diagnostics/Overview/ResourcePoolInfo/ResourcePoolUsage.js.map +1 -0
  23. package/dist/containers/Tenant/Diagnostics/Overview/ResourcePoolInfo/i18n/en.json +4 -1
  24. package/dist/containers/Tenant/Diagnostics/Overview/ResourcePoolInfo/i18n/index.d.ts +1 -1
  25. package/dist/containers/Tenant/Diagnostics/Overview/ResourcePoolInfo/index.d.ts +1 -0
  26. package/dist/containers/Tenant/Diagnostics/Overview/ResourcePoolInfo/index.js +1 -0
  27. package/dist/containers/Tenant/Diagnostics/Overview/ResourcePoolInfo/index.js.map +1 -1
  28. package/dist/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/SchemaObjectInfo.js +1 -1
  29. package/dist/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/SchemaObjectInfo.js.map +1 -1
  30. package/dist/containers/Tenant/Diagnostics/Overview/StreamingQueryInfo/StreamingQueryInfo.js +2 -17
  31. package/dist/containers/Tenant/Diagnostics/Overview/StreamingQueryInfo/StreamingQueryInfo.js.map +1 -1
  32. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/en.json +4 -0
  33. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/index.d.ts +1 -1
  34. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo/prepareColumnTableInfo.js +4 -1
  35. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo/prepareColumnTableInfo.js.map +1 -1
  36. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo/prepareRowTableInfo.js +4 -1
  37. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo/prepareRowTableInfo.js.map +1 -1
  38. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo/prepareTTL.js +41 -3
  39. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo/prepareTTL.js.map +1 -1
  40. package/dist/containers/Tenant/Diagnostics/StreamingQueryGraph/StreamingQueryGraph.d.ts +7 -0
  41. package/dist/containers/Tenant/Diagnostics/StreamingQueryGraph/StreamingQueryGraph.js +38 -0
  42. package/dist/containers/Tenant/Diagnostics/StreamingQueryGraph/StreamingQueryGraph.js.map +1 -0
  43. package/dist/containers/Tenant/Diagnostics/StreamingQueryGraph/StreamingQueryGraph.scss +18 -0
  44. package/dist/containers/Tenant/Diagnostics/StreamingQueryGraph/i18n/en.json +5 -0
  45. package/dist/containers/Tenant/Diagnostics/StreamingQueryGraph/i18n/index.d.ts +2 -0
  46. package/dist/containers/Tenant/Diagnostics/StreamingQueryGraph/i18n/index.js +5 -0
  47. package/dist/containers/Tenant/Diagnostics/StreamingQueryGraph/i18n/index.js.map +1 -0
  48. package/dist/containers/Tenant/Diagnostics/StreamingQueryGraph/index.d.ts +1 -0
  49. package/dist/containers/Tenant/Diagnostics/StreamingQueryGraph/index.js +2 -0
  50. package/dist/containers/Tenant/Diagnostics/StreamingQueryGraph/index.js.map +1 -0
  51. package/dist/containers/Tenant/Diagnostics/i18n/en.json +1 -0
  52. package/dist/containers/Tenant/Diagnostics/i18n/index.d.ts +1 -1
  53. package/dist/containers/Tenant/Diagnostics/tabsContent.js +2 -0
  54. package/dist/containers/Tenant/Diagnostics/tabsContent.js.map +1 -1
  55. package/dist/containers/Tenant/Query/QueryEditor/YqlEditor/YqlEditor.js +8 -1
  56. package/dist/containers/Tenant/Query/QueryEditor/YqlEditor/YqlEditor.js.map +1 -1
  57. package/dist/store/reducers/executeTopQueries/executeTopQueries.d.ts +7 -0
  58. package/dist/store/reducers/executeTopQueries/executeTopQueries.js +38 -0
  59. package/dist/store/reducers/executeTopQueries/executeTopQueries.js.map +1 -1
  60. package/dist/store/reducers/query/parsers/prepareStreamingQueryPlan.d.ts +8 -0
  61. package/dist/store/reducers/query/parsers/prepareStreamingQueryPlan.js +53 -0
  62. package/dist/store/reducers/query/parsers/prepareStreamingQueryPlan.js.map +1 -0
  63. package/dist/store/reducers/streamingQuery/streamingQuery.d.ts +5 -1
  64. package/dist/store/reducers/streamingQuery/streamingQuery.js +35 -10
  65. package/dist/store/reducers/streamingQuery/streamingQuery.js.map +1 -1
  66. package/dist/store/reducers/tenant/constants.d.ts +1 -0
  67. package/dist/store/reducers/tenant/constants.js +1 -0
  68. package/dist/store/reducers/tenant/constants.js.map +1 -1
  69. package/dist/types/api/schema/columnEntity.d.ts +2 -1
  70. package/dist/types/api/schema/sysView.d.ts +59 -2
  71. package/dist/types/api/schema/sysView.js +53 -1
  72. package/dist/types/api/schema/sysView.js.map +1 -1
  73. package/dist/types/api/schema/table.d.ts +8 -0
  74. package/dist/types/api/schema/table.js.map +1 -1
  75. package/dist/utils/query.d.ts +1 -0
  76. package/dist/utils/query.js +40 -0
  77. package/dist/utils/query.js.map +1 -1
  78. package/dist/utils/schema.d.ts +6 -2
  79. package/dist/utils/schema.js +14 -4
  80. package/dist/utils/schema.js.map +1 -1
  81. package/package.json +1 -1
  82. package/src/components/Graph/GravityGraph.tsx +9 -2
  83. package/src/components/Graph/utils.ts +9 -1
  84. package/src/components/PaginatedTable/rowLookup.ts +35 -10
  85. package/src/components/TableKeyboardNavigation/TableKeyboardNavigation.tsx +5 -1
  86. package/src/containers/Authentication/Authentication.tsx +29 -34
  87. package/src/containers/Authentication/utils.ts +3 -3
  88. package/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts +6 -1
  89. package/src/containers/Tenant/Diagnostics/Overview/Overview.tsx +17 -1
  90. package/src/containers/Tenant/Diagnostics/Overview/ResourcePoolInfo/ResourcePoolInfo.scss +24 -0
  91. package/src/containers/Tenant/Diagnostics/Overview/ResourcePoolInfo/ResourcePoolUsage.tsx +53 -0
  92. package/src/containers/Tenant/Diagnostics/Overview/ResourcePoolInfo/i18n/en.json +4 -1
  93. package/src/containers/Tenant/Diagnostics/Overview/ResourcePoolInfo/index.ts +1 -0
  94. package/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/SchemaObjectInfo.tsx +7 -1
  95. package/src/containers/Tenant/Diagnostics/Overview/StreamingQueryInfo/StreamingQueryInfo.tsx +2 -20
  96. package/src/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/en.json +4 -0
  97. package/src/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo/prepareColumnTableInfo.tsx +11 -1
  98. package/src/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo/prepareRowTableInfo.tsx +11 -1
  99. package/src/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo/prepareTTL.ts +45 -3
  100. package/src/containers/Tenant/Diagnostics/StreamingQueryGraph/StreamingQueryGraph.scss +18 -0
  101. package/src/containers/Tenant/Diagnostics/StreamingQueryGraph/StreamingQueryGraph.tsx +79 -0
  102. package/src/containers/Tenant/Diagnostics/StreamingQueryGraph/i18n/en.json +5 -0
  103. package/src/containers/Tenant/Diagnostics/StreamingQueryGraph/i18n/index.ts +7 -0
  104. package/src/containers/Tenant/Diagnostics/StreamingQueryGraph/index.ts +1 -0
  105. package/src/containers/Tenant/Diagnostics/i18n/en.json +1 -0
  106. package/src/containers/Tenant/Diagnostics/tabsContent.tsx +4 -0
  107. package/src/containers/Tenant/Query/QueryEditor/YqlEditor/YqlEditor.tsx +8 -1
  108. package/src/store/reducers/executeTopQueries/executeTopQueries.ts +46 -0
  109. package/src/store/reducers/query/parsers/prepareStreamingQueryPlan.ts +74 -0
  110. package/src/store/reducers/streamingQuery/streamingQuery.ts +43 -16
  111. package/src/store/reducers/tenant/constants.ts +1 -0
  112. package/src/types/api/schema/columnEntity.ts +2 -1
  113. package/src/types/api/schema/sysView.ts +60 -37
  114. package/src/types/api/schema/table.ts +7 -0
  115. package/src/utils/query.ts +55 -0
  116. package/src/utils/schema.ts +22 -5
@@ -21,7 +21,7 @@ import {NonSelectableConnection} from './NonSelectableConnection';
21
21
  import {graphColorsConfig} from './colorsConfig';
22
22
  import {runTreeLayout} from './runTreeLayout';
23
23
  import type {Data} from './types';
24
- import {parseCustomPropertyValue} from './utils';
24
+ import {getLayoutSignature, parseCustomPropertyValue} from './utils';
25
25
 
26
26
  import './GravityGraph.scss';
27
27
 
@@ -81,6 +81,7 @@ export function GravityGraph<T>({data, onError, theme}: Props<T>) {
81
81
  const {graph, start} = useGraph(config);
82
82
  const cameraSizeRef = React.useRef({width: 0, height: 0});
83
83
  const graphBlockIdsRef = React.useRef<TBlockId[]>([]);
84
+ const layoutSignatureRef = React.useRef<string>();
84
85
  const fitFrameRef = React.useRef<number>();
85
86
  const fitGraphToViewport = React.useCallback(() => {
86
87
  const {width, height} = graph.cameraService.getCameraState();
@@ -125,11 +126,17 @@ export function GravityGraph<T>({data, onError, theme}: Props<T>) {
125
126
  createWorker: createGraphLayoutWorker,
126
127
  onResult: ({layout, edges}) => {
127
128
  graphBlockIdsRef.current = layout.map(({id}) => id);
129
+ // Statistics-only updates keep the layout, so the camera stays where the user put it.
130
+ const signature = getLayoutSignature(layout, edges);
131
+ const sameLayout = layoutSignatureRef.current === signature;
132
+ layoutSignatureRef.current = signature;
128
133
  graph.setEntities({
129
134
  blocks: layout,
130
135
  connections: edges,
131
136
  });
132
- scheduleGraphFit();
137
+ if (!sameLayout) {
138
+ scheduleGraphFit();
139
+ }
133
140
  },
134
141
  onError,
135
142
  });
@@ -3,7 +3,7 @@ import type {TBlock} from '@gravity-ui/graph';
3
3
  import type {ExplainPlanNodeData} from '../../store/reducers/query/types';
4
4
 
5
5
  import type {AbstractGraphColorsConfig} from './colorsConfig';
6
- import type {Data, TreeConnection} from './types';
6
+ import type {Data, EdgeResult, TreeConnection} from './types';
7
7
 
8
8
  const BLOCK_TOP_PADDING = 8;
9
9
  const BLOCK_LINE_HEIGHT = 16;
@@ -110,3 +110,11 @@ export function parseCustomPropertyValue<T extends AbstractGraphColorsConfig>(
110
110
 
111
111
  return parsed as T;
112
112
  }
113
+
114
+ // Identifies the geometry the camera is aimed at: block placement and the edges between them.
115
+ export function getLayoutSignature(blocks: TBlock[], edges: EdgeResult[]) {
116
+ return JSON.stringify([
117
+ blocks.map(({id, x, y, width, height}) => [id, x, y, width, height]),
118
+ edges.map(({sourceBlockId, targetBlockId}) => [sourceBlockId, targetBlockId]),
119
+ ]);
120
+ }
@@ -33,15 +33,11 @@ function hasChunkAdvanced(
33
33
  return false;
34
34
  }
35
35
 
36
- if (!previous.has(offset)) {
37
- return true;
38
- }
39
-
40
36
  const previousFulfilledTimeStamp = previous.get(offset);
41
37
  return (
42
- previousFulfilledTimeStamp === null ||
43
- (previousFulfilledTimeStamp !== undefined &&
44
- fulfilledTimeStamp > previousFulfilledTimeStamp)
38
+ previousFulfilledTimeStamp !== null &&
39
+ previousFulfilledTimeStamp !== undefined &&
40
+ fulfilledTimeStamp > previousFulfilledTimeStamp
45
41
  );
46
42
  }
47
43
 
@@ -49,15 +45,44 @@ export function getChunkLookupRevision(states: ChunkLookupState[], revision?: st
49
45
  if (!revision) {
50
46
  return serializeChunkLookupRevision(states);
51
47
  }
48
+ if (states.length === 0) {
49
+ return revision;
50
+ }
52
51
 
53
52
  const previous = parseChunkLookupRevision(revision);
54
- return states.length > 0 && states.every((state) => hasChunkAdvanced(state, previous))
55
- ? serializeChunkLookupRevision(states)
53
+ if (states.every((state) => hasChunkAdvanced(state, previous))) {
54
+ return serializeChunkLookupRevision(states);
55
+ }
56
+
57
+ let changed = false;
58
+ const activeOffsets = new Set(states.map(({offset}) => offset));
59
+ for (const offset of previous.keys()) {
60
+ if (!activeOffsets.has(offset)) {
61
+ previous.delete(offset);
62
+ changed = true;
63
+ }
64
+ }
65
+ for (const {offset, status, fulfilledTimeStamp} of states) {
66
+ const previousFulfilledTimeStamp = previous.get(offset);
67
+ if (
68
+ (previousFulfilledTimeStamp === null || previousFulfilledTimeStamp === undefined) &&
69
+ status === QueryStatus.fulfilled &&
70
+ fulfilledTimeStamp !== undefined
71
+ ) {
72
+ // Initial loading establishes a baseline, not a completed polling cycle.
73
+ previous.set(offset, fulfilledTimeStamp);
74
+ changed = true;
75
+ }
76
+ }
77
+ return changed
78
+ ? JSON.stringify(
79
+ Array.from(previous).sort(([leftOffset], [rightOffset]) => leftOffset - rightOffset),
80
+ )
56
81
  : revision;
57
82
  }
58
83
 
59
84
  export function isChunkLookupPending(states: ChunkLookupState[], revision?: string) {
60
85
  const previous = parseChunkLookupRevision(revision);
61
86
 
62
- return states.some((state) => !hasChunkAdvanced(state, previous));
87
+ return states.length === 0 || states.some((state) => !hasChunkAdvanced(state, previous));
63
88
  }
@@ -338,7 +338,11 @@ export function useTableKeyboardAdapter(options: AdapterOptions) {
338
338
  ? findRowIndex(current.key, current.index)
339
339
  : current.index;
340
340
  if (matchedIndex === undefined && isRowLookupPending?.(current.lookupRevision)) {
341
- next = {...current, pending: true};
341
+ next = {
342
+ ...current,
343
+ lookupRevision: getRowLookupRevision?.(current.lookupRevision),
344
+ pending: true,
345
+ };
342
346
  } else {
343
347
  const position = matchedIndex ?? current.index;
344
348
  const index = isValidIndex?.(position) === false ? getLast() : position;
@@ -4,7 +4,7 @@ import {Eye, EyeSlash, Xmark} from '@gravity-ui/icons';
4
4
  import {ActionTooltip, Button, Link as ExternalLink, Icon, TextInput} from '@gravity-ui/uikit';
5
5
  import {useHistory, useLocation, useRouteMatch} from 'react-router-dom';
6
6
 
7
- import routes, {getHomePagePath, parseQuery} from '../../routes';
7
+ import routes, {getClusterPath, getHomePagePath, parseQuery} from '../../routes';
8
8
  import {basename} from '../../store';
9
9
  import {authenticationApi} from '../../store/reducers/authentication/authentication';
10
10
  import {useLoginWithDatabase, useOidcAvailable} from '../../store/reducers/capabilities/hooks';
@@ -12,11 +12,12 @@ import {cn} from '../../utils/cn';
12
12
  import {BRAND_BUTTON_CLASS} from '../../utils/constants';
13
13
  import {prepareCommonErrorMessage} from '../../utils/errors';
14
14
  import {useMetaAuth} from '../../utils/hooks/useMetaAuth';
15
+ import {useTypedSelector} from '../../utils/hooks/useTypedSelector';
15
16
 
16
17
  import i18n from './i18n';
17
18
  import {
18
19
  createSsoAuthorizeUrl,
19
- getSsoReturnTo,
20
+ getAuthReturnTo,
20
21
  isDatabaseError,
21
22
  isPasswordError,
22
23
  isUserError,
@@ -36,6 +37,7 @@ function Authentication({closable = false}: AuthenticationProps) {
36
37
  const history = useHistory();
37
38
  const location = useLocation();
38
39
  const isDirectAuthPage = Boolean(useRouteMatch({path: routes.auth, exact: true}));
40
+ const singleClusterMode = useTypedSelector((state) => state.singleClusterMode);
39
41
 
40
42
  const needDatabase = useLoginWithDatabase();
41
43
  const oidcAvailable = useOidcAvailable();
@@ -45,49 +47,40 @@ function Authentication({closable = false}: AuthenticationProps) {
45
47
  const {returnUrl, database: databaseFromQuery} = parseQuery(location);
46
48
  const currentHref = window.location.href;
47
49
 
48
- const path = React.useMemo(() => {
49
- let path: string | undefined;
50
+ const returnTo = React.useMemo(() => {
51
+ const fallbackPath = singleClusterMode
52
+ ? getClusterPath(undefined, undefined, {withBasename: true})
53
+ : getHomePagePath(undefined, undefined, {withBasename: true});
50
54
 
51
- if (returnUrl) {
52
- const decodedUrl = decodeURIComponent(returnUrl.toString());
53
-
54
- // to prevent page reload we use router history
55
- // history navigates relative to origin
56
- // so we remove origin to make it work properly
57
- const url = new URL(decodedUrl);
58
- let pathname = url.pathname;
59
-
60
- // Remove basename from pathname since history.replace expects paths relative to basename
61
- if (basename && pathname.startsWith(basename)) {
62
- pathname = pathname.slice(basename.length);
63
- }
64
-
65
- // Ensure pathname starts with /
66
- if (pathname && !pathname.startsWith('/')) {
67
- pathname = '/' + pathname;
68
- }
69
-
70
- path = pathname + url.search;
71
- }
72
- return path;
73
- }, [returnUrl]);
55
+ return getAuthReturnTo({
56
+ currentUrl: new URL(currentHref),
57
+ fallbackPath,
58
+ isDirectAuthPage,
59
+ returnUrl,
60
+ });
61
+ }, [currentHref, isDirectAuthPage, returnUrl, singleClusterMode]);
74
62
 
75
- const useMeta = useMetaAuth(path);
63
+ const pathname = new URL(returnTo, currentHref).pathname;
64
+ // Route matching for meta authentication expects a pathname without the router basename.
65
+ const authPath =
66
+ basename && pathname.startsWith(`${basename}/`)
67
+ ? pathname.slice(basename.length)
68
+ : pathname;
69
+ const useMeta = useMetaAuth(authPath);
76
70
 
77
71
  const ssoUrl = React.useMemo(() => {
78
72
  if (!oidcAvailable) {
79
73
  return undefined;
80
74
  }
81
75
 
82
- const homePath = getHomePagePath(undefined, undefined, {withBasename: true});
83
76
  const currentUrl = new URL(currentHref);
84
- const returnTo = getSsoReturnTo({
77
+ const ssoReturnTo = getAuthReturnTo({
85
78
  currentUrl,
86
- fallbackPath: homePath,
79
+ fallbackPath: getHomePagePath(undefined, undefined, {withBasename: true}),
87
80
  isDirectAuthPage,
88
81
  returnUrl,
89
82
  });
90
- return createSsoAuthorizeUrl(currentUrl.host, returnTo);
83
+ return createSsoAuthorizeUrl(currentUrl.host, ssoReturnTo);
91
84
  }, [currentHref, isDirectAuthPage, oidcAvailable, returnUrl]);
92
85
 
93
86
  const [login, setLogin] = React.useState('');
@@ -121,8 +114,10 @@ function Authentication({closable = false}: AuthenticationProps) {
121
114
  authenticate({user: login, password, database, useMeta})
122
115
  .unwrap()
123
116
  .then(() => {
124
- if (path) {
125
- history.replace(path);
117
+ if (isDirectAuthPage) {
118
+ window.location.replace(returnTo);
119
+ } else {
120
+ window.location.reload();
126
121
  }
127
122
  })
128
123
  .catch((error) => {
@@ -11,7 +11,7 @@ export function createSsoAuthorizeUrl(host: string, returnTo: string) {
11
11
  return url.href;
12
12
  }
13
13
 
14
- interface GetSsoReturnToParams {
14
+ interface GetAuthReturnToParams {
15
15
  currentUrl: URL;
16
16
  fallbackPath: string;
17
17
  isDirectAuthPage: boolean;
@@ -28,12 +28,12 @@ function isSafeLocalReturnTo(path: string) {
28
28
  );
29
29
  }
30
30
 
31
- export function getSsoReturnTo({
31
+ export function getAuthReturnTo({
32
32
  currentUrl,
33
33
  fallbackPath,
34
34
  isDirectAuthPage,
35
35
  returnUrl,
36
- }: GetSsoReturnToParams) {
36
+ }: GetAuthReturnToParams) {
37
37
  if (!isDirectAuthPage) {
38
38
  const path = `${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`;
39
39
  return isSafeLocalReturnTo(path) ? path : fallbackPath;
@@ -115,6 +115,11 @@ const graph = {
115
115
  title: i18n('title_graph'),
116
116
  };
117
117
 
118
+ const computationGraph = {
119
+ id: TENANT_DIAGNOSTICS_TABS_IDS.computationGraph,
120
+ title: i18n('title_computation-graph'),
121
+ };
122
+
118
123
  const consumers = {
119
124
  id: TENANT_DIAGNOSTICS_TABS_IDS.consumers,
120
125
  title: i18n('title_consumers'),
@@ -241,7 +246,7 @@ const EXTERNAL_TABLE_PAGES = [overview, schema, describe, access];
241
246
 
242
247
  const VIEW_PAGES = [overview, schema, describe, access];
243
248
 
244
- const STREAMING_QUERY_PAGES = [overview, describe, access];
249
+ const STREAMING_QUERY_PAGES = [overview, computationGraph, describe, access];
245
250
  const SECRET_PAGES = [overview, describe, access];
246
251
 
247
252
  // verbose mapping to guarantee correct tabs for new path types
@@ -6,6 +6,7 @@ import {Loader} from '../../../../components/Loader';
6
6
  import {useClusterWithProxy} from '../../../../store/reducers/cluster/cluster';
7
7
  import {overviewApi} from '../../../../store/reducers/overview/overview';
8
8
  import {EPathType} from '../../../../types/api/schema';
9
+ import {cn} from '../../../../utils/cn';
9
10
  import {useAutoRefreshInterval} from '../../../../utils/hooks';
10
11
  import {ViewInfo} from '../../Info/View/View';
11
12
  import {isDomain} from '../../ObjectSummary/transformPath';
@@ -16,6 +17,7 @@ import {ChangefeedInfo} from './ChangefeedInfo';
16
17
  import {DatabaseInfo} from './DatabaseInfo/DatabaseInfo';
17
18
  import {DefaultEntityInfo} from './DefaultEntityInfo';
18
19
  import {ResourcePoolInfo} from './ResourcePoolInfo';
20
+ import {ResourcePoolUsage} from './ResourcePoolInfo/ResourcePoolUsage';
19
21
  import {SchemaObjectInfoContainer} from './SchemaObjectInfo/SchemaObjectInfoContainer';
20
22
  import {StreamingQueryInfo} from './StreamingQueryInfo';
21
23
  import {TableInfo} from './TableInfo';
@@ -29,6 +31,8 @@ interface OverviewProps {
29
31
  databaseFullPath: string;
30
32
  }
31
33
 
34
+ const b = cn('ydb-diagnostics-resource-pool-info');
35
+
32
36
  function Overview({type, path, database, databaseFullPath}: OverviewProps) {
33
37
  const [autoRefreshInterval] = useAutoRefreshInterval();
34
38
  const useMetaProxy = useClusterWithProxy();
@@ -56,7 +60,19 @@ function Overview({type, path, database, databaseFullPath}: OverviewProps) {
56
60
  const pathTypeToComponent: Record<EPathType, (() => React.ReactNode) | undefined> = {
57
61
  [EPathType.EPathTypeInvalid]: undefined,
58
62
  [EPathType.EPathTypeDir]: undefined,
59
- [EPathType.EPathTypeResourcePool]: () => <ResourcePoolInfo data={data} />,
63
+ [EPathType.EPathTypeResourcePool]: () => (
64
+ <div className={b('row')}>
65
+ <div className={b('col')}>
66
+ <ResourcePoolInfo data={data} />
67
+ </div>
68
+ <div className={b('col')}>
69
+ <ResourcePoolUsage
70
+ database={database}
71
+ poolName={data?.PathDescription?.ResourcePoolDescription?.Name || ''}
72
+ />
73
+ </div>
74
+ </div>
75
+ ),
60
76
  [EPathType.EPathTypeSecret]: undefined,
61
77
  [EPathType.EPathTypeTable]: renderTableInfo,
62
78
  [EPathType.EPathTypeSysView]: undefined,
@@ -0,0 +1,24 @@
1
+ .ydb-diagnostics-resource-pool-info {
2
+ $column-width: 310px;
3
+
4
+ &__row {
5
+ display: flex;
6
+ flex-wrap: wrap;
7
+ justify-content: flex-start;
8
+ align-items: flex-start;
9
+ gap: var(--g-spacing-9);
10
+ }
11
+
12
+ &__col {
13
+ display: flex;
14
+ flex-direction: column;
15
+ justify-content: flex-start;
16
+ align-items: stretch;
17
+
18
+ width: $column-width;
19
+ }
20
+
21
+ &__loader {
22
+ margin-top: 50px;
23
+ }
24
+ }
@@ -0,0 +1,53 @@
1
+ import {Loader} from '../../../../../components/Loader';
2
+ import {YDBDefinitionList} from '../../../../../components/YDBDefinitionList/YDBDefinitionList';
3
+ import {topQueriesApi} from '../../../../../store/reducers/executeTopQueries/executeTopQueries';
4
+ import {cn} from '../../../../../utils/cn';
5
+ import {EMPTY_DATA_PLACEHOLDER} from '../../../../../utils/constants';
6
+ import {useAutoRefreshInterval} from '../../../../../utils/hooks';
7
+
8
+ import i18n from './i18n';
9
+
10
+ import './ResourcePoolInfo.scss';
11
+
12
+ const b = cn('ydb-diagnostics-resource-pool-info');
13
+
14
+ interface ResourcePoolUsageProps {
15
+ database: string;
16
+ poolName: string;
17
+ }
18
+
19
+ /** Displays running/queued query counts for a resource pool */
20
+ export function ResourcePoolUsage({database, poolName}: ResourcePoolUsageProps) {
21
+ const [autoRefreshInterval] = useAutoRefreshInterval();
22
+ const {currentData, isFetching, error} = topQueriesApi.useGetRunningQueriesByPoolQuery(
23
+ {database, poolName},
24
+ {skip: !poolName, pollingInterval: autoRefreshInterval, refetchOnMountOrArgChange: true},
25
+ );
26
+
27
+ const loading = isFetching && currentData === undefined;
28
+
29
+ if (loading) {
30
+ return <Loader size="s" className={b('loader')} />;
31
+ }
32
+
33
+ if (error || !poolName) {
34
+ return null;
35
+ }
36
+
37
+ return (
38
+ <YDBDefinitionList
39
+ items={[
40
+ {
41
+ name: i18n('field_running-queries'),
42
+ content: currentData?.runningQueriesCount ?? EMPTY_DATA_PLACEHOLDER,
43
+ },
44
+ {
45
+ name: i18n('field_queued-queries'),
46
+ content: currentData?.queuedQueriesCount ?? EMPTY_DATA_PLACEHOLDER,
47
+ },
48
+ ]}
49
+ title={i18n('title_usage')}
50
+ responsive
51
+ />
52
+ );
53
+ }
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "title_pool-settings": "Pool Settings",
3
+ "title_usage": "Usage",
3
4
  "group_queue": "Queue & Concurrency",
4
5
  "group_cpu": "CPU",
5
6
  "field_concurrent-query-limit": "Concurrent query limit",
6
7
  "field_queue-size": "Queue size",
7
8
  "field_database-load-cpu-threshold": "Database load CPU threshold",
8
- "field_total-cpu-limit-percent-per-node": "Total CPU limit % per node"
9
+ "field_total-cpu-limit-percent-per-node": "Total CPU limit % per node",
10
+ "field_running-queries": "Running queries",
11
+ "field_queued-queries": "Queued queries"
9
12
  }
@@ -1 +1,2 @@
1
1
  export * from './ResourcePoolInfo';
2
+ export * from './ResourcePoolUsage';
@@ -42,6 +42,12 @@ export function SchemaObjectInfo({
42
42
  );
43
43
 
44
44
  return (
45
- <YDBDefinitionList items={items} responsive className={b('list')} wrapperClassName={b()} />
45
+ <YDBDefinitionList
46
+ items={items}
47
+ responsive
48
+ className={b('list')}
49
+ wrapperClassName={b()}
50
+ dataQa="schema-object-info"
51
+ />
46
52
  );
47
53
  }
@@ -7,7 +7,6 @@ import type {YDBDefinitionListItem} from '../../../../../components/YDBDefinitio
7
7
  import {YDBDefinitionList} from '../../../../../components/YDBDefinitionList/YDBDefinitionList';
8
8
  import {YQLCodePreview} from '../../../../../components/YQLCodePreview/YQLCodePreview';
9
9
  import {streamingQueriesApi} from '../../../../../store/reducers/streamingQuery/streamingQuery';
10
- import type {ErrorResponse} from '../../../../../types/api/query';
11
10
  import type {IQueryResult} from '../../../../../types/store/query';
12
11
  import {cn} from '../../../../../utils/cn';
13
12
  import {
@@ -15,7 +14,7 @@ import {
15
14
  stripIndentByFirstLine,
16
15
  trimOuterEmptyLines,
17
16
  } from '../../../../../utils/dataFormatters/dataFormatters';
18
- import {isErrorResponse} from '../../../../../utils/query';
17
+ import {parseIssuesData} from '../../../../../utils/query';
19
18
  import {ResultIssuesModal} from '../../../Query/Issues/Issues';
20
19
 
21
20
  import i18n from './i18n';
@@ -89,7 +88,7 @@ function prepareStreamingQueryItems(sysData?: IQueryResult) {
89
88
  const errorRaw = sysData.resultSets?.[0]?.result?.[0]?.Error;
90
89
 
91
90
  // We use custom error check, because error type can be non-standard
92
- const errorData = parseErrorData(errorRaw);
91
+ const errorData = parseIssuesData(errorRaw);
93
92
 
94
93
  info.push({
95
94
  name: i18n('field_query-state'),
@@ -105,20 +104,3 @@ function prepareStreamingQueryItems(sysData?: IQueryResult) {
105
104
 
106
105
  return {items: info, queryText: normalizedQueryText};
107
106
  }
108
-
109
- function parseErrorData(raw: unknown): ErrorResponse | string | undefined {
110
- if (typeof raw === 'string') {
111
- try {
112
- const parsed = JSON.parse(raw);
113
- return isErrorResponse(parsed) ? parsed : undefined;
114
- } catch {
115
- return raw;
116
- }
117
- }
118
-
119
- if (isErrorResponse(raw)) {
120
- return raw;
121
- }
122
-
123
- return undefined;
124
- }
@@ -26,6 +26,10 @@
26
26
  "field_compression-groups": "Compression & families",
27
27
 
28
28
  "value_ttl-config": "column: '{{columnName}}', expire after: {{expireTime}}",
29
+ "value_ttl-tiered-config": "column: '{{columnName}}'\n{{tiers}}",
30
+ "value_ttl-evict": "evict to: '{{storageName}}', after: {{expireTime}}",
31
+ "value_ttl-delete": "delete after: {{expireTime}}",
32
+ "value_ttl-unknown-action": "unknown action, after: {{expireTime}}",
29
33
  "value_partitioning-by-size-enabled": "Enabled, split size: {{size}}",
30
34
  "value_enabled": "Enabled",
31
35
  "value_disabled": "Disabled",
@@ -42,7 +42,17 @@ export function prepareColumnTableGeneralInfo(columnTable: TColumnTableDescripti
42
42
  if (columnTable.TtlSettings) {
43
43
  const ttlInfo = prepareTTL(columnTable?.TtlSettings);
44
44
  if (ttlInfo) {
45
- left.push(ttlInfo);
45
+ left.push({
46
+ ...ttlInfo,
47
+ content: (
48
+ <span
49
+ style={{whiteSpace: 'break-spaces', wordBreak: 'break-word'}}
50
+ data-qa="table-ttl"
51
+ >
52
+ {ttlInfo.content}
53
+ </span>
54
+ ),
55
+ });
46
56
  }
47
57
  }
48
58
 
@@ -66,7 +66,17 @@ export function prepareRowTableGeneralInfo(
66
66
  if (TTLSettings) {
67
67
  const ttlInfo = prepareTTL(TTLSettings);
68
68
  if (ttlInfo) {
69
- left.push(ttlInfo);
69
+ left.push({
70
+ ...ttlInfo,
71
+ content: (
72
+ <span
73
+ style={{whiteSpace: 'break-spaces', wordBreak: 'break-word'}}
74
+ data-qa="table-ttl"
75
+ >
76
+ {ttlInfo.content}
77
+ </span>
78
+ ),
79
+ });
70
80
  }
71
81
  }
72
82
 
@@ -1,4 +1,5 @@
1
1
  import type {TColumnDataLifeCycle, TTTLSettings} from '../../../../../../types/api/schema';
2
+ import {EMPTY_DATA_PLACEHOLDER} from '../../../../../../utils/constants';
2
3
  import {formatDurationToShortTimeFormat} from '../../../../../../utils/timeParsers';
3
4
  import i18n from '../i18n';
4
5
 
@@ -8,11 +9,52 @@ import i18n from '../i18n';
8
9
  * @returns Definition list item with TTL info, or undefined if TTL is not configured
9
10
  */
10
11
  export function prepareTTL(ttl: TTTLSettings | TColumnDataLifeCycle) {
12
+ const enabled = ttl.Enabled;
13
+ if (!enabled?.ColumnName) {
14
+ return undefined;
15
+ }
16
+
17
+ if (enabled.Tiers?.length) {
18
+ const tiers = enabled.Tiers.flatMap((tier) => {
19
+ const seconds = tier.ApplyAfterSeconds;
20
+ if (seconds === undefined || !Number.isFinite(seconds) || seconds < 0) {
21
+ return [];
22
+ }
23
+
24
+ const expireTime = formatDurationToShortTimeFormat(seconds * 1000, 1);
25
+ if (tier.EvictToExternalStorage) {
26
+ return [
27
+ i18n('value_ttl-evict', {
28
+ storageName: tier.EvictToExternalStorage.Storage || EMPTY_DATA_PLACEHOLDER,
29
+ expireTime,
30
+ }),
31
+ ];
32
+ }
33
+ if (tier.Delete) {
34
+ return [i18n('value_ttl-delete', {expireTime})];
35
+ }
36
+ return [i18n('value_ttl-unknown-action', {expireTime})];
37
+ });
38
+
39
+ // Nonempty tiers supersede the legacy expiry, even if all delays are invalid.
40
+ if (!tiers.length) {
41
+ return undefined;
42
+ }
43
+
44
+ return {
45
+ name: i18n('field_ttl-for-rows'),
46
+ content: i18n('value_ttl-tiered-config', {
47
+ columnName: enabled.ColumnName,
48
+ tiers: tiers.join('\n'),
49
+ }),
50
+ };
51
+ }
52
+
11
53
  // ExpireAfterSeconds could be 0
12
- if (ttl.Enabled && ttl.Enabled.ColumnName && ttl.Enabled.ExpireAfterSeconds !== undefined) {
54
+ if (enabled.ExpireAfterSeconds !== undefined) {
13
55
  const value = i18n('value_ttl-config', {
14
- columnName: ttl.Enabled.ColumnName,
15
- expireTime: formatDurationToShortTimeFormat(ttl.Enabled.ExpireAfterSeconds * 1000, 1),
56
+ columnName: enabled.ColumnName,
57
+ expireTime: formatDurationToShortTimeFormat(enabled.ExpireAfterSeconds * 1000, 1),
16
58
  });
17
59
 
18
60
  return {name: i18n('field_ttl-for-rows'), content: value};
@@ -0,0 +1,18 @@
1
+ @use '../../../../styles/mixins.scss';
2
+
3
+ .ydb-streaming-query-graph {
4
+ overflow: auto;
5
+ flex-grow: 1;
6
+
7
+ height: 100%;
8
+
9
+ @include mixins.flex-container();
10
+
11
+ > * {
12
+ flex-shrink: 0;
13
+ }
14
+
15
+ &__loader {
16
+ margin: auto;
17
+ }
18
+ }