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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user