@netdata/charts
Version:
Netdata frontend SDK and chart utilities
611 lines • 19.8 kB
JavaScript
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
import { makeTestChart } from "@jest/testUtilities";
import { transformWeightsData, buildHierarchicalTree } from "./dataTransformer";
var firstNodeId = "9d445810-58a2-4d8c-9749-d80785f49d93";
var _makeTestChart = makeTestChart({
attributes: {
nodes: {
"lab-cloud-unpaid": {
nm: "Production node"
}
}
}
}),
chart = _makeTestChart.chart;
var weightsResponse = {
result: [{
id: "load1,system.load,".concat(firstNodeId),
nm: "load1,system.load,lab-cloud-unpaid",
v: [[0.3837659, 0.3837659, 0.3837659, 0.3837659, 1], [0.05, 0.3837659, 2, 69.0778583, 180, 0]]
}, {
id: "load5,system.load,".concat(firstNodeId),
nm: "load5,system.load,lab-cloud-unpaid",
v: [[0.3523553, 0.3523553, 0.3523553, 0.3523553, 1], [0.14, 0.3523553, 0.64, 63.4239464, 180, 0]]
}, {
id: "load1,system.load,b6d0e457-0281-4ef9-8c20-86bcbeffd1a8",
nm: "load1,system.load,nd-child-unpaid04",
v: [[0.3889211, 0.3889211, 0.3889211, 0.3889211, 1], [0.05, 0.3889211, 1.84, 70.0058063, 180, 0]]
}]
};
var makeTreeItem = function makeTreeItem(_ref, groupByOrder) {
var values = _ref.values,
_ref$names = _ref.names,
names = _ref$names === void 0 ? values : _ref$names,
_ref$nm = _ref.nm,
nm = _ref$nm === void 0 ? names.join(",") : _ref$nm,
_ref$weight = _ref.weight,
weight = _ref$weight === void 0 ? {
min: 1,
avg: 1,
max: 1,
sum: 1,
count: 1
} : _ref$weight,
_ref$timeframe = _ref.timeframe,
timeframe = _ref$timeframe === void 0 ? {
min: 1,
avg: 1,
max: 1,
sum: 1,
count: 1,
anomaly_count: 0
} : _ref$timeframe;
var groupedBy = groupByOrder.reduce(function (result, field, index) {
result[field] = values[index];
return result;
}, {});
var groupedByNames = groupByOrder.reduce(function (result, field, index) {
result[field] = names[index];
return result;
}, {});
return {
id: values.join(","),
nm: nm,
label: names[0],
groupedBy: groupedBy,
groupedByNames: groupedByNames,
weight: weight,
timeframe: timeframe,
anomalyRate: timeframe.count > 0 ? timeframe.anomaly_count * 100 / timeframe.count : 0,
contribution: weight.sum,
level: 0,
parentId: null
};
};
var _countTreeNodes = function countTreeNodes(nodes) {
return nodes.reduce(function (count, node) {
return count + 1 + _countTreeNodes(node.children || []);
}, 0);
};
describe("dataTransformer", function () {
describe("transformWeightsData", function () {
it("returns empty array when no data", function () {
expect(transformWeightsData(null, ["dimension"], chart)).toEqual([]);
expect(transformWeightsData({
result: []
}, ["dimension"], chart)).toEqual([]);
expect(transformWeightsData(weightsResponse, [], chart)).toEqual([]);
});
it("transforms weights response to flat data", function () {
var groupBy = ["dimension", "context", "node"];
var result = transformWeightsData(weightsResponse, groupBy, chart);
expect(result).toHaveLength(3);
var firstItem = result[0];
expect(firstItem.id).toBe("load1,system.load,".concat(firstNodeId));
expect(firstItem.nm).toBe("load1,system.load,lab-cloud-unpaid");
expect(firstItem.label).toBe("load1");
expect(firstItem.groupedBy).toEqual({
dimension: "load1",
context: "system.load",
node: firstNodeId
});
expect(firstItem.groupedByNames).toEqual({
dimension: "load1",
context: "system.load",
node: "Production node"
});
});
it("maps weight statistics correctly", function () {
var groupBy = ["dimension", "context", "node"];
var result = transformWeightsData(weightsResponse, groupBy, chart);
expect(result[0].weight).toEqual({
min: 0.3837659,
avg: 0.3837659,
max: 0.3837659,
sum: 0.3837659,
count: 1
});
});
it("maps timeframe statistics correctly", function () {
var groupBy = ["dimension", "context", "node"];
var result = transformWeightsData(weightsResponse, groupBy, chart);
expect(result[0].timeframe).toEqual({
min: 0.05,
avg: 0.3837659,
max: 2,
sum: 69.0778583,
count: 180,
anomaly_count: 0
});
});
it("uses schema indexes and sanitizes non-finite timeframe statistics", function () {
var response = {
v_schema: {
items: [{
name: "timeframe"
}, {
name: "weight"
}]
},
result: [{
id: "dimension",
nm: "Dimension",
v: [[Infinity, -Infinity, NaN, undefined, null, Infinity], [1, 2, 3, 4, 5]]
}]
};
var _transformWeightsData = transformWeightsData(response, ["dimension"], chart),
_transformWeightsData2 = _slicedToArray(_transformWeightsData, 1),
result = _transformWeightsData2[0];
expect(result.weight).toEqual({
min: 1,
avg: 2,
max: 3,
sum: 4,
count: 5
});
expect(result.timeframe).toEqual({
min: null,
avg: null,
max: null,
sum: null,
count: null,
anomaly_count: null
});
expect(result.anomalyRate).toBe(0);
});
it("calculates anomaly rate correctly", function () {
var groupBy = ["dimension", "context", "node"];
var response = {
result: [{
id: "test,test,test",
nm: "test",
v: [[1, 1, 1, 1, 1], [1, 1, 1, 1, 100, 10]]
}]
};
expect(transformWeightsData(weightsResponse, groupBy, chart)[0].anomalyRate).toBe(0);
expect(transformWeightsData(response, groupBy, chart)[0].anomalyRate).toBe(10);
});
it("handles different groupBy orders", function () {
var groupBy = ["node", "dimension", "context"];
var result = transformWeightsData(weightsResponse, groupBy, chart);
expect(result[0].label).toBe("load1");
expect(result[0].groupedBy).toEqual({
node: "load1",
dimension: "system.load",
context: firstNodeId
});
});
});
describe("buildHierarchicalTree", function () {
it("returns the original flat data when no groupBy order is provided", function () {
var flatData = [{
id: "test",
level: 0
}];
expect(buildHierarchicalTree(flatData, [])).toBe(flatData);
});
it("builds the hierarchy with stable parent-child order", function () {
var groupBy = ["dimension", "context", "node"];
var flatData = transformWeightsData(weightsResponse, groupBy, chart);
var tree = buildHierarchicalTree(flatData, groupBy);
expect(tree.map(function (node) {
return node.label;
})).toEqual(["load1", "load5"]);
var _tree = _slicedToArray(tree, 2),
load1Group = _tree[0],
load5Group = _tree[1];
expect(load1Group).toMatchObject({
level: 0,
parentId: null,
isGroupNode: true
});
expect(load5Group).toMatchObject({
level: 0,
parentId: null,
isGroupNode: true
});
expect(load1Group.children).toHaveLength(1);
var _load1Group$children = _slicedToArray(load1Group.children, 1),
contextGroup = _load1Group$children[0];
expect(contextGroup).toMatchObject({
label: "system.load",
level: 1,
parentId: "load1",
isGroupNode: true
});
expect(contextGroup.children.map(function (node) {
return node.label;
})).toEqual(["Production node", "nd-child-unpaid04"]);
expect(contextGroup.children.every(function (node) {
return !node.isGroupNode;
})).toBe(true);
});
it("preserves exact aggregate semantics for nested groups and duplicate leaves", function () {
var groupBy = ["region", "node", "dimension"];
var flatData = [makeTreeItem({
values: ["west", "node-a", "cpu"],
nm: "cpu",
weight: {
min: 1,
avg: 5,
max: 5,
sum: 10,
count: 2
},
timeframe: {
min: 2,
avg: 4,
max: 6,
sum: 8,
count: 2,
anomaly_count: 1
}
}, groupBy), makeTreeItem({
values: ["west", "node-a", "memory"],
nm: "memory-first",
weight: {
min: null,
avg: 20,
max: 8,
sum: 20,
count: 0
},
timeframe: {
min: null,
avg: 10,
max: 12,
sum: 20,
count: 3,
anomaly_count: 2
}
}, groupBy), makeTreeItem({
values: ["west", "node-a", "memory"],
nm: "memory-last",
weight: {
min: 0,
avg: 5,
max: null,
sum: 5,
count: 1
},
timeframe: {
min: 1,
avg: 2,
max: null,
sum: 2,
count: 1,
anomaly_count: 0
}
}, groupBy), makeTreeItem({
values: ["west", "node-b", "disk"],
nm: "disk",
weight: {
min: -1,
avg: 0,
max: 9,
sum: 0,
count: 4
},
timeframe: {
min: 0,
avg: 0,
max: 0,
sum: 0,
count: 0,
anomaly_count: 3
}
}, groupBy), makeTreeItem({
values: ["east", "node-c", "network"]
}, groupBy)];
var tree = buildHierarchicalTree(flatData, groupBy);
var _tree2 = _slicedToArray(tree, 1),
west = _tree2[0];
var _west$children = _slicedToArray(west.children, 2),
nodeA = _west$children[0],
nodeB = _west$children[1];
expect(west.weight).toEqual({
min: -1,
max: 9,
sum: 35,
avg: 35 / 3,
count: 7,
anomaly_count: undefined
});
expect(west.timeframe).toEqual({
min: 0,
max: 12,
sum: 30,
avg: 40 / 6,
count: 6,
anomaly_count: 6
});
expect(west).toMatchObject({
contribution: 35,
anomalyRate: 100
});
expect(nodeA.weight).toEqual({
min: 0,
max: 8,
sum: 35,
avg: 17.5,
count: 3,
anomaly_count: undefined
});
expect(nodeA.timeframe).toEqual({
min: 1,
max: 12,
sum: 30,
avg: 40 / 6,
count: 6,
anomaly_count: 3
});
expect(nodeA).toMatchObject({
contribution: 35,
anomalyRate: 50
});
expect(nodeA.children.map(function (node) {
return node.nm;
})).toEqual(["cpu", "memory-last"]);
expect(nodeA.children[1].weight).toEqual(flatData[2].weight);
expect(nodeB.timeframe).toEqual({
min: 0,
max: 0,
sum: 0,
avg: 0,
count: 0,
anomaly_count: 3
});
expect(nodeB.anomalyRate).toBe(0);
});
it("keeps aggregate membership distinct when public path keys collide", function () {
var groupBy = ["first", "second", "leaf"];
var firstItem = makeTreeItem({
values: ["a|b", "c", "leaf-a"],
nm: "leaf-a",
weight: {
min: 1,
avg: 2,
max: 3,
sum: 2,
count: 1
},
timeframe: {
min: 1,
avg: 2,
max: 3,
sum: 2,
count: 1,
anomaly_count: 1
}
}, groupBy);
var secondItem = makeTreeItem({
values: ["a", "b|c", "leaf-b"],
nm: "leaf-b",
weight: {
min: 100,
avg: 100,
max: 100,
sum: 100,
count: 1
},
timeframe: {
min: 100,
avg: 100,
max: 100,
sum: 100,
count: 1,
anomaly_count: 0
}
}, groupBy);
var tree = buildHierarchicalTree([firstItem, secondItem], groupBy);
var _tree3 = _slicedToArray(tree, 2),
firstRoot = _tree3[0],
secondRoot = _tree3[1];
var _firstRoot$children = _slicedToArray(firstRoot.children, 1),
collidingGroup = _firstRoot$children[0];
expect(tree.map(function (node) {
return node.id;
})).toEqual(["a|b", "a"]);
expect(collidingGroup.id).toBe("a|b|c");
expect(collidingGroup.children.map(function (node) {
return node.nm;
})).toEqual(["leaf-a", "leaf-b"]);
expect(collidingGroup.weight).toEqual({
min: 1,
max: 3,
sum: 2,
avg: 2,
count: 1,
anomaly_count: undefined
});
expect(collidingGroup.timeframe).toEqual({
min: 1,
max: 3,
sum: 2,
avg: 2,
count: 1,
anomaly_count: 1
});
expect(secondRoot.weight.sum).toBe(100);
expect(secondRoot.children).toEqual([]);
});
it("preserves strict-equality membership behavior for NaN grouping values", function () {
var groupBy = ["group", "leaf"];
var flatData = [makeTreeItem({
values: [NaN, "dimension"]
}, groupBy)];
var _buildHierarchicalTre = buildHierarchicalTree(flatData, groupBy),
_buildHierarchicalTre2 = _slicedToArray(_buildHierarchicalTre, 1),
group = _buildHierarchicalTre2[0];
expect(group.id).toBe("NaN");
expect(group.children).toHaveLength(1);
expect(group).not.toHaveProperty("weight");
expect(group).not.toHaveProperty("timeframe");
});
it("handles grouping values that match object prototype property names", function () {
var groupBy = ["group", "leaf"];
var flatData = [makeTreeItem({
values: ["__proto__", "constructor"],
nm: "prototype leaf"
}, groupBy)];
var _buildHierarchicalTre3 = buildHierarchicalTree(flatData, groupBy),
_buildHierarchicalTre4 = _slicedToArray(_buildHierarchicalTre3, 1),
group = _buildHierarchicalTre4[0];
expect(group).toMatchObject({
id: "__proto__",
label: "__proto__",
isGroupNode: true
});
expect(group.children).toHaveLength(1);
expect(group.children[0]).toMatchObject({
id: "__proto__|constructor",
nm: "prototype leaf",
isGroupNode: false
});
});
it("preserves object property ordering for integer-like root keys", function () {
var groupBy = ["group", "leaf"];
var flatData = ["10", "2", "alpha"].map(function (value) {
return makeTreeItem({
values: [value, "leaf-".concat(value)]
}, groupBy);
});
var tree = buildHierarchicalTree(flatData, groupBy);
expect(tree.map(function (node) {
return node.id;
})).toEqual(["2", "10", "alpha"]);
});
it("handles single-level grouping", function () {
var groupBy = ["dimension"];
var flatData = transformWeightsData(weightsResponse, groupBy, chart);
var tree = buildHierarchicalTree(flatData, groupBy);
expect(tree.map(function (node) {
return node.label;
})).toEqual(["load1", "load5"]);
expect(tree.every(function (node) {
return !node.isGroupNode;
})).toBe(true);
});
it("builds and exactly aggregates a 6,000-row high-cardinality hierarchy", function () {
var groupBy = ["region", "cluster", "node", "dimension"];
var flatData = Array.from({
length: 6000
}, function (_, index) {
var region = Math.floor(index / 600);
var cluster = Math.floor(index % 600 / 30);
var node = index % 30;
var value = index + 1;
return makeTreeItem({
values: ["region-".concat(region), "cluster-".concat(cluster), "node-".concat(node), "dimension-".concat(index)],
weight: {
min: value - 0.5,
avg: value,
max: value + 0.5,
sum: value,
count: index % 4 === 0 ? 0 : 2
},
timeframe: {
min: value - 1,
avg: value,
max: value + 1,
sum: value * 2,
count: 2,
anomaly_count: index % 3 === 0 ? 1 : 0
}
}, groupBy);
});
var tree = buildHierarchicalTree(flatData, groupBy);
var region = tree[0];
var cluster = region.children[0];
var node = cluster.children[1];
expect(tree.map(function (item) {
return item.label;
})).toEqual(Array.from({
length: 10
}, function (_, index) {
return "region-".concat(index);
}));
expect(_countTreeNodes(tree)).toBe(12210);
expect(region.children.map(function (item) {
return item.label;
})).toEqual(Array.from({
length: 20
}, function (_, index) {
return "cluster-".concat(index);
}));
expect(cluster.children.map(function (item) {
return item.label;
})).toEqual(Array.from({
length: 30
}, function (_, index) {
return "node-".concat(index);
}));
expect(region.weight).toEqual({
min: 0.5,
max: 600.5,
sum: 180300,
avg: 180300 / 450,
count: 900,
anomaly_count: undefined
});
expect(region.timeframe).toEqual({
min: 0,
max: 601,
sum: 360600,
avg: 300.5,
count: 1200,
anomaly_count: 200
});
expect(region.anomalyRate).toBe(200 * 100 / 1200);
expect(cluster.weight).toEqual({
min: 0.5,
max: 30.5,
sum: 465,
avg: 465 / 22,
count: 44,
anomaly_count: undefined
});
expect(cluster.timeframe).toEqual({
min: 0,
max: 31,
sum: 930,
avg: 15.5,
count: 60,
anomaly_count: 10
});
expect(node.weight).toEqual({
min: 1.5,
max: 2.5,
sum: 2,
avg: 2,
count: 2,
anomaly_count: undefined
});
expect(node.timeframe).toEqual({
min: 1,
max: 3,
sum: 4,
avg: 2,
count: 2,
anomaly_count: 0
});
expect(node.children).toHaveLength(1);
expect(node.children[0].label).toBe("dimension-1");
});
});
});