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
@@ -0,0 +1,35 @@
1
+ import {Text} from '@gravity-ui/uikit';
2
+
3
+ import {cn} from '../../../utils/cn';
4
+ import {TooltipComponent} from '../TooltipComponent';
5
+ import i18n from '../i18n';
6
+ import type {ExtendedTBlock} from '../types';
7
+
8
+ const b = cn('ydb-gravity-graph');
9
+
10
+ type Props = {
11
+ block: ExtendedTBlock;
12
+ className: string;
13
+ };
14
+
15
+ export const StageBlockComponent = ({className, block}: Props) => {
16
+ const content = (
17
+ <div className={className}>
18
+ {block.operators?.length
19
+ ? block.operators.map((item) => <div key={item}>{item}</div>)
20
+ : block.name}
21
+ {block.tables?.map((table, index) => (
22
+ <div key={`${table}-${index}`} className={b('stage-table-row')} title={table}>
23
+ {index === 0 ? <Text color="secondary">{i18n('label_tables')}: </Text> : null}
24
+ {table}
25
+ </div>
26
+ ))}
27
+ </div>
28
+ );
29
+
30
+ if (!block.stats?.length) {
31
+ return content;
32
+ }
33
+
34
+ return <TooltipComponent block={block}>{content}</TooltipComponent>;
35
+ };
@@ -0,0 +1,122 @@
1
+ import {Component} from '@gravity-ui/graph';
2
+
3
+ interface FullViewportBackgroundContext extends Record<string, unknown> {
4
+ camera: {
5
+ getCameraState(): {
6
+ relativeX: number;
7
+ relativeY: number;
8
+ relativeWidth: number;
9
+ relativeHeight: number;
10
+ scale: number;
11
+ };
12
+ };
13
+ colors: {
14
+ canvas: {
15
+ dots: string;
16
+ layerBackground: string;
17
+ };
18
+ };
19
+ constants: {
20
+ block: {
21
+ SCALES: number[];
22
+ };
23
+ system: {
24
+ GRID_SIZE: number;
25
+ };
26
+ };
27
+ ctx: CanvasRenderingContext2D;
28
+ }
29
+
30
+ const VIEWPORT_PADDING = 10;
31
+ const DOT_SIZE = 2;
32
+ const LARGE_DOT_MULTIPLIER = 3;
33
+ const PATTERN_GRID_SIZE = 5;
34
+
35
+ class FullViewportBackgroundComponent extends Component {
36
+ private patterns?: {normal: CanvasPattern; simple: CanvasPattern};
37
+ private patternsColor?: string;
38
+ private patternsGridSize?: number;
39
+
40
+ protected render() {
41
+ const {camera, colors, constants, ctx} = this.context as FullViewportBackgroundContext;
42
+ const cameraState = camera.getCameraState();
43
+ const x = -cameraState.relativeX - VIEWPORT_PADDING;
44
+ const y = -cameraState.relativeY - VIEWPORT_PADDING;
45
+ const width = cameraState.relativeWidth + VIEWPORT_PADDING * 2;
46
+ const height = cameraState.relativeHeight + VIEWPORT_PADDING * 2;
47
+
48
+ ctx.fillStyle = colors.canvas.layerBackground;
49
+ ctx.fillRect(x, y, width, height);
50
+
51
+ if (cameraState.scale < constants.block.SCALES[0]) {
52
+ return;
53
+ }
54
+
55
+ this.preparePatterns(colors.canvas.dots, constants.system.GRID_SIZE);
56
+ if (!this.patterns) {
57
+ return;
58
+ }
59
+
60
+ ctx.fillStyle =
61
+ cameraState.scale > constants.block.SCALES[2]
62
+ ? this.patterns.normal
63
+ : this.patterns.simple;
64
+ ctx.fillRect(x, y, width, height);
65
+ }
66
+
67
+ private preparePatterns(color: string, gridSize: number) {
68
+ if (this.patterns && this.patternsColor === color && this.patternsGridSize === gridSize) {
69
+ return;
70
+ }
71
+
72
+ const normal = this.createPattern(color, gridSize, false);
73
+ const simple = this.createPattern(color, gridSize, true);
74
+
75
+ if (normal && simple) {
76
+ this.patterns = {normal, simple};
77
+ this.patternsColor = color;
78
+ this.patternsGridSize = gridSize;
79
+ }
80
+ }
81
+
82
+ private createPattern(color: string, gridSize: number, simple: boolean) {
83
+ const {ctx} = this.context as FullViewportBackgroundContext;
84
+ const patternCanvas = document.createElement('canvas');
85
+ const patternSize = PATTERN_GRID_SIZE * gridSize;
86
+ patternCanvas.width = patternSize;
87
+ patternCanvas.height = patternSize;
88
+
89
+ const patternContext = patternCanvas.getContext('2d');
90
+ if (!patternContext) {
91
+ return null;
92
+ }
93
+
94
+ patternContext.fillStyle = color;
95
+
96
+ for (let row = -1; row <= PATTERN_GRID_SIZE; row += 1) {
97
+ for (let column = -1; column <= PATTERN_GRID_SIZE; column += 1) {
98
+ if (row % PATTERN_GRID_SIZE === 0 && column % PATTERN_GRID_SIZE === 0) {
99
+ const largeDotSize = DOT_SIZE * LARGE_DOT_MULTIPLIER;
100
+ patternContext.fillRect(
101
+ row * gridSize - largeDotSize / 2,
102
+ column * gridSize - largeDotSize / 2,
103
+ largeDotSize,
104
+ largeDotSize,
105
+ );
106
+ } else if (!simple) {
107
+ patternContext.fillRect(
108
+ row * gridSize - DOT_SIZE / 2,
109
+ column * gridSize - DOT_SIZE / 2,
110
+ DOT_SIZE,
111
+ DOT_SIZE,
112
+ );
113
+ }
114
+ }
115
+ }
116
+
117
+ return ctx.createPattern(patternCanvas, 'repeat');
118
+ }
119
+ }
120
+
121
+ export const FullViewportBackground =
122
+ FullViewportBackgroundComponent as unknown as typeof Component;
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+
3
+ import type {Graph} from '@gravity-ui/graph';
4
+ import {Button, Icon} from '@gravity-ui/uikit';
5
+
6
+ import {cn} from '../../utils/cn';
7
+
8
+ import i18n from './i18n';
9
+
10
+ import MagnifierMinusIcon from '@gravity-ui/icons/svgs/magnifier-minus.svg';
11
+ import MagnifierPlusIcon from '@gravity-ui/icons/svgs/magnifier-plus.svg';
12
+
13
+ const b = cn('ydb-gravity-graph');
14
+
15
+ const ZOOM_STEP = 1.25;
16
+
17
+ interface Props {
18
+ graph: Graph;
19
+ }
20
+
21
+ export const GraphControls = ({graph}: Props) => {
22
+ const onZoomInClick = React.useCallback(() => {
23
+ const cameraScale = graph.cameraService.getCameraScale();
24
+ graph.zoom({scale: cameraScale * ZOOM_STEP});
25
+ }, [graph]);
26
+
27
+ const onZoomOutClick = React.useCallback(() => {
28
+ const cameraScale = graph.cameraService.getCameraScale();
29
+ graph.zoom({scale: cameraScale / ZOOM_STEP});
30
+ }, [graph]);
31
+
32
+ const onResetZoomClick = React.useCallback(() => {
33
+ graph.zoom({scale: 1});
34
+ }, [graph]);
35
+
36
+ return (
37
+ <div className={b('zoom-controls')}>
38
+ <Button view="raised" onClick={onZoomInClick} aria-label={i18n('action_zoom-in')}>
39
+ <Icon data={MagnifierPlusIcon} size={16} />
40
+ </Button>
41
+ <Button view="raised" onClick={onZoomOutClick} aria-label={i18n('action_zoom-out')}>
42
+ <Icon data={MagnifierMinusIcon} size={16} />
43
+ </Button>
44
+ <Button view="raised" onClick={onResetZoomClick}>
45
+ 1:1
46
+ </Button>
47
+ </div>
48
+ );
49
+ };
@@ -0,0 +1,131 @@
1
+ .ydb-gravity-graph {
2
+ &__block {
3
+ cursor: auto;
4
+
5
+ border: none;
6
+ background: none;
7
+ }
8
+
9
+ &__block-content {
10
+ width: 100%;
11
+ padding: 8px 12px;
12
+
13
+ font-family: var(--g-font-family);
14
+ font-size: var(--g-text-body-short-font-size);
15
+ line-height: var(--g-text-body-short-line-height);
16
+
17
+ border: 1px solid var(--g-color-line-generic);
18
+ background: var(--g-color-base-float);
19
+ box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.3);
20
+
21
+ &[aria-haspopup='dialog'] {
22
+ cursor: pointer;
23
+ }
24
+ }
25
+
26
+ &__block-id {
27
+ position: absolute;
28
+ top: 4px;
29
+ right: 4px;
30
+
31
+ font-size: 10px;
32
+ line-height: 1;
33
+
34
+ color: var(--g-color-text-secondary);
35
+ }
36
+
37
+ &__block-content.query {
38
+ height: 100%;
39
+
40
+ border-radius: 50%;
41
+ }
42
+
43
+ &__block-content.result {
44
+ display: flex;
45
+ justify-content: center;
46
+ align-items: center;
47
+ }
48
+
49
+ &__block-content.stage {
50
+ border-radius: 6px;
51
+ }
52
+
53
+ &__stage-table-row {
54
+ overflow: hidden;
55
+
56
+ white-space: nowrap;
57
+ text-overflow: ellipsis;
58
+ }
59
+
60
+ &__block-content.connection {
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 4px;
64
+
65
+ color: var(--g-color-text-info-heavy);
66
+ border: 1px solid var(--g-color-line-info);
67
+ border-radius: 6px;
68
+ background: var(--g-color-base-info-light);
69
+ box-shadow: none;
70
+ }
71
+
72
+ &__tooltip-content {
73
+ overflow: auto;
74
+
75
+ width: 300px;
76
+ max-height: 90vh;
77
+ padding: 0 8px 8px;
78
+
79
+ font-family: var(--g-font-family);
80
+ font-size: var(--g-text-body-short-font-size);
81
+ line-height: var(--g-text-body-short-line-height);
82
+ }
83
+
84
+ &__tooltip-tabs {
85
+ margin-bottom: 8px;
86
+ }
87
+
88
+ &__tooltip-stat-row {
89
+ display: grid;
90
+ grid-template-columns: 100px auto;
91
+ gap: 8px;
92
+
93
+ margin: 4px 0 0;
94
+
95
+ span {
96
+ overflow: hidden;
97
+
98
+ text-overflow: ellipsis;
99
+ }
100
+ span:nth-child(1) {
101
+ color: var(--g-color-text-secondary);
102
+ }
103
+ span:nth-child(2) {
104
+ word-wrap: break-word;
105
+ }
106
+ }
107
+
108
+ &__tooltip-stat-group {
109
+ margin-top: 8px;
110
+ }
111
+
112
+ &__tooltip-stat-group + &__tooltip-stat-group {
113
+ padding-top: 8px;
114
+
115
+ border-top: 1px dotted var(--g-color-line-generic);
116
+ }
117
+
118
+ &__tooltip-stat-group-name {
119
+ font-weight: bold;
120
+ }
121
+
122
+ &__zoom-controls {
123
+ position: absolute;
124
+ z-index: 999;
125
+ top: 8px;
126
+ right: 50px;
127
+
128
+ display: flex;
129
+ gap: 2px;
130
+ }
131
+ }
@@ -0,0 +1,174 @@
1
+ import React from 'react';
2
+
3
+ import {GraphState} from '@gravity-ui/graph';
4
+ import type {TBlockId} from '@gravity-ui/graph';
5
+ import type {HookGraphParams} from '@gravity-ui/graph/react';
6
+ import {GraphBlock, GraphCanvas, useGraph, useGraphEvent} from '@gravity-ui/graph/react';
7
+
8
+ import {uiFactory} from '../../uiFactory/uiFactory';
9
+ import {cn} from '../../utils/cn';
10
+
11
+ const b = cn('ydb-gravity-graph');
12
+ const GRAPH_FIT_PADDING = 40;
13
+
14
+ import {ConnectionBlockComponent} from './BlockComponents/ConnectionBlockComponent';
15
+ import {QueryBlockComponent} from './BlockComponents/QueryBlockComponent';
16
+ import {ResultBlockComponent} from './BlockComponents/ResultBlockComponent';
17
+ import {StageBlockComponent} from './BlockComponents/StageBlockComponent';
18
+ import {FullViewportBackground} from './FullViewportBackground';
19
+ import {GraphControls} from './GraphControls';
20
+ import {NonSelectableConnection} from './NonSelectableConnection';
21
+ import {graphColorsConfig} from './colorsConfig';
22
+ import {runTreeLayout} from './runTreeLayout';
23
+ import type {Data} from './types';
24
+ import {parseCustomPropertyValue} from './utils';
25
+
26
+ import './GravityGraph.scss';
27
+
28
+ interface Props<T> {
29
+ data: Data<T>;
30
+ onError: (error: Error) => void;
31
+ theme?: string;
32
+ }
33
+
34
+ const config: HookGraphParams = {
35
+ settings: {
36
+ background: FullViewportBackground,
37
+ connection: NonSelectableConnection,
38
+ showConnectionArrows: false,
39
+ },
40
+ };
41
+
42
+ const createGraphLayoutWorker = () => {
43
+ if (uiFactory.createGraphLayoutWorker) {
44
+ return uiFactory.createGraphLayoutWorker();
45
+ }
46
+
47
+ return new Worker(new URL('../../graphLayout.worker', import.meta.url));
48
+ };
49
+
50
+ const renderBlockFn = (graph: any, block: any) => {
51
+ const map: Record<string, React.ComponentType<any>> = {
52
+ query: QueryBlockComponent,
53
+ result: ResultBlockComponent,
54
+ stage: StageBlockComponent,
55
+ connection: ConnectionBlockComponent,
56
+ };
57
+
58
+ const Component = map[block.is as keyof typeof map];
59
+
60
+ return (
61
+ <GraphBlock graph={graph} block={block} className={b('block')}>
62
+ {Component ? (
63
+ <React.Fragment>
64
+ <Component
65
+ graph={graph}
66
+ block={block}
67
+ className={b('block-content', block.is)}
68
+ />
69
+ {block.planNodeId !== undefined && block.is !== 'result' && (
70
+ <div className={b('block-id')}>#{block.planNodeId}</div>
71
+ )}
72
+ </React.Fragment>
73
+ ) : (
74
+ block.id
75
+ )}
76
+ </GraphBlock>
77
+ );
78
+ };
79
+
80
+ export function GravityGraph<T>({data, onError, theme}: Props<T>) {
81
+ const {graph, start} = useGraph(config);
82
+ const cameraSizeRef = React.useRef({width: 0, height: 0});
83
+ const graphBlockIdsRef = React.useRef<TBlockId[]>([]);
84
+ const fitFrameRef = React.useRef<number>();
85
+ const fitGraphToViewport = React.useCallback(() => {
86
+ const {width, height} = graph.cameraService.getCameraState();
87
+
88
+ if (
89
+ graph.state !== GraphState.READY ||
90
+ width <= 0 ||
91
+ height <= 0 ||
92
+ graphBlockIdsRef.current.length === 0
93
+ ) {
94
+ return;
95
+ }
96
+
97
+ graph.zoomTo(graphBlockIdsRef.current, {padding: GRAPH_FIT_PADDING});
98
+ }, [graph]);
99
+ const scheduleGraphFit = React.useCallback(() => {
100
+ if (fitFrameRef.current !== undefined) {
101
+ globalThis.cancelAnimationFrame(fitFrameRef.current);
102
+ }
103
+
104
+ // camera-change is emitted before its new size is committed.
105
+ fitFrameRef.current = globalThis.requestAnimationFrame(() => {
106
+ fitFrameRef.current = undefined;
107
+ fitGraphToViewport();
108
+ });
109
+ }, [fitGraphToViewport]);
110
+
111
+ React.useEffect(() => {
112
+ return () => {
113
+ if (fitFrameRef.current !== undefined) {
114
+ globalThis.cancelAnimationFrame(fitFrameRef.current);
115
+ }
116
+ };
117
+ }, []);
118
+
119
+ React.useEffect(() => {
120
+ return runTreeLayout({
121
+ request: {
122
+ nodes: data.nodes,
123
+ links: data.links,
124
+ },
125
+ createWorker: createGraphLayoutWorker,
126
+ onResult: ({layout, edges}) => {
127
+ graphBlockIdsRef.current = layout.map(({id}) => id);
128
+ graph.setEntities({
129
+ blocks: layout,
130
+ connections: edges,
131
+ });
132
+ scheduleGraphFit();
133
+ },
134
+ onError,
135
+ });
136
+ }, [data.nodes, data.links, graph, onError, scheduleGraphFit]);
137
+
138
+ React.useEffect(() => {
139
+ graph.setColors(parseCustomPropertyValue(graphColorsConfig));
140
+ }, [graph, theme]);
141
+
142
+ useGraphEvent(graph, 'state-change', ({state}) => {
143
+ if (state === GraphState.ATTACHED) {
144
+ graph.cameraService.set({
145
+ scale: 1,
146
+ scaleMax: 1.5,
147
+ });
148
+ graph.setConstants({
149
+ block: {
150
+ SCALES: [0.125, 0.225, 0.5], // Detailed view stays until zoom = 0.5
151
+ },
152
+ });
153
+ start();
154
+ } else if (state === GraphState.READY) {
155
+ scheduleGraphFit();
156
+ }
157
+ });
158
+
159
+ useGraphEvent(graph, 'camera-change', ({width, height}) => {
160
+ const previousSize = cameraSizeRef.current;
161
+ cameraSizeRef.current = {width, height};
162
+
163
+ if (previousSize.width !== width || previousSize.height !== height) {
164
+ scheduleGraphFit();
165
+ }
166
+ });
167
+
168
+ return (
169
+ <React.Fragment>
170
+ <GraphCanvas graph={graph} renderBlock={renderBlockFn} />
171
+ <GraphControls graph={graph} />
172
+ </React.Fragment>
173
+ );
174
+ }
@@ -0,0 +1,14 @@
1
+ import {MultipointConnection} from '@gravity-ui/graph/react';
2
+
3
+ /**
4
+ * Кастомный класс соединения, который отключает визуальное выделение
5
+ * Наследуется от MultipointConnection и переопределяет поведение
6
+ */
7
+ export class NonSelectableConnection extends MultipointConnection {
8
+ override cursor: 'pointer' = 'pointer';
9
+
10
+ // Переопределяем метод для предотвращения выделения при клике
11
+ protected override handleEvent(event: Event) {
12
+ event.stopPropagation();
13
+ }
14
+ }
@@ -0,0 +1,114 @@
1
+ import React from 'react';
2
+
3
+ import {Popover, Tab, TabList, TabPanel, TabProvider} from '@gravity-ui/uikit';
4
+
5
+ import type {
6
+ TopologyNodeDataStatsItem,
7
+ TopologyNodeDataStatsSection,
8
+ } from '../../store/reducers/query/types';
9
+ import {cn} from '../../utils/cn';
10
+
11
+ import type {ExtendedTBlock} from './types';
12
+
13
+ const b = cn('ydb-gravity-graph');
14
+ type Props = {
15
+ block: ExtendedTBlock;
16
+ children: React.ReactElement<React.HTMLAttributes<HTMLElement>>;
17
+ };
18
+
19
+ const getStatsContent = (stat: TopologyNodeDataStatsItem | TopologyNodeDataStatsSection) => {
20
+ if ('value' in stat) {
21
+ return (
22
+ <p className={b('tooltip-stat-row')} key={stat.name}>
23
+ <span>{stat.name}:</span>
24
+ <span>{stat.value}</span>
25
+ </p>
26
+ );
27
+ }
28
+
29
+ return (
30
+ <section className={b('tooltip-stat-group')} key={stat.name}>
31
+ <div className={b('tooltip-stat-group-name')}>{stat.name}:</div>
32
+ {stat.items?.map(({name, value}: TopologyNodeDataStatsItem) => (
33
+ <p className={b('tooltip-stat-row')} key={name}>
34
+ <span>{name}:</span>
35
+ <span>{value}</span>
36
+ </p>
37
+ ))}
38
+ </section>
39
+ );
40
+ };
41
+
42
+ const useTooltipContent = (block: ExtendedTBlock) => {
43
+ const firstTab = block?.stats?.[0]?.group || '';
44
+ const [activeTab, setActiveTab] = React.useState(firstTab);
45
+ const validatedActiveTab = block.stats?.some(({group}) => group === activeTab)
46
+ ? activeTab
47
+ : firstTab;
48
+
49
+ React.useEffect(() => {
50
+ if (activeTab !== validatedActiveTab) {
51
+ setActiveTab(validatedActiveTab);
52
+ }
53
+ }, [activeTab, validatedActiveTab]);
54
+
55
+ return React.useMemo(
56
+ () => (
57
+ <TabProvider value={validatedActiveTab} onUpdate={setActiveTab}>
58
+ <TabList className={b('tooltip-tabs')}>
59
+ {block?.stats?.map((item) => (
60
+ <Tab value={item.group} key={item.group}>
61
+ {item.group}
62
+ </Tab>
63
+ ))}
64
+ </TabList>
65
+ {block?.stats?.map((item) => (
66
+ <TabPanel value={item.group} key={item.group}>
67
+ {item.stats?.map(getStatsContent)}
68
+ </TabPanel>
69
+ ))}
70
+ </TabProvider>
71
+ ),
72
+ [block?.stats, validatedActiveTab],
73
+ );
74
+ };
75
+
76
+ export const TooltipComponent = ({block, children}: Props) => {
77
+ const content = useTooltipContent(block);
78
+ const trigger = React.cloneElement(children, {
79
+ role: children.props.role ?? 'button',
80
+ tabIndex: children.props.tabIndex ?? 0,
81
+ onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => {
82
+ children.props.onKeyDown?.(event);
83
+ if (event.defaultPrevented) {
84
+ return;
85
+ }
86
+
87
+ if (event.key === 'Enter') {
88
+ event.preventDefault();
89
+ event.currentTarget.click();
90
+ } else if (event.key === ' ') {
91
+ event.preventDefault();
92
+ }
93
+ },
94
+ onKeyUp: (event: React.KeyboardEvent<HTMLElement>) => {
95
+ children.props.onKeyUp?.(event);
96
+ if (!event.defaultPrevented && event.key === ' ') {
97
+ event.currentTarget.click();
98
+ }
99
+ },
100
+ });
101
+
102
+ return (
103
+ <Popover
104
+ content={content}
105
+ hasArrow
106
+ trigger="click"
107
+ placement="right-start"
108
+ className="ydb-gravity-graph__tooltip-content"
109
+ strategy="fixed"
110
+ >
111
+ {trigger}
112
+ </Popover>
113
+ );
114
+ };
@@ -0,0 +1,21 @@
1
+ export type AbstractGraphColorsConfig = Partial<Record<string, Partial<Record<string, string>>>>;
2
+
3
+ export const graphColorsConfig = {
4
+ // Default @gravity-ui/graph colors
5
+ canvas: {
6
+ belowLayerBackground: '#0000',
7
+ border: '#0000',
8
+ dots: 'var(--g-color-line-generic)',
9
+ layerBackground: 'var(--g-color-base-background)',
10
+ },
11
+ block: {
12
+ text: 'var(--g-color-text-primary)',
13
+ background: 'var(--g-color-base-float)',
14
+ border: 'var(--g-color-line-generic-solid)',
15
+ },
16
+ connection: {
17
+ background: 'var(--g-color-line-generic-solid)',
18
+ hoverBackground: 'var(--g-color-line-generic-solid)',
19
+ selectedBackground: 'var(--g-color-line-generic-solid)',
20
+ },
21
+ } as const satisfies AbstractGraphColorsConfig;
@@ -0,0 +1,5 @@
1
+ {
2
+ "action_zoom-in": "Zoom in",
3
+ "action_zoom-out": "Zoom out",
4
+ "label_tables": "Tables"
5
+ }
@@ -0,0 +1,7 @@
1
+ import {registerKeysets} from '../../../utils/i18n';
2
+
3
+ import en from './en.json';
4
+
5
+ const COMPONENT = 'ydb-gravity-graph';
6
+
7
+ export default registerKeysets(COMPONENT, {en});