-
Notifications
You must be signed in to change notification settings - Fork 15
feat: migrate to gravity-ui/graph #2735
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
16 files reviewed, 8 comments
const onZoomInClick = () => { | ||
const cameraScale = graph.cameraService.getCameraScale(); | ||
graph.zoom({scale: cameraScale * ZOOM_STEP}); | ||
}; | ||
|
||
const onZoomOutClick = () => { | ||
const cameraScale = graph.cameraService.getCameraScale(); | ||
graph.zoom({scale: cameraScale / ZOOM_STEP}); | ||
}; | ||
|
||
const onResetZoomClick = () => { | ||
graph.zoom({scale: 1}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Event handlers should be memoized with useCallback to prevent unnecessary re-renders when this component is re-rendered
const onZoomInClick = () => { | |
const cameraScale = graph.cameraService.getCameraScale(); | |
graph.zoom({scale: cameraScale * ZOOM_STEP}); | |
}; | |
const onZoomOutClick = () => { | |
const cameraScale = graph.cameraService.getCameraScale(); | |
graph.zoom({scale: cameraScale / ZOOM_STEP}); | |
}; | |
const onResetZoomClick = () => { | |
graph.zoom({scale: 1}); | |
}; | |
import {useCallback} from 'react'; | |
const onZoomInClick = useCallback(() => { | |
const cameraScale = graph.cameraService.getCameraScale(); | |
graph.zoom({scale: cameraScale * ZOOM_STEP}); | |
}, [graph]); | |
const onZoomOutClick = useCallback(() => { | |
const cameraScale = graph.cameraService.getCameraScale(); | |
graph.zoom({scale: cameraScale / ZOOM_STEP}); | |
}, [graph]); | |
const onResetZoomClick = useCallback(() => { | |
graph.zoom({scale: 1}); | |
}, [graph]); |
: block.name} | ||
{block.tables ? ( | ||
<div> | ||
<Text color="secondary">Tables: </Text> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Consider using i18n for user-facing text instead of hardcoded 'Tables: '
Context Used: Style Guide - description of repository for agents (link)
// Настройки отступов | ||
this.options = { | ||
horizontalSpacing: options.horizontalSpacing || 40, // расстояние между блоками по горизонтали | ||
verticalSpacing: options.verticalSpacing || 20, // расстояние между уровнями | ||
...options, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Russian comments violate the project's internationalization standards. All code comments should be in English.
// Настройки отступов | |
this.options = { | |
horizontalSpacing: options.horizontalSpacing || 40, // расстояние между блоками по горизонтали | |
verticalSpacing: options.verticalSpacing || 20, // расстояние между уровнями | |
...options, | |
}; | |
// Spacing settings | |
this.options = { | |
horizontalSpacing: options.horizontalSpacing || 40, // horizontal distance between blocks | |
verticalSpacing: options.verticalSpacing || 20, // vertical distance between levels | |
...options, | |
}; |
Context Used: Style Guide - description of repository for agents (link)
const getIcon = (name: string): IconData | undefined => { | ||
switch (name) { | ||
case 'Merge': | ||
return DatabaseFill; | ||
case 'UnionAll': | ||
return GripHorizontal; | ||
case 'HashShuffle': | ||
return Shuffle; | ||
case 'Map': | ||
return MapPin; | ||
case 'Broadcast': | ||
return ArrowsExpandHorizontal; | ||
default: | ||
return undefined; | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Consider memoizing this function with useMemo
or moving it outside the component to avoid recreation on every render
Context Used: Style Guide - description of repository for agents (link)
} | ||
}; | ||
|
||
export const ConnectionBlockComponent = ({className, block}: Props) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Component should be memoized with React.memo
since it receives object props that may not change between renders
Context Used: Style Guide - description of repository for agents (link)
type Props = { | ||
className: string; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Props interface is inconsistent with other block components. StageBlockComponent and ConnectionBlockComponent expect both block
and className
props, but this only accepts className
.
const useTooltipContent = (block: ExtendedTBlock) => { | ||
const firstTab = block?.stats?.[0]?.group || ''; | ||
const [activeTab, setActiveTab] = useState(firstTab); | ||
|
||
return ( | ||
<TabProvider value={activeTab} onUpdate={setActiveTab}> | ||
<TabList className={b('tooltip-tabs')}> | ||
{block?.stats?.map((item) => ( | ||
<Tab value={item.group} key={item.group}> | ||
{item.group} | ||
</Tab> | ||
))} | ||
</TabList> | ||
{block?.stats?.map((item) => ( | ||
<TabPanel value={item.group} key={item.group}> | ||
{item.stats?.map(getStatsContent)} | ||
</TabPanel> | ||
))} | ||
</TabProvider> | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Performance issue: useTooltipContent creates new JSX on every render, causing unnecessary re-renders. Should use useMemo to memoize the JSX content.
const useTooltipContent = (block: ExtendedTBlock) => { | |
const firstTab = block?.stats?.[0]?.group || ''; | |
const [activeTab, setActiveTab] = useState(firstTab); | |
return ( | |
<TabProvider value={activeTab} onUpdate={setActiveTab}> | |
<TabList className={b('tooltip-tabs')}> | |
{block?.stats?.map((item) => ( | |
<Tab value={item.group} key={item.group}> | |
{item.group} | |
</Tab> | |
))} | |
</TabList> | |
{block?.stats?.map((item) => ( | |
<TabPanel value={item.group} key={item.group}> | |
{item.stats?.map(getStatsContent)} | |
</TabPanel> | |
))} | |
</TabProvider> | |
); | |
const useTooltipContent = (block: ExtendedTBlock) => { | |
const firstTab = block?.stats?.[0]?.group || ''; | |
const [activeTab, setActiveTab] = useState(firstTab); | |
return useMemo( | |
() => ( | |
<TabProvider value={activeTab} onUpdate={setActiveTab}> | |
<TabList className={b('tooltip-tabs')}> | |
{block?.stats?.map((item) => ( | |
<Tab value={item.group} key={item.group}> | |
{item.group} | |
</Tab> | |
))} | |
</TabList> | |
{block?.stats?.map((item) => ( | |
<TabPanel value={item.group} key={item.group}> | |
{item.stats?.map(getStatsContent)} | |
</TabPanel> | |
))} | |
</TabProvider> | |
), | |
[activeTab, block?.stats] | |
); |
export const TooltipComponent = ({block, children}: Props) => { | ||
const content = useTooltipContent(block); | ||
|
||
return ( | ||
<Popover | ||
content={content} | ||
hasArrow | ||
trigger="click" | ||
placement="right-start" | ||
className="ydb-gravity-graph__tooltip-content" | ||
disablePortal | ||
strategy="fixed" | ||
> | ||
{children as React.ReactElement} | ||
</Popover> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Component should be wrapped with React.memo for performance optimization, especially since it's likely to be rendered many times in graph nodes.
CI Results
Test Status:⚠️ FLAKY
📊 Full Report
Test Changes Summary ⏭️2
⏭️ Skipped Tests (2)
Bundle Size:⚠️
Current: 0.00 KB | Main: 0.00 KB
Diff: 0.00 KB (N/A)
ℹ️ CI Information
Greptile Summary
Updated On: 2025-09-15 16:32:11 UTC
This PR migrates the Graph component from a custom implementation to the official
@gravity-ui/graph
library (version 1.1.4). The migration is part of a broader architectural strategy to leverage Gravity UI components throughout the codebase rather than maintaining custom implementations.The core change replaces the existing
YDBGraph
component with a newGravityGraph
component that uses web workers for layout calculations and provides better performance. The migration maintains backward compatibility by implementing a conditional rendering approach with a hardcodedtrue
condition, allowing for easy rollback if issues arise during testing.Key architectural changes include:
QueryBlockComponent
,ResultBlockComponent
,StageBlockComponent
,ConnectionBlockComponent
) that render different node types in query execution planstreeLayout.ts
) that runs in a web worker to prevent UI blocking during graph calculationsGravityGraph.scss
) that use Gravity UI design tokens for consistent themingGraphControls
component for zoom functionality andTooltipComponent
for displaying statistical informationutils.ts
that bridge existing data structures with the new library's expected formatsThe migration preserves all existing functionality while leveraging the official library's features like camera management, zoom controls, and built-in state handling. The implementation follows the project's established patterns with BEM naming conventions, TypeScript typing, and proper integration with the Gravity UI design system.
Important Files Changed
Changed Files
Confidence score: 4/5
Sequence Diagram
Context used:
Style Guide - description of repository for agents (link)