mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NodeGraph: Use layered layout instead of force based layout (#78957)
This commit is contained in:
parent
395a06ab86
commit
cb945aa5df
35
.drone.yml
35
.drone.yml
@ -123,7 +123,8 @@ steps:
|
||||
image: alpine:3.18.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- apt-get update -yq && apt-get install python -y
|
||||
- apk add --no-cache --virtual .gyp python3 make g++ && ln -sf /usr/bin/python3
|
||||
/usr/bin/python
|
||||
- yarn install --immutable
|
||||
depends_on: []
|
||||
image: node:20.9.0-alpine
|
||||
@ -225,7 +226,8 @@ steps:
|
||||
image: alpine:3.18.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- apt-get update -yq && apt-get install python -y
|
||||
- apk add --no-cache --virtual .gyp python3 make g++ && ln -sf /usr/bin/python3
|
||||
/usr/bin/python
|
||||
- yarn install --immutable
|
||||
depends_on: []
|
||||
image: node:20.9.0-alpine
|
||||
@ -537,7 +539,8 @@ steps:
|
||||
image: golang:1.21.6-alpine
|
||||
name: wire-install
|
||||
- commands:
|
||||
- apt-get update -yq && apt-get install python -y
|
||||
- apk add --no-cache --virtual .gyp python3 make g++ && ln -sf /usr/bin/python3
|
||||
/usr/bin/python
|
||||
- yarn install --immutable
|
||||
depends_on: []
|
||||
image: node:20.9.0-alpine
|
||||
@ -1047,7 +1050,8 @@ steps:
|
||||
image: alpine:3.18.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- apt-get update -yq && apt-get install python -y
|
||||
- apk add --no-cache --virtual .gyp python3 make g++ && ln -sf /usr/bin/python3
|
||||
/usr/bin/python
|
||||
- yarn install --immutable
|
||||
depends_on: []
|
||||
image: node:20.9.0-alpine
|
||||
@ -1407,7 +1411,8 @@ steps:
|
||||
image: alpine:3.18.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- apt-get update -yq && apt-get install python -y
|
||||
- apk add --no-cache --virtual .gyp python3 make g++ && ln -sf /usr/bin/python3
|
||||
/usr/bin/python
|
||||
- yarn install --immutable
|
||||
depends_on: []
|
||||
image: node:20.9.0-alpine
|
||||
@ -1484,7 +1489,8 @@ steps:
|
||||
image: alpine:3.18.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- apt-get update -yq && apt-get install python -y
|
||||
- apk add --no-cache --virtual .gyp python3 make g++ && ln -sf /usr/bin/python3
|
||||
/usr/bin/python
|
||||
- yarn install --immutable
|
||||
depends_on: []
|
||||
image: node:20.9.0-alpine
|
||||
@ -1543,7 +1549,8 @@ steps:
|
||||
image: alpine:3.18.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- apt-get update -yq && apt-get install python -y
|
||||
- apk add --no-cache --virtual .gyp python3 make g++ && ln -sf /usr/bin/python3
|
||||
/usr/bin/python
|
||||
- yarn install --immutable
|
||||
depends_on: []
|
||||
image: node:20.9.0-alpine
|
||||
@ -1806,7 +1813,8 @@ steps:
|
||||
image: golang:1.21.6-alpine
|
||||
name: wire-install
|
||||
- commands:
|
||||
- apt-get update -yq && apt-get install python -y
|
||||
- apk add --no-cache --virtual .gyp python3 make g++ && ln -sf /usr/bin/python3
|
||||
/usr/bin/python
|
||||
- yarn install --immutable
|
||||
depends_on: []
|
||||
image: node:20.9.0-alpine
|
||||
@ -2722,7 +2730,8 @@ steps:
|
||||
image: golang:1.21.6-alpine
|
||||
name: compile-build-cmd
|
||||
- commands:
|
||||
- apt-get update -yq && apt-get install python -y
|
||||
- apk add --no-cache --virtual .gyp python3 make g++ && ln -sf /usr/bin/python3
|
||||
/usr/bin/python
|
||||
- yarn install --immutable
|
||||
depends_on: []
|
||||
image: node:20.9.0-alpine
|
||||
@ -2996,7 +3005,8 @@ steps:
|
||||
image: alpine:3.18.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- apt-get update -yq && apt-get install python -y
|
||||
- apk add --no-cache --virtual .gyp python3 make g++ && ln -sf /usr/bin/python3
|
||||
/usr/bin/python
|
||||
- yarn install --immutable
|
||||
depends_on: []
|
||||
image: node:20.9.0-alpine
|
||||
@ -3420,7 +3430,8 @@ steps:
|
||||
image: alpine:3.18.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- apt-get update -yq && apt-get install python -y
|
||||
- apk add --no-cache --virtual .gyp python3 make g++ && ln -sf /usr/bin/python3
|
||||
/usr/bin/python
|
||||
- yarn install --immutable
|
||||
depends_on: []
|
||||
image: node:20.9.0-alpine
|
||||
@ -4788,6 +4799,6 @@ kind: secret
|
||||
name: gcr_credentials
|
||||
---
|
||||
kind: signature
|
||||
hmac: d61ada44fb898f089416a8f2afcea19481065e311a771ce4b08e03f2d13b4853
|
||||
hmac: 88526f5c0dd7e3e225914c39c331b827eb7846fd83be24d9e055c0aff72a7e67
|
||||
|
||||
...
|
||||
|
@ -33,6 +33,10 @@ packageExtensions:
|
||||
react-resizable@3.0.4:
|
||||
peerDependencies:
|
||||
react-dom: 17.0.1
|
||||
'@msagl/drawing@*':
|
||||
dependencies:
|
||||
queue-typescript: "^1.0.1"
|
||||
"@esfx/collections-sortedmap": "^1.0.0"
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-outdated.cjs
|
||||
|
@ -58,10 +58,11 @@ title: TestDataDataQuery kind
|
||||
|
||||
### NodesQuery
|
||||
|
||||
| Property | Type | Required | Default | Description |
|
||||
|----------|---------|----------|---------|------------------------------------------------------------|
|
||||
| `count` | integer | No | | |
|
||||
| `type` | string | No | | Possible values are: `random`, `response`, `random edges`. |
|
||||
| Property | Type | Required | Default | Description |
|
||||
|----------|---------|----------|---------|-------------------------------------------------------------------------------------|
|
||||
| `count` | integer | No | | |
|
||||
| `seed` | integer | No | | |
|
||||
| `type` | string | No | | Possible values are: `random`, `response_small`, `response_medium`, `random edges`. |
|
||||
|
||||
### PulseWaveQuery
|
||||
|
||||
|
@ -174,6 +174,7 @@ Experimental features might be changed or removed without prior notice.
|
||||
| `newFolderPicker` | Enables the nested folder picker without having nested folders enabled |
|
||||
| `onPremToCloudMigrations` | In-development feature that will allow users to easily migrate their on-prem Grafana instances to Grafana Cloud. |
|
||||
| `promQLScope` | In-development feature that will allow injection of labels into prometheus queries. |
|
||||
| `nodeGraphDotLayout` | Changed the layout algorithm for the node graph |
|
||||
|
||||
## Development feature toggles
|
||||
|
||||
|
@ -257,6 +257,8 @@
|
||||
"@locker/near-membrane-dom": "0.13.3",
|
||||
"@locker/near-membrane-shared": "0.13.3",
|
||||
"@locker/near-membrane-shared-dom": "0.13.3",
|
||||
"@msagl/core": "^1.1.16",
|
||||
"@msagl/parser": "^1.1.16",
|
||||
"@opentelemetry/api": "1.7.0",
|
||||
"@opentelemetry/exporter-collector": "0.25.0",
|
||||
"@opentelemetry/semantic-conventions": "1.21.0",
|
||||
|
@ -175,4 +175,5 @@ export interface FeatureToggles {
|
||||
alertingSaveStatePeriodic?: boolean;
|
||||
promQLScope?: boolean;
|
||||
slateAutocomplete?: boolean;
|
||||
nodeGraphDotLayout?: boolean;
|
||||
}
|
||||
|
@ -75,7 +75,8 @@ export interface SimulationQuery {
|
||||
|
||||
export interface NodesQuery {
|
||||
count?: number;
|
||||
type?: ('random' | 'response' | 'random edges');
|
||||
seed?: number;
|
||||
type?: ('random' | 'response_small' | 'response_medium' | 'random edges');
|
||||
}
|
||||
|
||||
export interface USAQuery {
|
||||
|
@ -1329,5 +1329,13 @@ var (
|
||||
Owner: grafanaFrontendPlatformSquad,
|
||||
Created: time.Date(2024, time.January, 29, 12, 0, 0, 0, time.UTC),
|
||||
},
|
||||
{
|
||||
Name: "nodeGraphDotLayout",
|
||||
Description: "Changed the layout algorithm for the node graph",
|
||||
Stage: FeatureStageExperimental,
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaObservabilityTracesAndProfilingSquad,
|
||||
Created: time.Date(2024, time.January, 2, 12, 0, 0, 0, time.UTC),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
@ -156,3 +156,4 @@ onPremToCloudMigrations,experimental,@grafana/grafana-operator-experience-squad,
|
||||
alertingSaveStatePeriodic,privatePreview,@grafana/alerting-squad,2024-01-22,false,false,false
|
||||
promQLScope,experimental,@grafana/observability-metrics,2024-01-29,false,false,false
|
||||
slateAutocomplete,GA,@grafana/grafana-frontend-platform,2024-01-29,false,false,true
|
||||
nodeGraphDotLayout,experimental,@grafana/observability-traces-and-profiling,2024-01-02,false,false,true
|
||||
|
|
@ -634,4 +634,8 @@ const (
|
||||
// FlagSlateAutocomplete
|
||||
// Adjusts the behaviour of the slate editor to properly handle autocomplete. Feature toggled for safety.
|
||||
FlagSlateAutocomplete = "slateAutocomplete"
|
||||
|
||||
// FlagNodeGraphDotLayout
|
||||
// Changed the layout algorithm for the node graph
|
||||
FlagNodeGraphDotLayout = "nodeGraphDotLayout"
|
||||
)
|
||||
|
@ -11,9 +11,10 @@ package dataquery
|
||||
|
||||
// Defines values for NodesQueryType.
|
||||
const (
|
||||
NodesQueryTypeRandom NodesQueryType = "random"
|
||||
NodesQueryTypeRandomEdges NodesQueryType = "random edges"
|
||||
NodesQueryTypeResponse NodesQueryType = "response"
|
||||
NodesQueryTypeRandom NodesQueryType = "random"
|
||||
NodesQueryTypeRandomEdges NodesQueryType = "random edges"
|
||||
NodesQueryTypeResponseMedium NodesQueryType = "response_medium"
|
||||
NodesQueryTypeResponseSmall NodesQueryType = "response_small"
|
||||
)
|
||||
|
||||
// Defines values for StreamingQueryType.
|
||||
@ -100,6 +101,7 @@ type DataQuery struct {
|
||||
// NodesQuery defines model for NodesQuery.
|
||||
type NodesQuery struct {
|
||||
Count *int64 `json:"count,omitempty"`
|
||||
Seed *int64 `json:"seed,omitempty"`
|
||||
Type *NodesQueryType `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,6 @@ import * as histogramPanel from 'app/plugins/panel/histogram/module';
|
||||
import * as livePanel from 'app/plugins/panel/live/module';
|
||||
import * as logsPanel from 'app/plugins/panel/logs/module';
|
||||
import * as newsPanel from 'app/plugins/panel/news/module';
|
||||
import * as nodeGraph from 'app/plugins/panel/nodeGraph/module';
|
||||
import * as pieChartPanel from 'app/plugins/panel/piechart/module';
|
||||
import * as statPanel from 'app/plugins/panel/stat/module';
|
||||
import * as stateTimelinePanel from 'app/plugins/panel/state-timeline/module';
|
||||
@ -80,6 +79,9 @@ const heatmapPanel = async () =>
|
||||
const tableOldPanel = async () =>
|
||||
await import(/* webpackChunkName: "tableOldPlugin" */ 'app/plugins/panel/table-old/module');
|
||||
|
||||
const nodeGraph = async () =>
|
||||
await import(/* webpackChunkName: "nodeGraphPanel" */ 'app/plugins/panel/nodeGraph/module');
|
||||
|
||||
const builtInPlugins: Record<string, System.Module | (() => Promise<System.Module>)> = {
|
||||
// datasources
|
||||
'core:plugin/graphite': graphitePlugin,
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"extends": "@grafana/plugin-configs/tsconfig.json",
|
||||
"include": ["."]
|
||||
"include": ["."],
|
||||
}
|
||||
|
@ -24,21 +24,34 @@ export function NodeGraphEditor({ query, onChange }: Props) {
|
||||
/>
|
||||
</InlineField>
|
||||
{(type === 'random' || type === 'random edges') && (
|
||||
<InlineField label="Count" labelWidth={14}>
|
||||
<Input
|
||||
type="number"
|
||||
name="count"
|
||||
value={query.nodes?.count}
|
||||
width={32}
|
||||
onChange={(e) =>
|
||||
onChange({ ...query.nodes, count: e.currentTarget.value ? parseInt(e.currentTarget.value, 10) : 0 })
|
||||
}
|
||||
placeholder="10"
|
||||
/>
|
||||
</InlineField>
|
||||
<>
|
||||
<InlineField label="Count" labelWidth={14}>
|
||||
<Input
|
||||
type="number"
|
||||
name="count"
|
||||
value={query.nodes?.count}
|
||||
width={32}
|
||||
onChange={(e) =>
|
||||
onChange({ ...query.nodes, count: e.currentTarget.value ? parseInt(e.currentTarget.value, 10) : 0 })
|
||||
}
|
||||
placeholder="10"
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Seed" labelWidth={14}>
|
||||
<Input
|
||||
type="number"
|
||||
name="seed"
|
||||
value={query.nodes?.seed}
|
||||
width={16}
|
||||
onChange={(e) =>
|
||||
onChange({ ...query.nodes, seed: e.currentTarget.value ? parseInt(e.currentTarget.value, 10) : 0 })
|
||||
}
|
||||
/>
|
||||
</InlineField>
|
||||
</>
|
||||
)}
|
||||
</InlineFieldRow>
|
||||
);
|
||||
}
|
||||
|
||||
const options: Array<NodesQuery['type']> = ['random', 'response', 'random edges'];
|
||||
const options: Array<NodesQuery['type']> = ['random', 'response_small', 'response_medium', 'random edges'];
|
||||
|
@ -83,8 +83,9 @@ composableKinds: DataQuery: {
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
#NodesQuery: {
|
||||
type?: "random" | "response" | "random edges"
|
||||
type?: "random" | "response_small" | "response_medium" | "random edges"
|
||||
count?: int64
|
||||
seed?: int64
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
#USAQuery: {
|
||||
|
@ -72,7 +72,8 @@ export interface SimulationQuery {
|
||||
|
||||
export interface NodesQuery {
|
||||
count?: number;
|
||||
type?: ('random' | 'response' | 'random edges');
|
||||
seed?: number;
|
||||
type?: ('random' | 'response_small' | 'response_medium' | 'random edges');
|
||||
}
|
||||
|
||||
export interface USAQuery {
|
||||
|
@ -232,13 +232,16 @@ export class TestDataDataSource extends DataSourceWithBackend<TestData> {
|
||||
let frames: DataFrame[];
|
||||
switch (type) {
|
||||
case 'random':
|
||||
frames = generateRandomNodes(target.nodes?.count);
|
||||
frames = generateRandomNodes(target.nodes?.count, target.nodes?.seed);
|
||||
break;
|
||||
case 'response':
|
||||
frames = savedNodesResponse();
|
||||
case 'response_small':
|
||||
frames = savedNodesResponse('small');
|
||||
break;
|
||||
case 'response_medium':
|
||||
frames = savedNodesResponse('medium');
|
||||
break;
|
||||
case 'random edges':
|
||||
frames = [generateRandomEdges(target.nodes?.count)];
|
||||
frames = [generateRandomEdges(target.nodes?.count, target.nodes?.seed)];
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown node_graph sub type ${type}`);
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { randomLcg } from 'd3-random';
|
||||
|
||||
import {
|
||||
FieldColorModeId,
|
||||
FieldDTO,
|
||||
@ -7,11 +9,13 @@ import {
|
||||
DataFrame,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { nodes, edges } from './testData/serviceMapResponse';
|
||||
import * as serviceMapResponseSmall from './testData/serviceMapResponse';
|
||||
import * as serviceMapResponsMedium from './testData/serviceMapResponseMedium';
|
||||
|
||||
export function generateRandomNodes(count = 10) {
|
||||
export function generateRandomNodes(count = 10, seed?: number) {
|
||||
const nodes = [];
|
||||
const edges: string[] = [];
|
||||
const rand = randomLcg(seed);
|
||||
|
||||
const root = {
|
||||
id: 'root',
|
||||
@ -31,7 +35,7 @@ export function generateRandomNodes(count = 10) {
|
||||
for (let i = 1; i < count; i++) {
|
||||
const node = makeRandomNode(i);
|
||||
nodes.push(node);
|
||||
const sourceIndex = Math.floor(Math.random() * Math.floor(nodesWithoutMaxEdges.length - 1));
|
||||
const sourceIndex = Math.floor(rand() * Math.floor(nodesWithoutMaxEdges.length - 1));
|
||||
const source = nodesWithoutMaxEdges[sourceIndex];
|
||||
source.edges.push(node.id);
|
||||
if (source.edges.length >= maxEdges) {
|
||||
@ -43,8 +47,8 @@ export function generateRandomNodes(count = 10) {
|
||||
// Add some random edges to create possible cycle
|
||||
const additionalEdges = Math.floor(count / 2);
|
||||
for (let i = 0; i <= additionalEdges; i++) {
|
||||
const sourceIndex = Math.floor(Math.random() * Math.floor(nodes.length - 1));
|
||||
const targetIndex = Math.floor(Math.random() * Math.floor(nodes.length - 1));
|
||||
const sourceIndex = Math.floor(rand() * Math.floor(nodes.length - 1));
|
||||
const targetIndex = Math.floor(rand() * Math.floor(nodes.length - 1));
|
||||
if (sourceIndex === targetIndex || nodes[sourceIndex].id === '0' || nodes[targetIndex].id === '0') {
|
||||
continue;
|
||||
}
|
||||
@ -185,11 +189,12 @@ function makeRandomNode(index: number) {
|
||||
};
|
||||
}
|
||||
|
||||
export function savedNodesResponse() {
|
||||
return [new MutableDataFrame(nodes), new MutableDataFrame(edges)];
|
||||
export function savedNodesResponse(size: 'small' | 'medium'): [DataFrame, DataFrame] {
|
||||
const response = size === 'small' ? serviceMapResponseSmall : serviceMapResponsMedium;
|
||||
return [new MutableDataFrame(response.nodes), new MutableDataFrame(response.edges)];
|
||||
}
|
||||
|
||||
// Generates node graph data but only returns the edges
|
||||
export function generateRandomEdges(count = 10) {
|
||||
return generateRandomNodes(count)[1];
|
||||
export function generateRandomEdges(count = 10, seed = 1) {
|
||||
return generateRandomNodes(count, seed)[1];
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
"@grafana/runtime": "10.4.0-pre",
|
||||
"@grafana/schema": "10.4.0-pre",
|
||||
"@grafana/ui": "10.4.0-pre",
|
||||
"d3-random": "^3.0.1",
|
||||
"lodash": "4.17.21",
|
||||
"micro-memoize": "^4.1.2",
|
||||
"react": "18.2.0",
|
||||
@ -23,6 +24,7 @@
|
||||
"@grafana/plugin-configs": "10.4.0-pre",
|
||||
"@testing-library/react": "14.2.0",
|
||||
"@testing-library/user-event": "14.5.2",
|
||||
"@types/d3-random": "^3.0.2",
|
||||
"@types/jest": "29.5.11",
|
||||
"@types/lodash": "4.14.202",
|
||||
"@types/node": "20.11.13",
|
||||
|
@ -57,7 +57,7 @@ export const nodes = {
|
||||
},
|
||||
],
|
||||
},
|
||||
values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
|
||||
values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||
},
|
||||
{
|
||||
name: NodeGraphDataFrameFieldNames.title,
|
||||
@ -79,6 +79,8 @@ export const nodes = {
|
||||
'api',
|
||||
'www',
|
||||
'products',
|
||||
'test-client',
|
||||
'test-api',
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -101,6 +103,8 @@ export const nodes = {
|
||||
'client',
|
||||
'client',
|
||||
'Compute',
|
||||
'ephemeral',
|
||||
'ephemeral-api',
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -110,7 +114,7 @@ export const nodes = {
|
||||
values: [
|
||||
3.5394042646735553, 15.906441318223264, 4.913011921591567, 7.4163203042094095, 1092, 22.85961441405067,
|
||||
56.135855729084696, 4.45946191601527, 12.818300278280843, 4.25, 12.565442646791492, 77.63447512700567,
|
||||
40.387096774193544, 77.63447512700567, 27.648950187374872,
|
||||
40.387096774193544, 77.63447512700567, 27.648950187374872, 77.63447512700567, 27.648950187374872,
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -120,7 +124,7 @@ export const nodes = {
|
||||
values: [
|
||||
50.56317154501667, 682.4, 512.8416666666667, 125.64444444444445, 0.005585812037424941, 137.59722222222223,
|
||||
300.0527777777778, 30.582348853370394, 125.77222222222223, 0.028706417080318163, 30.582348853370394, 165.675,
|
||||
0.100021510002151, 165.675, 162.33055555555555,
|
||||
0.100021510002151, 165.675, 162.33055555555555, 165.675, 162.33055555555555,
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -129,7 +133,7 @@ export const nodes = {
|
||||
config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'green' }, displayName: 'Sucesss' },
|
||||
values: [
|
||||
0.9338865684765882, 1, 1, 1, 0.5, 1, 0.9901128505170387, 0.9069260134520997, 1, 0, 0.9069260134520997,
|
||||
0.9624432037288534, 0, 0.9624432037288534, 0.9824945669843769,
|
||||
0.9624432037288534, 0, 0.9624432037288534, 0.9824945669843769, 0.9624432037288534, 0.9824945669843769,
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -138,7 +142,7 @@ export const nodes = {
|
||||
config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'red' }, displayName: 'Faults' },
|
||||
values: [
|
||||
0, 0, 0, 0, 0.5, 0, 0.009479813736472288, 0, 0, 0, 0, 0.017168821152524185, 0, 0.017168821152524185,
|
||||
0.01750543301562313,
|
||||
0.01750543301562313, 0.017168821152524185, 0.01750543301562313,
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -147,14 +151,14 @@ export const nodes = {
|
||||
config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'semi-dark-yellow' }, displayName: 'Errors' },
|
||||
values: [
|
||||
0.06611343152341174, 0, 0, 0, 0, 0, 0.0004073357464890436, 0.09307398654790038, 0, 1, 0.09307398654790038,
|
||||
0.02038797511862247, 1, 0.02038797511862247, 0,
|
||||
0.02038797511862247, 1, 0.02038797511862247, 0, 0.02038797511862247, 0,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: NodeGraphDataFrameFieldNames.arc + 'throttled',
|
||||
type: FieldType.number,
|
||||
config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'purple' }, displayName: 'Throttled' },
|
||||
values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
},
|
||||
],
|
||||
meta: { preferredVisualisationType: 'nodeGraph' as const },
|
||||
@ -236,13 +240,14 @@ export const edges = {
|
||||
'14__1',
|
||||
'14__2',
|
||||
'14__10',
|
||||
'15__16',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: NodeGraphDataFrameFieldNames.source,
|
||||
type: FieldType.string,
|
||||
config: {},
|
||||
values: [0, 5, 6, 6, 6, 6, 6, 6, 8, 10, 11, 11, 12, 13, 14, 14, 14],
|
||||
values: [0, 5, 6, 6, 6, 6, 6, 6, 8, 10, 11, 11, 12, 13, 14, 14, 14, 15],
|
||||
},
|
||||
{
|
||||
name: 'sourceName',
|
||||
@ -266,13 +271,14 @@ export const edges = {
|
||||
'products',
|
||||
'products',
|
||||
'products',
|
||||
'test-client',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: NodeGraphDataFrameFieldNames.target,
|
||||
type: FieldType.string,
|
||||
config: {},
|
||||
values: [2, 8, 0, 5, 9, 2, 14, 4, 3, 7, 0, 6, 6, 11, 1, 2, 10],
|
||||
values: [2, 8, 0, 5, 9, 2, 14, 4, 3, 7, 0, 6, 6, 11, 1, 2, 10, 16],
|
||||
},
|
||||
{
|
||||
name: 'targetName',
|
||||
@ -296,6 +302,7 @@ export const edges = {
|
||||
'products',
|
||||
'customers',
|
||||
'shipping',
|
||||
'test-api',
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -320,6 +327,7 @@ export const edges = {
|
||||
'Success 100.00%',
|
||||
'Success 100.00%',
|
||||
'Faults 9.30%',
|
||||
'Faults 9.30%',
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -330,6 +338,7 @@ export const edges = {
|
||||
50.56317154501667, 125.77222222222223, 0.03333333333333333, 137.59722222222223, 0.022222222222222223,
|
||||
299.96666666666664, 162.33055555555555, 0.005555555555555556, 125.64444444444445, 30.582348853370394,
|
||||
50.51111111111111, 299.9166666666667, 0.100021510002151, 165.675, 682.4, 162.33055555555555, 30.558333333333334,
|
||||
30.558333333333334,
|
||||
],
|
||||
},
|
||||
],
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,4 @@
|
||||
import { CorsWorker as Worker } from 'app/core/utils/CorsWorker';
|
||||
|
||||
export const createWorker = () => new Worker(new URL('./layout.worker.js', import.meta.url));
|
||||
export const createMsaglWorker = () => new Worker(new URL('./layoutMsagl.worker.js', import.meta.url));
|
||||
|
@ -3,8 +3,9 @@ import { useUnmount } from 'react-use';
|
||||
import useMountedState from 'react-use/lib/useMountedState';
|
||||
|
||||
import { Field } from '@grafana/data';
|
||||
import { config as grafanaConfig } from '@grafana/runtime';
|
||||
|
||||
import { createWorker } from './createLayoutWorker';
|
||||
import { createWorker, createMsaglWorker } from './createLayoutWorker';
|
||||
import { EdgeDatum, EdgeDatumLayout, NodeDatum } from './types';
|
||||
import { useNodeLimit } from './useNodeLimit';
|
||||
import { graphBounds } from './utils';
|
||||
@ -83,11 +84,14 @@ export function useLayout(
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
// Layered layout is better but also more expensive, so we switch to default force based layout for bigger graphs.
|
||||
const layoutType =
|
||||
grafanaConfig.featureToggles.nodeGraphDotLayout && rawNodes.length <= 500 ? 'layered' : 'default';
|
||||
|
||||
// This is async but as I wanted to still run the sync grid layout and you cannot return promise from effect so
|
||||
setLoading(true);
|
||||
// This is async but as I wanted to still run the sync grid layout, and you cannot return promise from effect so
|
||||
// having callback seems ok here.
|
||||
const cancel = defaultLayout(rawNodes, rawEdges, ({ nodes, edges }) => {
|
||||
const cancel = layout(rawNodes, rawEdges, layoutType, ({ nodes, edges }) => {
|
||||
if (isMounted()) {
|
||||
setNodesGraph(nodes);
|
||||
setEdgesGraph(edges as EdgeDatumLayout[]);
|
||||
@ -146,12 +150,14 @@ export function useLayout(
|
||||
* Wraps the layout code in a worker as it can take long and we don't want to block the main thread.
|
||||
* Returns a cancel function to terminate the worker.
|
||||
*/
|
||||
function defaultLayout(
|
||||
function layout(
|
||||
nodes: NodeDatum[],
|
||||
edges: EdgeDatum[],
|
||||
engine: 'default' | 'layered',
|
||||
done: (data: { nodes: NodeDatum[]; edges: EdgeDatum[] }) => void
|
||||
) {
|
||||
const worker = createWorker();
|
||||
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
|
||||
|
250
public/app/plugins/panel/nodeGraph/layoutMsagl.worker.js
Normal file
250
public/app/plugins/panel/nodeGraph/layoutMsagl.worker.js
Normal file
@ -0,0 +1,250 @@
|
||||
import {
|
||||
GeomGraph,
|
||||
GeomEdge,
|
||||
GeomNode,
|
||||
Point,
|
||||
CurveFactory,
|
||||
SugiyamaLayoutSettings,
|
||||
LayerDirectionEnum,
|
||||
layoutGeomGraph,
|
||||
} from '@msagl/core';
|
||||
import { parseDot } from '@msagl/parser';
|
||||
|
||||
addEventListener('message', async (event) => {
|
||||
const { nodes, edges, config } = event.data;
|
||||
const [newNodes, newEdges] = layout(nodes, edges, config);
|
||||
postMessage({ nodes: newNodes, edges: newEdges });
|
||||
});
|
||||
|
||||
/**
|
||||
* Use d3 force layout to lay the nodes in a sensible way. This function modifies the nodes adding the x,y positions
|
||||
* and also fills in node references in edges instead of node ids.
|
||||
*/
|
||||
export function layout(nodes, edges) {
|
||||
const { mappedEdges, DOTToIdMap } = createMappings(nodes, edges);
|
||||
|
||||
const dot = edgesToDOT(mappedEdges);
|
||||
const graph = parseDot(dot);
|
||||
const geomGraph = new GeomGraph(graph);
|
||||
for (const e of graph.deepEdges) {
|
||||
new GeomEdge(e);
|
||||
}
|
||||
|
||||
for (const n of graph.nodesBreadthFirst) {
|
||||
const gn = new GeomNode(n);
|
||||
gn.boundaryCurve = CurveFactory.mkCircle(50, new Point(0, 0));
|
||||
}
|
||||
geomGraph.layoutSettings = new SugiyamaLayoutSettings();
|
||||
geomGraph.layoutSettings.layerDirection = LayerDirectionEnum.LR;
|
||||
geomGraph.layoutSettings.LayerSeparation = 60;
|
||||
geomGraph.layoutSettings.commonSettings.NodeSeparation = 40;
|
||||
layoutGeomGraph(geomGraph);
|
||||
|
||||
const nodesMap = {};
|
||||
for (const node of geomGraph.nodesBreadthFirst) {
|
||||
nodesMap[DOTToIdMap[node.id]] = {
|
||||
obj: node,
|
||||
};
|
||||
}
|
||||
|
||||
for (const node of nodes) {
|
||||
nodesMap[node.id] = {
|
||||
...nodesMap[node.id],
|
||||
datum: {
|
||||
...node,
|
||||
x: nodesMap[node.id].obj.center.x,
|
||||
y: nodesMap[node.id].obj.center.y,
|
||||
},
|
||||
};
|
||||
}
|
||||
const edgesMapped = edges.map((e) => {
|
||||
return {
|
||||
...e,
|
||||
source: nodesMap[e.source].datum,
|
||||
target: nodesMap[e.target].datum,
|
||||
};
|
||||
});
|
||||
|
||||
// This section checks if there are separate disjointed subgraphs. If so it groups nodes for each and then aligns
|
||||
// each subgraph, so it starts on a single vertical line. Otherwise, they are laid out randomly from left to right.
|
||||
const subgraphs = [];
|
||||
for (const e of edgesMapped) {
|
||||
const sourceGraph = subgraphs.find((g) => g.nodes.has(e.source));
|
||||
const targetGraph = subgraphs.find((g) => g.nodes.has(e.target));
|
||||
if (sourceGraph && targetGraph) {
|
||||
// if the node sets are not the same we merge them
|
||||
if (sourceGraph !== targetGraph) {
|
||||
targetGraph.nodes.forEach(sourceGraph.nodes.add, sourceGraph.nodes);
|
||||
subgraphs.splice(subgraphs.indexOf(targetGraph), 1);
|
||||
sourceGraph.top = Math.min(sourceGraph.top, targetGraph.top);
|
||||
sourceGraph.bottom = Math.max(sourceGraph.bottom, targetGraph.bottom);
|
||||
sourceGraph.left = Math.min(sourceGraph.left, targetGraph.left);
|
||||
sourceGraph.right = Math.max(sourceGraph.right, targetGraph.right);
|
||||
}
|
||||
// if the sets are the same nothing to do.
|
||||
} else if (sourceGraph) {
|
||||
sourceGraph.nodes.add(e.target);
|
||||
sourceGraph.top = Math.min(sourceGraph.top, e.target.y);
|
||||
sourceGraph.bottom = Math.max(sourceGraph.bottom, e.target.y);
|
||||
sourceGraph.left = Math.min(sourceGraph.left, e.target.x);
|
||||
sourceGraph.right = Math.max(sourceGraph.right, e.target.x);
|
||||
} else if (targetGraph) {
|
||||
targetGraph.nodes.add(e.source);
|
||||
targetGraph.top = Math.min(targetGraph.top, e.source.y);
|
||||
targetGraph.bottom = Math.max(targetGraph.bottom, e.source.y);
|
||||
targetGraph.left = Math.min(targetGraph.left, e.source.x);
|
||||
targetGraph.right = Math.max(targetGraph.right, e.source.x);
|
||||
} else {
|
||||
// we don't have these nodes
|
||||
subgraphs.push({
|
||||
top: Math.min(e.source.y, e.target.y),
|
||||
bottom: Math.max(e.source.y, e.target.y),
|
||||
left: Math.min(e.source.x, e.target.x),
|
||||
right: Math.max(e.source.x, e.target.x),
|
||||
nodes: new Set([e.source, e.target]),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let top = 0;
|
||||
let left = 0;
|
||||
for (const g of subgraphs) {
|
||||
if (top === 0) {
|
||||
top = g.bottom + 200;
|
||||
left = g.left;
|
||||
} else {
|
||||
const topDiff = top - g.top;
|
||||
const leftDiff = left - g.left;
|
||||
for (const n of g.nodes) {
|
||||
n.x += leftDiff;
|
||||
n.y += topDiff;
|
||||
}
|
||||
top += g.bottom - g.top + 200;
|
||||
}
|
||||
}
|
||||
|
||||
const finalNodes = Object.values(nodesMap).map((v) => v.datum);
|
||||
|
||||
centerNodes(finalNodes);
|
||||
return [finalNodes, edgesMapped];
|
||||
}
|
||||
|
||||
// We create mapping because the DOT language we use later to create the graph doesn't support arbitrary IDs. So we
|
||||
// map our IDs to just an index of the node so the IDs are safe for the DOT parser and also create and inverse mapping
|
||||
// for quick lookup.
|
||||
function createMappings(nodes, edges) {
|
||||
// Edges where the source and target IDs are the indexes we use for layout
|
||||
const mappedEdges = [];
|
||||
|
||||
// Key is an ID of the node and value is new ID which is just iteration index
|
||||
const idToDOTMap = {};
|
||||
|
||||
// Key is an iteration index and value is actual ID of the node
|
||||
const DOTToIdMap = {};
|
||||
|
||||
// Crate the maps both ways
|
||||
let index = 0;
|
||||
for (const edge of edges) {
|
||||
if (!idToDOTMap[edge.source]) {
|
||||
idToDOTMap[edge.source] = index.toString(10);
|
||||
DOTToIdMap[index.toString(10)] = edge.source;
|
||||
index++;
|
||||
}
|
||||
|
||||
if (!idToDOTMap[edge.target]) {
|
||||
idToDOTMap[edge.target] = index.toString(10);
|
||||
DOTToIdMap[index.toString(10)] = edge.target;
|
||||
index++;
|
||||
}
|
||||
mappedEdges.push({ source: idToDOTMap[edge.source], target: idToDOTMap[edge.target] });
|
||||
}
|
||||
|
||||
return {
|
||||
mappedEdges,
|
||||
DOTToIdMap,
|
||||
};
|
||||
}
|
||||
|
||||
function toDOT(edges, graphAttr = '', edgeAttr = '') {
|
||||
let dot = `
|
||||
digraph G {
|
||||
${graphAttr}
|
||||
`;
|
||||
for (const edge of edges) {
|
||||
dot += edge.source + '->' + edge.target + ' ' + edgeAttr + '\n';
|
||||
}
|
||||
dot += nodesDOT(edges);
|
||||
dot += '}';
|
||||
return dot;
|
||||
}
|
||||
|
||||
function edgesToDOT(edges) {
|
||||
return toDOT(edges, 'rankdir="LR"; TBbalance="min"', '[ minlen=3 ]');
|
||||
}
|
||||
|
||||
function nodesDOT(edges) {
|
||||
let dot = '';
|
||||
const visitedNodes = new Set();
|
||||
// TODO: height/width for default sizing but nodes can have variable size now
|
||||
const attr = '[fixedsize=true, width=1.2, height=1.7] \n';
|
||||
for (const edge of edges) {
|
||||
if (!visitedNodes.has(edge.source)) {
|
||||
dot += edge.source + attr;
|
||||
}
|
||||
if (!visitedNodes.has(edge.target)) {
|
||||
dot += edge.target + attr;
|
||||
}
|
||||
}
|
||||
return dot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that the center of the graph based on its bound is in 0, 0 coordinates.
|
||||
* Modifies the nodes directly.
|
||||
*/
|
||||
function centerNodes(nodes) {
|
||||
const bounds = graphBounds(nodes);
|
||||
for (let node of nodes) {
|
||||
node.x = node.x - bounds.center.x;
|
||||
node.y = node.y - bounds.center.y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bounds of the graph meaning the extent of the nodes in all directions.
|
||||
*/
|
||||
function graphBounds(nodes) {
|
||||
if (nodes.length === 0) {
|
||||
return { top: 0, right: 0, bottom: 0, left: 0, center: { x: 0, y: 0 } };
|
||||
}
|
||||
|
||||
const bounds = nodes.reduce(
|
||||
(acc, node) => {
|
||||
if (node.x > acc.right) {
|
||||
acc.right = node.x;
|
||||
}
|
||||
if (node.x < acc.left) {
|
||||
acc.left = node.x;
|
||||
}
|
||||
if (node.y > acc.bottom) {
|
||||
acc.bottom = node.y;
|
||||
}
|
||||
if (node.y < acc.top) {
|
||||
acc.top = node.y;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ top: Infinity, right: -Infinity, bottom: -Infinity, left: Infinity }
|
||||
);
|
||||
|
||||
const y = bounds.top + (bounds.bottom - bounds.top) / 2;
|
||||
const x = bounds.left + (bounds.right - bounds.left) / 2;
|
||||
|
||||
return {
|
||||
...bounds,
|
||||
center: {
|
||||
x,
|
||||
y,
|
||||
},
|
||||
};
|
||||
}
|
@ -35,7 +35,8 @@ def yarn_install_step():
|
||||
"name": "yarn-install",
|
||||
"image": images["node"],
|
||||
"commands": [
|
||||
"apt-get update -yq && apt-get install python -y",
|
||||
# Python is needed to build `esfx`, which is needed by `msagl`
|
||||
"apk add --no-cache --virtual .gyp python3 make g++ && ln -sf /usr/bin/python3 /usr/bin/python",
|
||||
"yarn install --immutable",
|
||||
],
|
||||
"depends_on": [],
|
||||
|
@ -127,6 +127,13 @@ module.exports = {
|
||||
test: /(unicons|mono|custom)[\\/].*\.svg$/,
|
||||
type: 'asset/source',
|
||||
},
|
||||
{
|
||||
// Required for msagl library (used in Nodegraph panel) to work
|
||||
test: /\.m?js$/,
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// https://webpack.js.org/plugins/split-chunks-plugin/#split-chunks-example-3
|
||||
|
281
yarn.lock
281
yarn.lock
@ -2742,6 +2742,98 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esfx/collection-core@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "@esfx/collection-core@npm:1.0.0"
|
||||
checksum: 10/7e26d89953a201a6c965af18516e48ea0628ca2674c56c51368d61a3dc47b7d47b05984dc2392fecbbda4bb7134e735da0a01445818dd843a2b54e5b2010b4a2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esfx/collections-hashmap@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "@esfx/collections-hashmap@npm:1.0.2"
|
||||
dependencies:
|
||||
"@esfx/collection-core": "npm:^1.0.0"
|
||||
"@esfx/equatable": "npm:^1.0.2"
|
||||
checksum: 10/caef6a231ad6bb9aee06cce1a5da4918c07868be9732c96cf268762d1e7cf06365409d59c2e4d47acd3ad81022a5e552f6c1b75660ecdb2eb68265b70f67749d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esfx/collections-hashset@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "@esfx/collections-hashset@npm:1.0.2"
|
||||
dependencies:
|
||||
"@esfx/collection-core": "npm:^1.0.0"
|
||||
"@esfx/equatable": "npm:^1.0.2"
|
||||
checksum: 10/75250e1682f42af85ff5e6b837e02faf27977515f52551857fcb28854025bc02d019801f062b92eef25b1781d1686453cb18726fa1468ed1c41eabe517860a4f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esfx/collections-linkedlist@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "@esfx/collections-linkedlist@npm:1.0.2"
|
||||
dependencies:
|
||||
"@esfx/collection-core": "npm:^1.0.0"
|
||||
"@esfx/equatable": "npm:^1.0.2"
|
||||
checksum: 10/ddaafab0d90a8588b4292639e194217dfa5403fe4e6acc375b235ba1736a69dd43893b507f9c4aaae04377d66b23a1da0bee063be578940a03124cf0a6372096
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esfx/collections-multimap@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "@esfx/collections-multimap@npm:1.0.2"
|
||||
dependencies:
|
||||
"@esfx/collection-core": "npm:^1.0.0"
|
||||
"@esfx/collections-hashmap": "npm:^1.0.2"
|
||||
"@esfx/collections-hashset": "npm:^1.0.2"
|
||||
"@esfx/equatable": "npm:^1.0.2"
|
||||
checksum: 10/ba6aebb8c737d9f6d9c6a06edd6904bf731c0e0eddc4aaa1fd67cf074f04441c368feee7a37af7dcab658602959d75002cc314283accf37dc1789c7a81d65914
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esfx/collections-sortedmap@npm:^1.0.0, @esfx/collections-sortedmap@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "@esfx/collections-sortedmap@npm:1.0.2"
|
||||
dependencies:
|
||||
"@esfx/collection-core": "npm:^1.0.0"
|
||||
"@esfx/equatable": "npm:^1.0.2"
|
||||
checksum: 10/428f752583b5fa78e57137952f4925ca8f54daa3816a34b699adcf2206e816f3771c0bf7cfcbeafcf36e4036df200ab90bd7e6d1067bc7dc04e1dcc10f24706a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esfx/collections-sortedset@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "@esfx/collections-sortedset@npm:1.0.2"
|
||||
dependencies:
|
||||
"@esfx/collection-core": "npm:^1.0.0"
|
||||
"@esfx/equatable": "npm:^1.0.2"
|
||||
checksum: 10/3f0f1fac9a861bcad3965ffa23b9519b92b6aa54bd163e72a4220cc699a7a0f8fa120deb05152fdc3a2071bd543103fe0de74760e5ce3366e3b79874b2749b1d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esfx/collections@npm:^1.0.0":
|
||||
version: 1.0.2
|
||||
resolution: "@esfx/collections@npm:1.0.2"
|
||||
dependencies:
|
||||
"@esfx/collections-hashmap": "npm:^1.0.2"
|
||||
"@esfx/collections-hashset": "npm:^1.0.2"
|
||||
"@esfx/collections-linkedlist": "npm:^1.0.2"
|
||||
"@esfx/collections-multimap": "npm:^1.0.2"
|
||||
"@esfx/collections-sortedmap": "npm:^1.0.2"
|
||||
"@esfx/collections-sortedset": "npm:^1.0.2"
|
||||
checksum: 10/3f7f8d908e0e4c7ec31f48b5f52a44d3ba00fa74dc20aa2fdb811a69612ff477b2c96a288970a29073c8a7281d1cdda58614272dd1ce7d2a88305a5f98b33f9f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esfx/equatable@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "@esfx/equatable@npm:1.0.2"
|
||||
dependencies:
|
||||
"@mapbox/node-pre-gyp": "npm:^1.0.9"
|
||||
checksum: 10/54e9de6092116699fb206676c5afd7b513e7b3e3fa03d08b8ee84a6f3b4d0057346606ceb2745f47cb1e73be28e77ca3fa403f1dcc66151a1f7a2e9cb48f7df3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0":
|
||||
version: 4.4.0
|
||||
resolution: "@eslint-community/eslint-utils@npm:4.4.0"
|
||||
@ -3070,12 +3162,14 @@ __metadata:
|
||||
"@grafana/ui": "npm:10.4.0-pre"
|
||||
"@testing-library/react": "npm:14.2.0"
|
||||
"@testing-library/user-event": "npm:14.5.2"
|
||||
"@types/d3-random": "npm:^3.0.2"
|
||||
"@types/jest": "npm:29.5.11"
|
||||
"@types/lodash": "npm:4.14.202"
|
||||
"@types/node": "npm:20.11.13"
|
||||
"@types/react": "npm:18.2.48"
|
||||
"@types/testing-library__jest-dom": "npm:5.14.9"
|
||||
"@types/uuid": "npm:9.0.8"
|
||||
d3-random: "npm:^3.0.1"
|
||||
lodash: "npm:4.17.21"
|
||||
micro-memoize: "npm:^4.1.2"
|
||||
react: "npm:18.2.0"
|
||||
@ -4618,6 +4712,25 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mapbox/node-pre-gyp@npm:^1.0.9":
|
||||
version: 1.0.11
|
||||
resolution: "@mapbox/node-pre-gyp@npm:1.0.11"
|
||||
dependencies:
|
||||
detect-libc: "npm:^2.0.0"
|
||||
https-proxy-agent: "npm:^5.0.0"
|
||||
make-dir: "npm:^3.1.0"
|
||||
node-fetch: "npm:^2.6.7"
|
||||
nopt: "npm:^5.0.0"
|
||||
npmlog: "npm:^5.0.1"
|
||||
rimraf: "npm:^3.0.2"
|
||||
semver: "npm:^7.3.5"
|
||||
tar: "npm:^6.1.11"
|
||||
bin:
|
||||
node-pre-gyp: bin/node-pre-gyp
|
||||
checksum: 10/59529a2444e44fddb63057152452b00705aa58059079191126c79ac1388ae4565625afa84ed4dd1bf017d1111ab6e47907f7c5192e06d83c9496f2f3e708680a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mapbox/point-geometry@npm:^0.1.0":
|
||||
version: 0.1.0
|
||||
resolution: "@mapbox/point-geometry@npm:0.1.0"
|
||||
@ -4677,6 +4790,42 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@msagl/core@npm:^1.1.16, @msagl/core@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@msagl/core@npm:1.1.17"
|
||||
dependencies:
|
||||
"@esfx/collections": "npm:^1.0.0"
|
||||
"@esfx/collections-sortedmap": "npm:^1.0.0"
|
||||
queue-typescript: "npm:^1.0.1"
|
||||
reliable-random: "npm:^0.0.1"
|
||||
stack-typescript: "npm:^1.0.4"
|
||||
typescript-string-operations: "npm:^1.4.1"
|
||||
checksum: 10/6a0c83911d0802cf4372a9d976017117d9ecbec8e8bda8850fb58dec76558c0453290a51ad1c55b451ae70deeb3a57b1623bcbf7d054b3bfdacb9ea2a8190d5a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@msagl/drawing@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@msagl/drawing@npm:1.1.17"
|
||||
dependencies:
|
||||
"@msagl/core": "npm:^1.1.17"
|
||||
checksum: 10/7a3f38cf2cafeda292cbd7c3f78d55f39f11a5923c3675ecd5a51d362660461fca50b42e5c9aa58431ad34f650fff0443be99670947c6fcd6f07701057a08c52
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@msagl/parser@npm:^1.1.16":
|
||||
version: 1.1.17
|
||||
resolution: "@msagl/parser@npm:1.1.17"
|
||||
dependencies:
|
||||
"@msagl/core": "npm:^1.1.17"
|
||||
"@msagl/drawing": "npm:^1.1.17"
|
||||
"@types/parse-color": "npm:^1.0.1"
|
||||
dotparser: "npm:^1.1.1"
|
||||
parse-color: "npm:^1.0.0"
|
||||
checksum: 10/eae9c0a02410177b9c8f73edcad3c9e4610a31a972b3c58a870fe1db7c75b857304a87b82e8b7f9d6afedf91be73969856626ebb0f3fdae0d47c4e62da805ca1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mswjs/cookies@npm:^0.2.2":
|
||||
version: 0.2.2
|
||||
resolution: "@mswjs/cookies@npm:0.2.2"
|
||||
@ -8321,6 +8470,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-random@npm:^3.0.2":
|
||||
version: 3.0.3
|
||||
resolution: "@types/d3-random@npm:3.0.3"
|
||||
checksum: 10/2c126dda6846f6c7e02c9123a30b4cdf27f3655d19b78456bbb330fbac27acceeeb987318055d3964dba8e6450377ff737db91d81f27c81ca6f4522c9b994ef2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-scale-chromatic@npm:*, @types/d3-scale-chromatic@npm:3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "@types/d3-scale-chromatic@npm:3.0.3"
|
||||
@ -8990,6 +9146,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/parse-color@npm:^1.0.1":
|
||||
version: 1.0.3
|
||||
resolution: "@types/parse-color@npm:1.0.3"
|
||||
checksum: 10/d9c822f820f277502cc58a4c52f5758980e6972c9a4710df515ec93dc5dde05e8e3da6210ea6514035d49c1bf36a3f720c0e5ec9c214bdc0b8c3fdb9240e36ea
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/parse-json@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@types/parse-json@npm:4.0.0"
|
||||
@ -10792,6 +10955,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"are-we-there-yet@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "are-we-there-yet@npm:2.0.0"
|
||||
dependencies:
|
||||
delegates: "npm:^1.0.0"
|
||||
readable-stream: "npm:^3.6.0"
|
||||
checksum: 10/ea6f47d14fc33ae9cbea3e686eeca021d9d7b9db83a306010dd04ad5f2c8b7675291b127d3fcbfcbd8fec26e47b3324ad5b469a6cc3733a582f2fe4e12fc6756
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"are-we-there-yet@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "are-we-there-yet@npm:3.0.0"
|
||||
@ -12535,6 +12708,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"color-convert@npm:~0.5.0":
|
||||
version: 0.5.3
|
||||
resolution: "color-convert@npm:0.5.3"
|
||||
checksum: 10/00dc4256c07ed8760d7bbba234ff969c139eb964fe165853696852001002695c492e327d83ddb7a8cad8d27b49fa543d001328928c12474ee8ecb335bf5f2eb4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"color-name@npm:1.1.3":
|
||||
version: 1.1.3
|
||||
resolution: "color-name@npm:1.1.3"
|
||||
@ -12549,7 +12729,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"color-support@npm:^1.1.3":
|
||||
"color-support@npm:^1.1.2, color-support@npm:^1.1.3":
|
||||
version: 1.1.3
|
||||
resolution: "color-support@npm:1.1.3"
|
||||
bin:
|
||||
@ -13848,7 +14028,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"d3-random@npm:3":
|
||||
"d3-random@npm:3, d3-random@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "d3-random@npm:3.0.1"
|
||||
checksum: 10/9f41d6ca3a1826cea8d88392917b5039504337d442a4d1357c870fa3031701e60209a2689a6ddae7df8fca824383d038c957eb545bc49a7428c71aaf3b11f56f
|
||||
@ -14380,6 +14560,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"detect-libc@npm:^2.0.0":
|
||||
version: 2.0.2
|
||||
resolution: "detect-libc@npm:2.0.2"
|
||||
checksum: 10/6118f30c0c425b1e56b9d2609f29bec50d35a6af0b762b6ad127271478f3bbfda7319ce869230cf1a351f2b219f39332cde290858553336d652c77b970f15de8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"detect-newline@npm:^3.0.0":
|
||||
version: 3.1.0
|
||||
resolution: "detect-newline@npm:3.1.0"
|
||||
@ -14714,6 +14901,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dotparser@npm:^1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "dotparser@npm:1.1.1"
|
||||
checksum: 10/a66a02f5add34b53356ee18b418589ccf055ef6ffec073e91230f5f34cb5ac9579ce20e843b9a3213f42a26bd89fce9232b9364cc08844dade575095dc40d88a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"duplexer@npm:^0.1.1, duplexer@npm:^0.1.2":
|
||||
version: 0.1.2
|
||||
resolution: "duplexer@npm:0.1.2"
|
||||
@ -16808,6 +17002,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"gauge@npm:^3.0.0":
|
||||
version: 3.0.2
|
||||
resolution: "gauge@npm:3.0.2"
|
||||
dependencies:
|
||||
aproba: "npm:^1.0.3 || ^2.0.0"
|
||||
color-support: "npm:^1.1.2"
|
||||
console-control-strings: "npm:^1.0.0"
|
||||
has-unicode: "npm:^2.0.1"
|
||||
object-assign: "npm:^4.1.1"
|
||||
signal-exit: "npm:^3.0.0"
|
||||
string-width: "npm:^4.2.3"
|
||||
strip-ansi: "npm:^6.0.1"
|
||||
wide-align: "npm:^1.1.2"
|
||||
checksum: 10/46df086451672a5fecd58f7ec86da74542c795f8e00153fbef2884286ce0e86653c3eb23be2d0abb0c4a82b9b2a9dec3b09b6a1cf31c28085fa0376599a26589
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"gauge@npm:^4.0.3":
|
||||
version: 4.0.4
|
||||
resolution: "gauge@npm:4.0.4"
|
||||
@ -17433,6 +17644,8 @@ __metadata:
|
||||
"@locker/near-membrane-dom": "npm:0.13.3"
|
||||
"@locker/near-membrane-shared": "npm:0.13.3"
|
||||
"@locker/near-membrane-shared-dom": "npm:0.13.3"
|
||||
"@msagl/core": "npm:^1.1.16"
|
||||
"@msagl/parser": "npm:^1.1.16"
|
||||
"@opentelemetry/api": "npm:1.7.0"
|
||||
"@opentelemetry/exporter-collector": "npm:0.25.0"
|
||||
"@opentelemetry/semantic-conventions": "npm:1.21.0"
|
||||
@ -20870,6 +21083,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"linked-list-typescript@npm:^1.0.11":
|
||||
version: 1.0.15
|
||||
resolution: "linked-list-typescript@npm:1.0.15"
|
||||
checksum: 10/91c87ab00fe4bb9850f169adc62b811ae7e91a743bac1a9fc969a815f20ddd0fb285f84a4d31e98900512234f59297f7beef6b076dd382cf879a319c7a14c68b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"listr2@npm:^3.8.3":
|
||||
version: 3.14.0
|
||||
resolution: "listr2@npm:3.14.0"
|
||||
@ -21266,7 +21486,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"make-dir@npm:^3.0.0, make-dir@npm:^3.0.2":
|
||||
"make-dir@npm:^3.0.0, make-dir@npm:^3.0.2, make-dir@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "make-dir@npm:3.1.0"
|
||||
dependencies:
|
||||
@ -22680,6 +22900,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"npmlog@npm:^5.0.1":
|
||||
version: 5.0.1
|
||||
resolution: "npmlog@npm:5.0.1"
|
||||
dependencies:
|
||||
are-we-there-yet: "npm:^2.0.0"
|
||||
console-control-strings: "npm:^1.1.0"
|
||||
gauge: "npm:^3.0.0"
|
||||
set-blocking: "npm:^2.0.0"
|
||||
checksum: 10/f42c7b9584cdd26a13c41a21930b6f5912896b6419ab15be88cc5721fc792f1c3dd30eb602b26ae08575694628ba70afdcf3675d86e4f450fc544757e52726ec
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"npmlog@npm:^6.0.0, npmlog@npm:^6.0.2":
|
||||
version: 6.0.2
|
||||
resolution: "npmlog@npm:6.0.2"
|
||||
@ -23330,6 +23562,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"parse-color@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "parse-color@npm:1.0.0"
|
||||
dependencies:
|
||||
color-convert: "npm:~0.5.0"
|
||||
checksum: 10/9f988462af30929acbbce135286ce20dc2fb8efe48fd0693ed8bfd7da3a60e2465cffff6d35047fe94fd7863b43159bf41b377a9982939b7c65fdba1e30af24a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"parse-entities@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "parse-entities@npm:2.0.0"
|
||||
@ -24582,6 +24823,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"queue-typescript@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "queue-typescript@npm:1.0.1"
|
||||
dependencies:
|
||||
linked-list-typescript: "npm:^1.0.11"
|
||||
checksum: 10/403e38579902af1ac533911876c31dd1fd62e94d352263b9ed9786db3677494b15236ad43182178e5103915605a8b07da5780d84b6cc075d7f54cfb056091489
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"quick-lru@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "quick-lru@npm:4.0.1"
|
||||
@ -26216,6 +26466,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"reliable-random@npm:^0.0.1":
|
||||
version: 0.0.1
|
||||
resolution: "reliable-random@npm:0.0.1"
|
||||
checksum: 10/dbd1fe95dcea9004a0b64540a29c72832c605957a06d26eb762e6600835981ee4949ce7e15b303dcc5c44e9a976357d560f4b6f625fdfaf17f611c65a0150975
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"remark-external-links@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "remark-external-links@npm:8.0.0"
|
||||
@ -27778,6 +28035,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"stack-typescript@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "stack-typescript@npm:1.0.4"
|
||||
dependencies:
|
||||
linked-list-typescript: "npm:^1.0.11"
|
||||
checksum: 10/a3211a70b01ab4ece788a9c0c4612a132be4019427dc1d9daa7f289b5ab495d4a30cdc947888858eb9225b4c55428530ae60526c56b141f843047db1d878114e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"stack-utils@npm:^2.0.3":
|
||||
version: 2.0.5
|
||||
resolution: "stack-utils@npm:2.0.5"
|
||||
@ -29348,6 +29614,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript-string-operations@npm:^1.4.1":
|
||||
version: 1.5.0
|
||||
resolution: "typescript-string-operations@npm:1.5.0"
|
||||
checksum: 10/a884c4241fc45b9cceb2744c20fb229e6dfcb48f96afd68ef2d67fda1e4586c96d20fd0343b57c14ac33b2ebe000aa7524a4932424aea3077fe9e692555384f2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@npm:4.8.4, typescript@npm:^4.2.4":
|
||||
version: 4.8.4
|
||||
resolution: "typescript@npm:4.8.4"
|
||||
@ -30648,7 +30921,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wide-align@npm:^1.1.0, wide-align@npm:^1.1.5":
|
||||
"wide-align@npm:^1.1.0, wide-align@npm:^1.1.2, wide-align@npm:^1.1.5":
|
||||
version: 1.1.5
|
||||
resolution: "wide-align@npm:1.1.5"
|
||||
dependencies:
|
||||
|
Loading…
Reference in New Issue
Block a user