mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NodeGraph: Detect dataframes more accurately based on fields (#47213)
* NodeGraph: Detect dataframes more accurately based on fields * Make get fields case insensitive * Update node graph docs
This commit is contained in:
parent
6db470c11e
commit
939a778111
@ -88,14 +88,14 @@ Required fields:
|
||||
|
||||
Optional fields:
|
||||
|
||||
| Field name | Type | Description |
|
||||
| ------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| title | string | Name of the node visible in just under the node. |
|
||||
| subTitle | string | Additional, name, type or other identifier that will be shown right under the title. |
|
||||
| mainStat | string/number | First stat shown inside the node itself. Can be either string in which case the value will be shown as it is or it can be a number in which case any unit associated with that field will be also shown. |
|
||||
| secondaryStat | string/number | Same as mainStat but shown right under it inside the node. |
|
||||
| arc\_\_\* | number | Any field prefixed with `arc__` will be used to create the color circle around the node. All values in these fields should add up to 1. You can specify color using `config.color.fixedColor`. |
|
||||
| detail\_\_\* | string/number | Any field prefixed with `detail__` will be shown in the header of context menu when clicked on the node. Use `config.displayName` for more human readable label. |
|
||||
| Field name | Type | Description |
|
||||
| ------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| title | string | Name of the node visible in just under the node. |
|
||||
| subtitle | string | Additional, name, type or other identifier shown under the title. |
|
||||
| mainstat | string/number | First stat shown inside the node itself. It can either be a string showing the value as is or a number. If it is a number, any unit associated with that field is also shown. |
|
||||
| secondarystat | string/number | Same as mainStat, but shown under it inside the node. |
|
||||
| arc\_\_\* | number | Any field prefixed with `arc__` will be used to create the color circle around the node. All values in these fields should add up to 1. You can specify color using `config.color.fixedColor`. |
|
||||
| detail\_\_\* | string/number | Any field prefixed with `detail__` will be shown in the header of context menu when clicked on the node. Use `config.displayName` for more human readable label. |
|
||||
|
||||
### Edge parameters
|
||||
|
||||
@ -109,8 +109,8 @@ Required fields:
|
||||
|
||||
Optional fields:
|
||||
|
||||
| Field name | Type | Description |
|
||||
| ------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| mainStat | string/number | First stat shown in the overlay when hovering over the edge. Can be either string in which case the value will be shown as it is or it can be a number in which case any unit associated with that field will be also shown |
|
||||
| secondaryStat | string/number | Same as mainStat but shown right under it. |
|
||||
| detail\_\_\* | string/number | Any field prefixed with `detail__` will be shown in the header of context menu when clicked on the edge. Use `config.displayName` for more human readable label. |
|
||||
| Field name | Type | Description |
|
||||
| ------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| mainstat | string/number | First stat shown in the overlay when hovering over the edge. It can be a string showing the value as is or it can be a number. If it is a number, any unit associated with that field is also shown |
|
||||
| secondarystat | string/number | Same as mainStat, but shown right under it. |
|
||||
| detail\_\_\* | string/number | Any field prefixed with `detail__` will be shown in the header of context menu when clicked on the edge. Use `config.displayName` for more human readable label. |
|
||||
|
@ -1,9 +1,9 @@
|
||||
export enum NodeGraphDataFrameFieldNames {
|
||||
id = 'id',
|
||||
title = 'title',
|
||||
subTitle = 'subTitle',
|
||||
mainStat = 'mainStat',
|
||||
secondaryStat = 'secondaryStat',
|
||||
subTitle = 'subtitle',
|
||||
mainStat = 'mainstat',
|
||||
secondaryStat = 'secondarystat',
|
||||
source = 'source',
|
||||
target = 'target',
|
||||
detail = 'detail__',
|
||||
|
@ -111,9 +111,9 @@ export function toNodesFrame(values: any[]) {
|
||||
return toVectors([
|
||||
{ name: 'id', values: values[0] },
|
||||
{ name: 'title', values: values[1] },
|
||||
{ name: 'subTitle', values: values[2] },
|
||||
{ name: 'mainStat', values: values[3] },
|
||||
{ name: 'secondaryStat', values: values[4] },
|
||||
{ name: 'subtitle', values: values[2] },
|
||||
{ name: 'mainstat', values: values[3] },
|
||||
{ name: 'secondarystat', values: values[4] },
|
||||
{
|
||||
name: 'color',
|
||||
config: {
|
||||
|
@ -77,9 +77,9 @@ describe('Tempo data source', () => {
|
||||
).toMatchObject([
|
||||
{ name: 'id', values: ['4322526419282105830'] },
|
||||
{ name: 'title', values: ['service'] },
|
||||
{ name: 'subTitle', values: ['store.validateQueryTimeRange'] },
|
||||
{ name: 'mainStat', values: ['14.98ms (100%)'] },
|
||||
{ name: 'secondaryStat', values: ['14.98ms (100%)'] },
|
||||
{ name: 'subtitle', values: ['store.validateQueryTimeRange'] },
|
||||
{ name: 'mainstat', values: ['14.98ms (100%)'] },
|
||||
{ name: 'secondarystat', values: ['14.98ms (100%)'] },
|
||||
{ name: 'color', values: [1.000007560204647] },
|
||||
]);
|
||||
|
||||
|
@ -13,18 +13,18 @@ describe('createGraphFrames', () => {
|
||||
expect(view.get(0)).toMatchObject({
|
||||
id: '4322526419282105830',
|
||||
title: 'loki-all',
|
||||
subTitle: 'store.validateQueryTimeRange',
|
||||
mainStat: '0ms (0.02%)',
|
||||
secondaryStat: '0ms (100%)',
|
||||
subtitle: 'store.validateQueryTimeRange',
|
||||
mainstat: '0ms (0.02%)',
|
||||
secondarystat: '0ms (100%)',
|
||||
color: 0.00021968356127648162,
|
||||
});
|
||||
|
||||
expect(view.get(29)).toMatchObject({
|
||||
id: '4450900759028499335',
|
||||
title: 'loki-all',
|
||||
subTitle: 'HTTP GET - loki_api_v1_query_range',
|
||||
mainStat: '18.21ms (100%)',
|
||||
secondaryStat: '3.22ms (17.71%)',
|
||||
subtitle: 'HTTP GET - loki_api_v1_query_range',
|
||||
mainstat: '18.21ms (100%)',
|
||||
secondarystat: '3.22ms (17.71%)',
|
||||
color: 0.17707117189595056,
|
||||
});
|
||||
|
||||
@ -43,9 +43,9 @@ describe('createGraphFrames', () => {
|
||||
expect(view.get(0)).toMatchObject({
|
||||
id: '4322526419282105830',
|
||||
title: 'loki-all',
|
||||
subTitle: 'store.validateQueryTimeRange',
|
||||
mainStat: '14.98ms (100%)',
|
||||
secondaryStat: '14.98ms (100%)',
|
||||
subtitle: 'store.validateQueryTimeRange',
|
||||
mainstat: '14.98ms (100%)',
|
||||
secondarystat: '14.98ms (100%)',
|
||||
color: 1.000007560204647,
|
||||
});
|
||||
});
|
||||
@ -75,8 +75,8 @@ describe('mapPromMetricsToServiceMap', () => {
|
||||
expect(nodes.fields).toMatchObject([
|
||||
{ name: 'id', values: new ArrayVector(['db', 'app', 'lb']) },
|
||||
{ name: 'title', values: new ArrayVector(['db', 'app', 'lb']) },
|
||||
{ name: 'mainStat', values: new ArrayVector([1000, 2000, NaN]) },
|
||||
{ name: 'secondaryStat', values: new ArrayVector([0.17, 0.33, NaN]) },
|
||||
{ name: 'mainstat', values: new ArrayVector([1000, 2000, NaN]) },
|
||||
{ name: 'secondarystat', values: new ArrayVector([0.17, 0.33, NaN]) },
|
||||
{ name: 'arc__success', values: new ArrayVector([0.8, 0.25, 1]) },
|
||||
{ name: 'arc__failed', values: new ArrayVector([0.2, 0.75, 0]) },
|
||||
]);
|
||||
@ -84,8 +84,8 @@ describe('mapPromMetricsToServiceMap', () => {
|
||||
{ name: 'id', values: new ArrayVector(['app_db', 'lb_app']) },
|
||||
{ name: 'source', values: new ArrayVector(['app', 'lb']) },
|
||||
{ name: 'target', values: new ArrayVector(['db', 'app']) },
|
||||
{ name: 'mainStat', values: new ArrayVector([10, 20]) },
|
||||
{ name: 'secondaryStat', values: new ArrayVector([1000, 2000]) },
|
||||
{ name: 'mainstat', values: new ArrayVector([10, 20]) },
|
||||
{ name: 'secondarystat', values: new ArrayVector([1000, 2000]) },
|
||||
]);
|
||||
});
|
||||
|
||||
@ -107,8 +107,8 @@ describe('mapPromMetricsToServiceMap', () => {
|
||||
expect(nodes.fields).toMatchObject([
|
||||
{ name: 'id', values: new ArrayVector(['db', 'app', 'lb']) },
|
||||
{ name: 'title', values: new ArrayVector(['db', 'app', 'lb']) },
|
||||
{ name: 'mainStat', values: new ArrayVector([1000, 2000, NaN]) },
|
||||
{ name: 'secondaryStat', values: new ArrayVector([0.17, 0.33, NaN]) },
|
||||
{ name: 'mainstat', values: new ArrayVector([1000, 2000, NaN]) },
|
||||
{ name: 'secondarystat', values: new ArrayVector([0.17, 0.33, NaN]) },
|
||||
{ name: 'arc__success', values: new ArrayVector([0, 0, 1]) },
|
||||
{ name: 'arc__failed', values: new ArrayVector([1, 1, 0]) },
|
||||
]);
|
||||
|
@ -95,9 +95,9 @@ export function toNodesFrame(values: any[]) {
|
||||
return toVectors([
|
||||
{ name: 'id', values: values[0] },
|
||||
{ name: 'title', values: values[1] },
|
||||
{ name: 'subTitle', values: values[2] },
|
||||
{ name: 'mainStat', values: values[3] },
|
||||
{ name: 'secondaryStat', values: values[4] },
|
||||
{ name: 'subtitle', values: values[2] },
|
||||
{ name: 'mainstat', values: values[3] },
|
||||
{ name: 'secondarystat', values: values[4] },
|
||||
{
|
||||
name: 'color',
|
||||
config: {
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { ArrayVector, createTheme } from '@grafana/data';
|
||||
import { makeEdgesDataFrame, makeNodesDataFrame, processNodes } from './utils';
|
||||
import { ArrayVector, createTheme, DataFrame, FieldType, MutableDataFrame } from '@grafana/data';
|
||||
import {
|
||||
getEdgeFields,
|
||||
getNodeFields,
|
||||
getNodeGraphDataFrames,
|
||||
makeEdgesDataFrame,
|
||||
makeNodesDataFrame,
|
||||
processNodes,
|
||||
} from './utils';
|
||||
|
||||
describe('processNodes', () => {
|
||||
const theme = createTheme();
|
||||
@ -62,14 +69,14 @@ describe('processNodes', () => {
|
||||
mainStat: {
|
||||
config: {},
|
||||
index: 3,
|
||||
name: 'mainStat',
|
||||
name: 'mainstat',
|
||||
type: 'number',
|
||||
values: new ArrayVector([0.1, 0.1, 0.1]),
|
||||
},
|
||||
secondaryStat: {
|
||||
config: {},
|
||||
index: 4,
|
||||
name: 'secondaryStat',
|
||||
name: 'secondarystat',
|
||||
type: 'number',
|
||||
values: new ArrayVector([2, 2, 2]),
|
||||
},
|
||||
@ -106,14 +113,14 @@ describe('processNodes', () => {
|
||||
mainStat: {
|
||||
config: {},
|
||||
index: 3,
|
||||
name: 'mainStat',
|
||||
name: 'mainstat',
|
||||
type: 'number',
|
||||
values: new ArrayVector([0.1, 0.1, 0.1]),
|
||||
},
|
||||
secondaryStat: {
|
||||
config: {},
|
||||
index: 4,
|
||||
name: 'secondaryStat',
|
||||
name: 'secondarystat',
|
||||
type: 'number',
|
||||
values: new ArrayVector([2, 2, 2]),
|
||||
},
|
||||
@ -150,14 +157,14 @@ describe('processNodes', () => {
|
||||
mainStat: {
|
||||
config: {},
|
||||
index: 3,
|
||||
name: 'mainStat',
|
||||
name: 'mainstat',
|
||||
type: 'number',
|
||||
values: new ArrayVector([0.1, 0.1, 0.1]),
|
||||
},
|
||||
secondaryStat: {
|
||||
config: {},
|
||||
index: 4,
|
||||
name: 'secondaryStat',
|
||||
name: 'secondarystat',
|
||||
type: 'number',
|
||||
values: new ArrayVector([2, 2, 2]),
|
||||
},
|
||||
@ -204,4 +211,85 @@ describe('processNodes', () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('detects dataframes correctly', () => {
|
||||
const validFrames = [
|
||||
new MutableDataFrame({
|
||||
refId: 'hasPreferredVisualisationType',
|
||||
fields: [],
|
||||
meta: {
|
||||
preferredVisualisationType: 'nodeGraph',
|
||||
},
|
||||
}),
|
||||
new MutableDataFrame({
|
||||
refId: 'hasName',
|
||||
fields: [],
|
||||
name: 'nodes',
|
||||
}),
|
||||
new MutableDataFrame({
|
||||
refId: 'nodes', // hasRefId
|
||||
fields: [],
|
||||
}),
|
||||
new MutableDataFrame({
|
||||
refId: 'hasValidNodesShape',
|
||||
fields: [{ name: 'id', type: FieldType.string }],
|
||||
}),
|
||||
new MutableDataFrame({
|
||||
refId: 'hasValidEdgesShape',
|
||||
fields: [
|
||||
{ name: 'id', type: FieldType.string },
|
||||
{ name: 'source', type: FieldType.string },
|
||||
{ name: 'target', type: FieldType.string },
|
||||
],
|
||||
}),
|
||||
];
|
||||
const invalidFrames = [
|
||||
new MutableDataFrame({
|
||||
refId: 'invalidData',
|
||||
fields: [],
|
||||
}),
|
||||
];
|
||||
const frames = [...validFrames, ...invalidFrames];
|
||||
|
||||
const nodeGraphFrames = getNodeGraphDataFrames(frames as DataFrame[]);
|
||||
expect(nodeGraphFrames.length).toBe(5);
|
||||
expect(nodeGraphFrames).toEqual(validFrames);
|
||||
});
|
||||
|
||||
it('getting fields is case insensitive', () => {
|
||||
const nodeFrame = new MutableDataFrame({
|
||||
refId: 'nodes',
|
||||
fields: [
|
||||
{ name: 'id', type: FieldType.string, values: ['id'] },
|
||||
{ name: 'title', type: FieldType.string, values: ['title'] },
|
||||
{ name: 'SUBTITLE', type: FieldType.string, values: ['subTitle'] },
|
||||
{ name: 'mainstat', type: FieldType.string, values: ['mainStat'] },
|
||||
{ name: 'seconDarysTat', type: FieldType.string, values: ['secondaryStat'] },
|
||||
],
|
||||
});
|
||||
|
||||
const nodeFields = getNodeFields(nodeFrame);
|
||||
expect(nodeFields.id).toBeDefined();
|
||||
expect(nodeFields.title).toBeDefined();
|
||||
expect(nodeFields.subTitle).toBeDefined();
|
||||
expect(nodeFields.mainStat).toBeDefined();
|
||||
expect(nodeFields.secondaryStat).toBeDefined();
|
||||
|
||||
const edgeFrame = new MutableDataFrame({
|
||||
refId: 'nodes',
|
||||
fields: [
|
||||
{ name: 'id', type: FieldType.string, values: ['id'] },
|
||||
{ name: 'source', type: FieldType.string, values: ['title'] },
|
||||
{ name: 'TARGET', type: FieldType.string, values: ['subTitle'] },
|
||||
{ name: 'mainstat', type: FieldType.string, values: ['mainStat'] },
|
||||
{ name: 'secondarystat', type: FieldType.string, values: ['secondaryStat'] },
|
||||
],
|
||||
});
|
||||
const edgeFields = getEdgeFields(edgeFrame);
|
||||
expect(edgeFields.id).toBeDefined();
|
||||
expect(edgeFields.source).toBeDefined();
|
||||
expect(edgeFields.target).toBeDefined();
|
||||
expect(edgeFields.mainStat).toBeDefined();
|
||||
expect(edgeFields.secondaryStat).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
@ -35,13 +35,17 @@ export function shortenLine(line: Line, length: number): Line {
|
||||
}
|
||||
|
||||
export function getNodeFields(nodes: DataFrame) {
|
||||
const fieldsCache = new FieldCache(nodes);
|
||||
const normalizedFrames = {
|
||||
...nodes,
|
||||
fields: nodes.fields.map((field) => ({ ...field, name: field.name.toLowerCase() })),
|
||||
};
|
||||
const fieldsCache = new FieldCache(normalizedFrames);
|
||||
return {
|
||||
id: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.id),
|
||||
title: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.title),
|
||||
subTitle: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.subTitle),
|
||||
mainStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.mainStat),
|
||||
secondaryStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.secondaryStat),
|
||||
id: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.id.toLowerCase()),
|
||||
title: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.title.toLowerCase()),
|
||||
subTitle: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.subTitle.toLowerCase()),
|
||||
mainStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.mainStat.toLowerCase()),
|
||||
secondaryStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.secondaryStat.toLowerCase()),
|
||||
arc: findFieldsByPrefix(nodes, NodeGraphDataFrameFieldNames.arc),
|
||||
details: findFieldsByPrefix(nodes, NodeGraphDataFrameFieldNames.detail),
|
||||
color: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.color),
|
||||
@ -49,14 +53,18 @@ export function getNodeFields(nodes: DataFrame) {
|
||||
}
|
||||
|
||||
export function getEdgeFields(edges: DataFrame) {
|
||||
const fieldsCache = new FieldCache(edges);
|
||||
const normalizedFrames = {
|
||||
...edges,
|
||||
fields: edges.fields.map((field) => ({ ...field, name: field.name.toLowerCase() })),
|
||||
};
|
||||
const fieldsCache = new FieldCache(normalizedFrames);
|
||||
return {
|
||||
id: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.id),
|
||||
source: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.source),
|
||||
target: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.target),
|
||||
mainStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.mainStat),
|
||||
secondaryStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.secondaryStat),
|
||||
details: findFieldsByPrefix(edges, NodeGraphDataFrameFieldNames.detail),
|
||||
id: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.id.toLowerCase()),
|
||||
source: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.source.toLowerCase()),
|
||||
target: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.target.toLowerCase()),
|
||||
mainStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.mainStat.toLowerCase()),
|
||||
secondaryStat: fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.secondaryStat.toLowerCase()),
|
||||
details: findFieldsByPrefix(edges, NodeGraphDataFrameFieldNames.detail.toLowerCase()),
|
||||
};
|
||||
}
|
||||
|
||||
@ -172,11 +180,11 @@ function makeNode(index: number) {
|
||||
return {
|
||||
id: index.toString(),
|
||||
title: `service:${index}`,
|
||||
subTitle: 'service',
|
||||
subtitle: 'service',
|
||||
arc__success: 0.5,
|
||||
arc__errors: 0.5,
|
||||
mainStat: 0.1,
|
||||
secondaryStat: 2,
|
||||
mainstat: 0.1,
|
||||
secondarystat: 2,
|
||||
color: 0.5,
|
||||
};
|
||||
}
|
||||
@ -327,7 +335,13 @@ export function getNodeGraphDataFrames(frames: DataFrame[]) {
|
||||
if (frame.meta?.preferredVisualisationType === 'nodeGraph') {
|
||||
return true;
|
||||
}
|
||||
if (frame.name === 'nodes' || frame.name === 'edges') {
|
||||
|
||||
if (frame.name === 'nodes' || frame.name === 'edges' || frame.refId === 'nodes' || frame.refId === 'edges') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const fieldsCache = new FieldCache(frame);
|
||||
if (fieldsCache.getFieldByName(NodeGraphDataFrameFieldNames.id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user