ydb-embedded-ui 20.5.0 → 21.1.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 (173) hide show
  1. package/README.md +41 -0
  2. package/dist/components/AutoRefreshControl/i18n/index.d.ts +1 -1
  3. package/dist/components/CriticalActionDialog/CriticalActionDialog.js +10 -6
  4. package/dist/components/CriticalActionDialog/CriticalActionDialog.js.map +1 -1
  5. package/dist/components/CriticalActionDialog/i18n/en.json +1 -2
  6. package/dist/components/CriticalActionDialog/i18n/index.d.ts +1 -1
  7. package/dist/components/DropdownMenu/DropdownMenu.d.ts +2 -1
  8. package/dist/components/DropdownMenu/DropdownMenu.js +1 -1
  9. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  10. package/dist/components/FullNodeViewer/i18n/index.d.ts +1 -1
  11. package/dist/components/Graph/BlockComponents/ConnectionBlockComponent.d.ts +7 -0
  12. package/dist/components/Graph/BlockComponents/ConnectionBlockComponent.js +30 -0
  13. package/dist/components/Graph/BlockComponents/ConnectionBlockComponent.js.map +1 -0
  14. package/dist/components/Graph/BlockComponents/QueryBlockComponent.d.ts +5 -0
  15. package/dist/components/Graph/BlockComponents/QueryBlockComponent.js +5 -0
  16. package/dist/components/Graph/BlockComponents/QueryBlockComponent.js.map +1 -0
  17. package/dist/components/Graph/BlockComponents/ResultBlockComponent.d.ts +7 -0
  18. package/dist/components/Graph/BlockComponents/ResultBlockComponent.js +5 -0
  19. package/dist/components/Graph/BlockComponents/ResultBlockComponent.js.map +1 -0
  20. package/dist/components/Graph/BlockComponents/StageBlockComponent.d.ts +7 -0
  21. package/dist/components/Graph/BlockComponents/StageBlockComponent.js +17 -0
  22. package/dist/components/Graph/BlockComponents/StageBlockComponent.js.map +1 -0
  23. package/dist/components/Graph/FullViewportBackground.d.ts +2 -0
  24. package/dist/components/Graph/FullViewportBackground.js +67 -0
  25. package/dist/components/Graph/FullViewportBackground.js.map +1 -0
  26. package/dist/components/Graph/GraphControls.d.ts +6 -0
  27. package/dist/components/Graph/GraphControls.js +24 -0
  28. package/dist/components/Graph/GraphControls.js.map +1 -0
  29. package/dist/components/Graph/GravityGraph.d.ts +9 -0
  30. package/dist/components/Graph/GravityGraph.js +122 -0
  31. package/dist/components/Graph/GravityGraph.js.map +1 -0
  32. package/dist/components/Graph/GravityGraph.scss +131 -0
  33. package/dist/components/Graph/NonSelectableConnection.d.ts +9 -0
  34. package/dist/components/Graph/NonSelectableConnection.js +16 -0
  35. package/dist/components/Graph/NonSelectableConnection.js.map +1 -0
  36. package/dist/components/Graph/TooltipComponent.d.ts +8 -0
  37. package/dist/components/Graph/TooltipComponent.js +63 -0
  38. package/dist/components/Graph/TooltipComponent.js.map +1 -0
  39. package/dist/components/Graph/colorsConfig.d.ts +19 -0
  40. package/dist/components/Graph/colorsConfig.js +20 -0
  41. package/dist/components/Graph/colorsConfig.js.map +1 -0
  42. package/dist/components/Graph/i18n/en.json +5 -0
  43. package/dist/components/Graph/i18n/index.d.ts +2 -0
  44. package/dist/components/Graph/i18n/index.js +5 -0
  45. package/dist/components/Graph/i18n/index.js.map +1 -0
  46. package/dist/components/Graph/runTreeLayout.d.ts +12 -0
  47. package/dist/components/Graph/runTreeLayout.js +75 -0
  48. package/dist/components/Graph/runTreeLayout.js.map +1 -0
  49. package/dist/components/Graph/treeLayout.d.ts +2 -0
  50. package/dist/components/Graph/treeLayout.js +284 -0
  51. package/dist/components/Graph/treeLayout.js.map +1 -0
  52. package/dist/components/Graph/types.d.ts +52 -0
  53. package/dist/components/Graph/types.js +2 -0
  54. package/dist/components/Graph/types.js.map +1 -0
  55. package/dist/components/Graph/utils.d.ts +6 -0
  56. package/dist/components/Graph/utils.js +88 -0
  57. package/dist/components/Graph/utils.js.map +1 -0
  58. package/dist/components/PDiskInfo/i18n/index.d.ts +1 -1
  59. package/dist/components/VDiskInfo/i18n/index.d.ts +1 -1
  60. package/dist/containers/Clusters/constants.d.ts +1 -1
  61. package/dist/containers/Header/HeaderActionsMenu.js +19 -3
  62. package/dist/containers/Header/HeaderActionsMenu.js.map +1 -1
  63. package/dist/containers/Header/hooks/useSharedDatabasePath.d.ts +4 -1
  64. package/dist/containers/Header/hooks/useSharedDatabasePath.js +14 -10
  65. package/dist/containers/Header/hooks/useSharedDatabasePath.js.map +1 -1
  66. package/dist/containers/Header/i18n/en.json +1 -0
  67. package/dist/containers/Header/i18n/index.d.ts +1 -1
  68. package/dist/containers/Nodes/i18n/index.d.ts +1 -1
  69. package/dist/containers/Operations/columns.js +2 -2
  70. package/dist/containers/Operations/columns.js.map +1 -1
  71. package/dist/containers/Operations/constants.d.ts +1 -1
  72. package/dist/containers/Operations/i18n/en.json +2 -2
  73. package/dist/containers/PDiskPage/DecommissionButton/DecommissionButton.js +1 -1
  74. package/dist/containers/PDiskPage/DecommissionButton/DecommissionButton.js.map +1 -1
  75. package/dist/containers/PDiskPage/PDiskPage.js +1 -1
  76. package/dist/containers/PDiskPage/PDiskPage.js.map +1 -1
  77. package/dist/containers/PDiskPage/i18n/en.json +3 -3
  78. package/dist/containers/Storage/PDisks/PDisks.js +1 -1
  79. package/dist/containers/Storage/PDisks/PDisks.js.map +1 -1
  80. package/dist/containers/Tablet/components/TabletControls/TabletControls.js +1 -1
  81. package/dist/containers/Tablet/components/TabletControls/TabletControls.js.map +1 -1
  82. package/dist/containers/Tablet/i18n/en.json +3 -3
  83. package/dist/containers/Tablets/TabletsTable.js +1 -1
  84. package/dist/containers/Tablets/TabletsTable.js.map +1 -1
  85. package/dist/containers/Tablets/i18n/en.json +1 -1
  86. package/dist/containers/Tenant/Diagnostics/Overview/TransferInfo/Credentials.d.ts +1 -1
  87. package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.js +3 -1
  88. package/dist/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.js.map +1 -1
  89. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +2 -2
  90. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js.map +1 -1
  91. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/useTenantStorageNewData.d.ts +2 -2
  92. package/dist/containers/Tenant/Healthcheck/components/HealthcheckDrawer.js +10 -2
  93. package/dist/containers/Tenant/Healthcheck/components/HealthcheckDrawer.js.map +1 -1
  94. package/dist/containers/Tenant/Query/QueryResult/components/Graph/Graph.js +9 -4
  95. package/dist/containers/Tenant/Query/QueryResult/components/Graph/Graph.js.map +1 -1
  96. package/dist/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss +2 -0
  97. package/dist/containers/Tenant/TenantNavigation/useTenantNavigation.d.ts +1 -1
  98. package/dist/graphLayout.worker.d.ts +1 -0
  99. package/dist/graphLayout.worker.js +17 -0
  100. package/dist/graphLayout.worker.js.map +1 -0
  101. package/dist/lib.d.ts +1 -1
  102. package/dist/lib.js.map +1 -1
  103. package/dist/store/reducers/query/parsers/preparePlanData.js.map +1 -1
  104. package/dist/store/reducers/query/types.d.ts +38 -1
  105. package/dist/store/reducers/settings/utils.d.ts +2 -2
  106. package/dist/store/reducers/storage/utils.js +3 -3
  107. package/dist/store/reducers/storage/utils.js.map +1 -1
  108. package/dist/uiFactory/types.d.ts +9 -0
  109. package/dist/uiFactory/uiFactory.js +5 -2
  110. package/dist/uiFactory/uiFactory.js.map +1 -1
  111. package/dist/utils/disks/helpers.d.ts +2 -2
  112. package/dist/utils/hooks/useChangedQuerySettings.d.ts +2 -2
  113. package/dist/utils/hooks/useLastQueryExecutionSettings.d.ts +2 -2
  114. package/dist/utils/hooks/useQueryExecutionSettings.d.ts +2 -2
  115. package/dist/utils/prepareQueryExplain.d.ts +1 -2
  116. package/dist/utils/prepareQueryExplain.js +10 -2
  117. package/dist/utils/prepareQueryExplain.js.map +1 -1
  118. package/dist/utils/query.d.ts +1 -1
  119. package/package.json +3 -3
  120. package/src/components/CriticalActionDialog/CriticalActionDialog.tsx +12 -25
  121. package/src/components/CriticalActionDialog/i18n/en.json +1 -2
  122. package/src/components/DropdownMenu/DropdownMenu.tsx +3 -2
  123. package/src/components/Graph/BlockComponents/ConnectionBlockComponent.tsx +49 -0
  124. package/src/components/Graph/BlockComponents/QueryBlockComponent.tsx +7 -0
  125. package/src/components/Graph/BlockComponents/ResultBlockComponent.tsx +10 -0
  126. package/src/components/Graph/BlockComponents/StageBlockComponent.tsx +35 -0
  127. package/src/components/Graph/FullViewportBackground.ts +122 -0
  128. package/src/components/Graph/GraphControls.tsx +49 -0
  129. package/src/components/Graph/GravityGraph.scss +131 -0
  130. package/src/components/Graph/GravityGraph.tsx +174 -0
  131. package/src/components/Graph/NonSelectableConnection.tsx +14 -0
  132. package/src/components/Graph/TooltipComponent.tsx +114 -0
  133. package/src/components/Graph/colorsConfig.ts +21 -0
  134. package/src/components/Graph/i18n/en.json +5 -0
  135. package/src/components/Graph/i18n/index.ts +7 -0
  136. package/src/components/Graph/runTreeLayout.ts +105 -0
  137. package/src/components/Graph/treeLayout.ts +378 -0
  138. package/src/components/Graph/types.ts +56 -0
  139. package/src/components/Graph/utils.ts +112 -0
  140. package/src/containers/Header/HeaderActionsMenu.tsx +22 -2
  141. package/src/containers/Header/hooks/useSharedDatabasePath.ts +33 -26
  142. package/src/containers/Header/i18n/en.json +1 -0
  143. package/src/containers/Operations/columns.tsx +4 -0
  144. package/src/containers/Operations/i18n/en.json +2 -2
  145. package/src/containers/PDiskPage/DecommissionButton/DecommissionButton.tsx +2 -0
  146. package/src/containers/PDiskPage/PDiskPage.tsx +3 -1
  147. package/src/containers/PDiskPage/i18n/en.json +3 -3
  148. package/src/containers/Storage/PDisks/PDisks.tsx +1 -1
  149. package/src/containers/Tablet/components/TabletControls/TabletControls.tsx +7 -1
  150. package/src/containers/Tablet/i18n/en.json +3 -3
  151. package/src/containers/Tablets/TabletsTable.tsx +3 -1
  152. package/src/containers/Tablets/i18n/en.json +1 -1
  153. package/src/containers/Tenant/Diagnostics/TenantOverview/Healthcheck/HealthcheckPreview.tsx +3 -1
  154. package/src/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +2 -2
  155. package/src/containers/Tenant/Healthcheck/components/HealthcheckDrawer.tsx +16 -1
  156. package/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss +2 -0
  157. package/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.tsx +13 -7
  158. package/src/graphLayout.worker.ts +21 -0
  159. package/src/lib.ts +1 -0
  160. package/src/store/reducers/query/parsers/preparePlanData.ts +1 -3
  161. package/src/store/reducers/query/types.ts +42 -2
  162. package/src/store/reducers/storage/utils.ts +3 -3
  163. package/src/uiFactory/types.ts +10 -0
  164. package/src/uiFactory/uiFactory.ts +12 -2
  165. package/src/utils/prepareQueryExplain.ts +13 -5
  166. package/dist/components/Graph/Graph.d.ts +0 -12
  167. package/dist/components/Graph/Graph.js +0 -44
  168. package/dist/components/Graph/Graph.js.map +0 -1
  169. package/dist/components/Graph/colors.d.ts +0 -10
  170. package/dist/components/Graph/colors.js +0 -53
  171. package/dist/components/Graph/colors.js.map +0 -1
  172. package/src/components/Graph/Graph.tsx +0 -70
  173. package/src/components/Graph/colors.ts +0 -75
@@ -132,28 +132,30 @@ export function useSharedDatabasePath({
132
132
  const shouldResolveSharedDatabaseFromViewer =
133
133
  shouldResolveSharedDatabaseName && !window.api.meta;
134
134
 
135
- const {currentData: sharedDatabaseFromViewer} = tenantsApi.useGetSharedDatabaseQuery(
136
- shouldResolveSharedDatabaseFromViewer && databaseData?.ResourceId
137
- ? {
138
- clusterName,
139
- database: databaseData.Name,
140
- backend: activeBackend,
141
- environmentName: environment,
142
- isMonitoringAllowed: isMonitoringAllowed === true,
143
- needNodeIds: needSharedDatabaseNodeIds,
144
- resourceId: databaseData.ResourceId,
145
- }
146
- : skipToken,
147
- );
135
+ const {currentData: sharedDatabaseFromViewer, isFetching: isSharedDatabaseFromViewerFetching} =
136
+ tenantsApi.useGetSharedDatabaseQuery(
137
+ shouldResolveSharedDatabaseFromViewer && databaseData?.ResourceId
138
+ ? {
139
+ clusterName,
140
+ database: databaseData.Name,
141
+ backend: activeBackend,
142
+ environmentName: environment,
143
+ isMonitoringAllowed: isMonitoringAllowed === true,
144
+ needNodeIds: needSharedDatabaseNodeIds,
145
+ resourceId: databaseData.ResourceId,
146
+ }
147
+ : skipToken,
148
+ );
148
149
 
149
150
  const shouldResolveSharedDatabaseFromMetaList =
150
151
  shouldResolveSharedDatabaseName && Boolean(window.api.meta);
151
152
 
152
- const {currentData: databases} = tenantsApi.useGetTenantsInfoQuery(
153
- shouldResolveSharedDatabaseFromMetaList
154
- ? {clusterName, environmentName: environment, isMetaDatabasesAvailable}
155
- : skipToken,
156
- );
153
+ const {currentData: databases, isFetching: isDatabasesFetching} =
154
+ tenantsApi.useGetTenantsInfoQuery(
155
+ shouldResolveSharedDatabaseFromMetaList
156
+ ? {clusterName, environmentName: environment, isMetaDatabasesAvailable}
157
+ : skipToken,
158
+ );
157
159
 
158
160
  const sharedDatabaseTarget = React.useMemo<SharedDatabaseTarget | undefined>(() => {
159
161
  if (databaseData?.sharedTenantName) {
@@ -184,12 +186,17 @@ export function useSharedDatabasePath({
184
186
  const sharedDatabase = useDatabaseId ? databaseData?.ResourceId : sharedDatabaseTarget?.name;
185
187
  const backend = getSharedDatabaseBackend(sharedDatabaseTarget, prepareTenantBackend);
186
188
 
187
- return getResolvedSharedDatabasePath({
188
- backend,
189
- clusterName,
190
- databaseData,
191
- isClusterBaseInfoResolved: isClusterBaseInfoAvailable,
192
- isViewerUser,
193
- sharedDatabase,
194
- });
189
+ return {
190
+ isSharedDatabaseLoading:
191
+ (isSharedDatabaseFromViewerFetching && sharedDatabaseFromViewer === undefined) ||
192
+ (isDatabasesFetching && databases === undefined),
193
+ sharedDatabasePath: getResolvedSharedDatabasePath({
194
+ backend,
195
+ clusterName,
196
+ databaseData,
197
+ isClusterBaseInfoResolved: isClusterBaseInfoAvailable,
198
+ isViewerUser,
199
+ sharedDatabase,
200
+ }),
201
+ };
195
202
  }
@@ -16,6 +16,7 @@
16
16
  "action_delete-cluster": "Delete Cluster",
17
17
  "action_connect-to-db": "Connect to database",
18
18
  "action_go-to-shared-db": "Go to shared database",
19
+ "status_loading-shared-db": "Loading shared database",
19
20
  "action_manage": "Manage",
20
21
 
21
22
  "title_developer-ui": "Developer UI",
@@ -216,6 +216,8 @@ function OperationsActions({operation, database}: OperationsActionsProps) {
216
216
  buttonView="outlined"
217
217
  dialogHeader={i18n('header_forget')}
218
218
  dialogText={i18n('text_forget')}
219
+ applyButtonText={i18n('header_forget')}
220
+ applyButtonView="outlined-danger"
219
221
  onConfirmAction={() =>
220
222
  forgetOperation({id, database})
221
223
  .unwrap()
@@ -244,6 +246,8 @@ function OperationsActions({operation, database}: OperationsActionsProps) {
244
246
  buttonView="outlined"
245
247
  dialogHeader={i18n('header_cancel')}
246
248
  dialogText={i18n('text_cancel')}
249
+ applyButtonText={i18n('header_cancel')}
250
+ applyButtonView="outlined-danger"
247
251
  onConfirmAction={() =>
248
252
  cancelOperation({id, database})
249
253
  .unwrap()
@@ -33,8 +33,8 @@
33
33
  "value_progress_create_changefeeds": "Creating Changefeeds",
34
34
  "header_cancel": "Cancel operation",
35
35
  "header_forget": "Forget operation",
36
- "text_cancel": "The operation will be cancelled. Do you want to proceed?",
37
- "text_forget": "The operation will be forgotten. Do you want to proceed?",
36
+ "text_cancel": "This will cancel the operation in progress. This action cannot be undone.",
37
+ "text_forget": "This will remove the operation from the list. This action cannot be undone.",
38
38
  "text_forgotten": "The operation {{id}} has been forgotten",
39
39
  "text_cancelled": "The operation {{id}} has been cancelled"
40
40
  }
@@ -139,6 +139,8 @@ export function DecommissionButton({
139
139
  warningText={getDecommissionWarningText(newDecommission)}
140
140
  withRetry={withRetry}
141
141
  withCheckBox
142
+ applyButtonText={pDiskPageKeyset('decommission-dialog-title')}
143
+ applyButtonView="outlined-danger"
142
144
  retryButtonText={pDiskPageKeyset('decommission-dialog-force-change')}
143
145
  onConfirm={handleConfirmAction}
144
146
  onConfirmActionSuccess={handleConfirmActionSuccess}
@@ -187,7 +187,9 @@ export function PDiskPage() {
187
187
  buttonDisabled={!pDiskParamsDefined || !isUserAllowedToMakeChanges}
188
188
  buttonView="normal"
189
189
  dialogHeader={pDiskPageKeyset('restart-pdisk-dialog-header')}
190
- dialogWarning={pDiskPageKeyset('restart-pdisk-dialog-text')}
190
+ dialogText={pDiskPageKeyset('restart-pdisk-dialog-text')}
191
+ applyButtonText={pDiskPageKeyset('restart-pdisk-button')}
192
+ applyButtonView="action"
191
193
  retryButtonText={pDiskPageKeyset('force-restart-pdisk-button')}
192
194
  withPopover
193
195
  popoverContent={pDiskPageKeyset('restart-pdisk-not-allowed')}
@@ -16,11 +16,11 @@
16
16
  "no-slots-data": "No slots data",
17
17
 
18
18
  "restart-pdisk-button": "Restart PDisk",
19
- "force-restart-pdisk-button": "Restart anyway",
19
+ "force-restart-pdisk-button": "Restart PDisk anyway",
20
20
  "restart-pdisk-not-allowed": "You don't have enough rights to restart PDisk",
21
21
 
22
22
  "restart-pdisk-dialog-header": "Restart PDisk",
23
- "restart-pdisk-dialog-text": "PDisk will be restarted. Do you want to proceed?",
23
+ "restart-pdisk-dialog-text": "Are you sure you want to restart this PDisk?",
24
24
 
25
25
  "decommission-none": "None",
26
26
  "decommission-imminent": "Imminent",
@@ -34,7 +34,7 @@
34
34
  "decommission-dialog-title": "Change decommission status",
35
35
  "decommission-dialog-force-change": "Change anyway",
36
36
 
37
- "decommission-dialog-imminent-warning": "This will start imminent decommission. Existing slots will be moved from the disk",
37
+ "decommission-dialog-imminent-warning": "This will start imminent decommission. All existing slots will be moved from the disk.",
38
38
  "decommission-dialog-pending-warning": "This will start pending decommission. Decommission will be planned for this disk, but will not start immediatelly. Existing slots will not be moved from the disk, but no new slots will be allocated on it",
39
39
  "decommission-dialog-rejected-warning": "This will start rejected decommission. No slots from other disks are placed on this disk in the process of decommission",
40
40
  "decommission-dialog-none-warning": "This will reset decommission mode, allowing the disk to be used by the storage"
@@ -31,7 +31,7 @@ export function PDisks({pDisks = [], vDisks = [], viewContext, pDiskWidth}: PDis
31
31
 
32
32
  const relatedVDisks = vDisks.filter((vdisk) => vdisk.PDiskId === pDisk.PDiskId);
33
33
 
34
- const highlighted = highlightedDisk === id;
34
+ const highlighted = id !== undefined && highlightedDisk === id;
35
35
 
36
36
  return (
37
37
  <div className={b('pdisks-item')} key={id}>
@@ -42,7 +42,9 @@ export const TabletControls = ({tablet}: TabletControlsProps) => {
42
42
  <Flex gap={2} wrap="nowrap">
43
43
  <ButtonWithConfirmDialog
44
44
  dialogHeader={i18n('dialog.kill-header')}
45
- dialogWarning={i18n('dialog.kill-text')}
45
+ dialogText={i18n('dialog.kill-text')}
46
+ applyButtonText={i18n('dialog.kill-header')}
47
+ applyButtonView="action"
46
48
  onConfirmAction={() => killTablet({id: TabletId}).unwrap()}
47
49
  buttonDisabled={isDisabledRestart || !isUserAllowedToMakeChanges}
48
50
  withPopover
@@ -59,6 +61,8 @@ export const TabletControls = ({tablet}: TabletControlsProps) => {
59
61
  <ButtonWithConfirmDialog
60
62
  dialogHeader={i18n('dialog.stop-header')}
61
63
  dialogText={i18n('dialog.stop-text')}
64
+ applyButtonText={i18n('dialog.stop-header')}
65
+ applyButtonView="outlined-danger"
62
66
  onConfirmAction={() => stopTablet({id: TabletId, hiveId: HiveId}).unwrap()}
63
67
  buttonDisabled={isDisabledStop || !isUserAllowedToMakeChanges}
64
68
  withPopover
@@ -73,6 +77,8 @@ export const TabletControls = ({tablet}: TabletControlsProps) => {
73
77
  <ButtonWithConfirmDialog
74
78
  dialogHeader={i18n('dialog.resume-header')}
75
79
  dialogText={i18n('dialog.resume-text')}
80
+ applyButtonText={i18n('dialog.resume-header')}
81
+ applyButtonView="action"
76
82
  onConfirmAction={() =>
77
83
  resumeTablet({id: TabletId, hiveId: HiveId}).unwrap()
78
84
  }
@@ -16,9 +16,9 @@
16
16
  "dialog.stop-header": "Stop tablet",
17
17
  "dialog.resume-header": "Resume tablet",
18
18
 
19
- "dialog.kill-text": "The tablet will be restarted. Do you want to proceed?",
20
- "dialog.stop-text": "The tablet will be stopped. Do you want to proceed?",
21
- "dialog.resume-text": "The tablet will be resumed. Do you want to proceed?",
19
+ "dialog.kill-text": "The tablet will restart. A portion of table data will be temporarily unavailable. Active transactions and scans will be interrupted with a retriable error and can be retried.",
20
+ "dialog.stop-text": "The tablet will be stopped. The key range will be unavailable until the tablet is manually resumed. Data is not deleted and will be restored after resume.",
21
+ "dialog.resume-text": "The tablet will be resumed and return to Active state.",
22
22
 
23
23
  "emptyState": "The tablet was not found",
24
24
 
@@ -190,7 +190,9 @@ function TabletActions(tablet: TTabletStateInfo) {
190
190
  <ButtonWithConfirmDialog
191
191
  buttonView="outlined"
192
192
  dialogHeader={i18n('dialog.kill-header')}
193
- dialogWarning={i18n('dialog.kill-text')}
193
+ dialogText={i18n('dialog.kill-text')}
194
+ applyButtonText={i18n('dialog.kill-header')}
195
+ applyButtonView="action"
194
196
  onConfirmAction={() => {
195
197
  return killTablet({id}).unwrap();
196
198
  }}
@@ -10,7 +10,7 @@
10
10
  "Generation": "Generation",
11
11
  "Uptime": "Uptime",
12
12
  "dialog.kill-header": "Restart tablet",
13
- "dialog.kill-text": "The tablet will be restarted. Do you want to proceed?",
13
+ "dialog.kill-text": "The tablet will restart. A portion of table data will be temporarily unavailable. Active transactions and scans will be interrupted with a retriable error and can be retried.",
14
14
  "controls.kill-not-allowed": "You don't have enough rights to restart tablet",
15
15
  "controls.kill-impossible-follower": "It's impossible to restart a follower",
16
16
  "controls.search-placeholder": "Tablet ID",
@@ -7,6 +7,7 @@ import {healthcheckApi} from '../../../../../store/reducers/healthcheckInfo/heal
7
7
  import {SelfCheckResult} from '../../../../../types/api/healthcheck';
8
8
  import {cn} from '../../../../../utils/cn';
9
9
  import {useAutoRefreshInterval} from '../../../../../utils/hooks';
10
+ import {useClusterNameFromQuery} from '../../../../../utils/hooks/useDatabaseFromQuery';
10
11
  import {isEnumMember} from '../../../../../utils/typecheckers';
11
12
  import {HEALTHCHECK_RESULT_TO_TEXT} from '../../../constants';
12
13
  import {useTenantQueryParams} from '../../../useTenantQueryParams';
@@ -24,6 +25,7 @@ interface HealthcheckPreviewProps {
24
25
 
25
26
  export function HealthcheckPreview(props: HealthcheckPreviewProps) {
26
27
  const {database} = props;
28
+ const clusterName = useClusterNameFromQuery();
27
29
  const [autoRefreshInterval] = useAutoRefreshInterval();
28
30
 
29
31
  const {handleShowHealthcheckChange} = useTenantQueryParams();
@@ -33,7 +35,7 @@ export function HealthcheckPreview(props: HealthcheckPreviewProps) {
33
35
  isFetching,
34
36
  error,
35
37
  } = healthcheckApi.useGetHealthcheckInfoQuery(
36
- {database},
38
+ {database, clusterName},
37
39
  {
38
40
  pollingInterval: autoRefreshInterval,
39
41
  },
@@ -311,11 +311,11 @@ export function TenantOverview({
311
311
 
312
312
  // Use healthcheck self_check_result as the database status color when available;
313
313
  // fall back to tenantinfo.Overall (e.g. for Serverless databases where healthcheck is skipped).
314
- // Polling is managed by HealthcheckPreview (deduped via the same cache key); the database
314
+ // Polling is managed by HealthcheckPreview via the same {database, clusterName} cache key. The database
315
315
  // status badge is only rendered when !isV2NavigationEnabled, so skip the query in V2 mode
316
316
  // and until tenant info is loaded to avoid spurious requests.
317
317
  const {currentData: healthcheckData} = healthcheckApi.useGetHealthcheckInfoQuery(
318
- {database},
318
+ {database, clusterName},
319
319
  {
320
320
  skip: shouldSkipHealthcheck({isServerless, isV2NavigationEnabled, tenant}),
321
321
  },
@@ -7,6 +7,7 @@ import {DrawerWrapper} from '../../../../components/Drawer';
7
7
  import type {DrawerControl} from '../../../../components/Drawer';
8
8
  import {EnableFullscreenButton} from '../../../../components/EnableFullscreenButton/EnableFullscreenButton';
9
9
  import type {SelfCheckResult} from '../../../../types/api/healthcheck';
10
+ import {uiFactory} from '../../../../uiFactory/uiFactory';
10
11
  import {createAndDownloadJsonFile} from '../../../../utils/downloadFile';
11
12
 
12
13
  import {HealthcheckDrawerTitle} from './HealthcheckDrawerTitle';
@@ -42,6 +43,20 @@ export function HealthcheckDrawer({
42
43
  downloadTooltip,
43
44
  isDownloadDisabled,
44
45
  }: HealthcheckDrawerProps) {
46
+ const renderDrawerExtension = uiFactory.healthcheck.renderDrawerExtension;
47
+ const renderContentWithExtension = React.useCallback(() => {
48
+ if (!renderDrawerExtension) {
49
+ return renderDrawerContent();
50
+ }
51
+
52
+ return (
53
+ <React.Fragment>
54
+ {renderDrawerExtension()}
55
+ {renderDrawerContent()}
56
+ </React.Fragment>
57
+ );
58
+ }, [renderDrawerContent, renderDrawerExtension]);
59
+
45
60
  const handleDownload = React.useCallback(
46
61
  (event: React.MouseEvent<HTMLButtonElement>) => {
47
62
  event.stopPropagation();
@@ -86,7 +101,7 @@ export function HealthcheckDrawer({
86
101
  isDrawerVisible={isDrawerVisible}
87
102
  onCloseDrawer={onCloseDrawer}
88
103
  onTransitionInComplete={onTransitionInComplete}
89
- renderDrawerContent={renderDrawerContent}
104
+ renderDrawerContent={renderContentWithExtension}
90
105
  drawerId={drawerId}
91
106
  storageKey={storageKey}
92
107
  detectClickOutside
@@ -1,5 +1,7 @@
1
1
  .ydb-query-explain-graph {
2
2
  &__canvas-container {
3
+ position: relative;
4
+
3
5
  overflow-y: auto;
4
6
 
5
7
  width: 100%;
@@ -1,8 +1,6 @@
1
1
  import React from 'react';
2
2
 
3
- import type {Data} from '@gravity-ui/paranoid';
4
-
5
- import {YDBGraph} from '../../../../../../components/Graph/Graph';
3
+ import {GravityGraph} from '../../../../../../components/Graph/GravityGraph';
6
4
  import type {PreparedPlan} from '../../../../../../store/reducers/query/types';
7
5
  import {cn} from '../../../../../../utils/cn';
8
6
  import i18n from '../../i18n';
@@ -17,22 +15,30 @@ interface GraphProps {
17
15
  theme?: string;
18
16
  }
19
17
 
20
- function isValidGraphData(data: Partial<Data>): data is Data {
18
+ function isValidGraphData(data: Partial<PreparedPlan>) {
21
19
  return Boolean(data.links && data.nodes && data.nodes.length);
22
20
  }
23
21
 
24
22
  export function Graph({explain = {}, theme}: GraphProps) {
25
23
  const {links, nodes} = explain;
26
24
 
27
- const data = React.useMemo(() => ({links, nodes}), [links, nodes]);
25
+ const data = React.useMemo(() => ({links: links || [], nodes: nodes || []}), [links, nodes]);
26
+ const [failedData, setFailedData] = React.useState<typeof data>();
27
+ const handleLayoutError = React.useCallback(
28
+ (error: Error) => {
29
+ console.error(error);
30
+ setFailedData(data);
31
+ },
32
+ [data],
33
+ );
28
34
 
29
- if (!isValidGraphData(data)) {
35
+ if (!isValidGraphData(data) || failedData === data) {
30
36
  return <StubMessage message={i18n('description.graph-is-not-supported')} />;
31
37
  }
32
38
 
33
39
  return (
34
40
  <div className={b('canvas-container')}>
35
- <YDBGraph key={theme} data={data} />
41
+ <GravityGraph data={data} onError={handleLayoutError} theme={theme} />
36
42
  </div>
37
43
  );
38
44
  }
@@ -0,0 +1,21 @@
1
+ import {prepareTreeLayout} from './components/Graph/treeLayout';
2
+ import type {TreeLayoutRequest, TreeLayoutWorkerResponse} from './components/Graph/types';
3
+
4
+ const workerScope = globalThis as unknown as {
5
+ onmessage: ((event: MessageEvent<TreeLayoutRequest>) => void) | null;
6
+ postMessage: (response: TreeLayoutWorkerResponse) => void;
7
+ };
8
+
9
+ workerScope.onmessage = ({data}) => {
10
+ try {
11
+ workerScope.postMessage({
12
+ type: 'success',
13
+ result: prepareTreeLayout(data),
14
+ });
15
+ } catch (error) {
16
+ workerScope.postMessage({
17
+ type: 'error',
18
+ error: error instanceof Error ? error.message : String(error),
19
+ });
20
+ }
21
+ };
package/src/lib.ts CHANGED
@@ -40,6 +40,7 @@ export type {ExternalQueryToOpen} from './containers/Tenant/Query/hooks/useOpenE
40
40
  export type {DrawerContextType} from './components/Drawer';
41
41
  export type {GetMonitoringLink, GetMonitoringClusterLink} from './utils/monitoring';
42
42
  export type {
43
+ CreateGraphLayoutWorker,
43
44
  GetAdditionalDatabaseInfoItems,
44
45
  GetReportProblemUrl,
45
46
  IllustrationComponent,
@@ -1,9 +1,7 @@
1
- import type {ExplainPlanNodeData, GraphNode, Link} from '@gravity-ui/paranoid';
2
-
3
1
  import type {QueryPlan, ScriptPlan, TKqpStatsQuery} from '../../../../types/api/query';
4
2
  import {preparePlan, prepareSimplifiedPlan} from '../../../../utils/prepareQueryExplain';
5
3
  import {parseQueryExplainPlan} from '../../../../utils/query';
6
- import type {PreparedQueryData} from '../types';
4
+ import type {ExplainPlanNodeData, GraphNode, Link, PreparedQueryData} from '../types';
7
5
 
8
6
  const explainVersions = {
9
7
  v2: '0.2',
@@ -1,5 +1,3 @@
1
- import type {ExplainPlanNodeData, GraphNode, Link} from '@gravity-ui/paranoid';
2
-
3
1
  import type {
4
2
  PlanTable,
5
3
  QueryPlan,
@@ -27,6 +25,48 @@ export interface QueryInHistory extends RawQueryInHistory {
27
25
  status?: QueryExecutionStatusType;
28
26
  }
29
27
 
28
+ export interface Link {
29
+ from: string;
30
+ to: string;
31
+ }
32
+
33
+ export interface Metric {
34
+ name: string;
35
+ value: string;
36
+ theme?: 'warning' | 'danger';
37
+ }
38
+
39
+ export interface GraphNode<TData = any> {
40
+ name: string;
41
+ status?: string;
42
+ meta?: string;
43
+ group?: string;
44
+ data?: TData;
45
+ metrics?: Metric[];
46
+ }
47
+
48
+ export interface ExplainPlanNodeData {
49
+ id?: number;
50
+ type: 'query' | 'result' | 'stage' | 'connection' | 'materialize';
51
+ name?: string;
52
+ operators?: string[];
53
+ tables?: string[];
54
+ cte?: string;
55
+ stats?: TopologyNodeDataStats[];
56
+ }
57
+ export interface TopologyNodeDataStatsItem {
58
+ name: string;
59
+ value: string | number;
60
+ }
61
+ export interface TopologyNodeDataStatsSection {
62
+ name: string;
63
+ items: TopologyNodeDataStatsItem[];
64
+ }
65
+ export interface TopologyNodeDataStats {
66
+ group: string;
67
+ stats: TopologyNodeDataStatsSection[] | TopologyNodeDataStatsItem[];
68
+ }
69
+
30
70
  export interface PreparedPlan {
31
71
  links?: Link[];
32
72
  nodes?: GraphNode<ExplainPlanNodeData>[];
@@ -237,10 +237,10 @@ const prepareStorageNodeData = (
237
237
  }).length || 0;
238
238
 
239
239
  const pDisks = PDisks?.map((pDisk) => {
240
- return {
241
- ...prepareWhiteboardPDiskData(pDisk),
240
+ return prepareWhiteboardPDiskData({
241
+ ...pDisk,
242
242
  NodeId,
243
- };
243
+ });
244
244
  });
245
245
  const vDisks = VDisks?.map((vDisk) => {
246
246
  return {
@@ -19,6 +19,7 @@ import type {GetLogsLink} from '../utils/logs';
19
19
  import type {GetMonitoringClusterLink, GetMonitoringLink} from '../utils/monitoring';
20
20
 
21
21
  export type IllustrationComponent = React.ComponentType<React.SVGProps<SVGSVGElement>>;
22
+ export type CreateGraphLayoutWorker = () => Worker;
22
23
 
23
24
  export type IllustrationName =
24
25
  | 'InternalError'
@@ -52,14 +53,23 @@ export interface UIFactory<H extends string = CommonIssueCategory, T extends str
52
53
  renderNodeTooltipActions?: RenderNodeTooltipActions;
53
54
  clusterOrDatabaseAccessError?: Partial<EmptyStateProps>;
54
55
 
56
+ /** Defaults to true. Undefined preserves the current value when configuring the UI. */
55
57
  enableMultiTabQueryEditor?: boolean;
56
58
 
59
+ /** Creates a fresh computation-graph layout worker for each layout run. */
60
+ createGraphLayoutWorker?: CreateGraphLayoutWorker;
61
+
57
62
  healthcheck: {
58
63
  issueCategories: ReadonlyArray<H>;
59
64
  isIssueTypeOfCategory: (type: string, category: H) => boolean;
60
65
  getHealthckechViewTitles: GetHealthcheckViewTitles<H>;
61
66
  getHealthcheckViewsOrder: GetHealthcheckViewsOrder<H>;
62
67
  renderAssistantAction?: RenderHealthcheckAssistantAction;
68
+ /**
69
+ * Renders after the drawer header and before Healthcheck content.
70
+ * Stays mounted while the drawer is open, independent of loading, error or issue state.
71
+ */
72
+ renderDrawerExtension?: () => React.ReactNode;
63
73
  };
64
74
  hasAccess: HasAccess;
65
75
  hideGrantAccess?: boolean;
@@ -30,7 +30,7 @@ const uiFactoryBase: ResolvedUIFactory = {
30
30
  useDatabaseId: false,
31
31
  settingsBackend: undefined,
32
32
  docs: undefined,
33
- enableMultiTabQueryEditor: false,
33
+ enableMultiTabQueryEditor: true,
34
34
  hasDeveloperUi: true,
35
35
  isDetailedStorageViewAvailable: () => true,
36
36
  maxVDisksInStorageGroup: 9,
@@ -46,10 +46,20 @@ type UIFactoryOverrides<H extends string, T extends string> = Omit<
46
46
  export function configureUIFactory<H extends string, T extends string = string>(
47
47
  overrides: UIFactoryOverrides<H, T>,
48
48
  ) {
49
- const {healthcheck, hasAccess, maxVDisksInStorageGroup, ...restOverrides} = overrides;
49
+ const {
50
+ healthcheck,
51
+ hasAccess,
52
+ maxVDisksInStorageGroup,
53
+ enableMultiTabQueryEditor,
54
+ ...restOverrides
55
+ } = overrides;
50
56
 
51
57
  Object.assign(uiFactoryBase, restOverrides);
52
58
 
59
+ if (typeof enableMultiTabQueryEditor === 'boolean') {
60
+ uiFactoryBase.enableMultiTabQueryEditor = enableMultiTabQueryEditor;
61
+ }
62
+
53
63
  // Only override hasAccess when an actual function is provided.
54
64
  // Forwarding an optional config field could pass `undefined` here,
55
65
  // which would overwrite the default and crash on the next hasAccess(...) call.
@@ -2,12 +2,11 @@ import type {
2
2
  ExplainPlanNodeData,
3
3
  GraphNode,
4
4
  Link,
5
+ SimplifiedPlanItem,
5
6
  TopologyNodeDataStats,
6
7
  TopologyNodeDataStatsItem,
7
8
  TopologyNodeDataStatsSection,
8
- } from '@gravity-ui/paranoid';
9
-
10
- import type {SimplifiedPlanItem} from '../store/reducers/query/types';
9
+ } from '../store/reducers/query/types';
11
10
  import type {PlanNode, SimplifiedNode} from '../types/api/query';
12
11
 
13
12
  const CONNECTION_NODE_META_FIELDS = new Set(['PlanNodeId', 'PlanNodeType', 'Node Type', 'Plans']);
@@ -96,11 +95,19 @@ function getNodeType(plan: PlanNode) {
96
95
  export function preparePlan(plan: PlanNode) {
97
96
  const nodes: GraphNode<ExplainPlanNodeData>[] = [];
98
97
  const links: Link[] = [];
98
+ let generatedNodeId = 0;
99
+
100
+ const getGraphNodeId = (node: PlanNode) => {
101
+ return node.PlanNodeId === undefined
102
+ ? `generated-plan-node:${generatedNodeId++}`
103
+ : String(node.PlanNodeId);
104
+ };
99
105
 
100
106
  function parsePlans(plans: PlanNode[] = [], from: string) {
101
107
  plans.forEach((p) => {
108
+ const nodeId = getGraphNodeId(p);
102
109
  const node: GraphNode<ExplainPlanNodeData> = {
103
- name: String(p.PlanNodeId),
110
+ name: nodeId,
104
111
  data: {
105
112
  id: p.PlanNodeId,
106
113
  type: getNodeType(p),
@@ -117,8 +124,9 @@ export function preparePlan(plan: PlanNode) {
117
124
  }
118
125
 
119
126
  const rootPlan = plan;
127
+ const rootNodeId = getGraphNodeId(rootPlan);
120
128
  const rootNode: GraphNode<ExplainPlanNodeData> = {
121
- name: String(rootPlan.PlanNodeId),
129
+ name: rootNodeId,
122
130
  data: {
123
131
  id: rootPlan.PlanNodeId,
124
132
  type: getNodeType(rootPlan),
@@ -1,12 +0,0 @@
1
- import type { Data, Options, Shapes } from '@gravity-ui/paranoid';
2
- interface GraphProps<T> {
3
- data: Data<T>;
4
- opts?: Options;
5
- shapes?: Shapes;
6
- }
7
- export declare function Graph<T>(props: GraphProps<T>): import("react/jsx-runtime").JSX.Element;
8
- interface YDBGraphProps<T> {
9
- data: Data<T>;
10
- }
11
- export declare function YDBGraph<T>(props: YDBGraphProps<T>): import("react/jsx-runtime").JSX.Element;
12
- export {};