Skip to content

Commit a0ebd25

Browse files
authored
Fixes 6035: Add theme-aware lineage canvas controls (#32538)
* Fixes 6035: Add theme-aware lineage canvas controls * test(ui): provide theme context in lineage tests
1 parent 0684058 commit a0ebd25

7 files changed

Lines changed: 319 additions & 26 deletions

File tree

openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CanvasEdgeRenderer.component.test.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
* See the License for the specific language governing permissions and
1111
* limitations under the License.
1212
*/
13-
import { fireEvent, render, waitFor } from '@testing-library/react';
13+
import { act, fireEvent, render, waitFor } from '@testing-library/react';
1414
import { Edge } from 'reactflow';
15+
import { ThemeProvider } from '../../../context/UntitledUIThemeProvider/theme-provider';
1516
import { CanvasEdgeRenderer } from './CanvasEdgeRenderer.component';
1617

1718
const mockRedraw = jest.fn();
@@ -130,7 +131,7 @@ describe('CanvasEdgeRenderer', () => {
130131
reactFlowContainer.appendChild(wrapper);
131132
document.body.appendChild(reactFlowContainer);
132133

133-
return render(ui, { container: wrapper });
134+
return render(ui, { container: wrapper, wrapper: ThemeProvider });
134135
};
135136

136137
it('renders canvas element', () => {
@@ -459,7 +460,7 @@ describe('CanvasEdgeRenderer', () => {
459460
},
460461
] as unknown as ResizeObserverEntry[];
461462

462-
resizeCallback(mockEntries, {} as ResizeObserver);
463+
act(() => resizeCallback?.(mockEntries, {} as ResizeObserver));
463464
}
464465

465466
await waitFor(() => {

openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CanvasEdgeRenderer.component.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
1414
import { useTranslation } from 'react-i18next';
1515
import { Edge, useReactFlow, useViewport } from 'reactflow';
1616
import { useLineageProvider } from '../../../context/LineageProvider/LineageProvider';
17+
import { useTheme } from '../../../context/UntitledUIThemeProvider/theme-provider';
1718
import { useCanvasEdgeRenderer } from '../../../hooks/useCanvasEdgeRenderer';
1819
import { useCanvasMouseEvents } from '../../../hooks/useCanvasMouseEvents';
1920
import { useLineageEdgeColors } from '../../../hooks/useLineageEdgeColors';
@@ -39,6 +40,7 @@ export const CanvasEdgeRenderer: React.FC<CanvasEdgeRendererProps> = ({
3940
hoverEdge,
4041
}) => {
4142
const { t } = useTranslation();
43+
const { theme } = useTheme();
4244
const edgeColors = useLineageEdgeColors();
4345
const canvasRef = useRef<HTMLCanvasElement>(null);
4446
const containerRef = useRef<HTMLDivElement>(null);
@@ -114,6 +116,7 @@ export const CanvasEdgeRenderer: React.FC<CanvasEdgeRendererProps> = ({
114116
hoverEdge,
115117
containerWidth: containerSize.width,
116118
containerHeight: containerSize.height,
119+
theme,
117120
});
118121

119122
useEffect(() => {

openmetadata-ui/src/main/resources/ui/src/components/Lineage/Lineage.test.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@
1010
* See the License for the specific language governing permissions and
1111
* limitations under the License.
1212
*/
13-
import { fireEvent, render, screen } from '@testing-library/react';
13+
import {
14+
fireEvent,
15+
render as renderComponent,
16+
screen,
17+
} from '@testing-library/react';
1418
import { DragEvent } from 'react';
1519
import ReactFlow from 'reactflow';
1620
import { useLineageProvider } from '../../context/LineageProvider/LineageProvider';
21+
import { ThemeProvider } from '../../context/UntitledUIThemeProvider/theme-provider';
1722
import { EntityType } from '../../enums/entity.enum';
1823
import { Table } from '../../generated/entity/data/table';
1924
import { useLineageStore } from '../../hooks/useLineageStore';
@@ -150,6 +155,9 @@ const mockLineageStore = {
150155
activeLayer: [],
151156
};
152157

158+
const render = (component: React.ReactElement) =>
159+
renderComponent(component, { wrapper: ThemeProvider });
160+
153161
jest.mock('../../context/LineageProvider/LineageProvider', () => ({
154162
useLineageProvider: jest.fn(),
155163
}));

openmetadata-ui/src/main/resources/ui/src/hooks/useCanvasEdgeRenderer.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ describe('useCanvasEdgeRenderer', () => {
167167
colors: createMockColors(),
168168
containerWidth: 800,
169169
containerHeight: 600,
170+
theme: 'light',
170171
})
171172
);
172173

@@ -183,6 +184,7 @@ describe('useCanvasEdgeRenderer', () => {
183184
colors: createMockColors(),
184185
containerWidth: 800,
185186
containerHeight: 600,
187+
theme: 'light',
186188
})
187189
);
188190

@@ -191,6 +193,32 @@ describe('useCanvasEdgeRenderer', () => {
191193
expect(requestAnimationFrame).toHaveBeenCalled();
192194
});
193195

196+
it('schedules a redraw when the active theme changes', () => {
197+
const colors = createMockColors();
198+
const dqHighlightedEdges = new Set<string>();
199+
const edges: Edge[] = [];
200+
const initialProps: { theme: 'light' | 'dark' } = { theme: 'light' };
201+
const { rerender } = renderHook(
202+
({ theme }: { theme: 'light' | 'dark' }) =>
203+
useCanvasEdgeRenderer({
204+
canvasRef,
205+
colors,
206+
containerHeight: 600,
207+
containerWidth: 800,
208+
dqHighlightedEdges,
209+
edges,
210+
theme,
211+
}),
212+
{ initialProps }
213+
);
214+
const initialRedrawCount = (requestAnimationFrame as jest.Mock).mock.calls
215+
.length;
216+
217+
rerender({ theme: 'dark' });
218+
219+
expect(requestAnimationFrame).toHaveBeenCalledTimes(initialRedrawCount + 1);
220+
});
221+
194222
it('draws visible edges', () => {
195223
const edge = createMockEdge();
196224
const node1 = createMockNode('node-1');
@@ -215,6 +243,7 @@ describe('useCanvasEdgeRenderer', () => {
215243
colors: createMockColors(),
216244
containerWidth: 800,
217245
containerHeight: 600,
246+
theme: 'light',
218247
})
219248
);
220249

@@ -235,6 +264,7 @@ describe('useCanvasEdgeRenderer', () => {
235264
colors: createMockColors(),
236265
containerWidth: 800,
237266
containerHeight: 600,
267+
theme: 'light',
238268
})
239269
);
240270

@@ -263,6 +293,7 @@ describe('useCanvasEdgeRenderer', () => {
263293
colors: createMockColors(),
264294
containerWidth: 800,
265295
containerHeight: 600,
296+
theme: 'light',
266297
})
267298
);
268299

@@ -305,6 +336,7 @@ describe('useCanvasEdgeRenderer', () => {
305336
colors: createMockColors(),
306337
containerWidth: 800,
307338
containerHeight: 600,
339+
theme: 'light',
308340
})
309341
);
310342

@@ -343,6 +375,7 @@ describe('useCanvasEdgeRenderer', () => {
343375
colors: createMockColors(),
344376
containerWidth: 800,
345377
containerHeight: 600,
378+
theme: 'light',
346379
})
347380
);
348381

@@ -392,6 +425,7 @@ describe('useCanvasEdgeRenderer', () => {
392425
colors: createMockColors(),
393426
containerWidth: 800,
394427
containerHeight: 600,
428+
theme: 'light',
395429
})
396430
);
397431

@@ -447,6 +481,7 @@ describe('useCanvasEdgeRenderer', () => {
447481
colors: createMockColors(),
448482
containerWidth: 800,
449483
containerHeight: 600,
484+
theme: 'light',
450485
})
451486
);
452487

@@ -465,6 +500,7 @@ describe('useCanvasEdgeRenderer', () => {
465500
colors: createMockColors(),
466501
containerWidth: 800,
467502
containerHeight: 600,
503+
theme: 'light',
468504
})
469505
);
470506

@@ -502,6 +538,7 @@ describe('useCanvasEdgeRenderer', () => {
502538
colors: createMockColors(),
503539
containerWidth: 800,
504540
containerHeight: 600,
541+
theme: 'light',
505542
})
506543
);
507544

openmetadata-ui/src/main/resources/ui/src/hooks/useCanvasEdgeRenderer.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ import {
2020
} from 'react';
2121
import type { Edge } from 'reactflow';
2222
import { Position, useNodes, useReactFlow, useViewport } from 'reactflow';
23+
import { Theme } from '../context/UntitledUIThemeProvider/theme-provider.interface';
2324
import {
2425
CanvasButton,
26+
CanvasButtonColors,
2527
createCanvasButton,
2628
drawCanvasButton,
2729
ECanvasButtonType,
2830
isPointInButton,
31+
resolveCanvasButtonColors,
2932
} from '../utils/CanvasButtonUtils';
3033
import {
3134
drawArrowMarker,
@@ -51,6 +54,7 @@ interface UseCanvasEdgeRendererProps {
5154
colors: LineageEdgeColors;
5255
containerWidth: number;
5356
containerHeight: number;
57+
theme: Theme;
5458
}
5559

5660
interface EdgeHitEntry {
@@ -99,7 +103,8 @@ const getCanvasButtonHit = (
99103
>
100104
>,
101105
hoveredButtonRef: MutableRefObject<CanvasButton | null>,
102-
isDQEnabled: boolean
106+
isDQEnabled: boolean,
107+
getButtonColors: () => CanvasButtonColors
103108
): CanvasButtonHitData | null => {
104109
const edgeData = edge.data ?? {};
105110
const { hasPipeline, hasFunction } = getEdgeButtonFlags(edgeData);
@@ -128,7 +133,13 @@ const getCanvasButtonHit = (
128133
hoveredButtonRef.current?.type === button.type;
129134

130135
ctx.save();
131-
drawCanvasButton(ctx, button, isButtonHovered, isDQEnabled);
136+
drawCanvasButton(
137+
ctx,
138+
button,
139+
getButtonColors(),
140+
isButtonHovered,
141+
isDQEnabled
142+
);
132143
ctx.restore();
133144

134145
return { button, edge };
@@ -142,6 +153,7 @@ export function useCanvasEdgeRenderer({
142153
colors,
143154
containerWidth,
144155
containerHeight,
156+
theme,
145157
}: UseCanvasEdgeRendererProps) {
146158
const rafIdRef = useRef<number>();
147159
const isDirtyRef = useRef(false);
@@ -353,6 +365,14 @@ export function useCanvasEdgeRenderer({
353365

354366
const hitPaths: EdgeHitEntry[] = [];
355367
const canvasButtons: CanvasButtonHitData[] = [];
368+
// Resolve lazily inside the scheduled draw so the root theme class is current,
369+
// while graphs without pipeline/function controls avoid unnecessary DOM probes.
370+
let buttonColors: CanvasButtonColors | undefined;
371+
const getButtonColors = () => {
372+
buttonColors ??= resolveCanvasButtonColors();
373+
374+
return buttonColors;
375+
};
356376

357377
visibleEdges.forEach((edge) => {
358378
ctx.save();
@@ -368,7 +388,8 @@ export function useCanvasEdgeRenderer({
368388
edge,
369389
edgePathCacheRef,
370390
hoveredButtonRef,
371-
isDQEnabled
391+
isDQEnabled,
392+
getButtonColors
372393
);
373394

374395
if (buttonHit) {
@@ -507,6 +528,7 @@ export function useCanvasEdgeRenderer({
507528
selectedColumn,
508529
dqHighlightedEdges,
509530
colors,
531+
theme,
510532
]);
511533

512534
useEffect(() => {

0 commit comments

Comments
 (0)