NodeGraph: Fix possible metadata mismatch between nodes in graph (#85254)

pull/85239/head
Andrej Ocenas 1 year ago committed by GitHub
parent 222f93794d
commit 20e70838e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 303
      public/app/plugins/panel/nodeGraph/layout.test.ts
  2. 16
      public/app/plugins/panel/nodeGraph/layout.ts

@ -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);
};

Loading…
Cancel
Save