You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1013 lines
34 KiB

/*
* The PMTreeView creates the hierarchy tree and acts as controller for interactions with it
*/
define([ "pm/contrib/d3/d3.amd",
'/static/app/DA-ITSI-CP-vmware-dashboards/libs/backbone.js',
"/static/app/DA-ITSI-CP-vmware-dashboards/swc-vmware-cp/index.js",
"pm/PMEventDispatcher",
"pm/PMParentNodeView",
"pm/PMTooltipView",
"text!pm/PMTreeControls.html",
'/static/app/DA-ITSI-CP-vmware-dashboards/libs/underscore.js'],
function (d3, Backbone, SWCVMware, dispatcher, ParentNodeView, TooltipView, tree_controls_snippet, _) {
const Messages = SWCVMware.MVCMessages;
const SimpleSplunkView = SWCVMware.SimpleSplunkView;
const mvc = SWCVMware.MVC;
var custom_messages = {
//currently unused message due to data format change, but still valid example of custom messages
"processing-hierarchy" : {
icon: "info-circle",
level: "info",
message_template: _.template("Processing hierarchy: <%= text.complete%>% complete", null, {variable: "text"}),
message: "Processing hierarchy, please wait."
},
"no-root-nodes" : {
icon: "warning-sign",
level: "error",
message: "Could not find any root nodes in the hierarchy. Please check your hierarchy data."
},
"parse-error" : {
icon: "warning-sign",
level: "error",
message: "Error parsing data from search. Please check searches."
},
//blank will not empty the icon from the container need to have a extra class applied
"empty" : {
icon: "blank",
level: "blank alert-hide-icon",
message: ""
},
"no-results-performance" : {
icon: "warning-sign",
level: "error",
message: "No results from performance search. Please check that you have performance data."
},
"no-results" : {
icon: "warning-sign",
level: "error",
message: "No results from hierarchy search, please check that you have hierarchy data in your index over the last 8 hours."
}
};
var Tree = SimpleSplunkView.extend({
className: "proactive-monitoring-tree",
output_mode: "json_rows",
resultOptions: { output_time_format: "%s.%Q" },
events: {
"click .pm-tree-icon-circle.pm-tree-action-zoomin": "zoomInTree",
"click .pm-tree-icon-circle.pm-tree-action-zoomout": "zoomOutTree",
"click .pm-tree-icon-circle.pm-tree-action-center": "centerTree"
},
/*
* The Tree acts as the controller for the visualization and as such
* has a large number of configuration options:
* -> managerid: is the splunk manager for the hierarchy information
* -> metric: is a read only prop representing the current metric
*
* perf_* options are for the severity overlay rendering
* -> perf_managerid: is the splunk manager for the performance (severity) data
* -> perf_message_container: is the jquery selection representing where to display the perf search messages
*
* tooltip_* options are for the node tooltip
* -> tooltip_distribution_managerid: is the splunk manager for the global performance distribution
* -> tooltip_specific_managerid: is the splunk manager for the hovered node performance search
* -> tooltip_tree_token: is the writable token to which the hovered node's tree id will be set
* -> tooltip_node_token: is the writable token to which the hovered node's node id will be set
*/
options: {
data: "preview",
id_field: "moid",
name_field: "name",
parent_field: "parent",
tree_field: "host",
type_field: "type",
leaf_type: "VirtualMachine",
root_type: "RootFolder",
managerid: undefined,
metric: undefined,
threshold_data: undefined,
perf_managerid: undefined,
perf_message_container: undefined,
tooltip_distribution_managerid: undefined,
tooltip_specific_managerid: undefined,
tooltip_tree_token: undefined,
tooltip_node_token: undefined,
tooltip_earliest: undefined,
tooltip_latest: undefined
},
//This is a reference to the distilled threshold data
threshold_data: undefined,
//This is the timeout id for tooltip delayed shows
tooltip_show_timeout: null,
/*
* We overload displayMessage to include our own custom messages
* with message text overloading. If you wish to use a message
* template you must pass a text object with keys equal to the
* template tokens to replace.
*
* In addition we allow for the control of the message container
* with a default of this.$el. this._viz will not be destroyed if
* container is specified.
*/
displayMessage: function(info, text, container) {
if (container === null || container === undefined) {
container = this.$el;
this._viz = null;
}
if (custom_messages.hasOwnProperty(info)) {
var info_obj = _.clone(custom_messages[info]);
if (text !== null && text !== undefined) {
info_obj.message = info_obj.message_template(text, {variable: "text"});
}
Messages.render(info_obj, container);
}
else {
Messages.render(info, container);
}
return this;
},
/*
* Note we overload initialize to shim in our initialization code.
* You MUST still call the parent or you are going to be knee deep
* in doodie. Also note that the parent needs to be first.
*/
initialize: function(options) {
SimpleSplunkView.prototype.initialize.apply(this, arguments);
//Enable push on our tokens
this.settings.enablePush("tooltip_tree_token");
this.settings.enablePush("tooltip_node_token");
//Bind to perf search manager
this.bindToComponentSetting('perf_managerid', this._onPerfManagerChange, this);
if (!this.perf_manager) {
this._onPerfManagerChange(mvc.Components, null);
}
//Bind to dispatcher events
dispatcher.on("layout:resize", this.onLayoutResize, this);
//Bind to threshold data
this.threshold_data = options.threshold_data;
this.settings.on("change:metric", this.updateLegend, this);
},
/*
* Note we overload this method to include the field list because
* we are using json_rows to save message size in the server
* response.
*/
formatResults: function(resultsModel) {
if (!resultsModel) {
return {fields: [],
rows: [[]],
parse_error: true
};
}
// First try the legacy one, and if it isn't there, use the real one.
var outputMode = this.output_mode || this.outputMode;
var data_type = this.data_types[outputMode];
var data = resultsModel.data();
//override to return fields as well, thus our data looks like: {fields: [fieldname1, fieldname2, ...], rows: [row1_array, row2_array, ...]}
return this.formatData({fields: data.fields,
rows: data[data_type],
parse_error: false
});
},
/*
* Transform the tabular search results into the tree data structures
* required for rendering.
* Note the following assumptions are being made:
* -> leaf type nodes never have children
* -> leaf nodes never have parents as siblings
*/
formatData: function(data) {
//Get the indices of the identifying fields
var id_index = _.indexOf(data.fields, this.settings.get("id_field"));
var parent_index = _.indexOf(data.fields, this.settings.get("parent_field"));
var tree_index = _.indexOf(data.fields, this.settings.get("tree_field"));
var type_index = _.indexOf(data.fields, this.settings.get("type_field"));
var name_index = _.indexOf(data.fields, this.settings.get("name_field"));
//Get Tree Processing Parameters
var leaf_type = this.settings.get("leaf_type").toLowerCase();
//Create Trees
var root_nodes = [];
var tree_manifest = {};
var ii, parent_id, node_id, row, tree, node_manifest, complete, node, parent, type;
var prev_complete = 0;
this.displayMessage("processing-hierarchy");
for (ii=0; ii < data.rows.length; ii++) {
row = data.rows[ii];
//Get the tree, create if does not exist yet
tree = row[tree_index];
if (!tree_manifest.hasOwnProperty(tree)) {
tree_manifest[tree] = {};
}
node_manifest = tree_manifest[tree];
//Get this node, create if does not exist
node_id = row[id_index];
if (!node_manifest.hasOwnProperty(node_id)) {
node_manifest[node_id] = {
id: null,
parent: null,
name: null,
type: null,
tree: tree,
node_id: node_id,
_children: [],
//These keys will be used to affect rendering style
node_view: null,
penultimate: false,
expanded: false,
x0: null,
y0: null,
//Note that the form of value is [<num critical>, <num warning>, <num normal>, <num unknown>]
value: [0, 0, 0, 0]
};
}
node = node_manifest[node_id];
//Get this node's parent node object, create if does not exist
parent_id = row[parent_index];
if (!node_manifest.hasOwnProperty(parent_id)) {
node_manifest[parent_id] = {
id: null,
name: null,
parent: null,
type: null,
tree: tree,
node_id: parent_id,
_children: [],
//These keys will be used to affect rendering style
node_view: null,
penultimate: false,
expanded: false,
x0: null,
y0: null,
//Note that the form of value is [<num critical>, <num warning>, <num normal>, <num unknown>]
value: [0, 0, 0, 0]
};
}
parent = node_manifest[parent_id];
//Update node properties
parent._children.push(node);
parent.id = tree + ":" + parent_id;
type = row[type_index];
node.id = tree + ":" + node_id;
node.type = type;
node.parent = parent;
node.name = row[name_index];
//Set type based properties
if (type.toLowerCase() === leaf_type) {
if (parent.type !== this.settings.get("root_type")) {
parent.penultimate = true;
}
node._children = null;
}
else if (type === this.settings.get("root_type")) {
//Default to all root nodes expanded
node.expanded = true;
node.parent = null;
//Root nodes cannot be penultimate as they may contain parent nodes.
node.penultimate = false;
root_nodes.push(node);
}
//Render the hierarchy processing progress
complete = Math.floor(ii/data.rows.length * 100);
if (complete > prev_complete) {
this.displayMessage("processing-hierarchy", {complete: complete});
}
}
//Create d3 tree layout
var root;
if (root_nodes.length === 1) {
root = root_nodes[0];
}
else if (root_nodes.length === 0) {
this.displayMessage("no-root-nodes");
return {
parse_error: true,
message: "no-root-nodes"
};
}
else {
root = {
id: "__ENV__:__ROOT__",
name: "Environment",
parent: null,
type: null,
tree: null,
node_id: null,
_children: root_nodes,
//These keys will be used to affect rendering style
node_view: null,
penultimate: false,
expanded: true,
x0: null,
y0: null,
//Note that the form of value is [<num critical>, <num warning>, <num normal>, <num unknown>]
value: [0, 0, 0, 0]
};
}
var tree_layout = d3.layout.cluster();
tree_layout.children(function(d) {
if (d.expanded && !d.penultimate) {
return d._children;
}
else {
return null;
}
});
//Sort by the criticality
var get_criticality_array = function(d) {
var val = d.value;
if (val[0] > 0) {
return [4, val[0]];
}
else if (val[1] > 0) {
return [3, val[1]];
}
else if (val[2] > 0) {
return [1, val[2]];
}
else {
return [0, val[3]];
}
};
tree_layout.sort(function(a, b) {
var val_a = get_criticality_array(a);
var val_b = get_criticality_array(b);
if (val_a[0] === val_b[0]) {
//Same severity, use severity count
return d3.descending(val_a[1], val_b[1]);
} else {
//different severity, use severity
return d3.descending(val_a[0], val_b[0]);
}
});
//Set the layout to function off a set node size instead of the view size
//Note that the vertical takes into account the spacing on the vertical axis
tree_layout.nodeSize([45, 245]);
tree_layout.separation(function(a, b) {
if (b.penultimate && b.expanded) {
//Tried and tested, this is the spacing that looks best
return 3.7;
}
else {
return a.parent === b.parent ? 1 : 2;
}
});
return {
tree_manifest: tree_manifest,
root_node: root,
tree_layout: tree_layout,
parse_error: false
};
},
createView: function() {
//SET UP DOM
this.$el.html("");
var el_width = this.$el.width();
var el_height = this.$el.height();
var d3this = d3.select(this.$el.get(0));
//Set up controls
this.$el.html(tree_controls_snippet);
//Set up tooltip
var d3tooltip = d3this.selectAll("div.proactive-monitoring-node-tooltip-container")
.data([
{
tooltip_view: new TooltipView({
distribution_managerid: this.settings.get("tooltip_distribution_managerid"),
specific_managerid: this.settings.get("tooltip_specific_managerid")
})
}
]
)
.enter().append("div")
.attr("class", "proactive-monitoring-node-tooltip-container")
.style("opacity","1e-6")
.style("display", "none")
.each(function(d) {
d.tooltip_view.setElement(this);
d.tooltip_view.render();
})
.on("mouseover", function(d) {
if (d.tooltip_view.current_node !== undefined) {
d.tooltip_view.current_node.node_view.highlightNode();
}
var tooltip = d3.select(this);
tooltip.transition()
.duration(2)
.style("display", "block")
.transition()
.duration(150)
.style("opacity","1");
})
.on("mouseout", function(d) {
var tooltip = d3.select(this);
tooltip.transition()
.duration(300)
.style("opacity","1e-6")
.transition()
.duration(2)
.style("display", "none")
.each("end", function(d) {
if (d.tooltip_view.current_node !== undefined && d.tooltip_view.current_node.node_view !== null && d.tooltip_view.current_node.node_view !== undefined) {
d.tooltip_view.current_node.node_view.unhighlightNode();
}
});
});
var d3stage = d3this.append("svg")
.attr("class", "proactive-monitoring-main-stage")
.attr("width", el_width)
.attr("height", el_height)
.append("g")
.attr("transform", "translate(" + 20 + "," + 20 + ")scale(1,1)");
//Set the drag scroll movement behavior for the d3stage
var drag = d3.behavior.drag()
.origin(Object)
.on("dragstart", function() {
$(this).parent().addClass("active-drag-move");
})
.on("dragend", function() {
$(this).parent().removeClass("active-drag-move");
})
.on("drag", function(d,ii) {
var d3this = d3stage;
var transform = d3.transform(d3this.attr("transform"));
var translate = transform.translate;
translate[0] = translate[0] + d3.event.dx;
translate[1] = translate[1] + d3.event.dy;
d3this.attr("transform", transform.toString());
});
d3.select(this.$el.get(0)).select("svg.proactive-monitoring-main-stage").call(drag);
return {
el_size: {width: el_width, height: el_height},
d3stage: d3stage,
d3tooltip: d3tooltip
};
},
updateView: function(viz, data) {
if (data.parse_error) {
console.error("[PMTree] Failed to parse data properly, cannot render tree.");
if (data.hasOwnProperty("message")) {
this.displayMessage(data.message);
}
else {
this.displayMessage("parse-error");
}
return;
}
//Initialize the root centered
this._centerTree(viz);
var root = data.root_node;
root.x0 = 0;
root.y0 = 35;
if (!this.applyPerfToTree()) {
//This means that the perf data isn't ready, so just render the hierarchy
this._renderTree(root, viz, data);
}
this.updateLegend();
},
/*
* Updates/renders the tree visualization to represent the current state of the data
* Here the root argument implies the place to center the animation on for new nodes.
*/
_renderTree: function(root, viz, data) {
//Establish rendering helpers
var duration = 300;
var diagonal = d3.svg.diagonal();
var tree_layout = data.tree_layout;
var fixed_depth = this._getFixedDepth(viz);
//FIXME: remove all tree_layout sizing in favor of node sizing
//tree_layout.size([viz.el_size.width - 20, fixed_depth]);
var nodes = tree_layout.nodes(data.root_node);
var links = tree_layout.links(nodes);
var that = this;
//Render
//Bind node layout data to the node g's
var node = viz.d3stage.selectAll("g.pm-node.proactive-monitoring-parent-node")
.data(nodes, function(d, ii) { return d.id || (d.id = ++ii); });
//Add new nodes at root
var node_enter = node.enter().append("g")
.attr("class", "pm-node proactive-monitoring-parent-node")
.attr("transform", function(d) { return "translate(" + root.x0 + "," + root.y0 + ") scale(0.2)"; })
.each(function(d) {
//Technically if a leaf node's parent has other parent nodes as children it may be rendered in this manner but it is fine
d.node_view = new ParentNodeView({
tree_controller: that
});
d.node_view.setElement(this);
})
.on("click", this._toggleChildren.bind(this))
.on("mouseover", function(d) {
if (d.id === "__ENV__:__ROOT__") {
return;
}
var d3this = d3.select(this);
var node_transform = d3.transform(d3this.attr("transform"));
var offset = [node_transform.translate[0], node_transform.translate[1]];
that.tooltip_show_timeout = setTimeout(function() {that._showNodeTooltip(d, offset);}, 350);
})
.on("mouseout", function(d) {
if (d.id === "__ENV__:__ROOT__") {
return;
}
clearTimeout(that.tooltip_show_timeout);
that._hideNodeTooltip(d);
});
//Remove old nodes view data
var node_exit = node.exit()
.each(function(d) {
d.node_view = null;
});
//Update all nodes
var node_update = node
.each(function(d) {
if (d.node_view !== null) {
d.node_view.render(d);
}
})
.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node_exit.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + root.x + "," + root.y + ") scale(0.2)"; })
.remove();
//Handle Links
var link = viz.d3stage.selectAll("path.pm-link")
.data(links, function(d) { return d.target.id; });
link.enter().insert("path", "g")
.attr("class", "pm-link")
.attr("d", function(d) {
var o = {x: root.x0, y: root.y0};
return diagonal({source: o, target: o});
});
link.transition()
.duration(duration)
.attr("d", diagonal);
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: root.x, y: root.y};
return diagonal({source: o, target: o});
})
.remove();
//Mark all positions for future use
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
//Save off visible nodes for rendering purposes
},
//Helper function for getting the fixed depth of the tree
_getFixedDepth: function(viz) {
var available_height = viz.el_size.height;
if (available_height > 300) {
available_height = 300;
}
return available_height;
},
//Binding for showing the tooltip for any node
_showNodeTooltip: function(d, offset) {
//Set our tokens
this.settings.set("tooltip_tree_token", d.tree);
this.settings.set("tooltip_node_token", d.node_id);
//Calculate the position for the tooltip
var container_transform = d3.transform(this._viz.d3stage.attr("transform"));
var tooltip_position = [((offset[0] + 20) * container_transform.scale[0]) + container_transform.translate[0], ((offset[1] - 15) * container_transform.scale[1]) + container_transform.translate[1]];
var tooltip = this._viz.d3tooltip;
var tooltip_view = tooltip.data()[0].tooltip_view;
tooltip_view.showDetail(d, this.settings.get("metric"), this.settings.get("tooltip_earliest"), this.settings.get("tooltip_latest"));
tooltip.style("display", "block").style("opacity", "1e-6");
tooltip.style("left", String(tooltip_position[0])+"px").style("top", String(tooltip_position[1])+"px");
tooltip.transition()
.style("opacity","1");
},
//Binding for hiding the tooltip for any node
_hideNodeTooltip: function(d) {
var tooltip = this._viz.d3tooltip;
tooltip.transition()
.duration(300)
.style("opacity","1e-6")
.transition()
.duration(2)
.style("display", "none")
.each("end", function(d) {
//Unhighlight the hovered node
if (d.tooltip_view.current_node !== undefined && d.tooltip_view.current_node.node_view !== null && d.tooltip_view.current_node.node_view !== undefined) {
d.tooltip_view.current_node.node_view.unhighlightNode();
}
});
},
//Binding for showing and hiding children for any node
_toggleChildren: function(d, ii) {
if (d3.event.defaultPrevented) {
//click suppressed due to drag
return;
}
d3.event.stopPropagation();
if (d.expanded) {
d.expanded = false;
}
else {
d.expanded = true;
}
if (d.penultimate) {
d.node_view.toggleChildren(d);
}
this._renderTree(d, this._viz, this._data);
},
//Binding to layout resizing
onLayoutResize: function() {
if (this._data !== null && this._viz !== null) {
this._updateViz();
this.updateView(this._viz, this._data);
}
},
//Update the viz object with the current dimensions of the svg viewport
_updateViz: function() {
var el_width = this.$el.width();
var el_height = this.$el.height();
d3.select(this.$el.get(0)).select("svg.proactive-monitoring-main-stage")
.attr("width", el_width)
.attr("height", el_height);
this._viz.el_size = {width: el_width, height: el_height};
},
/*
* ================================================================
* LEGEND EVENT HANDLERS
* ================================================================
*/
updateLegend: function() {
if (this._viz === undefined || this._viz === null || !this._viz.hasOwnProperty("d3stage")) {
console.log("PMTree failed to set legend due to lack of viz");
return;
}
var metric = this.settings.get("metric");
var entity_type = this.settings.get("leaf_type");
var entity_threshold_info = this.threshold_data[entity_type] || {};
var threshold_info = entity_threshold_info["p_" + metric];
if (threshold_info !== null && threshold_info !== undefined) {
this.$(".pm-tree-legend-undefined-container").hide();
var $legend = this.$(".pm-tree-legend-container");
$(".pm-tree-legend-comparator", $legend).text(threshold_info.comparator);
$(".pm-tree-legend-critical.pm-tree-legend-label ", $legend).text(threshold_info.critical);
$(".pm-tree-legend-warning.pm-tree-legend-label ", $legend).text(threshold_info.warning);
$legend.hide();
}
else {
this.$(".pm-tree-legend-container").hide();
this.$(".pm-tree-legend-undefined-container").hide();
}
},
/*
* ================================================================
* TREE CONTROLS EVENT HANDLERS
* ================================================================
*/
zoomInTree: function(e) {
//Prevent any propagation or text highlighting
e.preventDefault();
e.stopPropagation();
console.log("PMTree zoom in called on tree");
this._zoomTree(1.25);
},
zoomOutTree: function(e) {
//Prevent any propagation or text highlighting
e.preventDefault();
e.stopPropagation();
console.log("PMTree zoom out called on tree");
this._zoomTree(0.8);
},
/*
* Scrolling Zoom presented a lot of compatibility problems on browsers and mice, so screw it
scrollZoomTree: function(e) {
//Prevent any propagation or text highlighting
e.preventDefault();
e.stopPropagation();
console.log("PMTree scroll zoom called on tree");
var zoom_factor = 0;
var wheel_delta = e.originalEvent.wheelDelta;
if (wheel_delta > 0) {
zoom_factor = 0.2 * Math.abs(wheel_delta / 12);
}
else if (wheel_delta < 0) {
zoom_factor = -0.2 * Math.abs(wheel_delta / 12);
}
this._zoomTree(zoom_factor);
},
*/
//Abstract the zoom method for use in multiple callbacks
_zoomTree: function(zoom_factor) {
if (this._viz === undefined || this._viz === null || !this._viz.hasOwnProperty("d3stage")) {
console.log("PMTree failed to zoom due to lack of d3stage in viz");
return;
}
var d3stage = this._viz.d3stage;
var transform = d3.transform(d3stage.attr("transform"));
var scale = transform.scale;
scale[0] = scale[0] * zoom_factor;
scale[1] = scale[1] * zoom_factor;
d3stage.transition().attr("transform", transform.toString());
},
centerTree: function(e) {
//Prevent any propagation or text highlighting
e.preventDefault();
e.stopPropagation();
console.log("PMTree center called on tree");
this._centerTree(this._viz);
},
// Abstract method for usage in updateView as well as center binding
_centerTree: function(viz) {
if (viz === undefined || viz === null || !viz.hasOwnProperty("d3stage")) {
console.log("PMTree failed to center due to lack of d3stage in viz");
return;
}
var d3stage = viz.d3stage;
var transform = d3.transform(d3stage.attr("transform"));
var scale = transform.scale;
scale[0] = 1;
scale[1] = 1;
var translate = transform.translate;
translate[0] = (viz.el_size.width - 40) / 2;
translate[1] = 35;
d3stage.transition().attr("transform", transform.toString());
},
/*
* ================================================================
* PERFORMANCE DATA BINDINGS
* ================================================================
*/
formatPerfResults: function(results_model) {
if (!results_model) {
return {fields: [],
rows: [[]],
parse_error: true
};
}
// First try the legacy one, and if it isn't there, use the real one.
var outputMode = this.output_mode || this.outputMode;
var data_type = this.data_types[outputMode];
var data = results_model.data();
var rows = data[data_type];
var id_index = _.indexOf(data.fields, this.settings.get("id_field"));
var tree_index = _.indexOf(data.fields, this.settings.get("tree_field"));
var threshold_index_index = _.indexOf(data.fields, "threshold_index");
return {
id_index: id_index,
tree_index: tree_index,
threshold_index_index: threshold_index_index,
rows: rows,
fields: data.fields
};
},
/*
* Left-joins the hierarchy data to the performance data. If it
* could not complete due to lack of data it returns false,
* otherwise it returns true.
*/
applyPerfToTree: function() {
if (this.perf_data === null || this._isPerfJobDone === false) {
console.log("[PMTree] Cannot Apply Performance Data to Tree: performance data is not available");
return;
}
else if (this._viz === null || this._viz === undefined || this._data === null || this._data === undefined || !this._data.hasOwnProperty("tree_manifest")) {
console.log("[PMTree] Cannot Apply Performance Data to Tree: hierarchy data is not available");
return;
}
var tree_manifest = this._data.tree_manifest;
//Reset everyone to unknown in case they are not represented in the performance data
_.each(tree_manifest, function(node_manifest, tree, tree_manifest) {
//Update the node manifest entries to unknown
_.each(node_manifest, function(node, node_id, node_manifest) {
node.value = [0, 0, 0, 1];
});
});
var ii, row, tree, node_id, threshold_index, value, node_manifest, node;
for (ii = 0; ii < this.perf_data.rows.length; ii++ ) {
row = this.perf_data.rows[ii];
tree = row[this.perf_data.tree_index];
node_id = row[this.perf_data.id_index];
threshold_index = row[this.perf_data.threshold_index_index];
value = [0, 0, 0, 0];
value[threshold_index] = 1;
node_manifest = tree_manifest[tree];
if (node_manifest === undefined || node_manifest === null) {
console.log("[PMTree] Cannot apply performance data to tree: could not find node manifest for tree "+ tree);
}
else {
node = node_manifest[node_id];
if (node === undefined || node === null) {
console.log("[PMTree] Cannot apply performance data to node: could not find node for id " + node_id + " in tree " + tree);
}
else {
node.value = value;
}
}
}
//Aggregate the value arrays
this._aggregateNodeValues(this._data.root_node);
//Render the pretty colors!
this._renderTree(this._data.root_node, this._viz, this._data);
return true;
},
/*
* Recursively calculates the aggregated value array for a
* particular root node (note will all children as a side effect).
* If the view exists call the bake pie method on it.
* Returns the value array for the root.
*/
_aggregateNodeValues: function(root) {
var value;
if (root._children === null) {
value = root.value;
}
else {
value = _.reduce(root._children, function(memo, node) {
var child_value = this._aggregateNodeValues(node);
memo[0] = memo[0] + child_value[0];
memo[1] = memo[1] + child_value[1];
memo[2] = memo[2] + child_value[2];
memo[3] = memo[3] + child_value[3];
return memo;
}, [0, 0, 0, 0], this);
}
root.value = value;
if (root.node_view !== null && root._children !== null) {
root.node_view.bakePie(root);
if (root.penultimate && root.expanded) {
root.node_view.updateChildren(root);
}
}
return value;
},
_onPerfManagerChange: function(managers, manager) {
// Called when our associated perf manager changes. Updates
// listeners on the new manager and its associated
// results model.
if (this.perf_manager) {
this.perf_manager.off(null, null, this);
this.perf_manager = null;
}
if (this.perf_results_model) {
this.perf_results_model.off(null, null, this);
this.perf_results_model.destroy();
this.perf_results_model = null;
}
this.perf_manager = manager;
if (!manager) {
this.displayMessage('no-search', null, this.settings.get("perf_message_container"));
return;
}
// Clear any messages, since we have a new manager.
this.displayMessage("empty", null, this.settings.get("perf_message_container"));
// First try the legacy one, and if it isn't there, use the real one.
var outputMode = this.output_mode || this.outputMode;
this.perf_results_model = this.perf_manager.data(this.settings.get("data") || "preview", _.extend({
output_mode: outputMode,
count: this.returnCount,
offset: this.offset
}, this.resultOptions));
manager.on("search:start", this._onPerfSearchStart, this);
manager.on("search:progress", this._onPerfSearchProgress, this);
manager.on("search:cancelled", this._onPerfSearchCancelled, this);
manager.on("search:error", this._onPerfSearchError, this);
manager.on("search:fail", this._onPerfSearchFailed, this);
this.perf_results_model.on("data", this._onPerfDataChanged, this);
this.perf_results_model.on("error", this._onPerfSearchError, this);
this._checkPerfManagerState();
manager.replayLastSearchEvent(this);
},
_checkPerfManagerState: function() {
// A splunk search job that has ended with no valid result
// will not generate the events necessary to display
// failure messages. Check for that here.
var manager = this.perf_manager;
if (!manager) {
return;
}
if ((!manager.job) && (manager.lastError)) {
this._onPerfSearchError(manager.lastError);
return;
}
if (!manager.job) {
return;
}
var state = manager.job.state();
if (state && state.content && ((state.content.isDone) || (state.content.isFailed))) {
this._onPerfSearchProgress(state);
return;
}
},
_onPerfDataChanged: function() {
if (!this.perf_results_model.hasData()) {
if (this._isPerfJobDone) {
this.displayMessage('no-results-performance', null, this.settings.get("perf_message_container"));
}
return;
}
this.perf_data = this.formatPerfResults(this.perf_results_model);
this.applyPerfToTree();
this.displayMessage('empty', null, this.settings.get("perf_message_container"));
},
_onPerfSearchProgress: function(properties) {
properties = properties || {};
var content = properties.content || {};
var previewCount = content.resultPreviewCount || 0;
var isJobDone = this._isPerfJobDone = content.isDone || false;
if (previewCount === 0) {
this.displayMessage(isJobDone ? 'no-results-performance' : 'waiting', null, this.settings.get("perf_message_container"));
return;
}
},
_onPerfSearchStart: function() {
this._isPerfJobDone = false;
this.perf_data = null;
this.displayMessage('waiting', null, this.settings.get("perf_message_container"));
},
_onPerfSearchCancelled: function() {
this._isPerfJobDone = false;
this.displayMessage('cancelled', null, this.settings.get("perf_message_container"));
},
_onPerfSearchError: function(message, err) {
this._isPerfJobDone = false;
var msg = Messages.getSearchErrorMessage(err) || message;
this.displayMessage({
level: "error",
icon: "warning-sign",
message: msg
}, null, this.settings.get("perf_message_container"));
},
_onPerfSearchFailed: function(state, job) {
var msg = Messages.getSearchFailureMessage(state);
this.displayMessage({
level: "error",
icon: "warning-sign",
message: msg
}, null, this.settings.get("perf_message_container"));
}
});
return Tree;
});