NodeGraph: Fix possible metadata mismatch between nodes in graph (#85254)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react-test-renderer';
|
||||
|
||||
import { useLayout } from './layout';
|
||||
import { EdgeDatum, NodeDatum } from './types';
|
||||
@@ -6,6 +7,11 @@ import { EdgeDatum, NodeDatum } from './types';
|
||||
let onmessage: jest.Mock;
|
||||
let postMessage: jest.Mock;
|
||||
let terminate: jest.Mock;
|
||||
let worker: {
|
||||
onmessage: (e: MessageEvent) => void;
|
||||
postMessage: (d: unknown) => void;
|
||||
terminate: () => void;
|
||||
};
|
||||
|
||||
jest.mock('./createLayoutWorker', () => {
|
||||
return {
|
||||
@@ -14,11 +20,23 @@ jest.mock('./createLayoutWorker', () => {
|
||||
onmessage = jest.fn();
|
||||
postMessage = jest.fn();
|
||||
terminate = jest.fn();
|
||||
return {
|
||||
worker = {
|
||||
onmessage: onmessage,
|
||||
postMessage: postMessage,
|
||||
terminate: terminate,
|
||||
};
|
||||
return worker;
|
||||
},
|
||||
createMsaglWorker: () => {
|
||||
onmessage = jest.fn();
|
||||
postMessage = jest.fn();
|
||||
terminate = jest.fn();
|
||||
worker = {
|
||||
onmessage: onmessage,
|
||||
postMessage: postMessage,
|
||||
terminate: terminate,
|
||||
};
|
||||
return worker;
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -64,6 +82,32 @@ describe('layout', () => {
|
||||
expect(result.current.edges).toEqual([]);
|
||||
expect(localTerminate).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('correctly maps the layout data', async () => {
|
||||
// This specifically tests whether the mapping of data from the layout code back to the original nodes is correct.
|
||||
|
||||
const { result, rerender } = renderHook(
|
||||
({ nodes, edges }) => {
|
||||
return useLayout(nodes, edges, undefined, 100, 1000);
|
||||
},
|
||||
{ initialProps: { nodes: rawNodes, edges: rawEdges } }
|
||||
);
|
||||
|
||||
// Simulate response from the layout worker
|
||||
act(() => {
|
||||
worker.onmessage(new MessageEvent('message', { data: { nodes: testNodes, edges: testEdges } }));
|
||||
});
|
||||
|
||||
rerender({ nodes: rawNodes, edges: rawEdges });
|
||||
|
||||
expect(result.current.nodes).toMatchObject([
|
||||
{ id: 'otel-demo-alt/frontend', title: 'frontend' },
|
||||
{ id: 'otel-demo-alt/cartservice', title: 'cartservice' },
|
||||
{ id: 'otel-demo-alt/checkoutservice', title: 'checkoutservice' },
|
||||
{ id: 'otel-demo-alt/frontend-proxy', title: 'frontend-proxy' },
|
||||
{ id: 'otel-demo-alt/recommendationservice', title: 'recommendationservice' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
function makeNode(index: number, incoming: number): NodeDatum {
|
||||
@@ -92,3 +136,260 @@ function makeEdge(source: number, target: number): EdgeDatum {
|
||||
thickness: 1,
|
||||
};
|
||||
}
|
||||
|
||||
const rawNodes: NodeDatum[] = [
|
||||
{
|
||||
dataFrameRowIndex: 0,
|
||||
id: 'otel-demo-alt/cartservice',
|
||||
incoming: 1,
|
||||
subTitle: 'otel-demo-alt',
|
||||
title: 'cartservice',
|
||||
highlighted: false,
|
||||
arcSections: [],
|
||||
},
|
||||
{
|
||||
dataFrameRowIndex: 1,
|
||||
id: 'otel-demo-alt/frontend',
|
||||
incoming: 1,
|
||||
subTitle: 'otel-demo-alt',
|
||||
title: 'frontend',
|
||||
highlighted: false,
|
||||
arcSections: [],
|
||||
},
|
||||
{
|
||||
dataFrameRowIndex: 2,
|
||||
id: 'otel-demo-alt/checkoutservice',
|
||||
incoming: 1,
|
||||
subTitle: 'otel-demo-alt',
|
||||
title: 'checkoutservice',
|
||||
highlighted: false,
|
||||
arcSections: [],
|
||||
},
|
||||
{
|
||||
dataFrameRowIndex: 3,
|
||||
id: 'otel-demo-alt/frontend-proxy',
|
||||
incoming: 1,
|
||||
subTitle: 'otel-demo-alt',
|
||||
title: 'frontend-proxy',
|
||||
highlighted: false,
|
||||
arcSections: [],
|
||||
},
|
||||
{
|
||||
dataFrameRowIndex: 4,
|
||||
id: 'otel-demo-alt/recommendationservice',
|
||||
incoming: 1,
|
||||
subTitle: 'otel-demo-alt',
|
||||
title: 'recommendationservice',
|
||||
highlighted: false,
|
||||
arcSections: [],
|
||||
},
|
||||
];
|
||||
|
||||
const rawEdges = [
|
||||
{
|
||||
id: 'otel-demo-alt/frontend_otel-demo-alt/cartservice',
|
||||
dataFrameRowIndex: 0,
|
||||
source: 'otel-demo-alt/frontend',
|
||||
target: 'otel-demo-alt/cartservice',
|
||||
sourceNodeRadius: 40,
|
||||
targetNodeRadius: 40,
|
||||
mainStat: '1.08 ms/r',
|
||||
secondaryStat: '0.93 r/sec',
|
||||
highlighted: false,
|
||||
thickness: 1,
|
||||
},
|
||||
{
|
||||
id: 'otel-demo-alt/frontend_otel-demo-alt/checkoutservice',
|
||||
dataFrameRowIndex: 1,
|
||||
source: 'otel-demo-alt/frontend',
|
||||
target: 'otel-demo-alt/checkoutservice',
|
||||
sourceNodeRadius: 40,
|
||||
targetNodeRadius: 40,
|
||||
mainStat: '12.66 ms/r',
|
||||
secondaryStat: '0.12 r/sec',
|
||||
highlighted: false,
|
||||
thickness: 1,
|
||||
},
|
||||
{
|
||||
id: 'otel-demo-alt/frontend_otel-demo-alt/frontend-proxy',
|
||||
dataFrameRowIndex: 2,
|
||||
source: 'otel-demo-alt/frontend',
|
||||
target: 'otel-demo-alt/frontend-proxy',
|
||||
sourceNodeRadius: 40,
|
||||
targetNodeRadius: 40,
|
||||
mainStat: '7.45 ms/r',
|
||||
secondaryStat: '0.99 r/sec',
|
||||
highlighted: false,
|
||||
thickness: 1,
|
||||
},
|
||||
{
|
||||
id: 'otel-demo-alt/frontend_otel-demo-alt/recommendationservice',
|
||||
dataFrameRowIndex: 3,
|
||||
source: 'otel-demo-alt/frontend',
|
||||
target: 'otel-demo-alt/recommendationservice',
|
||||
sourceNodeRadius: 40,
|
||||
targetNodeRadius: 40,
|
||||
mainStat: '13.01 ms/r',
|
||||
secondaryStat: '0.18 r/sec',
|
||||
highlighted: false,
|
||||
thickness: 1,
|
||||
},
|
||||
{
|
||||
id: 'otel-demo-alt/frontend-proxy_otel-demo-alt/frontend',
|
||||
dataFrameRowIndex: 4,
|
||||
source: 'otel-demo-alt/frontend-proxy',
|
||||
target: 'otel-demo-alt/frontend',
|
||||
sourceNodeRadius: 40,
|
||||
targetNodeRadius: 40,
|
||||
mainStat: '7.50 ms/r',
|
||||
secondaryStat: '6.06 r/sec',
|
||||
highlighted: false,
|
||||
thickness: 1,
|
||||
},
|
||||
];
|
||||
|
||||
const testNodes = [
|
||||
{
|
||||
id: 'otel-demo-alt/frontend',
|
||||
incoming: 1,
|
||||
x: -80.84375,
|
||||
y: -70.49999999999997,
|
||||
},
|
||||
{
|
||||
id: 'otel-demo-alt/cartservice',
|
||||
incoming: 1,
|
||||
x: 80.84375,
|
||||
y: 211.5,
|
||||
},
|
||||
{
|
||||
id: 'otel-demo-alt/checkoutservice',
|
||||
incoming: 1,
|
||||
x: 80.84375,
|
||||
y: 70.5,
|
||||
},
|
||||
{
|
||||
id: 'otel-demo-alt/frontend-proxy',
|
||||
incoming: 1,
|
||||
x: 80.84375,
|
||||
y: -70.5,
|
||||
},
|
||||
{
|
||||
id: 'otel-demo-alt/recommendationservice',
|
||||
incoming: 1,
|
||||
x: 80.84375,
|
||||
y: -211.5,
|
||||
},
|
||||
];
|
||||
|
||||
const testEdges = [
|
||||
{
|
||||
id: 'otel-demo-alt/frontend_otel-demo-alt/cartservice',
|
||||
dataFrameRowIndex: 0,
|
||||
source: {
|
||||
id: 'otel-demo-alt/frontend',
|
||||
incoming: 1,
|
||||
x: -80.84375,
|
||||
y: -70.49999999999997,
|
||||
},
|
||||
target: {
|
||||
id: 'otel-demo-alt/cartservice',
|
||||
incoming: 1,
|
||||
x: 80.84375,
|
||||
y: 211.5,
|
||||
},
|
||||
sourceNodeRadius: 40,
|
||||
targetNodeRadius: 40,
|
||||
mainStat: '1.07 ms/r',
|
||||
secondaryStat: '1.08 r/sec',
|
||||
highlighted: false,
|
||||
thickness: 1,
|
||||
},
|
||||
{
|
||||
id: 'otel-demo-alt/frontend_otel-demo-alt/checkoutservice',
|
||||
dataFrameRowIndex: 1,
|
||||
source: {
|
||||
id: 'otel-demo-alt/frontend',
|
||||
incoming: 1,
|
||||
x: -80.84375,
|
||||
y: -70.49999999999997,
|
||||
},
|
||||
target: {
|
||||
id: 'otel-demo-alt/checkoutservice',
|
||||
incoming: 1,
|
||||
x: 80.84375,
|
||||
y: 70.5,
|
||||
},
|
||||
sourceNodeRadius: 40,
|
||||
targetNodeRadius: 40,
|
||||
mainStat: '8.18 ms/r',
|
||||
secondaryStat: '0.14 r/sec',
|
||||
highlighted: false,
|
||||
thickness: 1,
|
||||
},
|
||||
{
|
||||
id: 'otel-demo-alt/frontend_otel-demo-alt/frontend-proxy',
|
||||
dataFrameRowIndex: 2,
|
||||
source: {
|
||||
id: 'otel-demo-alt/frontend',
|
||||
incoming: 1,
|
||||
x: -80.84375,
|
||||
y: -70.49999999999997,
|
||||
},
|
||||
target: {
|
||||
id: 'otel-demo-alt/frontend-proxy',
|
||||
incoming: 1,
|
||||
x: 80.84375,
|
||||
y: -70.5,
|
||||
},
|
||||
sourceNodeRadius: 40,
|
||||
targetNodeRadius: 40,
|
||||
mainStat: '7.57 ms/r',
|
||||
secondaryStat: '1.14 r/sec',
|
||||
highlighted: false,
|
||||
thickness: 1,
|
||||
},
|
||||
{
|
||||
id: 'otel-demo-alt/frontend_otel-demo-alt/recommendationservice',
|
||||
dataFrameRowIndex: 3,
|
||||
source: {
|
||||
id: 'otel-demo-alt/frontend',
|
||||
incoming: 1,
|
||||
x: -80.84375,
|
||||
y: -70.49999999999997,
|
||||
},
|
||||
target: {
|
||||
id: 'otel-demo-alt/recommendationservice',
|
||||
incoming: 1,
|
||||
x: 80.84375,
|
||||
y: -211.5,
|
||||
},
|
||||
sourceNodeRadius: 40,
|
||||
targetNodeRadius: 40,
|
||||
mainStat: '8.60 ms/r',
|
||||
secondaryStat: '0.22 r/sec',
|
||||
highlighted: false,
|
||||
thickness: 1,
|
||||
},
|
||||
{
|
||||
id: 'otel-demo-alt/frontend-proxy_otel-demo-alt/frontend',
|
||||
dataFrameRowIndex: 4,
|
||||
source: {
|
||||
id: 'otel-demo-alt/frontend-proxy',
|
||||
incoming: 1,
|
||||
x: 80.84375,
|
||||
y: -70.5,
|
||||
},
|
||||
target: {
|
||||
id: 'otel-demo-alt/frontend',
|
||||
incoming: 1,
|
||||
x: -80.84375,
|
||||
y: -70.49999999999997,
|
||||
},
|
||||
sourceNodeRadius: 40,
|
||||
targetNodeRadius: 40,
|
||||
mainStat: '7.53 ms/r',
|
||||
secondaryStat: '6.82 r/sec',
|
||||
highlighted: false,
|
||||
thickness: 1,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { fromPairs } from 'lodash';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useUnmount } from 'react-use';
|
||||
import useMountedState from 'react-use/lib/useMountedState';
|
||||
@@ -159,13 +160,16 @@ function layout(
|
||||
const worker = engine === 'default' ? createWorker() : createMsaglWorker();
|
||||
|
||||
worker.onmessage = (event: MessageEvent<{ nodes: NodeDatum[]; edges: EdgeDatumLayout[] }>) => {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
// These stats needs to be Field class but the data is stringified over the worker boundary
|
||||
event.data.nodes[i] = {
|
||||
...nodes[i],
|
||||
...event.data.nodes[i],
|
||||
const nodesMap = fromPairs(nodes.map((node) => [node.id, node]));
|
||||
|
||||
// Add the x,y coordinates from the layout algorithm to the original nodes.
|
||||
event.data.nodes = event.data.nodes.map((node) => {
|
||||
return {
|
||||
...nodesMap[node.id],
|
||||
...node,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
done(event.data);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user