From 32fe431a3abaf34723ce2b6c0a8a7b055c729538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Fri, 17 Jun 2022 15:35:24 +0200 Subject: [PATCH 01/28] Adding initial flow diagram --- src/ServicePulse.Host/app/css/particular.css | 98 +++ src/ServicePulse.Host/app/index.html | 1 + .../directives/ui.particular.flow-diagram.js | 642 ++++++++++++++++++ .../js/services/services.service-control.js | 11 +- .../app/js/views/message/controller.js | 1 + .../app/js/views/message/messages-view.html | 2 + src/ServicePulse.Host/package-lock.json | 301 ++++---- src/ServicePulse.Host/package.json | 2 +- 8 files changed, 908 insertions(+), 150 deletions(-) create mode 100644 src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js diff --git a/src/ServicePulse.Host/app/css/particular.css b/src/ServicePulse.Host/app/css/particular.css index 56a96c6e81..d056fb398e 100644 --- a/src/ServicePulse.Host/app/css/particular.css +++ b/src/ServicePulse.Host/app/css/particular.css @@ -3370,4 +3370,102 @@ section[name="platformconnection"] ol { section[name="platformconnection"] li { margin-bottom: 15px; +} + +#tree-container { + position: absolute; + left: 0px; + width: 100%; +} + +.svgContainer { + display: block; + margin: auto; +} + +.node { + cursor: pointer; +} + +.node-rect { +} + +.node-rect-closed { + stroke-width: 2px; + stroke: rgb(0,0,0); +} + +.link { + fill: none; + stroke: lightsteelblue; + stroke-width: 2px; +} + +.linkselected { + fill: none; + stroke: tomato; + stroke-width: 2px; +} + +.arrow { + fill: lightsteelblue; + stroke-width: 1px; +} + +.arrowselected { + fill: tomato; + stroke-width: 2px; +} + +.link text { + font: 7px sans-serif; + fill: #CC0000; +} + +.wordwrap { + white-space: pre-wrap; /* CSS3 */ + white-space: -moz-pre-wrap; /* Firefox */ + white-space: -pre-wrap; /* Opera <7 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* IE */ +} + +.node-text { + font: 7px sans-serif; + color: white; +} + +.tooltip-text-container { + height: 100%; + width: 100%; +} + +.tooltip-text { + visibility: hidden; + font: 7px sans-serif; + color: white; + display: block; + padding: 5px; +} + +.tooltip-box { + background: rgba(0, 0, 0, 0.7); + visibility: hidden; + position: absolute; + border-style: solid; + border-width: 1px; + border-color: black; + border-top-right-radius: 0.5em; +} + +p { + display: inline; +} + +.textcolored { + color: orange; +} + +a.exchangeName { + color: orange; } \ No newline at end of file diff --git a/src/ServicePulse.Host/app/index.html b/src/ServicePulse.Host/app/index.html index ce01d6c5bb..eea7b9d433 100644 --- a/src/ServicePulse.Host/app/index.html +++ b/src/ServicePulse.Host/app/index.html @@ -191,6 +191,7 @@

Warning!

+ diff --git a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js new file mode 100644 index 0000000000..527a942f1c --- /dev/null +++ b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js @@ -0,0 +1,642 @@ +(function (window, angular, d3) { + 'use strict'; + + + function controller($scope, serviceControlService) { + serviceControlService.getConversation('ff15a05d-8ce5-4069-aff2-aeab00bd5fef').then(messages => { + var tree = createTreeStructure(messages.map(x => mapMessage(x))); + treeBoxes(null, tree[0]); + }); + function createTreeStructure(messages) { + var map = {}, node, roots = [], i; + + for (i = 0; i < messages.length; i += 1) { + map[messages[i].id] = i; // initialize the map + messages[i].children = []; // initialize the children + } + + for (i = 0; i < messages.length; i += 1) { + node = messages[i]; + if (node.parentId) { + // if you have dangling branches check that map[node.parentId] exists + messages[map[node.parentId]].children.push(node); + } else { + roots.push(node); + } + } + return roots; + } + + function mapMessage(message) { + var parentid; + var header = message.headers.find(x => x.key === 'NServiceBus.RelatedTo'); + if(header){ + parentid = header.value; + } + + return { + "nodeName": message.message_type, + "id": message.message_id, + "parentId": parentid, + "type": message.headers.find(x => x.key === 'NServiceBus.MessageIntent').value === 'Publish' ? 'Event' : 'Command', + "link" : { + "name" : "Link "+message.message_id, + "nodeName" : message.message_id + } + }; + } + + function treeBoxes(urlService, jsonData) + { + var urlService_ = ''; + + var blue = '#337ab7', + green = '#5cb85c', + yellow = '#f0ad4e', + blueText = '#4ab1eb', + purple = '#9467bd'; + + var margin = { + top : 0, + right : 0, + bottom : 0, + left : 0 + }, + // Height and width are redefined later in function of the size of the tree + // (after that the data are loaded) + width = 800 - margin.right - margin.left, + height = 400 - margin.top - margin.bottom; + + var rectNode = { width : 120, height : 45, textMargin : 5 }, + tooltip = { width : 150, height : 40, textMargin : 5 }; + var i = 0, + duration = 750, + root; + + var mousedown; // Use to save temporarily 'mousedown.zoom' value + var mouseWheel, + mouseWheelName, + isKeydownZoom = false; + + var tree; + var baseSvg, + svgGroup, + nodeGroup, // If nodes are not grouped together, after a click the svg node will be set after his corresponding tooltip and will hide it + nodeGroupTooltip, + linkGroup, + linkGroupToolTip, + defs; + + init(urlService, jsonData); + + function init(urlService, jsonData) + { + urlService_ = urlService; + if (urlService && urlService.length > 0) + { + if (urlService.charAt(urlService.length - 1) != '/') + urlService_ += '/'; + } + + if (jsonData) + drawTree(jsonData); + else + { + console.error(jsonData); + alert('Invalides data.'); + } + } + + function drawTree(jsonData) + { + jsonData = d3.hierarchy(jsonData); + tree = d3.tree(jsonData).size([ height, width ]); + root = jsonData; + root.fixed = true; + + // Dynamically set the height of the main svg container + // breadthFirstTraversal returns the max number of node on a same level + // and colors the nodes + var maxDepth = 0; + var maxTreeWidth = breadthFirstTraversal(tree(root), function(currentLevel) { + maxDepth++; + currentLevel.forEach(function(node) { + if (node.type == 'Event') + node.color = blue; + if (node.type == 'Command') + node.color = green; + }); + }); + height = maxTreeWidth * (rectNode.height + 20) + tooltip.height + 20 - margin.right - margin.left; + width = maxDepth * (rectNode.width * 1.5) + tooltip.width / 2 - margin.top - margin.bottom; + height = 600; + width = 600; + + tree = d3.tree().size([ height, width ]); + root.x0 = height / 2; + root.y0 = 0; + + baseSvg = d3.select('#tree-container').append('svg') + .attr('width', width + margin.right + margin.left) + .attr('height', height + margin.top + margin.bottom) + .attr('class', 'svgContainer'); + // .call(d3.behavior.zoom() + // //.scaleExtent([0.5, 1.5]) // Limit the zoom scale + // .on('zoom', zoomAndDrag)); + + // Mouse wheel is desactivated, else after a first drag of the tree, wheel event drags the tree (instead of scrolling the window) + getMouseWheelEvent(); + d3.select('#tree-container').select('svg').on(mouseWheelName, null); + d3.select('#tree-container').select('svg').on('dblclick.zoom', null); + + svgGroup = baseSvg.append('g') + .attr('class','drawarea') + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + // SVG elements under nodeGroupTooltip could be associated with nodeGroup, + // same for linkGroupToolTip and linkGroup, + // but this separation allows to manage the order on which elements are drew + // and so tooltips are always on top. + nodeGroup = svgGroup.append('g') + .attr('id', 'nodes'); + linkGroup = svgGroup.append('g') + .attr('id', 'links'); + linkGroupToolTip = svgGroup.append('g') + .attr('id', 'linksTooltips'); + nodeGroupTooltip = svgGroup.append('g') + .attr('id', 'nodesTooltips'); + + defs = baseSvg.append('defs'); + initArrowDef(); + initDropShadow(); + + update(root); + } + + function update(source) + { + // Compute the new tree layout + var nodes = root.descendants().reverse(), + links = root.links(); + + // Check if two nodes are in collision on the ordinates axe and move them + breadthFirstTraversal(root.descendants(), collision); + // Normalize for fixed-depth + nodes.forEach(function(d) { + d.y = d.depth * (rectNode.width * 1.5); + }); + + // 1) ******************* Update the nodes ******************* + var node = nodeGroup.selectAll('g.node').data(nodes, function(d) { + return d.id || (d.id = ++i); + }); + var nodesTooltip = nodeGroupTooltip.selectAll('g').data(nodes, function(d) { + return d.id || (d.id = ++i); + }); + + // Enter any new nodes at the parent's previous position + // We use "insert" rather than "append", so when a new child node is added (after a click) + // it is added at the top of the group, so it is drawed first + // else the nodes tooltips are drawed before their children nodes and they + // hide them + var nodeEnter = node.enter().insert('g', 'g.node') + .attr('class', 'node') + .attr('transform', function(d) { + return 'translate(' + source.y0 + ',' + source.x0 + ')'; }) + .on('click', function(d) { + click(d); + }); + var nodeEnterTooltip = nodesTooltip.enter().append('g') + .attr('transform', function(d) { + return 'translate(' + source.y0 + ',' + source.x0 + ')'; }); + + nodeEnter.append('g').append('rect') + .attr('rx', 6) + .attr('ry', 6) + .attr('width', rectNode.width) + .attr('height', rectNode.height) + .attr('class', 'node-rect') + .attr('fill', function (d) { return d.color; }) + .attr('filter', 'url(#drop-shadow)'); + + nodeEnter.append('foreignObject') + .attr('x', rectNode.textMargin) + .attr('y', rectNode.textMargin) + .attr('width', function() { + return (rectNode.width - rectNode.textMargin * 2) < 0 ? 0 + : (rectNode.width - rectNode.textMargin * 2) + }) + .attr('height', function() { + return (rectNode.height - rectNode.textMargin * 2) < 0 ? 0 + : (rectNode.height - rectNode.textMargin * 2) + }) + .append('xhtml').html(function(d) { + return '
' + + '' + d.data.nodeName + '

' + + 'Code: ' + d.data.id + '
' + + 'Type: ' + d.data.type + '
' + + '
'; + }) + .on('mouseover', function(d) { + $('#nodeInfoID' + d.id).css('visibility', 'visible'); + $('#nodeInfoTextID' + d.id).css('visibility', 'visible'); + }) + .on('mouseout', function(d) { + $('#nodeInfoID' + d.id).css('visibility', 'hidden'); + $('#nodeInfoTextID' + d.id).css('visibility', 'hidden'); + }); + + nodeEnterTooltip.append("rect") + .attr('id', function(d) { return 'nodeInfoID' + d.id; }) + .attr('x', rectNode.width / 2) + .attr('y', rectNode.height / 2) + .attr('width', tooltip.width) + .attr('height', tooltip.height) + .attr('class', 'tooltip-box') + .style('fill-opacity', 0.8) + .on('mouseover', function(d) { + $('#nodeInfoID' + d.id).css('visibility', 'visible'); + $('#nodeInfoTextID' + d.id).css('visibility', 'visible'); + removeMouseEvents(); + }) + .on('mouseout', function(d) { + $('#nodeInfoID' + d.id).css('visibility', 'hidden'); + $('#nodeInfoTextID' + d.id).css('visibility', 'hidden'); + reactivateMouseEvents(); + }); + + nodeEnterTooltip.append("text") + .attr('id', function(d) { return 'nodeInfoTextID' + d.id; }) + .attr('x', rectNode.width / 2 + tooltip.textMargin) + .attr('y', rectNode.height / 2 + tooltip.textMargin * 2) + .attr('width', tooltip.width) + .attr('height', tooltip.height) + .attr('class', 'tooltip-text') + .style('fill', 'white') + .append("tspan") + .text(function(d) {return 'Name: ' + d.data.nodeName;}) + .append("tspan") + .attr('x', rectNode.width / 2 + tooltip.textMargin) + .attr('dy', '1.5em') + .text(function(d) {return 'Id: ' + d.data.id;}); + + // Transition nodes to their new position. + var nodeUpdate = node.transition().duration(duration) + .attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; }); + nodesTooltip.transition().duration(duration) + .attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; }); + + nodeUpdate.select('rect') + .attr('class', function(d) { return d._children ? 'node-rect-closed' : 'node-rect'; }); + + nodeUpdate.select('text').style('fill-opacity', 1); + + // Transition exiting nodes to the parent's new position + var nodeExit = node.exit().transition().duration(duration) + .attr('transform', function(d) { return 'translate(' + source.y + ',' + source.x + ')'; }) + .remove(); + nodesTooltip.exit().transition().duration(duration) + .attr('transform', function(d) { return 'translate(' + source.y + ',' + source.x + ')'; }) + .remove(); + + nodeExit.select('text').style('fill-opacity', 1e-6); + + + // 2) ******************* Update the links ******************* + var link = linkGroup.selectAll('path').data(links, function(d) { + return d.target.id; + }); + var linkTooltip = linkGroupToolTip.selectAll('g').data(links, function(d) { + return d.target.id; + }); + + d3.selection.prototype.moveToFront = function() { + return this.each(function(){ + this.parentNode.appendChild(this); + }); + }; + + // Enter any new links at the parent's previous position. + // Enter any new links at the parent's previous position. + var linkenter = link.enter().insert('path', 'g') + .attr('class', 'link') + .attr('id', function(d) { return 'linkID' + d.target.id; }) + .attr('d', function(d) { return diagonal(d); }) + .attr('marker-end', 'url(#end-arrow)') + //.attr('marker-start', function(d) { return linkMarkerStart(d.target.link.direction, false); }) + .on('mouseover', function(d) { + d3.select(this).moveToFront(); + + d3.select(this).attr('marker-end', 'url(#end-arrow-selected)'); + d3.select(this).attr('marker-start', linkMarkerStart(d.target.link.direction, true)); + d3.select(this).attr('class', 'linkselected'); + + $('#tooltipLinkID' + d.target.id).attr('x', (d.target.y + rectNode.width - d.source.y) / 2 + d.source.y); + $('#tooltipLinkID' + d.target.id).attr('y', (d.target.x - d.source.x) / 2 + d.source.x); + $('#tooltipLinkID' + d.target.id).css('visibility', 'visible'); + $('#tooltipLinkTextID' + d.target.id).css('visibility', 'visible'); + }) + .on('mouseout', function(d) { + d3.select(this).attr('marker-end', 'url(#end-arrow)'); + d3.select(this).attr('marker-start', linkMarkerStart(d.target.link.direction, false)); + d3.select(this).attr('class', 'link'); + $('#tooltipLinkID' + d.target.id).css('visibility', 'hidden'); + $('#tooltipLinkTextID' + d.target.id).css('visibility', 'hidden'); + }); + + linkTooltip.enter().append('rect') + .attr('id', function(d) { return 'tooltipLinkID' + d.target.id; }) + .attr('class', 'tooltip-box') + .style('fill-opacity', 0.8) + .attr('x', function(d) { return (d.target.y + rectNode.width - d.source.y) / 2 + d.source.y; }) + .attr('y', function(d) { return (d.target.x - d.source.x) / 2 + d.source.x; }) + .attr('width', tooltip.width) + .attr('height', tooltip.height) + .on('mouseover', function(d) { + $('#tooltipLinkID' + d.target.id).css('visibility', 'visible'); + $('#tooltipLinkTextID' + d.target.id).css('visibility', 'visible'); + // After selected a link, the cursor can be hover the tooltip, that's why we still need to highlight the link and the arrow + $('#linkID' + d.target.id).attr('class', 'linkselected'); + $('#linkID' + d.target.id).attr('marker-end', 'url(#end-arrow-selected)'); + $('#linkID' + d.target.id).attr('marker-start', linkMarkerStart(d.target.link.direction, true)); + + removeMouseEvents(); + }) + .on('mouseout', function(d) { + $('#tooltipLinkID' + d.target.id).css('visibility', 'hidden'); + $('#tooltipLinkTextID' + d.target.id).css('visibility', 'hidden'); + $('#linkID' + d.target.id).attr('class', 'link'); + $('#linkID' + d.target.id).attr('marker-end', 'url(#end-arrow)'); + $('#linkID' + d.target.id).attr('marker-start', linkMarkerStart(d.target.link.direction, false)); + + reactivateMouseEvents(); + }); + + linkTooltip.enter().append('text') + .attr('id', function(d) { return 'tooltipLinkTextID' + d.target.id; }) + .attr('class', 'tooltip-text') + .attr('x', function(d) { return (d.target.y + rectNode.width - d.source.y) / 2 + d.source.y + tooltip.textMargin; }) + .attr('y', function(d) { return (d.target.x - d.source.x) / 2 + d.source.x + tooltip.textMargin * 2; }) + .attr('width', tooltip.width) + .attr('height', tooltip.height) + .style('fill', 'white') + .append("tspan") + //.text(function(d) { return linkType(d.target.link); }) + .append("tspan") + .attr('x', function(d) { return (d.target.y + rectNode.width - d.source.y) / 2 + d.source.y + tooltip.textMargin; }) + .attr('dy', '1.5em') + .text(function(d) {return d.target.data.nodeName;}); + + // Transition links to their new position. + var linkUpdate = link.transition().duration(duration) + .attr('d', function(d) { return diagonal(d); }); + linkTooltip.transition().duration(duration) + .attr('d', function(d) { return diagonal(d); }); + + // Transition exiting nodes to the parent's new position. + link.exit().transition() + .remove(); + + linkTooltip.exit().transition() + .remove(); + + // Stash the old positions for transition. + nodes.forEach(function(d) { + d.x0 = d.x; + d.y0 = d.y; + }); + } + + // Zoom functionnality is desactivated (user can use browser Ctrl + mouse wheel shortcut) + function zoomAndDrag() { + //var scale = d3.event.scale, + var scale = 1, + translation = d3.event.translate, + tbound = -height * scale, + bbound = height * scale, + lbound = (-width + margin.right) * scale, + rbound = (width - margin.left) * scale; + // limit translation to thresholds + translation = [ + Math.max(Math.min(translation[0], rbound), lbound), + Math.max(Math.min(translation[1], bbound), tbound) + ]; + d3.select('.drawarea') + .attr('transform', 'translate(' + translation + ')' + + ' scale(' + scale + ')'); + } + + // Toggle children on click. + function click(d) { + if (d.children) { + d._children = d.children; + d.children = null; + } else { + d.children = d._children; + d._children = null; + } + update(d); + } + + // Breadth-first traversal of the tree + // func function is processed on every node of a same level + // return the max level + function breadthFirstTraversal(tree, func) + { + var max = 0; + if (tree && tree.length > 0) + { + var currentDepth = tree[0].depth; + var fifo = []; + var currentLevel = []; + + fifo.push(tree[0]); + while (fifo.length > 0) { + var node = fifo.shift(); + if (node.depth > currentDepth) { + func(currentLevel); + currentDepth++; + max = Math.max(max, currentLevel.length); + currentLevel = []; + } + currentLevel.push(node); + if (node.children) { + for (var j = 0; j < node.children.length; j++) { + fifo.push(node.children[j]); + } + } + } + func(currentLevel); + return Math.max(max, currentLevel.length); + } + return 0; + } + + // x = ordoninates and y = abscissas + function collision(siblings) { + var minPadding = 5; + if (siblings) { + for (var i = 0; i < siblings.length - 1; i++) + { + if (siblings[i + 1].x - (siblings[i].x + rectNode.height) < minPadding) + siblings[i + 1].x = siblings[i].x + rectNode.height + minPadding; + } + } + } + + function removeMouseEvents() { + // Drag and zoom behaviors are temporarily disabled, so tooltip text can be selected + mousedown = d3.select('#tree-container').select('svg').on('mousedown.zoom'); + d3.select('#tree-container').select('svg').on("mousedown.zoom", null); + } + + function reactivateMouseEvents() { + // Reactivate the drag and zoom behaviors + d3.select('#tree-container').select('svg').on('mousedown.zoom', mousedown); + } + + // Name of the event depends of the browser + function getMouseWheelEvent() { + if (d3.select('#tree-container').select('svg').on('wheel.zoom')) + { + mouseWheelName = 'wheel.zoom'; + return d3.select('#tree-container').select('svg').on('wheel.zoom'); + } + if (d3.select('#tree-container').select('svg').on('mousewheel.zoom') != null) + { + mouseWheelName = 'mousewheel.zoom'; + return d3.select('#tree-container').select('svg').on('mousewheel.zoom'); + } + if (d3.select('#tree-container').select('svg').on('DOMMouseScroll.zoom')) + { + mouseWheelName = 'DOMMouseScroll.zoom'; + return d3.select('#tree-container').select('svg').on('DOMMouseScroll.zoom'); + } + } + + function diagonal(d) { + var p0 = { + x : d.source.x + rectNode.height / 2, + y : (d.source.y + rectNode.width) + }, p3 = { + x : d.target.x + rectNode.height / 2, + y : d.target.y - 12 // -12, so the end arrows are just before the rect node + }, m = (p0.y + p3.y) / 2, p = [ p0, { + x : p0.x, + y : m + }, { + x : p3.x, + y : m + }, p3 ]; + p = p.map(function(d) { + return [ d.y, d.x ]; + }); + return 'M' + p[0] + 'C' + p[1] + ' ' + p[2] + ' ' + p[3]; + } + + function initDropShadow() { + var filter = defs.append("filter") + .attr("id", "drop-shadow") + .attr("color-interpolation-filters", "sRGB"); + + filter.append("feOffset") + .attr("result", "offOut") + .attr("in", "SourceGraphic") + .attr("dx", 0) + .attr("dy", 0); + + filter.append("feGaussianBlur") + .attr("stdDeviation", 2); + + filter.append("feOffset") + .attr("dx", 2) + .attr("dy", 2) + .attr("result", "shadow"); + + filter.append("feComposite") + .attr("in", 'offOut') + .attr("in2", 'shadow') + .attr("operator", "over"); + } + + function initArrowDef() { + // Build the arrows definitions + // End arrow + defs.append('marker') + .attr('id', 'end-arrow') + .attr('viewBox', '0 -5 10 10') + .attr('refX', 0) + .attr('refY', 0) + .attr('markerWidth', 6) + .attr('markerHeight', 6) + .attr('orient', 'auto') + .attr('class', 'arrow') + .append('path') + .attr('d', 'M0,-5L10,0L0,5'); + + // End arrow selected + defs.append('marker') + .attr('id', 'end-arrow-selected') + .attr('viewBox', '0 -5 10 10') + .attr('refX', 0) + .attr('refY', 0) + .attr('markerWidth', 6) + .attr('markerHeight', 6) + .attr('orient', 'auto') + .attr('class', 'arrowselected') + .append('path') + .attr('d', 'M0,-5L10,0L0,5'); + + // Start arrow + defs.append('marker') + .attr('id', 'start-arrow') + .attr('viewBox', '0 -5 10 10') + .attr('refX', 0) + .attr('refY', 0) + .attr('markerWidth', 6) + .attr('markerHeight', 6) + .attr('orient', 'auto') + .attr('class', 'arrow') + .append('path') + .attr('d', 'M10,-5L0,0L10,5'); + + // Start arrow selected + defs.append('marker') + .attr('id', 'start-arrow-selected') + .attr('viewBox', '0 -5 10 10') + .attr('refX', 0) + .attr('refY', 0) + .attr('markerWidth', 6) + .attr('markerHeight', 6) + .attr('orient', 'auto') + .attr('class', 'arrowselected') + .append('path') + .attr('d', 'M10,-5L0,0L10,5'); + } + } + + } + + controller.$inject = ['$scope', 'serviceControlService']; + + function directive() { + return { + scope: { conversationId: '=conversationId' }, + restrict: 'E', + template: '
', + controller: controller + + }; + } + + directive.$inject = []; + + angular + .module('sc') + .directive('flowDiagram', directive); + +}(window, window.angular, window.d3)); + diff --git a/src/ServicePulse.Host/app/js/services/services.service-control.js b/src/ServicePulse.Host/app/js/services/services.service-control.js index 31e3268896..18ca636bb2 100644 --- a/src/ServicePulse.Host/app/js/services/services.service-control.js +++ b/src/ServicePulse.Host/app/js/services/services.service-control.js @@ -331,6 +331,14 @@ return $http.get(url); } + + function getConversation(conversationId) { + var url = uri.join(scu, 'conversations', conversationId); + return $http.get(url).then(function (response) { + return response.data; + }); + } + var service = { getServiceControlMetadata: getServiceControlMetadata, @@ -368,7 +376,8 @@ acknowledgeGroup: acknowledgeGroup, getFailedMessageById: getFailedMessageById, getEditAndRetryConfig: getEditAndRetryConfig, - retryEditedMessage : retryEditedMessage + retryEditedMessage : retryEditedMessage, + getConversation: getConversation }; return service; diff --git a/src/ServicePulse.Host/app/js/views/message/controller.js b/src/ServicePulse.Host/app/js/views/message/controller.js index 02c2886f12..b4dc9fc37e 100644 --- a/src/ServicePulse.Host/app/js/views/message/controller.js +++ b/src/ServicePulse.Host/app/js/views/message/controller.js @@ -66,6 +66,7 @@ if (!angular.isDefined(message.headers)) { return serviceControlService.getMessage(message.message_id).then(function (response) { message.headers = response.message.headers; + message.conversationId = message.headers["NServiceBus.ConversationId"].value; }, function () { message.headersUnavailable = "message headers unavailable"; }); diff --git a/src/ServicePulse.Host/app/js/views/message/messages-view.html b/src/ServicePulse.Host/app/js/views/message/messages-view.html index 99a6649a68..6afa4eb012 100644 --- a/src/ServicePulse.Host/app/js/views/message/messages-view.html +++ b/src/ServicePulse.Host/app/js/views/message/messages-view.html @@ -65,6 +65,7 @@
Stacktrace
Headers
Message body
+
Flow Diagram
{{ vm.message.exception.message }}
@@ -82,6 +83,7 @@
{{vm.message.headersUnavailable}}
{{vm.message.messageBody}}
{{vm.message.bodyUnavailable}}
+ diff --git a/src/ServicePulse.Host/package-lock.json b/src/ServicePulse.Host/package-lock.json index 3ef65fd165..c280c09230 100644 --- a/src/ServicePulse.Host/package-lock.json +++ b/src/ServicePulse.Host/package-lock.json @@ -19,7 +19,7 @@ "angularjs-toaster": "^1.1.0", "animate.css": "^3.4.0", "bootstrap": "3.4.1", - "d3": "^4.9.1", + "d3": "^5.16.0", "highlight.js": "^11.3.1", "highlightjs-badge": "^0.1.9", "jquery": "^3.0.0", @@ -2235,40 +2235,41 @@ "dev": true }, "node_modules/d3": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-4.13.0.tgz", - "integrity": "sha512-l8c4+0SldjVKLaE2WG++EQlqD7mh/dmQjvi2L2lKPadAVC+TbJC4ci7Uk9bRi+To0+ansgsS0iWfPjD7DBy+FQ==", - "dependencies": { - "d3-array": "1.2.1", - "d3-axis": "1.0.8", - "d3-brush": "1.0.4", - "d3-chord": "1.0.4", - "d3-collection": "1.0.4", - "d3-color": "1.0.3", - "d3-dispatch": "1.0.3", - "d3-drag": "1.2.1", - "d3-dsv": "1.0.8", - "d3-ease": "1.0.3", - "d3-force": "1.1.0", - "d3-format": "1.2.2", - "d3-geo": "1.9.1", - "d3-hierarchy": "1.1.5", - "d3-interpolate": "1.1.6", - "d3-path": "1.0.5", - "d3-polygon": "1.0.3", - "d3-quadtree": "1.0.3", - "d3-queue": "3.0.7", - "d3-random": "1.1.0", - "d3-request": "1.0.6", - "d3-scale": "1.0.7", - "d3-selection": "1.3.0", - "d3-shape": "1.2.0", - "d3-time": "1.0.8", - "d3-time-format": "2.1.1", - "d3-timer": "1.0.7", - "d3-transition": "1.1.1", - "d3-voronoi": "1.1.2", - "d3-zoom": "1.7.1" + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz", + "integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==", + "dependencies": { + "d3-array": "1", + "d3-axis": "1", + "d3-brush": "1", + "d3-chord": "1", + "d3-collection": "1", + "d3-color": "1", + "d3-contour": "1", + "d3-dispatch": "1", + "d3-drag": "1", + "d3-dsv": "1", + "d3-ease": "1", + "d3-fetch": "1", + "d3-force": "1", + "d3-format": "1", + "d3-geo": "1", + "d3-hierarchy": "1", + "d3-interpolate": "1", + "d3-path": "1", + "d3-polygon": "1", + "d3-quadtree": "1", + "d3-random": "1", + "d3-scale": "2", + "d3-scale-chromatic": "1", + "d3-selection": "1", + "d3-shape": "1", + "d3-time": "1", + "d3-time-format": "2", + "d3-timer": "1", + "d3-transition": "1", + "d3-voronoi": "1", + "d3-zoom": "1" } }, "node_modules/d3-array": { @@ -2312,6 +2313,14 @@ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.0.3.tgz", "integrity": "sha1-vHZD/KjlOoNH4vva/6I2eWtYUJs=" }, + "node_modules/d3-contour": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", + "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", + "dependencies": { + "d3-array": "^1.1.1" + } + }, "node_modules/d3-dispatch": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.3.tgz", @@ -2327,9 +2336,9 @@ } }, "node_modules/d3-dsv": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.0.8.tgz", - "integrity": "sha512-IVCJpQ+YGe3qu6odkPQI0KPqfxkhbP/oM1XhhE/DFiYmcXKfCRub4KXyiuehV1d4drjWVXHUWx4gHqhdZb6n/A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", + "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", "dependencies": { "commander": "2", "iconv-lite": "0.4", @@ -2352,6 +2361,14 @@ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.3.tgz", "integrity": "sha1-aL+8NJM4o4DETYrMT7wzBKotjA4=" }, + "node_modules/d3-fetch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.2.0.tgz", + "integrity": "sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==", + "dependencies": { + "d3-dsv": "1" + } + }, "node_modules/d3-force": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.1.0.tgz", @@ -2364,9 +2381,9 @@ } }, "node_modules/d3-format": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.2.2.tgz", - "integrity": "sha512-zH9CfF/3C8zUI47nsiKfD0+AGDEuM8LwBIP7pBVpyR4l/sKkZqITmMtxRp04rwBrlshIZ17XeFAaovN3++wzkw==" + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" }, "node_modules/d3-geo": { "version": "1.9.1", @@ -2404,41 +2421,33 @@ "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.3.tgz", "integrity": "sha1-rHmH4+I/6AWpkPKOG1DTj8uCJDg=" }, - "node_modules/d3-queue": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/d3-queue/-/d3-queue-3.0.7.tgz", - "integrity": "sha1-yTouVLQXwJWRKdfXP2z31Ckudhg=" - }, "node_modules/d3-random": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.0.tgz", "integrity": "sha1-ZkLlBsb6OmSFldKyRpeIqNElKdM=" }, - "node_modules/d3-request": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-request/-/d3-request-1.0.6.tgz", - "integrity": "sha512-FJj8ySY6GYuAJHZMaCQ83xEYE4KbkPkmxZ3Hu6zA1xxG2GD+z6P+Lyp+zjdsHf0xEbp2xcluDI50rCS855EQ6w==", - "dependencies": { - "d3-collection": "1", - "d3-dispatch": "1", - "d3-dsv": "1", - "xmlhttprequest": "1" - } - }, "node_modules/d3-scale": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.7.tgz", - "integrity": "sha512-KvU92czp2/qse5tUfGms6Kjig0AhHOwkzXG0+PqIJB3ke0WUv088AHMZI0OssO9NCkXt4RP8yju9rpH8aGB7Lw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", + "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", "dependencies": { "d3-array": "^1.2.0", "d3-collection": "1", - "d3-color": "1", "d3-format": "1", "d3-interpolate": "1", "d3-time": "1", "d3-time-format": "2" } }, + "node_modules/d3-scale-chromatic": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", + "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "dependencies": { + "d3-color": "1", + "d3-interpolate": "1" + } + }, "node_modules/d3-selection": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.3.0.tgz", @@ -2453,14 +2462,14 @@ } }, "node_modules/d3-time": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.8.tgz", - "integrity": "sha512-YRZkNhphZh3KcnBfitvF3c6E0JOFGikHZ4YqD+Lzv83ZHn1/u6yGenRU1m+KAk9J1GnZMnKcrtfvSktlA1DXNQ==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" }, "node_modules/d3-time-format": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.1.tgz", - "integrity": "sha512-8kAkymq2WMfzW7e+s/IUNAtN/y3gZXGRrdGfo6R8NKPAA85UBTxZg5E61bR6nLwjPjj4d3zywSQe1CkYLPFyrw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", + "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", "dependencies": { "d3-time": "1" } @@ -6460,7 +6469,7 @@ "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, "node_modules/rx": { "version": "4.1.0", @@ -8344,14 +8353,6 @@ "node": ">=4" } }, - "node_modules/xmlhttprequest": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -10489,40 +10490,41 @@ "dev": true }, "d3": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-4.13.0.tgz", - "integrity": "sha512-l8c4+0SldjVKLaE2WG++EQlqD7mh/dmQjvi2L2lKPadAVC+TbJC4ci7Uk9bRi+To0+ansgsS0iWfPjD7DBy+FQ==", - "requires": { - "d3-array": "1.2.1", - "d3-axis": "1.0.8", - "d3-brush": "1.0.4", - "d3-chord": "1.0.4", - "d3-collection": "1.0.4", - "d3-color": "1.0.3", - "d3-dispatch": "1.0.3", - "d3-drag": "1.2.1", - "d3-dsv": "1.0.8", - "d3-ease": "1.0.3", - "d3-force": "1.1.0", - "d3-format": "1.2.2", - "d3-geo": "1.9.1", - "d3-hierarchy": "1.1.5", - "d3-interpolate": "1.1.6", - "d3-path": "1.0.5", - "d3-polygon": "1.0.3", - "d3-quadtree": "1.0.3", - "d3-queue": "3.0.7", - "d3-random": "1.1.0", - "d3-request": "1.0.6", - "d3-scale": "1.0.7", - "d3-selection": "1.3.0", - "d3-shape": "1.2.0", - "d3-time": "1.0.8", - "d3-time-format": "2.1.1", - "d3-timer": "1.0.7", - "d3-transition": "1.1.1", - "d3-voronoi": "1.1.2", - "d3-zoom": "1.7.1" + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz", + "integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==", + "requires": { + "d3-array": "1", + "d3-axis": "1", + "d3-brush": "1", + "d3-chord": "1", + "d3-collection": "1", + "d3-color": "1", + "d3-contour": "1", + "d3-dispatch": "1", + "d3-drag": "1", + "d3-dsv": "1", + "d3-ease": "1", + "d3-fetch": "1", + "d3-force": "1", + "d3-format": "1", + "d3-geo": "1", + "d3-hierarchy": "1", + "d3-interpolate": "1", + "d3-path": "1", + "d3-polygon": "1", + "d3-quadtree": "1", + "d3-random": "1", + "d3-scale": "2", + "d3-scale-chromatic": "1", + "d3-selection": "1", + "d3-shape": "1", + "d3-time": "1", + "d3-time-format": "2", + "d3-timer": "1", + "d3-transition": "1", + "d3-voronoi": "1", + "d3-zoom": "1" } }, "d3-array": { @@ -10566,6 +10568,14 @@ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.0.3.tgz", "integrity": "sha1-vHZD/KjlOoNH4vva/6I2eWtYUJs=" }, + "d3-contour": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", + "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", + "requires": { + "d3-array": "^1.1.1" + } + }, "d3-dispatch": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.3.tgz", @@ -10581,9 +10591,9 @@ } }, "d3-dsv": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.0.8.tgz", - "integrity": "sha512-IVCJpQ+YGe3qu6odkPQI0KPqfxkhbP/oM1XhhE/DFiYmcXKfCRub4KXyiuehV1d4drjWVXHUWx4gHqhdZb6n/A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", + "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", "requires": { "commander": "2", "iconv-lite": "0.4", @@ -10595,6 +10605,14 @@ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.3.tgz", "integrity": "sha1-aL+8NJM4o4DETYrMT7wzBKotjA4=" }, + "d3-fetch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.2.0.tgz", + "integrity": "sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==", + "requires": { + "d3-dsv": "1" + } + }, "d3-force": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.1.0.tgz", @@ -10607,9 +10625,9 @@ } }, "d3-format": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.2.2.tgz", - "integrity": "sha512-zH9CfF/3C8zUI47nsiKfD0+AGDEuM8LwBIP7pBVpyR4l/sKkZqITmMtxRp04rwBrlshIZ17XeFAaovN3++wzkw==" + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" }, "d3-geo": { "version": "1.9.1", @@ -10647,41 +10665,33 @@ "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.3.tgz", "integrity": "sha1-rHmH4+I/6AWpkPKOG1DTj8uCJDg=" }, - "d3-queue": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/d3-queue/-/d3-queue-3.0.7.tgz", - "integrity": "sha1-yTouVLQXwJWRKdfXP2z31Ckudhg=" - }, "d3-random": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.0.tgz", "integrity": "sha1-ZkLlBsb6OmSFldKyRpeIqNElKdM=" }, - "d3-request": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-request/-/d3-request-1.0.6.tgz", - "integrity": "sha512-FJj8ySY6GYuAJHZMaCQ83xEYE4KbkPkmxZ3Hu6zA1xxG2GD+z6P+Lyp+zjdsHf0xEbp2xcluDI50rCS855EQ6w==", - "requires": { - "d3-collection": "1", - "d3-dispatch": "1", - "d3-dsv": "1", - "xmlhttprequest": "1" - } - }, "d3-scale": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.7.tgz", - "integrity": "sha512-KvU92czp2/qse5tUfGms6Kjig0AhHOwkzXG0+PqIJB3ke0WUv088AHMZI0OssO9NCkXt4RP8yju9rpH8aGB7Lw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", + "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", "requires": { "d3-array": "^1.2.0", "d3-collection": "1", - "d3-color": "1", "d3-format": "1", "d3-interpolate": "1", "d3-time": "1", "d3-time-format": "2" } }, + "d3-scale-chromatic": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", + "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "requires": { + "d3-color": "1", + "d3-interpolate": "1" + } + }, "d3-selection": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.3.0.tgz", @@ -10696,14 +10706,14 @@ } }, "d3-time": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.8.tgz", - "integrity": "sha512-YRZkNhphZh3KcnBfitvF3c6E0JOFGikHZ4YqD+Lzv83ZHn1/u6yGenRU1m+KAk9J1GnZMnKcrtfvSktlA1DXNQ==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" }, "d3-time-format": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.1.tgz", - "integrity": "sha512-8kAkymq2WMfzW7e+s/IUNAtN/y3gZXGRrdGfo6R8NKPAA85UBTxZg5E61bR6nLwjPjj4d3zywSQe1CkYLPFyrw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", + "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", "requires": { "d3-time": "1" } @@ -13918,7 +13928,7 @@ "rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, "rx": { "version": "4.1.0", @@ -15477,11 +15487,6 @@ "mkdirp": "^0.5.1" } }, - "xmlhttprequest": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/src/ServicePulse.Host/package.json b/src/ServicePulse.Host/package.json index af6c3c03be..bc77ef528c 100644 --- a/src/ServicePulse.Host/package.json +++ b/src/ServicePulse.Host/package.json @@ -14,7 +14,7 @@ "angularjs-toaster": "^1.1.0", "animate.css": "^3.4.0", "bootstrap": "3.4.1", - "d3": "^4.9.1", + "d3": "^5.16.0", "highlight.js": "^11.3.1", "highlightjs-badge": "^0.1.9", "jquery": "^3.0.0", From f7ba6bddc59dd448e60082ac6327149b41074078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Mon, 27 Jun 2022 13:34:21 +0200 Subject: [PATCH 02/28] Changing the flow-diagram to incorporate for events, timeouts, commands. --- src/ServicePulse.Host/app/css/particular.css | 97 +-- .../directives/ui.particular.flow-diagram.js | 757 +++++------------- .../app/js/views/message/controller.js | 2 +- .../app/js/views/message/messages-view.html | 2 +- src/ServicePulse.Host/package.json | 2 +- 5 files changed, 213 insertions(+), 647 deletions(-) diff --git a/src/ServicePulse.Host/app/css/particular.css b/src/ServicePulse.Host/app/css/particular.css index d056fb398e..09674fa457 100644 --- a/src/ServicePulse.Host/app/css/particular.css +++ b/src/ServicePulse.Host/app/css/particular.css @@ -3372,100 +3372,25 @@ section[name="platformconnection"] li { margin-bottom: 15px; } -#tree-container { - position: absolute; - left: 0px; - width: 100%; -} - -.svgContainer { - display: block; - margin: auto; -} - -.node { - cursor: pointer; +.node rect { + fill: #fff; + stroke: steelblue; + stroke-width: 3px; } -.node-rect { +.node rect.error { + stroke: red; } -.node-rect-closed { - stroke-width: 2px; - stroke: rgb(0,0,0); +.node text { + font: 12px sans-serif; } .link { fill: none; - stroke: lightsteelblue; + stroke: #ccc; stroke-width: 2px; } - -.linkselected { - fill: none; - stroke: tomato; - stroke-width: 2px; -} - -.arrow { - fill: lightsteelblue; - stroke-width: 1px; -} - -.arrowselected { - fill: tomato; - stroke-width: 2px; -} - -.link text { - font: 7px sans-serif; - fill: #CC0000; -} - -.wordwrap { - white-space: pre-wrap; /* CSS3 */ - white-space: -moz-pre-wrap; /* Firefox */ - white-space: -pre-wrap; /* Opera <7 */ - white-space: -o-pre-wrap; /* Opera 7 */ - word-wrap: break-word; /* IE */ -} - -.node-text { - font: 7px sans-serif; - color: white; -} - -.tooltip-text-container { - height: 100%; - width: 100%; -} - -.tooltip-text { - visibility: hidden; - font: 7px sans-serif; - color: white; - display: block; - padding: 5px; -} - -.tooltip-box { - background: rgba(0, 0, 0, 0.7); - visibility: hidden; - position: absolute; - border-style: solid; - border-width: 1px; - border-color: black; - border-top-right-radius: 0.5em; -} - -p { - display: inline; -} - -.textcolored { - color: orange; -} - -a.exchangeName { - color: orange; +.link.event{ + stroke-dasharray: 5,3; } \ No newline at end of file diff --git a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js index 527a942f1c..bf455310a0 100644 --- a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js +++ b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js @@ -2,24 +2,24 @@ 'use strict'; - function controller($scope, serviceControlService) { - serviceControlService.getConversation('ff15a05d-8ce5-4069-aff2-aeab00bd5fef').then(messages => { + function controller($scope, serviceControlService) { + serviceControlService.getConversation($scope.conversationId).then(messages => { var tree = createTreeStructure(messages.map(x => mapMessage(x))); - treeBoxes(null, tree[0]); + drawTree(tree[0]); }); function createTreeStructure(messages) { var map = {}, node, roots = [], i; for (i = 0; i < messages.length; i += 1) { - map[messages[i].id] = i; // initialize the map + map[messages[i].messageId] = i; // initialize the map messages[i].children = []; // initialize the children } for (i = 0; i < messages.length; i += 1) { node = messages[i]; - if (node.parentId) { + if (node.parentId && map[node.parentId] ) { // if you have dangling branches check that map[node.parentId] exists - messages[map[node.parentId]].children.push(node); + messages[map[node.parentId]].children.push(node); } else { roots.push(node); } @@ -36,397 +36,215 @@ return { "nodeName": message.message_type, - "id": message.message_id, + "id": message.id, + "messageId": message.message_id, "parentId": parentid, - "type": message.headers.find(x => x.key === 'NServiceBus.MessageIntent').value === 'Publish' ? 'Event' : 'Command', + "type": message.headers.findIndex(x => x.key === 'NServiceBus.DeliverAt') > -1 ? 'Delay' : message.headers.find(x => x.key === 'NServiceBus.MessageIntent').value === 'Publish' ? 'Event' : 'Command', + "isError": message.headers.findIndex(x => x.key === 'NServiceBus.ExceptionInfo.ExceptionType') > -1, "link" : { - "name" : "Link "+message.message_id, - "nodeName" : message.message_id + "name" : "Link "+message.id, + "nodeName" : message.id } }; } + + // Set the dimensions and margins of the diagram + var margin = {top: 20, right: 90, bottom: 30, left: 90}, + width = 960 - margin.left - margin.right, + height = 500 - margin.top - margin.bottom; + var rectNode = { width : 120, height : 90, textMargin : 5 } +// append the svg object to the body of the page +// appends a 'group' element to 'svg' +// moves the 'group' element to the top left margin + var svg = d3.select("#tree-container").append("svg") + .attr("width", width + margin.right + margin.left) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", "translate(" + + margin.left + "," + margin.top + ")"); + + svg.append('defs').append('marker') + .attr('id', 'end-arrow') + .attr('viewBox', '0 -5 10 10') + .attr('refX', 0) + .attr('refY', 0) + .attr('markerWidth', 6) + .attr('markerHeight', 6) + .attr('orient', 'auto') + .attr('class', 'arrow') + .append('path') + .attr('d', 'M10,-5L0,0L10,5'); - function treeBoxes(urlService, jsonData) - { - var urlService_ = ''; - - var blue = '#337ab7', - green = '#5cb85c', - yellow = '#f0ad4e', - blueText = '#4ab1eb', - purple = '#9467bd'; - - var margin = { - top : 0, - right : 0, - bottom : 0, - left : 0 - }, - // Height and width are redefined later in function of the size of the tree - // (after that the data are loaded) - width = 800 - margin.right - margin.left, - height = 400 - margin.top - margin.bottom; - - var rectNode = { width : 120, height : 45, textMargin : 5 }, - tooltip = { width : 150, height : 40, textMargin : 5 }; - var i = 0, - duration = 750, - root; - - var mousedown; // Use to save temporarily 'mousedown.zoom' value - var mouseWheel, - mouseWheelName, - isKeydownZoom = false; - - var tree; - var baseSvg, - svgGroup, - nodeGroup, // If nodes are not grouped together, after a click the svg node will be set after his corresponding tooltip and will hide it - nodeGroupTooltip, - linkGroup, - linkGroupToolTip, - defs; - - init(urlService, jsonData); - - function init(urlService, jsonData) - { - urlService_ = urlService; - if (urlService && urlService.length > 0) - { - if (urlService.charAt(urlService.length - 1) != '/') - urlService_ += '/'; - } + var i = 0, + duration = 750, + root; - if (jsonData) - drawTree(jsonData); - else - { - console.error(jsonData); - alert('Invalides data.'); - } - } +// declares a tree layout and assigns the size + var treemap = d3.tree().size([height, width]); - function drawTree(jsonData) - { - jsonData = d3.hierarchy(jsonData); - tree = d3.tree(jsonData).size([ height, width ]); - root = jsonData; - root.fixed = true; - - // Dynamically set the height of the main svg container - // breadthFirstTraversal returns the max number of node on a same level - // and colors the nodes - var maxDepth = 0; - var maxTreeWidth = breadthFirstTraversal(tree(root), function(currentLevel) { - maxDepth++; - currentLevel.forEach(function(node) { - if (node.type == 'Event') - node.color = blue; - if (node.type == 'Command') - node.color = green; - }); - }); - height = maxTreeWidth * (rectNode.height + 20) + tooltip.height + 20 - margin.right - margin.left; - width = maxDepth * (rectNode.width * 1.5) + tooltip.width / 2 - margin.top - margin.bottom; - height = 600; - width = 600; - - tree = d3.tree().size([ height, width ]); - root.x0 = height / 2; - root.y0 = 0; - - baseSvg = d3.select('#tree-container').append('svg') - .attr('width', width + margin.right + margin.left) - .attr('height', height + margin.top + margin.bottom) - .attr('class', 'svgContainer'); - // .call(d3.behavior.zoom() - // //.scaleExtent([0.5, 1.5]) // Limit the zoom scale - // .on('zoom', zoomAndDrag)); - - // Mouse wheel is desactivated, else after a first drag of the tree, wheel event drags the tree (instead of scrolling the window) - getMouseWheelEvent(); - d3.select('#tree-container').select('svg').on(mouseWheelName, null); - d3.select('#tree-container').select('svg').on('dblclick.zoom', null); - - svgGroup = baseSvg.append('g') - .attr('class','drawarea') - .append('g') - .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - // SVG elements under nodeGroupTooltip could be associated with nodeGroup, - // same for linkGroupToolTip and linkGroup, - // but this separation allows to manage the order on which elements are drew - // and so tooltips are always on top. - nodeGroup = svgGroup.append('g') - .attr('id', 'nodes'); - linkGroup = svgGroup.append('g') - .attr('id', 'links'); - linkGroupToolTip = svgGroup.append('g') - .attr('id', 'linksTooltips'); - nodeGroupTooltip = svgGroup.append('g') - .attr('id', 'nodesTooltips'); - - defs = baseSvg.append('defs'); - initArrowDef(); - initDropShadow(); - - update(root); + function drawTree(treeData) { +// Assigns parent, children, height, depth + root = d3.hierarchy(treeData, function (d) { + return d.children; + }); + root.x0 = height / 2; + root.y0 = 0; + +// Collapse after the second level + //root.children.forEach(collapse); + + update(root); + } + +// Collapse the node and all it's children + function collapse(d) { + if(d.children) { + d._children = d.children + d._children.forEach(collapse) + d.children = null } + } - function update(source) - { - // Compute the new tree layout - var nodes = root.descendants().reverse(), - links = root.links(); - - // Check if two nodes are in collision on the ordinates axe and move them - breadthFirstTraversal(root.descendants(), collision); - // Normalize for fixed-depth - nodes.forEach(function(d) { - d.y = d.depth * (rectNode.width * 1.5); - }); + function update(source) { - // 1) ******************* Update the nodes ******************* - var node = nodeGroup.selectAll('g.node').data(nodes, function(d) { - return d.id || (d.id = ++i); - }); - var nodesTooltip = nodeGroupTooltip.selectAll('g').data(nodes, function(d) { - return d.id || (d.id = ++i); + // Assigns the x and y position for the nodes + var treeData = treemap(root); + + // Compute the new tree layout. + var nodes = treeData.descendants(), + links = treeData.descendants().slice(1); + + // Normalize for fixed-depth. + nodes.forEach(function(d){ d.y = d.depth * 180}); + + // ****************** Nodes section *************************** + + // Update the nodes... + var node = svg.selectAll('g.node') + .data(nodes, function(d) {return d.id || (d.id = ++i); }); + + // Enter any new modes at the parent's previous position. + var nodeEnter = node.enter().append('g') + .attr('class', 'node') + .attr("transform", function(d) { + return "translate(" + source.y0 + "," + source.x0 + ")"; + }) + .on('click', click); + + // Add rectangle for the nodes + nodeEnter.append('rect') + .attr('class', function(d){return `node ${d.data.type.toLowerCase()} ${d.data.isError ? 'error' : ''}`;}) + .attr('rx', 6) + .attr('ry', 6) + .attr('width', rectNode.width) + .attr('height', rectNode.height) + .style("fill", function(d) { + return d._children ? "lightsteelblue" : "#fff"; }); - // Enter any new nodes at the parent's previous position - // We use "insert" rather than "append", so when a new child node is added (after a click) - // it is added at the top of the group, so it is drawed first - // else the nodes tooltips are drawed before their children nodes and they - // hide them - var nodeEnter = node.enter().insert('g', 'g.node') - .attr('class', 'node') - .attr('transform', function(d) { - return 'translate(' + source.y0 + ',' + source.x0 + ')'; }) - .on('click', function(d) { - click(d); - }); - var nodeEnterTooltip = nodesTooltip.enter().append('g') - .attr('transform', function(d) { - return 'translate(' + source.y0 + ',' + source.x0 + ')'; }); - - nodeEnter.append('g').append('rect') - .attr('rx', 6) - .attr('ry', 6) - .attr('width', rectNode.width) - .attr('height', rectNode.height) - .attr('class', 'node-rect') - .attr('fill', function (d) { return d.color; }) - .attr('filter', 'url(#drop-shadow)'); - - nodeEnter.append('foreignObject') - .attr('x', rectNode.textMargin) - .attr('y', rectNode.textMargin) - .attr('width', function() { - return (rectNode.width - rectNode.textMargin * 2) < 0 ? 0 - : (rectNode.width - rectNode.textMargin * 2) - }) - .attr('height', function() { - return (rectNode.height - rectNode.textMargin * 2) < 0 ? 0 - : (rectNode.height - rectNode.textMargin * 2) - }) - .append('xhtml').html(function(d) { - return '
' - + '' + d.data.nodeName + '

' - + 'Code: ' + d.data.id + '
' - + 'Type: ' + d.data.type + '
' - + '
'; + nodeEnter.append('foreignObject') + .attr('x', rectNode.textMargin) + .attr('y', rectNode.textMargin) + .attr('width', function() { + return (rectNode.width - rectNode.textMargin * 2) < 0 ? 0 + : (rectNode.width - rectNode.textMargin * 2) + }) + .attr('height', function() { + return (rectNode.height - rectNode.textMargin * 2) < 0 ? 0 + : (rectNode.height - rectNode.textMargin * 2) }) - .on('mouseover', function(d) { - $('#nodeInfoID' + d.id).css('visibility', 'visible'); - $('#nodeInfoTextID' + d.id).css('visibility', 'visible'); - }) - .on('mouseout', function(d) { - $('#nodeInfoID' + d.id).css('visibility', 'hidden'); - $('#nodeInfoTextID' + d.id).css('visibility', 'hidden'); - }); - - nodeEnterTooltip.append("rect") - .attr('id', function(d) { return 'nodeInfoID' + d.id; }) - .attr('x', rectNode.width / 2) - .attr('y', rectNode.height / 2) - .attr('width', tooltip.width) - .attr('height', tooltip.height) - .attr('class', 'tooltip-box') - .style('fill-opacity', 0.8) - .on('mouseover', function(d) { - $('#nodeInfoID' + d.id).css('visibility', 'visible'); - $('#nodeInfoTextID' + d.id).css('visibility', 'visible'); - removeMouseEvents(); - }) - .on('mouseout', function(d) { - $('#nodeInfoID' + d.id).css('visibility', 'hidden'); - $('#nodeInfoTextID' + d.id).css('visibility', 'hidden'); - reactivateMouseEvents(); - }); - - nodeEnterTooltip.append("text") - .attr('id', function(d) { return 'nodeInfoTextID' + d.id; }) - .attr('x', rectNode.width / 2 + tooltip.textMargin) - .attr('y', rectNode.height / 2 + tooltip.textMargin * 2) - .attr('width', tooltip.width) - .attr('height', tooltip.height) - .attr('class', 'tooltip-text') - .style('fill', 'white') - .append("tspan") - .text(function(d) {return 'Name: ' + d.data.nodeName;}) - .append("tspan") - .attr('x', rectNode.width / 2 + tooltip.textMargin) - .attr('dy', '1.5em') - .text(function(d) {return 'Id: ' + d.data.id;}); - - // Transition nodes to their new position. - var nodeUpdate = node.transition().duration(duration) - .attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; }); - nodesTooltip.transition().duration(duration) - .attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; }); - - nodeUpdate.select('rect') - .attr('class', function(d) { return d._children ? 'node-rect-closed' : 'node-rect'; }); - - nodeUpdate.select('text').style('fill-opacity', 1); - - // Transition exiting nodes to the parent's new position - var nodeExit = node.exit().transition().duration(duration) - .attr('transform', function(d) { return 'translate(' + source.y + ',' + source.x + ')'; }) - .remove(); - nodesTooltip.exit().transition().duration(duration) - .attr('transform', function(d) { return 'translate(' + source.y + ',' + source.x + ')'; }) - .remove(); - - nodeExit.select('text').style('fill-opacity', 1e-6); - - - // 2) ******************* Update the links ******************* - var link = linkGroup.selectAll('path').data(links, function(d) { - return d.target.id; + .append('xhtml').html(function(d) { + return '
' + + `${d.data.nodeName}
+Id: ${(d.data.isError ? `${d.data.id}` : d.data.id)}
+
`; + }) + + // UPDATE + var nodeUpdate = nodeEnter.merge(node); + + // Transition to the proper position for the node + nodeUpdate.transition() + .duration(duration) + .attr("transform", function(d) { + return "translate(" + d.y + "," + d.x + ")"; }); - var linkTooltip = linkGroupToolTip.selectAll('g').data(links, function(d) { - return d.target.id; - }); - - d3.selection.prototype.moveToFront = function() { - return this.each(function(){ - this.parentNode.appendChild(this); - }); - }; - - // Enter any new links at the parent's previous position. - // Enter any new links at the parent's previous position. - var linkenter = link.enter().insert('path', 'g') - .attr('class', 'link') - .attr('id', function(d) { return 'linkID' + d.target.id; }) - .attr('d', function(d) { return diagonal(d); }) - .attr('marker-end', 'url(#end-arrow)') - //.attr('marker-start', function(d) { return linkMarkerStart(d.target.link.direction, false); }) - .on('mouseover', function(d) { - d3.select(this).moveToFront(); - - d3.select(this).attr('marker-end', 'url(#end-arrow-selected)'); - d3.select(this).attr('marker-start', linkMarkerStart(d.target.link.direction, true)); - d3.select(this).attr('class', 'linkselected'); - - $('#tooltipLinkID' + d.target.id).attr('x', (d.target.y + rectNode.width - d.source.y) / 2 + d.source.y); - $('#tooltipLinkID' + d.target.id).attr('y', (d.target.x - d.source.x) / 2 + d.source.x); - $('#tooltipLinkID' + d.target.id).css('visibility', 'visible'); - $('#tooltipLinkTextID' + d.target.id).css('visibility', 'visible'); - }) - .on('mouseout', function(d) { - d3.select(this).attr('marker-end', 'url(#end-arrow)'); - d3.select(this).attr('marker-start', linkMarkerStart(d.target.link.direction, false)); - d3.select(this).attr('class', 'link'); - $('#tooltipLinkID' + d.target.id).css('visibility', 'hidden'); - $('#tooltipLinkTextID' + d.target.id).css('visibility', 'hidden'); - }); - - linkTooltip.enter().append('rect') - .attr('id', function(d) { return 'tooltipLinkID' + d.target.id; }) - .attr('class', 'tooltip-box') - .style('fill-opacity', 0.8) - .attr('x', function(d) { return (d.target.y + rectNode.width - d.source.y) / 2 + d.source.y; }) - .attr('y', function(d) { return (d.target.x - d.source.x) / 2 + d.source.x; }) - .attr('width', tooltip.width) - .attr('height', tooltip.height) - .on('mouseover', function(d) { - $('#tooltipLinkID' + d.target.id).css('visibility', 'visible'); - $('#tooltipLinkTextID' + d.target.id).css('visibility', 'visible'); - // After selected a link, the cursor can be hover the tooltip, that's why we still need to highlight the link and the arrow - $('#linkID' + d.target.id).attr('class', 'linkselected'); - $('#linkID' + d.target.id).attr('marker-end', 'url(#end-arrow-selected)'); - $('#linkID' + d.target.id).attr('marker-start', linkMarkerStart(d.target.link.direction, true)); - - removeMouseEvents(); - }) - .on('mouseout', function(d) { - $('#tooltipLinkID' + d.target.id).css('visibility', 'hidden'); - $('#tooltipLinkTextID' + d.target.id).css('visibility', 'hidden'); - $('#linkID' + d.target.id).attr('class', 'link'); - $('#linkID' + d.target.id).attr('marker-end', 'url(#end-arrow)'); - $('#linkID' + d.target.id).attr('marker-start', linkMarkerStart(d.target.link.direction, false)); - - reactivateMouseEvents(); - }); - - linkTooltip.enter().append('text') - .attr('id', function(d) { return 'tooltipLinkTextID' + d.target.id; }) - .attr('class', 'tooltip-text') - .attr('x', function(d) { return (d.target.y + rectNode.width - d.source.y) / 2 + d.source.y + tooltip.textMargin; }) - .attr('y', function(d) { return (d.target.x - d.source.x) / 2 + d.source.x + tooltip.textMargin * 2; }) - .attr('width', tooltip.width) - .attr('height', tooltip.height) - .style('fill', 'white') - .append("tspan") - //.text(function(d) { return linkType(d.target.link); }) - .append("tspan") - .attr('x', function(d) { return (d.target.y + rectNode.width - d.source.y) / 2 + d.source.y + tooltip.textMargin; }) - .attr('dy', '1.5em') - .text(function(d) {return d.target.data.nodeName;}); - - // Transition links to their new position. - var linkUpdate = link.transition().duration(duration) - .attr('d', function(d) { return diagonal(d); }); - linkTooltip.transition().duration(duration) - .attr('d', function(d) { return diagonal(d); }); - - // Transition exiting nodes to the parent's new position. - link.exit().transition() - .remove(); - - linkTooltip.exit().transition() - .remove(); - - // Stash the old positions for transition. - nodes.forEach(function(d) { - d.x0 = d.x; - d.y0 = d.y; + + // Update the node attributes and style + nodeUpdate.select('rect.node') + .attr('r', 10) + .style("fill", function(d) { + return d._children ? "lightsteelblue" : "#fff"; + }) + .attr('cursor', 'pointer'); + + + // Remove any exiting nodes + var nodeExit = node.exit().transition() + .duration(duration) + .attr("transform", function(d) { + return "translate(" + source.y + "," + source.x + ")"; + }) + .remove(); + + // On exit reduce the opacity of text labels + nodeExit.select('text') + .style('fill-opacity', 1e-6); + + // ****************** links section *************************** + + // Update the links... + var link = svg.selectAll('path.link') + .data(links, function(d) { return d.id; }); + + // Enter any new links at the parent's previous position. + var linkEnter = link.enter().insert('path', "g") + .attr("class", function(d){ + if(d.data.type === 'Event') + return 'link event'; + else + return 'link command'; + }) + .attr('marker-start', 'url(#end-arrow)') + .attr('d', function(d){ + var o = {x: source.x0, y: source.y0} + return diagonal(o, o) }); - } - // Zoom functionnality is desactivated (user can use browser Ctrl + mouse wheel shortcut) - function zoomAndDrag() { - //var scale = d3.event.scale, - var scale = 1, - translation = d3.event.translate, - tbound = -height * scale, - bbound = height * scale, - lbound = (-width + margin.right) * scale, - rbound = (width - margin.left) * scale; - // limit translation to thresholds - translation = [ - Math.max(Math.min(translation[0], rbound), lbound), - Math.max(Math.min(translation[1], bbound), tbound) - ]; - d3.select('.drawarea') - .attr('transform', 'translate(' + translation + ')' + - ' scale(' + scale + ')'); + // UPDATE + var linkUpdate = linkEnter.merge(link); + + // Transition back to the parent element position + linkUpdate.transition() + .duration(duration) + .attr('d', function(d){ return diagonal(d, d.parent) }); + + // Remove any exiting links + var linkExit = link.exit().transition() + .duration(duration) + .attr('d', function(d) { + var o = {x: source.x, y: source.y} + return diagonal(o, o) + }) + .remove(); + + // Store the old positions for transition. + nodes.forEach(function(d){ + d.x0 = d.x; + d.y0 = d.y; + }); + + // Creates a curved (diagonal) path from parent to the child nodes + function diagonal(s, d) { + + var path = `M ${s.y} ${s.x + rectNode.height / 2} + C ${(s.y + d.y) / 2} ${s.x + rectNode.height / 2}, + ${(s.y + d.y) / 2} ${d.x + rectNode.height / 2}, + ${d.y} ${d.x + rectNode.height / 2}` + + return path } // Toggle children on click. @@ -440,191 +258,14 @@ } update(d); } - - // Breadth-first traversal of the tree - // func function is processed on every node of a same level - // return the max level - function breadthFirstTraversal(tree, func) - { - var max = 0; - if (tree && tree.length > 0) - { - var currentDepth = tree[0].depth; - var fifo = []; - var currentLevel = []; - - fifo.push(tree[0]); - while (fifo.length > 0) { - var node = fifo.shift(); - if (node.depth > currentDepth) { - func(currentLevel); - currentDepth++; - max = Math.max(max, currentLevel.length); - currentLevel = []; - } - currentLevel.push(node); - if (node.children) { - for (var j = 0; j < node.children.length; j++) { - fifo.push(node.children[j]); - } - } - } - func(currentLevel); - return Math.max(max, currentLevel.length); - } - return 0; - } - - // x = ordoninates and y = abscissas - function collision(siblings) { - var minPadding = 5; - if (siblings) { - for (var i = 0; i < siblings.length - 1; i++) - { - if (siblings[i + 1].x - (siblings[i].x + rectNode.height) < minPadding) - siblings[i + 1].x = siblings[i].x + rectNode.height + minPadding; - } - } - } - - function removeMouseEvents() { - // Drag and zoom behaviors are temporarily disabled, so tooltip text can be selected - mousedown = d3.select('#tree-container').select('svg').on('mousedown.zoom'); - d3.select('#tree-container').select('svg').on("mousedown.zoom", null); - } - - function reactivateMouseEvents() { - // Reactivate the drag and zoom behaviors - d3.select('#tree-container').select('svg').on('mousedown.zoom', mousedown); - } - - // Name of the event depends of the browser - function getMouseWheelEvent() { - if (d3.select('#tree-container').select('svg').on('wheel.zoom')) - { - mouseWheelName = 'wheel.zoom'; - return d3.select('#tree-container').select('svg').on('wheel.zoom'); - } - if (d3.select('#tree-container').select('svg').on('mousewheel.zoom') != null) - { - mouseWheelName = 'mousewheel.zoom'; - return d3.select('#tree-container').select('svg').on('mousewheel.zoom'); - } - if (d3.select('#tree-container').select('svg').on('DOMMouseScroll.zoom')) - { - mouseWheelName = 'DOMMouseScroll.zoom'; - return d3.select('#tree-container').select('svg').on('DOMMouseScroll.zoom'); - } - } - - function diagonal(d) { - var p0 = { - x : d.source.x + rectNode.height / 2, - y : (d.source.y + rectNode.width) - }, p3 = { - x : d.target.x + rectNode.height / 2, - y : d.target.y - 12 // -12, so the end arrows are just before the rect node - }, m = (p0.y + p3.y) / 2, p = [ p0, { - x : p0.x, - y : m - }, { - x : p3.x, - y : m - }, p3 ]; - p = p.map(function(d) { - return [ d.y, d.x ]; - }); - return 'M' + p[0] + 'C' + p[1] + ' ' + p[2] + ' ' + p[3]; - } - - function initDropShadow() { - var filter = defs.append("filter") - .attr("id", "drop-shadow") - .attr("color-interpolation-filters", "sRGB"); - - filter.append("feOffset") - .attr("result", "offOut") - .attr("in", "SourceGraphic") - .attr("dx", 0) - .attr("dy", 0); - - filter.append("feGaussianBlur") - .attr("stdDeviation", 2); - - filter.append("feOffset") - .attr("dx", 2) - .attr("dy", 2) - .attr("result", "shadow"); - - filter.append("feComposite") - .attr("in", 'offOut') - .attr("in2", 'shadow') - .attr("operator", "over"); - } - - function initArrowDef() { - // Build the arrows definitions - // End arrow - defs.append('marker') - .attr('id', 'end-arrow') - .attr('viewBox', '0 -5 10 10') - .attr('refX', 0) - .attr('refY', 0) - .attr('markerWidth', 6) - .attr('markerHeight', 6) - .attr('orient', 'auto') - .attr('class', 'arrow') - .append('path') - .attr('d', 'M0,-5L10,0L0,5'); - - // End arrow selected - defs.append('marker') - .attr('id', 'end-arrow-selected') - .attr('viewBox', '0 -5 10 10') - .attr('refX', 0) - .attr('refY', 0) - .attr('markerWidth', 6) - .attr('markerHeight', 6) - .attr('orient', 'auto') - .attr('class', 'arrowselected') - .append('path') - .attr('d', 'M0,-5L10,0L0,5'); - - // Start arrow - defs.append('marker') - .attr('id', 'start-arrow') - .attr('viewBox', '0 -5 10 10') - .attr('refX', 0) - .attr('refY', 0) - .attr('markerWidth', 6) - .attr('markerHeight', 6) - .attr('orient', 'auto') - .attr('class', 'arrow') - .append('path') - .attr('d', 'M10,-5L0,0L10,5'); - - // Start arrow selected - defs.append('marker') - .attr('id', 'start-arrow-selected') - .attr('viewBox', '0 -5 10 10') - .attr('refX', 0) - .attr('refY', 0) - .attr('markerWidth', 6) - .attr('markerHeight', 6) - .attr('orient', 'auto') - .attr('class', 'arrowselected') - .append('path') - .attr('d', 'M10,-5L0,0L10,5'); - } } - } controller.$inject = ['$scope', 'serviceControlService']; function directive() { return { - scope: { conversationId: '=conversationId' }, + scope: { conversationId: '@' }, restrict: 'E', template: '
', controller: controller diff --git a/src/ServicePulse.Host/app/js/views/message/controller.js b/src/ServicePulse.Host/app/js/views/message/controller.js index b4dc9fc37e..5d47c9583b 100644 --- a/src/ServicePulse.Host/app/js/views/message/controller.js +++ b/src/ServicePulse.Host/app/js/views/message/controller.js @@ -66,7 +66,7 @@ if (!angular.isDefined(message.headers)) { return serviceControlService.getMessage(message.message_id).then(function (response) { message.headers = response.message.headers; - message.conversationId = message.headers["NServiceBus.ConversationId"].value; + message.conversationId = message.headers.find(x => x.key === 'NServiceBus.ConversationId').value; }, function () { message.headersUnavailable = "message headers unavailable"; }); diff --git a/src/ServicePulse.Host/app/js/views/message/messages-view.html b/src/ServicePulse.Host/app/js/views/message/messages-view.html index 6afa4eb012..54eae4f0c2 100644 --- a/src/ServicePulse.Host/app/js/views/message/messages-view.html +++ b/src/ServicePulse.Host/app/js/views/message/messages-view.html @@ -83,7 +83,7 @@
{{vm.message.headersUnavailable}}
{{vm.message.messageBody}}
{{vm.message.bodyUnavailable}}
- + diff --git a/src/ServicePulse.Host/package.json b/src/ServicePulse.Host/package.json index bc77ef528c..af6c3c03be 100644 --- a/src/ServicePulse.Host/package.json +++ b/src/ServicePulse.Host/package.json @@ -14,7 +14,7 @@ "angularjs-toaster": "^1.1.0", "animate.css": "^3.4.0", "bootstrap": "3.4.1", - "d3": "^5.16.0", + "d3": "^4.9.1", "highlight.js": "^11.3.1", "highlightjs-badge": "^0.1.9", "jquery": "^3.0.0", From d7574c2df4735df6f813fc0303b65cbc2af1b5bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Mon, 27 Jun 2022 13:35:05 +0200 Subject: [PATCH 03/28] Changing the message generators to include replies and events --- src/SmokeTest.Client/MyResponseHandler.cs | 18 ++++++++++++++++++ src/SmokeTest.Server/MyEventHandler.cs | 17 +++++++++++++++++ src/SmokeTest.Server/MyHandler.cs | 9 ++++----- src/SmokeTest.Server/Program.cs | 1 + src/SmokeTest.Shared/MyMessage.cs | 10 ++++++++++ 5 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 src/SmokeTest.Client/MyResponseHandler.cs create mode 100644 src/SmokeTest.Server/MyEventHandler.cs diff --git a/src/SmokeTest.Client/MyResponseHandler.cs b/src/SmokeTest.Client/MyResponseHandler.cs new file mode 100644 index 0000000000..e9701f812f --- /dev/null +++ b/src/SmokeTest.Client/MyResponseHandler.cs @@ -0,0 +1,18 @@ +namespace SmokeTest.Client +{ + using System; + using System.Threading.Tasks; + using NServiceBus; + + public class MyResponseHandler : IHandleMessages + { + // remove this pragma after upgrading to NServiceBus 8 +#pragma warning disable PS0018 // A task-returning method should have a CancellationToken parameter unless it has a parameter implementing ICancellableContext + public Task Handle(Response message, IMessageHandlerContext context) +#pragma warning restore PS0018 // A task-returning method should have a CancellationToken parameter unless it has a parameter implementing ICancellableContext + { + Console.WriteLine(@"Response received. Id: {0}", message.Id); + throw new InvalidOperationException(message + "Uh oh...Nulls are bad MK"); + } + } +} \ No newline at end of file diff --git a/src/SmokeTest.Server/MyEventHandler.cs b/src/SmokeTest.Server/MyEventHandler.cs new file mode 100644 index 0000000000..0f422de4d6 --- /dev/null +++ b/src/SmokeTest.Server/MyEventHandler.cs @@ -0,0 +1,17 @@ +namespace SmokeTest.Server.Particular.Core.Deliberately.Insanely.Long.NamespaceToEmulateTheCrazyNamespaceLengthsPeopleGiveNamespacesInTheirSystems +{ + using System; + using System.Threading.Tasks; + using NServiceBus; + + public class MyEventHnd : IHandleMessages + { +#pragma warning disable PS0018 + public Task Handle(MyEvent message, IMessageHandlerContext context) +#pragma warning restore PS0018 + { + Console.WriteLine(@"Evemt received. Id: {0}", message.Id); + return Task.FromResult(0); + } + } +} \ No newline at end of file diff --git a/src/SmokeTest.Server/MyHandler.cs b/src/SmokeTest.Server/MyHandler.cs index a31be05c52..116cbde87d 100644 --- a/src/SmokeTest.Server/MyHandler.cs +++ b/src/SmokeTest.Server/MyHandler.cs @@ -8,14 +8,16 @@ public class MyHandler : IHandleMessages { // remove this pragma after upgrading to NServiceBus 8 #pragma warning disable PS0018 // A task-returning method should have a CancellationToken parameter unless it has a parameter implementing ICancellableContext - public Task Handle(MyMessage message, IMessageHandlerContext context) + public async Task Handle(MyMessage message, IMessageHandlerContext context) #pragma warning restore PS0018 // A task-returning method should have a CancellationToken parameter unless it has a parameter implementing ICancellableContext { Console.WriteLine(@"Message received. Id: {0}", message.Id); + await context.Reply(new Response() { Id = Guid.NewGuid() }).ConfigureAwait(false); + await context.Publish(new MyEvent() { Id = Guid.NewGuid() }).ConfigureAwait(false); if (Program.goodretries || !message.KillMe) { - return Task.FromResult(0); + return; } if (!Program.emulateFailures) @@ -26,8 +28,6 @@ public Task Handle(MyMessage message, IMessageHandlerContext context) { throw new InvalidOperationException(message + "Uh oh...Nulls are bad MK"); } - - return Task.FromResult(0); } static void RandomException(string message) @@ -49,5 +49,4 @@ static void RandomException(string message) } } - } \ No newline at end of file diff --git a/src/SmokeTest.Server/Program.cs b/src/SmokeTest.Server/Program.cs index bd592d7b0c..1a7c3f17dd 100644 --- a/src/SmokeTest.Server/Program.cs +++ b/src/SmokeTest.Server/Program.cs @@ -12,6 +12,7 @@ static async Task Main() endpointConfiguration.UseTransport(); + endpointConfiguration.Recoverability().Immediate(c => c.NumberOfRetries(0)).Delayed(c => c.NumberOfRetries(0)); var enpointInstance = await Endpoint.Start(endpointConfiguration); var exit = false; diff --git a/src/SmokeTest.Shared/MyMessage.cs b/src/SmokeTest.Shared/MyMessage.cs index 31107ff449..74748e849a 100644 --- a/src/SmokeTest.Shared/MyMessage.cs +++ b/src/SmokeTest.Shared/MyMessage.cs @@ -7,4 +7,14 @@ public class MyMessage : IMessage public Guid Id { get; set; } public bool KillMe { get; set; } public string SomeText { get; set; } +} + +public class Response : IMessage +{ + public Guid Id { get; set; } +} + +public class MyEvent : IEvent +{ + public Guid Id { get; set; } } \ No newline at end of file From a6c46546a1f0ae8cd9ad7c80be50e07fcda79cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Tue, 28 Jun 2022 16:27:02 +0200 Subject: [PATCH 04/28] wip --- src/ServicePulse.Host/app/css/particular.css | 4 ++++ .../directives/ui.particular.flow-diagram.js | 19 +++++++++---------- .../app/js/views/message/controller.js | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/ServicePulse.Host/app/css/particular.css b/src/ServicePulse.Host/app/css/particular.css index 09674fa457..92e72bbc0c 100644 --- a/src/ServicePulse.Host/app/css/particular.css +++ b/src/ServicePulse.Host/app/css/particular.css @@ -3386,6 +3386,10 @@ section[name="platformconnection"] li { font: 12px sans-serif; } +.node .time-sent { + color: #777f7f; +} + .link { fill: none; stroke: #ccc; diff --git a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js index bf455310a0..5cb08ff34d 100644 --- a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js +++ b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js @@ -4,6 +4,7 @@ function controller($scope, serviceControlService) { serviceControlService.getConversation($scope.conversationId).then(messages => { + //serviceControlService.getConversation('1dc69cf1-1511-4c85-bd1f-aec200948225').then(messages =>{ var tree = createTreeStructure(messages.map(x => mapMessage(x))); drawTree(tree[0]); }); @@ -44,14 +45,15 @@ "link" : { "name" : "Link "+message.id, "nodeName" : message.id - } + }, + "timeSent": new Date(message.time_sent) }; } // Set the dimensions and margins of the diagram var margin = {top: 20, right: 90, bottom: 30, left: 90}, - width = 960 - margin.left - margin.right, - height = 500 - margin.top - margin.bottom; + width = 1860 - margin.left - margin.right, + height = 1500 - margin.top - margin.bottom; var rectNode = { width : 120, height : 90, textMargin : 5 } // append the svg object to the body of the page // appends a 'group' element to 'svg' @@ -90,9 +92,6 @@ root.x0 = height / 2; root.y0 = 0; -// Collapse after the second level - //root.children.forEach(collapse); - update(root); } @@ -115,8 +114,8 @@ links = treeData.descendants().slice(1); // Normalize for fixed-depth. - nodes.forEach(function(d){ d.y = d.depth * 180}); - + nodes.forEach(function(d){ d.y = d.depth * 180 }); + // ****************** Nodes section *************************** // Update the nodes... @@ -157,8 +156,8 @@ return '
' - + `${d.data.nodeName}
-Id: ${(d.data.isError ? `${d.data.id}` : d.data.id)}
+ + `${(d.data.isError ? `${d.data.nodeName}` : d.data.nodeName)}
+${d.data.timeSent.toLocaleString()}
`; }) diff --git a/src/ServicePulse.Host/app/js/views/message/controller.js b/src/ServicePulse.Host/app/js/views/message/controller.js index 5d47c9583b..473e57f399 100644 --- a/src/ServicePulse.Host/app/js/views/message/controller.js +++ b/src/ServicePulse.Host/app/js/views/message/controller.js @@ -66,7 +66,7 @@ if (!angular.isDefined(message.headers)) { return serviceControlService.getMessage(message.message_id).then(function (response) { message.headers = response.message.headers; - message.conversationId = message.headers.find(x => x.key === 'NServiceBus.ConversationId').value; + message.conversationId = message.headers.find(function(x){return x.key === 'NServiceBus.ConversationId';}).value; }, function () { message.headersUnavailable = "message headers unavailable"; }); From aa482214816544fc38ab9d56f6085f85c02ac4e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Wed, 29 Jun 2022 13:48:08 +0200 Subject: [PATCH 05/28] Reversing the tree to grow down --- .../js/directives/ui.particular.flow-diagram.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js index 5cb08ff34d..b9d14efd4c 100644 --- a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js +++ b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js @@ -126,7 +126,7 @@ var nodeEnter = node.enter().append('g') .attr('class', 'node') .attr("transform", function(d) { - return "translate(" + source.y0 + "," + source.x0 + ")"; + return "translate(" + source.x0 + "," + source.y0 + ")"; }) .on('click', click); @@ -168,7 +168,7 @@ nodeUpdate.transition() .duration(duration) .attr("transform", function(d) { - return "translate(" + d.y + "," + d.x + ")"; + return "translate(" + d.x + "," + d.y + ")"; }); // Update the node attributes and style @@ -184,7 +184,7 @@ var nodeExit = node.exit().transition() .duration(duration) .attr("transform", function(d) { - return "translate(" + source.y + "," + source.x + ")"; + return "translate(" + source.x + "," + source.y + ")"; }) .remove(); @@ -238,10 +238,10 @@ // Creates a curved (diagonal) path from parent to the child nodes function diagonal(s, d) { - var path = `M ${s.y} ${s.x + rectNode.height / 2} - C ${(s.y + d.y) / 2} ${s.x + rectNode.height / 2}, - ${(s.y + d.y) / 2} ${d.x + rectNode.height / 2}, - ${d.y} ${d.x + rectNode.height / 2}` + var path = "M" + (s.x + rectNode.width / 2) + "," + s.y + + "C" + (s.x + rectNode.width / 2) + "," + (s.y + d.y) / 2 + + " " + (d.x + rectNode.width / 2) + "," + (s.y + d.y) / 2 + + " " + (d.x + rectNode.width / 2) + "," + (d.y + rectNode.height); return path } From 710a51c3ad4fd25b21fe7178a68ab0124c88a0cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Fri, 1 Jul 2022 13:41:53 +0200 Subject: [PATCH 06/28] Another changes - Add information about saga - Automatically calculating height of the svg - experimenting with sizes --- .../directives/ui.particular.flow-diagram.js | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js index b9d14efd4c..bbc5cd7c86 100644 --- a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js +++ b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js @@ -29,11 +29,15 @@ } function mapMessage(message) { - var parentid; + var parentid, saga = ''; var header = message.headers.find(x => x.key === 'NServiceBus.RelatedTo'); if(header){ parentid = header.value; } + var sagaHeader = message.headers.find(x => x.key === 'NServiceBus.OriginatingSagaType'); + if(sagaHeader){ + saga = sagaHeader.value.split(', ')[0]; + } return { "nodeName": message.message_type, @@ -42,6 +46,7 @@ "parentId": parentid, "type": message.headers.findIndex(x => x.key === 'NServiceBus.DeliverAt') > -1 ? 'Delay' : message.headers.find(x => x.key === 'NServiceBus.MessageIntent').value === 'Publish' ? 'Event' : 'Command', "isError": message.headers.findIndex(x => x.key === 'NServiceBus.ExceptionInfo.ExceptionType') > -1, + "sagaName": saga, "link" : { "name" : "Link "+message.id, "nodeName" : message.id @@ -55,41 +60,45 @@ width = 1860 - margin.left - margin.right, height = 1500 - margin.top - margin.bottom; var rectNode = { width : 120, height : 90, textMargin : 5 } -// append the svg object to the body of the page -// appends a 'group' element to 'svg' -// moves the 'group' element to the top left margin - var svg = d3.select("#tree-container").append("svg") - .attr("width", width + margin.right + margin.left) - .attr("height", height + margin.top + margin.bottom) - .append("g") - .attr("transform", "translate(" - + margin.left + "," + margin.top + ")"); - - svg.append('defs').append('marker') - .attr('id', 'end-arrow') - .attr('viewBox', '0 -5 10 10') - .attr('refX', 0) - .attr('refY', 0) - .attr('markerWidth', 6) - .attr('markerHeight', 6) - .attr('orient', 'auto') - .attr('class', 'arrow') - .append('path') - .attr('d', 'M10,-5L0,0L10,5'); + var i = 0, duration = 750, - root; - -// declares a tree layout and assigns the size - var treemap = d3.tree().size([height, width]); + root, treemap, svg; function drawTree(treeData) { -// Assigns parent, children, height, depth + // Assigns parent, children, height, depth root = d3.hierarchy(treeData, function (d) { return d.children; }); - root.x0 = height / 2; + + height = 200 * (root.height + 1); + // append the svg object to the body of the page +// appends a 'group' element to 'svg' +// moves the 'group' element to the top left margin + svg = d3.select("#tree-container").append("svg") + .attr("width", width + margin.right + margin.left) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", "translate(" + + margin.left + "," + margin.top + ")"); + + svg.append('defs').append('marker') + .attr('id', 'end-arrow') + .attr('viewBox', '0 -5 10 10') + .attr('refX', 0) + .attr('refY', 0) + .attr('markerWidth', 6) + .attr('markerHeight', 6) + .attr('orient', 'auto') + .attr('class', 'arrow') + .append('path') + .attr('d', 'M10,-5L0,0L10,5'); + + // declares a tree layout and assigns the size + treemap = d3.tree().size([height, width]); + + root.x0 = width / 2; root.y0 = 0; update(root); @@ -158,6 +167,7 @@ + (rectNode.height - rectNode.textMargin * 2) + 'px;" class="node-text wordwrap">' + `${(d.data.isError ? `${d.data.nodeName}` : d.data.nodeName)}
${d.data.timeSent.toLocaleString()}
+${d.data.sagaName} `; }) From 2855166d6573d8622477561f1686781e24be9bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Tue, 5 Jul 2022 08:17:15 +0200 Subject: [PATCH 07/28] Adding if for sergio --- .../directives/ui.particular.flow-diagram.js | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js index bbc5cd7c86..e8d891908a 100644 --- a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js +++ b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js @@ -2,12 +2,8 @@ 'use strict'; - function controller($scope, serviceControlService) { - serviceControlService.getConversation($scope.conversationId).then(messages => { - //serviceControlService.getConversation('1dc69cf1-1511-4c85-bd1f-aec200948225').then(messages =>{ - var tree = createTreeStructure(messages.map(x => mapMessage(x))); - drawTree(tree[0]); - }); + function controller($scope, serviceControlService, $routeParams) { + function createTreeStructure(messages) { var map = {}, node, roots = [], i; @@ -268,9 +264,21 @@ update(d); } } + if($routeParams.sergioTest) + { + var messages = JSON.parse("[{\"id\":\"723dec34-1cd0-5b01-3694-4060ac53b2f2\",\"message_id\":\"6d945c78-d9bd-426f-8e0d-aeab00ba1752\",\"message_type\":\"CancelOrder\",\"sending_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"receiving_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"time_sent\":\"2022-06-05T11:17:32.218349Z\",\"processed_at\":\"2022-06-05T11:24:23.538739Z\",\"critical_time\":\"00:06:51.3203900\",\"processing_time\":\"00:00:00.0900640\",\"delivery_time\":\"00:06:51.2303260\",\"is_system_message\":false,\"conversation_id\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\",\"headers\":[{\"key\":\"NServiceBus.ContentType\",\"value\":\"text/xml\"},{\"key\":\"NServiceBus.ConversationId\",\"value\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\"},{\"key\":\"NServiceBus.CorrelationId\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.DeliverAt\",\"value\":\"2022-06-05 11:18:02:217916 Z\"},{\"key\":\"NServiceBus.EnclosedMessageTypes\",\"value\":\"CancelOrder, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.IsSagaTimeoutMessage\",\"value\":\"True\"},{\"key\":\"NServiceBus.MessageId\",\"value\":\"6d945c78-d9bd-426f-8e0d-aeab00ba1752\"},{\"key\":\"NServiceBus.MessageIntent\",\"value\":\"Send\"},{\"key\":\"NServiceBus.OriginatingEndpoint\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.OriginatingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.OriginatingSagaId\",\"value\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},{\"key\":\"NServiceBus.OriginatingSagaType\",\"value\":\"OrderSaga, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.RelatedTo\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.ReplyToAddress\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.SagaId\",\"value\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},{\"key\":\"NServiceBus.SagaType\",\"value\":\"OrderSaga, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.TimeSent\",\"value\":\"2022-06-05 11:17:32:218349 Z\"},{\"key\":\"NServiceBus.Version\",\"value\":\"7.7.3\"},{\"key\":\"NServiceBus.NonDurableMessage\",\"value\":\"False\"},{\"key\":\"NServiceBus.InvokedSagas\",\"value\":\"OrderSaga:fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},{\"key\":\"ServiceControl.SagaStateChange\",\"value\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77:Completed\"},{\"key\":\"NServiceBus.ProcessingStarted\",\"value\":\"2022-06-05 11:24:23:448675 Z\"},{\"key\":\"NServiceBus.ProcessingEnded\",\"value\":\"2022-06-05 11:24:23:538739 Z\"},{\"key\":\"NServiceBus.ProcessingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.ProcessingEndpoint\",\"value\":\"Samples.SimpleSaga\"}],\"status\":\"successful\",\"message_intent\":\"send\",\"body_url\":\"/messages/6d945c78-d9bd-426f-8e0d-aeab00ba1752/body?instance_id=aHR0cDovL2xvY2FsaG9zdDo0NDQ0NC9hcGkv\",\"body_size\":175,\"invoked_sagas\":[{\"change_status\":\"Completed\",\"saga_type\":\"OrderSaga\",\"saga_id\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"}],\"originates_from_saga\":{\"saga_type\":\"OrderSaga\",\"saga_id\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},\"instance_id\":\"aHR0cDovL2xvY2FsaG9zdDo0NDQ0NC9hcGkv\"},{\"id\":\"e1f0a28a-cf46-4365-76d1-c0526e75e16c\",\"message_id\":\"01a332a7-e72d-43ea-8af2-aeab00ba1752\",\"message_type\":\"CompleteOrder\",\"sending_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"receiving_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"time_sent\":\"2022-06-05T11:17:32.2179Z\",\"processed_at\":\"2022-06-05T11:24:23.624916Z\",\"critical_time\":\"00:00:00\",\"processing_time\":\"00:00:00\",\"delivery_time\":\"00:00:00\",\"is_system_message\":false,\"conversation_id\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\",\"headers\":[{\"key\":\"NServiceBus.ContentType\",\"value\":\"text/xml\"},{\"key\":\"NServiceBus.ConversationId\",\"value\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\"},{\"key\":\"NServiceBus.CorrelationId\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.DeliverAt\",\"value\":\"2022-06-05 11:17:42:217900 Z\"},{\"key\":\"NServiceBus.EnclosedMessageTypes\",\"value\":\"CompleteOrder, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.MessageId\",\"value\":\"01a332a7-e72d-43ea-8af2-aeab00ba1752\"},{\"key\":\"NServiceBus.MessageIntent\",\"value\":\"Send\"},{\"key\":\"NServiceBus.OriginatingEndpoint\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.OriginatingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.OriginatingSagaId\",\"value\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},{\"key\":\"NServiceBus.OriginatingSagaType\",\"value\":\"OrderSaga, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.RelatedTo\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.ReplyToAddress\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.TimeSent\",\"value\":\"2022-06-05 11:17:32:217900 Z\"},{\"key\":\"NServiceBus.Version\",\"value\":\"7.7.3\"},{\"key\":\"NServiceBus.NonDurableMessage\",\"value\":\"False\"},{\"key\":\"NServiceBus.ExceptionInfo.ExceptionType\",\"value\":\"System.IO.IOException\"},{\"key\":\"NServiceBus.ExceptionInfo.HelpLink\",\"value\":null},{\"key\":\"NServiceBus.ExceptionInfo.Message\",\"value\":\"The process cannot access the file 'C:\\\\Code\\\\docs.particular.net\\\\samples\\\\saga\\\\simple\\\\Core_7\\\\Sample\\\\bin\\\\Debug\\\\net5.0\\\\.sagas\\\\OrderSaga\\\\fac144c9-87cc-fc0d-84d3-49ae6f489f77.json' because it is being used by another process.\"},{\"key\":\"NServiceBus.ExceptionInfo.Source\",\"value\":\"System.Private.CoreLib\"},{\"key\":\"NServiceBus.ExceptionInfo.StackTrace\",\"value\":\"System.IO.IOException: The process cannot access the file 'C:\\\\Code\\\\docs.particular.net\\\\samples\\\\saga\\\\simple\\\\Core_7\\\\Sample\\\\bin\\\\Debug\\\\net5.0\\\\.sagas\\\\OrderSaga\\\\fac144c9-87cc-fc0d-84d3-49ae6f489f77.json' because it is being used by another process.\\r\\n at System.IO.FileStream.ValidateFileHandle(SafeFileHandle fileHandle)\\r\\n at System.IO.FileStream.CreateFileOpenHandle(FileMode mode, FileShare share, FileOptions options)\\r\\n at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)\\r\\n at NServiceBus.SagaStorageFile.OpenWithDelayOnConcurrency(String filePath, FileMode fileAccess) in /_/src/NServiceBus.Core/Persistence/Learning/SagaPersister/SagaStorageFile.cs:line 56\\r\\n at NServiceBus.SagaStorageFile.OpenWithDelayOnConcurrency(String filePath, FileMode fileAccess) in /_/src/NServiceBus.Core/Persistence/Learning/SagaPersister/SagaStorageFile.cs:line 64\\r\\n at NServiceBus.LearningSynchronizedStorageSession.Open(Guid sagaId, Type entityType) in /_/src/NServiceBus.Core/Persistence/Learning/LearningSynchronizedStorageSession.cs:line 72\\r\\n at NServiceBus.LearningSynchronizedStorageSession.Read[TSagaData](Guid sagaId) in /_/src/NServiceBus.Core/Persistence/Learning/LearningSynchronizedStorageSession.cs:line 38\\r\\n at NServiceBus.PropertySagaFinder`1.Find(IBuilder builder, SagaFinderDefinition finderDefinition, SynchronizedStorageSession storageSession, ContextBag context, Object message, IReadOnlyDictionary`2 messageHeaders) in /_/src/NServiceBus.Core/Sagas/PropertySagaFinder.cs:line 42\\r\\n at NServiceBus.SagaPersistenceBehavior.Invoke(IInvokeHandlerContext context, Func`2 next) in /_/src/NServiceBus.Core/Sagas/SagaPersistenceBehavior.cs:line 78\\r\\n at NServiceBus.SagaAudit.CaptureSagaStateBehavior.Invoke(IInvokeHandlerContext context, Func`1 next)\\r\\n at NServiceBus.LoadHandlersConnector.Invoke(IIncomingLogicalMessageContext context, Func`2 stage) in /_/src/NServiceBus.Core/Pipeline/Incoming/LoadHandlersConnector.cs:line 48\\r\\n at NServiceBus.ScheduledTaskHandlingBehavior.Invoke(IIncomingLogicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/Scheduling/ScheduledTaskHandlingBehavior.cs:line 22\\r\\n at NServiceBus.InvokeSagaNotFoundBehavior.Invoke(IIncomingLogicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/Sagas/InvokeSagaNotFoundBehavior.cs:line 16\\r\\n at NServiceBus.DeserializeMessageConnector.Invoke(IIncomingPhysicalMessageContext context, Func`2 stage) in /_/src/NServiceBus.Core/Pipeline/Incoming/DeserializeMessageConnector.cs:line 34\\r\\n at NServiceBus.UnitOfWorkBehavior.InvokeUnitsOfWork(IIncomingPhysicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/UnitOfWork/UnitOfWorkBehavior.cs:line 40\\r\\n at NServiceBus.UnitOfWorkBehavior.InvokeUnitsOfWork(IIncomingPhysicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/UnitOfWork/UnitOfWorkBehavior.cs:line 62\\r\\n at NServiceBus.MutateIncomingTransportMessageBehavior.InvokeIncomingTransportMessagesMutators(IIncomingPhysicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/MessageMutators/MutateTransportMessage/MutateIncomingTransportMessageBehavior.cs:line 59\\r\\n at NServiceBus.InvokeAuditPipelineBehavior.Invoke(IIncomingPhysicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/Audit/InvokeAuditPipelineBehavior.cs:line 18\\r\\n at NServiceBus.ProcessingStatisticsBehavior.Invoke(IIncomingPhysicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/Performance/Statistics/ProcessingStatisticsBehavior.cs:line 25\\r\\n at NServiceBus.TransportReceiveToPhysicalMessageConnector.Invoke(ITransportReceiveContext context, Func`2 next) in /_/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs:line 37\\r\\n at NServiceBus.RetryAcknowledgementBehavior.Invoke(ITransportReceiveContext context, Func`2 next) in /_/src/NServiceBus.Core/ServicePlatform/Retries/RetryAcknowledgementBehavior.cs:line 25\\r\\n at NServiceBus.MainPipelineExecutor.Invoke(MessageContext messageContext) in /_/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs:line 35\\r\\n at NServiceBus.TransportReceiver.InvokePipeline(MessageContext c) in /_/src/NServiceBus.Core/Transports/TransportReceiver.cs:line 58\\r\\n at NServiceBus.TransportReceiver.InvokePipeline(MessageContext c) in /_/src/NServiceBus.Core/Transports/TransportReceiver.cs:line 64\\r\\n at NServiceBus.Transport.RabbitMQ.MessagePump.Process(EventingBasicConsumer consumer, BasicDeliverEventArgs message, Byte[] messageBody) in /_/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs:line 368\"},{\"key\":\"NServiceBus.TimeOfFailure\",\"value\":\"2022-06-05 11:24:23:624916 Z\"},{\"key\":\"NServiceBus.ExceptionInfo.Data.Message ID\",\"value\":\"01a332a7-e72d-43ea-8af2-aeab00ba1752\"},{\"key\":\"NServiceBus.FailedQ\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.ProcessingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.ProcessingEndpoint\",\"value\":\"Samples.SimpleSaga\"}],\"status\":\"failed\",\"message_intent\":\"send\",\"body_url\":\"/messages/01a332a7-e72d-43ea-8af2-aeab00ba1752/body?instance_id=aHR0cDovL2xvY2FsaG9zdDozMzMzMy9hcGk.\",\"body_size\":234,\"instance_id\":\"aHR0cDovL2xvY2FsaG9zdDozMzMzMy9hcGk.\"},{\"id\":\"8b585ac2-edf2-737d-a9c1-c041694a710d\",\"message_id\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\",\"message_type\":\"StartOrder\",\"sending_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"receiving_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"time_sent\":\"2022-06-05T11:17:32.212755Z\",\"processed_at\":\"2022-06-05T11:17:32.231559Z\",\"critical_time\":\"00:00:00.0188040\",\"processing_time\":\"00:00:00.0159600\",\"delivery_time\":\"00:00:00.0028440\",\"is_system_message\":false,\"conversation_id\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\",\"headers\":[{\"key\":\"NServiceBus.MessageId\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.MessageIntent\",\"value\":\"Send\"},{\"key\":\"NServiceBus.ConversationId\",\"value\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\"},{\"key\":\"NServiceBus.CorrelationId\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.ReplyToAddress\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.OriginatingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.OriginatingEndpoint\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.ContentType\",\"value\":\"text/xml\"},{\"key\":\"NServiceBus.EnclosedMessageTypes\",\"value\":\"StartOrder, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.Version\",\"value\":\"7.7.3\"},{\"key\":\"NServiceBus.TimeSent\",\"value\":\"2022-06-05 11:17:32:212755 Z\"},{\"key\":\"NServiceBus.NonDurableMessage\",\"value\":\"False\"},{\"key\":\"NServiceBus.InvokedSagas\",\"value\":\"OrderSaga:fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},{\"key\":\"ServiceControl.SagaStateChange\",\"value\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77:New\"},{\"key\":\"NServiceBus.ProcessingStarted\",\"value\":\"2022-06-05 11:17:32:215599 Z\"},{\"key\":\"NServiceBus.ProcessingEnded\",\"value\":\"2022-06-05 11:17:32:231559 Z\"},{\"key\":\"NServiceBus.ProcessingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.ProcessingEndpoint\",\"value\":\"Samples.SimpleSaga\"}],\"status\":\"successful\",\"message_intent\":\"send\",\"body_url\":\"/messages/0976541c-3b4e-441b-a3b0-aeab00ba1750/body?instance_id=aHR0cDovL2xvY2FsaG9zdDo0NDQ0NC9hcGkv\",\"body_size\":228,\"invoked_sagas\":[{\"change_status\":\"New\",\"saga_type\":\"OrderSaga\",\"saga_id\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"}],\"instance_id\":\"aHR0cDovL2xvY2FsaG9zdDo0NDQ0NC9hcGkv\"}]"); + var tree = createTreeStructure(messages.map(x => mapMessage(x))); + drawTree(tree[0]); + } + else { + serviceControlService.getConversation($scope.conversationId).then(messages => { + var tree = createTreeStructure(messages.map(x => mapMessage(x))); + drawTree(tree[0]); + }); + } } - controller.$inject = ['$scope', 'serviceControlService']; + controller.$inject = ['$scope', 'serviceControlService', '$routeParams']; function directive() { return { From ae540a3de003d7325cdc17eaec45c611f9c52498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Thu, 7 Jul 2022 14:22:46 +0200 Subject: [PATCH 08/28] Doing a set of changes: - Increase box width - Adding zoom and pan to diagram - truncating long names - --- .../directives/ui.particular.flow-diagram.js | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js index e8d891908a..f90938297d 100644 --- a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js +++ b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js @@ -53,14 +53,16 @@ // Set the dimensions and margins of the diagram var margin = {top: 20, right: 90, bottom: 30, left: 90}, - width = 1860 - margin.left - margin.right, + width = 3600 - margin.left - margin.right, height = 1500 - margin.top - margin.bottom; - var rectNode = { width : 120, height : 90, textMargin : 5 } + var rectNode = { width : 250, height : 90, textMargin : 5 } var i = 0, duration = 750, - root, treemap, svg; + root, treemap, svg, parentSvg; + + var currentMessageId = $routeParams.messageId; function drawTree(treeData) { // Assigns parent, children, height, depth @@ -72,13 +74,17 @@ // append the svg object to the body of the page // appends a 'group' element to 'svg' // moves the 'group' element to the top left margin - svg = d3.select("#tree-container").append("svg") - .attr("width", width + margin.right + margin.left) - .attr("height", height + margin.top + margin.bottom) - .append("g") + parentSvg = d3.select("#tree-container").append("svg") + .attr("width", 'auto') + .attr("height", height + margin.top + margin.bottom); + svg = parentSvg.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + var zoom = d3.zoom() + .scaleExtent([1 / 2, 8]) + .on("zoom", zoomed); + svg.append('defs').append('marker') .attr('id', 'end-arrow') .attr('viewBox', '0 -5 10 10') @@ -92,21 +98,18 @@ .attr('d', 'M10,-5L0,0L10,5'); // declares a tree layout and assigns the size - treemap = d3.tree().size([height, width]); + treemap = d3.tree().nodeSize([rectNode.width, rectNode.height]); root.x0 = width / 2; root.y0 = 0; + parentSvg.call(zoom); + update(root); } -// Collapse the node and all it's children - function collapse(d) { - if(d.children) { - d._children = d.children - d._children.forEach(collapse) - d.children = null - } + function zoomed() { + svg.attr("transform", d3.event.transform); } function update(source) { @@ -137,7 +140,7 @@ // Add rectangle for the nodes nodeEnter.append('rect') - .attr('class', function(d){return `node ${d.data.type.toLowerCase()} ${d.data.isError ? 'error' : ''}`;}) + .attr('class', function(d){return `node ${d.data.type.toLowerCase()} ${d.data.isError ? 'error' : ''} ${d.data.id === currentMessageId ? 'current-message' : ''}`;}) .attr('rx', 6) .attr('ry', 6) .attr('width', rectNode.width) @@ -160,8 +163,8 @@ .append('xhtml').html(function(d) { return '
' - + `${(d.data.isError ? `${d.data.nodeName}` : d.data.nodeName)}
+ + (rectNode.height - rectNode.textMargin * 2) + 'px;" class="node-text wordwrap" uib-tooltip="' + d.data.nodeName + '">' + + `
${(d.data.isError ? `${d.data.nodeName}` : d.data.nodeName)}
${d.data.timeSent.toLocaleString()}
${d.data.sagaName}
`; @@ -215,7 +218,7 @@ .attr('marker-start', 'url(#end-arrow)') .attr('d', function(d){ var o = {x: source.x0, y: source.y0} - return diagonal(o, o) + return diagonal(o, o); }); // UPDATE @@ -230,8 +233,8 @@ var linkExit = link.exit().transition() .duration(duration) .attr('d', function(d) { - var o = {x: source.x, y: source.y} - return diagonal(o, o) + var o = {x: source.x, y: source.y}; + return diagonal(o, o); }) .remove(); @@ -251,6 +254,11 @@ return path } + + function straight(s, d){ + return "M" + (s.x + rectNode.width / 2) + "," + s.y + + "H" + (d.x + rectNode.width / 2) + "V" + (d.y + rectNode.height); + } // Toggle children on click. function click(d) { @@ -266,7 +274,7 @@ } if($routeParams.sergioTest) { - var messages = JSON.parse("[{\"id\":\"723dec34-1cd0-5b01-3694-4060ac53b2f2\",\"message_id\":\"6d945c78-d9bd-426f-8e0d-aeab00ba1752\",\"message_type\":\"CancelOrder\",\"sending_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"receiving_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"time_sent\":\"2022-06-05T11:17:32.218349Z\",\"processed_at\":\"2022-06-05T11:24:23.538739Z\",\"critical_time\":\"00:06:51.3203900\",\"processing_time\":\"00:00:00.0900640\",\"delivery_time\":\"00:06:51.2303260\",\"is_system_message\":false,\"conversation_id\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\",\"headers\":[{\"key\":\"NServiceBus.ContentType\",\"value\":\"text/xml\"},{\"key\":\"NServiceBus.ConversationId\",\"value\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\"},{\"key\":\"NServiceBus.CorrelationId\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.DeliverAt\",\"value\":\"2022-06-05 11:18:02:217916 Z\"},{\"key\":\"NServiceBus.EnclosedMessageTypes\",\"value\":\"CancelOrder, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.IsSagaTimeoutMessage\",\"value\":\"True\"},{\"key\":\"NServiceBus.MessageId\",\"value\":\"6d945c78-d9bd-426f-8e0d-aeab00ba1752\"},{\"key\":\"NServiceBus.MessageIntent\",\"value\":\"Send\"},{\"key\":\"NServiceBus.OriginatingEndpoint\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.OriginatingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.OriginatingSagaId\",\"value\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},{\"key\":\"NServiceBus.OriginatingSagaType\",\"value\":\"OrderSaga, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.RelatedTo\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.ReplyToAddress\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.SagaId\",\"value\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},{\"key\":\"NServiceBus.SagaType\",\"value\":\"OrderSaga, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.TimeSent\",\"value\":\"2022-06-05 11:17:32:218349 Z\"},{\"key\":\"NServiceBus.Version\",\"value\":\"7.7.3\"},{\"key\":\"NServiceBus.NonDurableMessage\",\"value\":\"False\"},{\"key\":\"NServiceBus.InvokedSagas\",\"value\":\"OrderSaga:fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},{\"key\":\"ServiceControl.SagaStateChange\",\"value\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77:Completed\"},{\"key\":\"NServiceBus.ProcessingStarted\",\"value\":\"2022-06-05 11:24:23:448675 Z\"},{\"key\":\"NServiceBus.ProcessingEnded\",\"value\":\"2022-06-05 11:24:23:538739 Z\"},{\"key\":\"NServiceBus.ProcessingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.ProcessingEndpoint\",\"value\":\"Samples.SimpleSaga\"}],\"status\":\"successful\",\"message_intent\":\"send\",\"body_url\":\"/messages/6d945c78-d9bd-426f-8e0d-aeab00ba1752/body?instance_id=aHR0cDovL2xvY2FsaG9zdDo0NDQ0NC9hcGkv\",\"body_size\":175,\"invoked_sagas\":[{\"change_status\":\"Completed\",\"saga_type\":\"OrderSaga\",\"saga_id\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"}],\"originates_from_saga\":{\"saga_type\":\"OrderSaga\",\"saga_id\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},\"instance_id\":\"aHR0cDovL2xvY2FsaG9zdDo0NDQ0NC9hcGkv\"},{\"id\":\"e1f0a28a-cf46-4365-76d1-c0526e75e16c\",\"message_id\":\"01a332a7-e72d-43ea-8af2-aeab00ba1752\",\"message_type\":\"CompleteOrder\",\"sending_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"receiving_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"time_sent\":\"2022-06-05T11:17:32.2179Z\",\"processed_at\":\"2022-06-05T11:24:23.624916Z\",\"critical_time\":\"00:00:00\",\"processing_time\":\"00:00:00\",\"delivery_time\":\"00:00:00\",\"is_system_message\":false,\"conversation_id\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\",\"headers\":[{\"key\":\"NServiceBus.ContentType\",\"value\":\"text/xml\"},{\"key\":\"NServiceBus.ConversationId\",\"value\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\"},{\"key\":\"NServiceBus.CorrelationId\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.DeliverAt\",\"value\":\"2022-06-05 11:17:42:217900 Z\"},{\"key\":\"NServiceBus.EnclosedMessageTypes\",\"value\":\"CompleteOrder, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.MessageId\",\"value\":\"01a332a7-e72d-43ea-8af2-aeab00ba1752\"},{\"key\":\"NServiceBus.MessageIntent\",\"value\":\"Send\"},{\"key\":\"NServiceBus.OriginatingEndpoint\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.OriginatingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.OriginatingSagaId\",\"value\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},{\"key\":\"NServiceBus.OriginatingSagaType\",\"value\":\"OrderSaga, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.RelatedTo\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.ReplyToAddress\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.TimeSent\",\"value\":\"2022-06-05 11:17:32:217900 Z\"},{\"key\":\"NServiceBus.Version\",\"value\":\"7.7.3\"},{\"key\":\"NServiceBus.NonDurableMessage\",\"value\":\"False\"},{\"key\":\"NServiceBus.ExceptionInfo.ExceptionType\",\"value\":\"System.IO.IOException\"},{\"key\":\"NServiceBus.ExceptionInfo.HelpLink\",\"value\":null},{\"key\":\"NServiceBus.ExceptionInfo.Message\",\"value\":\"The process cannot access the file 'C:\\\\Code\\\\docs.particular.net\\\\samples\\\\saga\\\\simple\\\\Core_7\\\\Sample\\\\bin\\\\Debug\\\\net5.0\\\\.sagas\\\\OrderSaga\\\\fac144c9-87cc-fc0d-84d3-49ae6f489f77.json' because it is being used by another process.\"},{\"key\":\"NServiceBus.ExceptionInfo.Source\",\"value\":\"System.Private.CoreLib\"},{\"key\":\"NServiceBus.ExceptionInfo.StackTrace\",\"value\":\"System.IO.IOException: The process cannot access the file 'C:\\\\Code\\\\docs.particular.net\\\\samples\\\\saga\\\\simple\\\\Core_7\\\\Sample\\\\bin\\\\Debug\\\\net5.0\\\\.sagas\\\\OrderSaga\\\\fac144c9-87cc-fc0d-84d3-49ae6f489f77.json' because it is being used by another process.\\r\\n at System.IO.FileStream.ValidateFileHandle(SafeFileHandle fileHandle)\\r\\n at System.IO.FileStream.CreateFileOpenHandle(FileMode mode, FileShare share, FileOptions options)\\r\\n at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)\\r\\n at NServiceBus.SagaStorageFile.OpenWithDelayOnConcurrency(String filePath, FileMode fileAccess) in /_/src/NServiceBus.Core/Persistence/Learning/SagaPersister/SagaStorageFile.cs:line 56\\r\\n at NServiceBus.SagaStorageFile.OpenWithDelayOnConcurrency(String filePath, FileMode fileAccess) in /_/src/NServiceBus.Core/Persistence/Learning/SagaPersister/SagaStorageFile.cs:line 64\\r\\n at NServiceBus.LearningSynchronizedStorageSession.Open(Guid sagaId, Type entityType) in /_/src/NServiceBus.Core/Persistence/Learning/LearningSynchronizedStorageSession.cs:line 72\\r\\n at NServiceBus.LearningSynchronizedStorageSession.Read[TSagaData](Guid sagaId) in /_/src/NServiceBus.Core/Persistence/Learning/LearningSynchronizedStorageSession.cs:line 38\\r\\n at NServiceBus.PropertySagaFinder`1.Find(IBuilder builder, SagaFinderDefinition finderDefinition, SynchronizedStorageSession storageSession, ContextBag context, Object message, IReadOnlyDictionary`2 messageHeaders) in /_/src/NServiceBus.Core/Sagas/PropertySagaFinder.cs:line 42\\r\\n at NServiceBus.SagaPersistenceBehavior.Invoke(IInvokeHandlerContext context, Func`2 next) in /_/src/NServiceBus.Core/Sagas/SagaPersistenceBehavior.cs:line 78\\r\\n at NServiceBus.SagaAudit.CaptureSagaStateBehavior.Invoke(IInvokeHandlerContext context, Func`1 next)\\r\\n at NServiceBus.LoadHandlersConnector.Invoke(IIncomingLogicalMessageContext context, Func`2 stage) in /_/src/NServiceBus.Core/Pipeline/Incoming/LoadHandlersConnector.cs:line 48\\r\\n at NServiceBus.ScheduledTaskHandlingBehavior.Invoke(IIncomingLogicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/Scheduling/ScheduledTaskHandlingBehavior.cs:line 22\\r\\n at NServiceBus.InvokeSagaNotFoundBehavior.Invoke(IIncomingLogicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/Sagas/InvokeSagaNotFoundBehavior.cs:line 16\\r\\n at NServiceBus.DeserializeMessageConnector.Invoke(IIncomingPhysicalMessageContext context, Func`2 stage) in /_/src/NServiceBus.Core/Pipeline/Incoming/DeserializeMessageConnector.cs:line 34\\r\\n at NServiceBus.UnitOfWorkBehavior.InvokeUnitsOfWork(IIncomingPhysicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/UnitOfWork/UnitOfWorkBehavior.cs:line 40\\r\\n at NServiceBus.UnitOfWorkBehavior.InvokeUnitsOfWork(IIncomingPhysicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/UnitOfWork/UnitOfWorkBehavior.cs:line 62\\r\\n at NServiceBus.MutateIncomingTransportMessageBehavior.InvokeIncomingTransportMessagesMutators(IIncomingPhysicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/MessageMutators/MutateTransportMessage/MutateIncomingTransportMessageBehavior.cs:line 59\\r\\n at NServiceBus.InvokeAuditPipelineBehavior.Invoke(IIncomingPhysicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/Audit/InvokeAuditPipelineBehavior.cs:line 18\\r\\n at NServiceBus.ProcessingStatisticsBehavior.Invoke(IIncomingPhysicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/Performance/Statistics/ProcessingStatisticsBehavior.cs:line 25\\r\\n at NServiceBus.TransportReceiveToPhysicalMessageConnector.Invoke(ITransportReceiveContext context, Func`2 next) in /_/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs:line 37\\r\\n at NServiceBus.RetryAcknowledgementBehavior.Invoke(ITransportReceiveContext context, Func`2 next) in /_/src/NServiceBus.Core/ServicePlatform/Retries/RetryAcknowledgementBehavior.cs:line 25\\r\\n at NServiceBus.MainPipelineExecutor.Invoke(MessageContext messageContext) in /_/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs:line 35\\r\\n at NServiceBus.TransportReceiver.InvokePipeline(MessageContext c) in /_/src/NServiceBus.Core/Transports/TransportReceiver.cs:line 58\\r\\n at NServiceBus.TransportReceiver.InvokePipeline(MessageContext c) in /_/src/NServiceBus.Core/Transports/TransportReceiver.cs:line 64\\r\\n at NServiceBus.Transport.RabbitMQ.MessagePump.Process(EventingBasicConsumer consumer, BasicDeliverEventArgs message, Byte[] messageBody) in /_/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs:line 368\"},{\"key\":\"NServiceBus.TimeOfFailure\",\"value\":\"2022-06-05 11:24:23:624916 Z\"},{\"key\":\"NServiceBus.ExceptionInfo.Data.Message ID\",\"value\":\"01a332a7-e72d-43ea-8af2-aeab00ba1752\"},{\"key\":\"NServiceBus.FailedQ\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.ProcessingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.ProcessingEndpoint\",\"value\":\"Samples.SimpleSaga\"}],\"status\":\"failed\",\"message_intent\":\"send\",\"body_url\":\"/messages/01a332a7-e72d-43ea-8af2-aeab00ba1752/body?instance_id=aHR0cDovL2xvY2FsaG9zdDozMzMzMy9hcGk.\",\"body_size\":234,\"instance_id\":\"aHR0cDovL2xvY2FsaG9zdDozMzMzMy9hcGk.\"},{\"id\":\"8b585ac2-edf2-737d-a9c1-c041694a710d\",\"message_id\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\",\"message_type\":\"StartOrder\",\"sending_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"receiving_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"time_sent\":\"2022-06-05T11:17:32.212755Z\",\"processed_at\":\"2022-06-05T11:17:32.231559Z\",\"critical_time\":\"00:00:00.0188040\",\"processing_time\":\"00:00:00.0159600\",\"delivery_time\":\"00:00:00.0028440\",\"is_system_message\":false,\"conversation_id\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\",\"headers\":[{\"key\":\"NServiceBus.MessageId\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.MessageIntent\",\"value\":\"Send\"},{\"key\":\"NServiceBus.ConversationId\",\"value\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\"},{\"key\":\"NServiceBus.CorrelationId\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.ReplyToAddress\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.OriginatingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.OriginatingEndpoint\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.ContentType\",\"value\":\"text/xml\"},{\"key\":\"NServiceBus.EnclosedMessageTypes\",\"value\":\"StartOrder, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.Version\",\"value\":\"7.7.3\"},{\"key\":\"NServiceBus.TimeSent\",\"value\":\"2022-06-05 11:17:32:212755 Z\"},{\"key\":\"NServiceBus.NonDurableMessage\",\"value\":\"False\"},{\"key\":\"NServiceBus.InvokedSagas\",\"value\":\"OrderSaga:fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},{\"key\":\"ServiceControl.SagaStateChange\",\"value\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77:New\"},{\"key\":\"NServiceBus.ProcessingStarted\",\"value\":\"2022-06-05 11:17:32:215599 Z\"},{\"key\":\"NServiceBus.ProcessingEnded\",\"value\":\"2022-06-05 11:17:32:231559 Z\"},{\"key\":\"NServiceBus.ProcessingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.ProcessingEndpoint\",\"value\":\"Samples.SimpleSaga\"}],\"status\":\"successful\",\"message_intent\":\"send\",\"body_url\":\"/messages/0976541c-3b4e-441b-a3b0-aeab00ba1750/body?instance_id=aHR0cDovL2xvY2FsaG9zdDo0NDQ0NC9hcGkv\",\"body_size\":228,\"invoked_sagas\":[{\"change_status\":\"New\",\"saga_type\":\"OrderSaga\",\"saga_id\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"}],\"instance_id\":\"aHR0cDovL2xvY2FsaG9zdDo0NDQ0NC9hcGkv\"}]"); + var messages = JSON.parse("[{\"id\":\"" + currentMessageId + "\",\"message_id\":\"6d945c78-d9bd-426f-8e0d-aeab00ba1752\",\"message_type\":\"CancelOrder\",\"sending_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"receiving_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"time_sent\":\"2022-06-05T11:17:32.218349Z\",\"processed_at\":\"2022-06-05T11:24:23.538739Z\",\"critical_time\":\"00:06:51.3203900\",\"processing_time\":\"00:00:00.0900640\",\"delivery_time\":\"00:06:51.2303260\",\"is_system_message\":false,\"conversation_id\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\",\"headers\":[{\"key\":\"NServiceBus.ContentType\",\"value\":\"text/xml\"},{\"key\":\"NServiceBus.ConversationId\",\"value\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\"},{\"key\":\"NServiceBus.CorrelationId\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.DeliverAt\",\"value\":\"2022-06-05 11:18:02:217916 Z\"},{\"key\":\"NServiceBus.EnclosedMessageTypes\",\"value\":\"CancelOrder, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.IsSagaTimeoutMessage\",\"value\":\"True\"},{\"key\":\"NServiceBus.MessageId\",\"value\":\"6d945c78-d9bd-426f-8e0d-aeab00ba1752\"},{\"key\":\"NServiceBus.MessageIntent\",\"value\":\"Send\"},{\"key\":\"NServiceBus.OriginatingEndpoint\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.OriginatingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.OriginatingSagaId\",\"value\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},{\"key\":\"NServiceBus.OriginatingSagaType\",\"value\":\"OrderSaga, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.RelatedTo\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.ReplyToAddress\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.SagaId\",\"value\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},{\"key\":\"NServiceBus.SagaType\",\"value\":\"OrderSaga, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.TimeSent\",\"value\":\"2022-06-05 11:17:32:218349 Z\"},{\"key\":\"NServiceBus.Version\",\"value\":\"7.7.3\"},{\"key\":\"NServiceBus.NonDurableMessage\",\"value\":\"False\"},{\"key\":\"NServiceBus.InvokedSagas\",\"value\":\"OrderSaga:fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},{\"key\":\"ServiceControl.SagaStateChange\",\"value\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77:Completed\"},{\"key\":\"NServiceBus.ProcessingStarted\",\"value\":\"2022-06-05 11:24:23:448675 Z\"},{\"key\":\"NServiceBus.ProcessingEnded\",\"value\":\"2022-06-05 11:24:23:538739 Z\"},{\"key\":\"NServiceBus.ProcessingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.ProcessingEndpoint\",\"value\":\"Samples.SimpleSaga\"}],\"status\":\"successful\",\"message_intent\":\"send\",\"body_url\":\"/messages/6d945c78-d9bd-426f-8e0d-aeab00ba1752/body?instance_id=aHR0cDovL2xvY2FsaG9zdDo0NDQ0NC9hcGkv\",\"body_size\":175,\"invoked_sagas\":[{\"change_status\":\"Completed\",\"saga_type\":\"OrderSaga\",\"saga_id\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"}],\"originates_from_saga\":{\"saga_type\":\"OrderSaga\",\"saga_id\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},\"instance_id\":\"aHR0cDovL2xvY2FsaG9zdDo0NDQ0NC9hcGkv\"},{\"id\":\"e1f0a28a-cf46-4365-76d1-c0526e75e16c\",\"message_id\":\"01a332a7-e72d-43ea-8af2-aeab00ba1752\",\"message_type\":\"CompleteOrder\",\"sending_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"receiving_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"time_sent\":\"2022-06-05T11:17:32.2179Z\",\"processed_at\":\"2022-06-05T11:24:23.624916Z\",\"critical_time\":\"00:00:00\",\"processing_time\":\"00:00:00\",\"delivery_time\":\"00:00:00\",\"is_system_message\":false,\"conversation_id\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\",\"headers\":[{\"key\":\"NServiceBus.ContentType\",\"value\":\"text/xml\"},{\"key\":\"NServiceBus.ConversationId\",\"value\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\"},{\"key\":\"NServiceBus.CorrelationId\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.DeliverAt\",\"value\":\"2022-06-05 11:17:42:217900 Z\"},{\"key\":\"NServiceBus.EnclosedMessageTypes\",\"value\":\"CompleteOrder, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.MessageId\",\"value\":\"01a332a7-e72d-43ea-8af2-aeab00ba1752\"},{\"key\":\"NServiceBus.MessageIntent\",\"value\":\"Send\"},{\"key\":\"NServiceBus.OriginatingEndpoint\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.OriginatingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.OriginatingSagaId\",\"value\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},{\"key\":\"NServiceBus.OriginatingSagaType\",\"value\":\"OrderSaga, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.RelatedTo\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.ReplyToAddress\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.TimeSent\",\"value\":\"2022-06-05 11:17:32:217900 Z\"},{\"key\":\"NServiceBus.Version\",\"value\":\"7.7.3\"},{\"key\":\"NServiceBus.NonDurableMessage\",\"value\":\"False\"},{\"key\":\"NServiceBus.ExceptionInfo.ExceptionType\",\"value\":\"System.IO.IOException\"},{\"key\":\"NServiceBus.ExceptionInfo.HelpLink\",\"value\":null},{\"key\":\"NServiceBus.ExceptionInfo.Message\",\"value\":\"The process cannot access the file 'C:\\\\Code\\\\docs.particular.net\\\\samples\\\\saga\\\\simple\\\\Core_7\\\\Sample\\\\bin\\\\Debug\\\\net5.0\\\\.sagas\\\\OrderSaga\\\\fac144c9-87cc-fc0d-84d3-49ae6f489f77.json' because it is being used by another process.\"},{\"key\":\"NServiceBus.ExceptionInfo.Source\",\"value\":\"System.Private.CoreLib\"},{\"key\":\"NServiceBus.ExceptionInfo.StackTrace\",\"value\":\"System.IO.IOException: The process cannot access the file 'C:\\\\Code\\\\docs.particular.net\\\\samples\\\\saga\\\\simple\\\\Core_7\\\\Sample\\\\bin\\\\Debug\\\\net5.0\\\\.sagas\\\\OrderSaga\\\\fac144c9-87cc-fc0d-84d3-49ae6f489f77.json' because it is being used by another process.\\r\\n at System.IO.FileStream.ValidateFileHandle(SafeFileHandle fileHandle)\\r\\n at System.IO.FileStream.CreateFileOpenHandle(FileMode mode, FileShare share, FileOptions options)\\r\\n at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)\\r\\n at NServiceBus.SagaStorageFile.OpenWithDelayOnConcurrency(String filePath, FileMode fileAccess) in /_/src/NServiceBus.Core/Persistence/Learning/SagaPersister/SagaStorageFile.cs:line 56\\r\\n at NServiceBus.SagaStorageFile.OpenWithDelayOnConcurrency(String filePath, FileMode fileAccess) in /_/src/NServiceBus.Core/Persistence/Learning/SagaPersister/SagaStorageFile.cs:line 64\\r\\n at NServiceBus.LearningSynchronizedStorageSession.Open(Guid sagaId, Type entityType) in /_/src/NServiceBus.Core/Persistence/Learning/LearningSynchronizedStorageSession.cs:line 72\\r\\n at NServiceBus.LearningSynchronizedStorageSession.Read[TSagaData](Guid sagaId) in /_/src/NServiceBus.Core/Persistence/Learning/LearningSynchronizedStorageSession.cs:line 38\\r\\n at NServiceBus.PropertySagaFinder`1.Find(IBuilder builder, SagaFinderDefinition finderDefinition, SynchronizedStorageSession storageSession, ContextBag context, Object message, IReadOnlyDictionary`2 messageHeaders) in /_/src/NServiceBus.Core/Sagas/PropertySagaFinder.cs:line 42\\r\\n at NServiceBus.SagaPersistenceBehavior.Invoke(IInvokeHandlerContext context, Func`2 next) in /_/src/NServiceBus.Core/Sagas/SagaPersistenceBehavior.cs:line 78\\r\\n at NServiceBus.SagaAudit.CaptureSagaStateBehavior.Invoke(IInvokeHandlerContext context, Func`1 next)\\r\\n at NServiceBus.LoadHandlersConnector.Invoke(IIncomingLogicalMessageContext context, Func`2 stage) in /_/src/NServiceBus.Core/Pipeline/Incoming/LoadHandlersConnector.cs:line 48\\r\\n at NServiceBus.ScheduledTaskHandlingBehavior.Invoke(IIncomingLogicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/Scheduling/ScheduledTaskHandlingBehavior.cs:line 22\\r\\n at NServiceBus.InvokeSagaNotFoundBehavior.Invoke(IIncomingLogicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/Sagas/InvokeSagaNotFoundBehavior.cs:line 16\\r\\n at NServiceBus.DeserializeMessageConnector.Invoke(IIncomingPhysicalMessageContext context, Func`2 stage) in /_/src/NServiceBus.Core/Pipeline/Incoming/DeserializeMessageConnector.cs:line 34\\r\\n at NServiceBus.UnitOfWorkBehavior.InvokeUnitsOfWork(IIncomingPhysicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/UnitOfWork/UnitOfWorkBehavior.cs:line 40\\r\\n at NServiceBus.UnitOfWorkBehavior.InvokeUnitsOfWork(IIncomingPhysicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/UnitOfWork/UnitOfWorkBehavior.cs:line 62\\r\\n at NServiceBus.MutateIncomingTransportMessageBehavior.InvokeIncomingTransportMessagesMutators(IIncomingPhysicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/MessageMutators/MutateTransportMessage/MutateIncomingTransportMessageBehavior.cs:line 59\\r\\n at NServiceBus.InvokeAuditPipelineBehavior.Invoke(IIncomingPhysicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/Audit/InvokeAuditPipelineBehavior.cs:line 18\\r\\n at NServiceBus.ProcessingStatisticsBehavior.Invoke(IIncomingPhysicalMessageContext context, Func`2 next) in /_/src/NServiceBus.Core/Performance/Statistics/ProcessingStatisticsBehavior.cs:line 25\\r\\n at NServiceBus.TransportReceiveToPhysicalMessageConnector.Invoke(ITransportReceiveContext context, Func`2 next) in /_/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs:line 37\\r\\n at NServiceBus.RetryAcknowledgementBehavior.Invoke(ITransportReceiveContext context, Func`2 next) in /_/src/NServiceBus.Core/ServicePlatform/Retries/RetryAcknowledgementBehavior.cs:line 25\\r\\n at NServiceBus.MainPipelineExecutor.Invoke(MessageContext messageContext) in /_/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs:line 35\\r\\n at NServiceBus.TransportReceiver.InvokePipeline(MessageContext c) in /_/src/NServiceBus.Core/Transports/TransportReceiver.cs:line 58\\r\\n at NServiceBus.TransportReceiver.InvokePipeline(MessageContext c) in /_/src/NServiceBus.Core/Transports/TransportReceiver.cs:line 64\\r\\n at NServiceBus.Transport.RabbitMQ.MessagePump.Process(EventingBasicConsumer consumer, BasicDeliverEventArgs message, Byte[] messageBody) in /_/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs:line 368\"},{\"key\":\"NServiceBus.TimeOfFailure\",\"value\":\"2022-06-05 11:24:23:624916 Z\"},{\"key\":\"NServiceBus.ExceptionInfo.Data.Message ID\",\"value\":\"01a332a7-e72d-43ea-8af2-aeab00ba1752\"},{\"key\":\"NServiceBus.FailedQ\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.ProcessingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.ProcessingEndpoint\",\"value\":\"Samples.SimpleSaga\"}],\"status\":\"failed\",\"message_intent\":\"send\",\"body_url\":\"/messages/01a332a7-e72d-43ea-8af2-aeab00ba1752/body?instance_id=aHR0cDovL2xvY2FsaG9zdDozMzMzMy9hcGk.\",\"body_size\":234,\"instance_id\":\"aHR0cDovL2xvY2FsaG9zdDozMzMzMy9hcGk.\"},{\"id\":\"8b585ac2-edf2-737d-a9c1-c041694a710d\",\"message_id\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\",\"message_type\":\"StartOrder\",\"sending_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"receiving_endpoint\":{\"name\":\"Samples.SimpleSaga\",\"host_id\":\"f3b4fa81-8fe7-9dae-19c8-b2b62070a6a0\",\"host\":\"DESKTOP-UHR90B3\"},\"time_sent\":\"2022-06-05T11:17:32.212755Z\",\"processed_at\":\"2022-06-05T11:17:32.231559Z\",\"critical_time\":\"00:00:00.0188040\",\"processing_time\":\"00:00:00.0159600\",\"delivery_time\":\"00:00:00.0028440\",\"is_system_message\":false,\"conversation_id\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\",\"headers\":[{\"key\":\"NServiceBus.MessageId\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.MessageIntent\",\"value\":\"Send\"},{\"key\":\"NServiceBus.ConversationId\",\"value\":\"6a57578a-2282-4251-9c0b-aeab00ba1750\"},{\"key\":\"NServiceBus.CorrelationId\",\"value\":\"0976541c-3b4e-441b-a3b0-aeab00ba1750\"},{\"key\":\"NServiceBus.ReplyToAddress\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.OriginatingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.OriginatingEndpoint\",\"value\":\"Samples.SimpleSaga\"},{\"key\":\"NServiceBus.ContentType\",\"value\":\"text/xml\"},{\"key\":\"NServiceBus.EnclosedMessageTypes\",\"value\":\"StartOrder, Sample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"},{\"key\":\"NServiceBus.Version\",\"value\":\"7.7.3\"},{\"key\":\"NServiceBus.TimeSent\",\"value\":\"2022-06-05 11:17:32:212755 Z\"},{\"key\":\"NServiceBus.NonDurableMessage\",\"value\":\"False\"},{\"key\":\"NServiceBus.InvokedSagas\",\"value\":\"OrderSaga:fac144c9-87cc-fc0d-84d3-49ae6f489f77\"},{\"key\":\"ServiceControl.SagaStateChange\",\"value\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77:New\"},{\"key\":\"NServiceBus.ProcessingStarted\",\"value\":\"2022-06-05 11:17:32:215599 Z\"},{\"key\":\"NServiceBus.ProcessingEnded\",\"value\":\"2022-06-05 11:17:32:231559 Z\"},{\"key\":\"NServiceBus.ProcessingMachine\",\"value\":\"DESKTOP-UHR90B3\"},{\"key\":\"NServiceBus.ProcessingEndpoint\",\"value\":\"Samples.SimpleSaga\"}],\"status\":\"successful\",\"message_intent\":\"send\",\"body_url\":\"/messages/0976541c-3b4e-441b-a3b0-aeab00ba1750/body?instance_id=aHR0cDovL2xvY2FsaG9zdDo0NDQ0NC9hcGkv\",\"body_size\":228,\"invoked_sagas\":[{\"change_status\":\"New\",\"saga_type\":\"OrderSaga\",\"saga_id\":\"fac144c9-87cc-fc0d-84d3-49ae6f489f77\"}],\"instance_id\":\"aHR0cDovL2xvY2FsaG9zdDo0NDQ0NC9hcGkv\"}]"); var tree = createTreeStructure(messages.map(x => mapMessage(x))); drawTree(tree[0]); } From 4c845eeb2aee981de350cc01260b91e825ace9e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Mon, 11 Jul 2022 11:02:33 +0200 Subject: [PATCH 09/28] Move diagram back to the center, use viewport to scale the diagram --- .../app/js/directives/ui.particular.flow-diagram.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js index f90938297d..25da28e8b9 100644 --- a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js +++ b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js @@ -75,11 +75,10 @@ // appends a 'group' element to 'svg' // moves the 'group' element to the top left margin parentSvg = d3.select("#tree-container").append("svg") - .attr("width", 'auto') - .attr("height", height + margin.top + margin.bottom); + .attr('viewBox', '-1000 -10 2000 2000'); + svg = parentSvg.append("g") - .attr("transform", "translate(" - + margin.left + "," + margin.top + ")"); + .attr("transform", "scale(.7,.7)"); var zoom = d3.zoom() .scaleExtent([1 / 2, 8]) From fea63b2446d399084f5fd9a802e2175d7a18f754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Mon, 11 Jul 2022 13:29:48 +0200 Subject: [PATCH 10/28] Introducing string template --- .../app/js/directives/ui.particular.flow-diagram.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js index 25da28e8b9..991cc5ff0e 100644 --- a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js +++ b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js @@ -160,10 +160,10 @@ : (rectNode.height - rectNode.textMargin * 2) }) .append('xhtml').html(function(d) { - return '
' - + `
${(d.data.isError ? `${d.data.nodeName}` : d.data.nodeName)}
+ return `
+
${(d.data.isError ? `${d.data.nodeName}` : d.data.nodeName)}
${d.data.timeSent.toLocaleString()}
${d.data.sagaName}
`; From 0774743cd6ac4c7d1f618ccd2c91eb2a07a449ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Mon, 11 Jul 2022 13:58:41 +0200 Subject: [PATCH 11/28] Straight lines --- .../directives/ui.particular.flow-diagram.js | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js index 991cc5ff0e..6350f15423 100644 --- a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js +++ b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js @@ -217,7 +217,7 @@ .attr('marker-start', 'url(#end-arrow)') .attr('d', function(d){ var o = {x: source.x0, y: source.y0} - return diagonal(o, o); + return straight(o, o); }); // UPDATE @@ -226,14 +226,14 @@ // Transition back to the parent element position linkUpdate.transition() .duration(duration) - .attr('d', function(d){ return diagonal(d, d.parent) }); + .attr('d', function(d){ return straight(d, d.parent) }); // Remove any exiting links var linkExit = link.exit().transition() .duration(duration) .attr('d', function(d) { var o = {x: source.x, y: source.y}; - return diagonal(o, o); + return straight(o, o); }) .remove(); @@ -242,21 +242,12 @@ d.x0 = d.x; d.y0 = d.y; }); - - // Creates a curved (diagonal) path from parent to the child nodes - function diagonal(s, d) { - - var path = "M" + (s.x + rectNode.width / 2) + "," + s.y - + "C" + (s.x + rectNode.width / 2) + "," + (s.y + d.y) / 2 - + " " + (d.x + rectNode.width / 2) + "," + (s.y + d.y) / 2 - + " " + (d.x + rectNode.width / 2) + "," + (d.y + rectNode.height); - - return path - } function straight(s, d){ - return "M" + (s.x + rectNode.width / 2) + "," + s.y - + "H" + (d.x + rectNode.width / 2) + "V" + (d.y + rectNode.height); + return `M ${s.x + rectNode.width / 2} ${s.y} + C ${s.x + rectNode.width / 2} ${s.y} , + ${d.x + rectNode.width / 2} ${d.y + rectNode.height} , + ${d.x + rectNode.width / 2} ${d.y + rectNode.height}`; } // Toggle children on click. From 3e8f6fb83210a22fc7a4f2e5d9b54da857d6f5a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Mon, 11 Jul 2022 15:12:25 +0200 Subject: [PATCH 12/28] Adding some space in between nodes --- .../app/js/directives/ui.particular.flow-diagram.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js index 6350f15423..f8a3bbc5a5 100644 --- a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js +++ b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js @@ -97,7 +97,7 @@ .attr('d', 'M10,-5L0,0L10,5'); // declares a tree layout and assigns the size - treemap = d3.tree().nodeSize([rectNode.width, rectNode.height]); + treemap = d3.tree().nodeSize([rectNode.width + 20, rectNode.height]); root.x0 = width / 2; root.y0 = 0; From 48cd362b4212206356d92633807aa0a87bc079d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Mon, 11 Jul 2022 15:25:51 +0200 Subject: [PATCH 13/28] Moving classes to g element --- .../app/js/directives/ui.particular.flow-diagram.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js index f8a3bbc5a5..5190f31b66 100644 --- a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js +++ b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js @@ -131,15 +131,14 @@ // Enter any new modes at the parent's previous position. var nodeEnter = node.enter().append('g') - .attr('class', 'node') + .attr('class', function(d){return `node ${d.data.type.toLowerCase()} ${d.data.isError ? 'error' : ''} ${d.data.id === currentMessageId ? 'current-message' : ''}`;}) .attr("transform", function(d) { return "translate(" + source.x0 + "," + source.y0 + ")"; }) .on('click', click); // Add rectangle for the nodes - nodeEnter.append('rect') - .attr('class', function(d){return `node ${d.data.type.toLowerCase()} ${d.data.isError ? 'error' : ''} ${d.data.id === currentMessageId ? 'current-message' : ''}`;}) + nodeEnter.append('rect') .attr('rx', 6) .attr('ry', 6) .attr('width', rectNode.width) From 3f4ef4e7a666010513e1367af03580130f14af8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Mon, 11 Jul 2022 15:49:10 +0200 Subject: [PATCH 14/28] Adding SVG's --- src/ServicePulse.Host/app/img/command.svg | 14 ++++++++++++++ src/ServicePulse.Host/app/img/event.svg | 20 ++++++++++++++++++++ src/ServicePulse.Host/app/img/saga.svg | 9 +++++++++ src/ServicePulse.Host/app/img/timeout.svg | 9 +++++++++ 4 files changed, 52 insertions(+) create mode 100644 src/ServicePulse.Host/app/img/command.svg create mode 100644 src/ServicePulse.Host/app/img/event.svg create mode 100644 src/ServicePulse.Host/app/img/saga.svg create mode 100644 src/ServicePulse.Host/app/img/timeout.svg diff --git a/src/ServicePulse.Host/app/img/command.svg b/src/ServicePulse.Host/app/img/command.svg new file mode 100644 index 0000000000..9f39793085 --- /dev/null +++ b/src/ServicePulse.Host/app/img/command.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/ServicePulse.Host/app/img/event.svg b/src/ServicePulse.Host/app/img/event.svg new file mode 100644 index 0000000000..c4222f78c9 --- /dev/null +++ b/src/ServicePulse.Host/app/img/event.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/ServicePulse.Host/app/img/saga.svg b/src/ServicePulse.Host/app/img/saga.svg new file mode 100644 index 0000000000..f284fcc527 --- /dev/null +++ b/src/ServicePulse.Host/app/img/saga.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/ServicePulse.Host/app/img/timeout.svg b/src/ServicePulse.Host/app/img/timeout.svg new file mode 100644 index 0000000000..40869a1988 --- /dev/null +++ b/src/ServicePulse.Host/app/img/timeout.svg @@ -0,0 +1,9 @@ + + + + + + + + + From 7b07417803ce46efd891fd9368e000b64c387f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Mon, 11 Jul 2022 16:06:59 +0200 Subject: [PATCH 15/28] Apply icons to the diagram --- src/ServicePulse.Host/app/css/particular.css | 32 +++++++++++++++++++ .../directives/ui.particular.flow-diagram.js | 4 +-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/ServicePulse.Host/app/css/particular.css b/src/ServicePulse.Host/app/css/particular.css index 92e72bbc0c..a16aa059a4 100644 --- a/src/ServicePulse.Host/app/css/particular.css +++ b/src/ServicePulse.Host/app/css/particular.css @@ -3397,4 +3397,36 @@ section[name="platformconnection"] li { } .link.event{ stroke-dasharray: 5,3; +} + +.pa-flow-saga { + background-image: url('../img/saga.svg'); + background-position: center; + background-repeat: no-repeat; + height: 15px; + width: 15px; +} + +.pa-flow-timeout { + background-image: url('../img/timeout.svg'); + background-position: center; + background-repeat: no-repeat; + height: 15px; + width: 15px; +} + +.pa-flow-event { + background-image: url('../img/event.svg'); + background-position: center; + background-repeat: no-repeat; + height: 15px; + width: 15px; +} + +.pa-flow-command { + background-image: url('../img/command.svg'); + background-position: center; + background-repeat: no-repeat; + height: 15px; + width: 15px; } \ No newline at end of file diff --git a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js index 5190f31b66..bffe78f721 100644 --- a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js +++ b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js @@ -162,9 +162,9 @@ return `
-
${(d.data.isError ? `${d.data.nodeName}` : d.data.nodeName)}
+
${(d.data.isError ? `${d.data.nodeName}` : d.data.nodeName)}
${d.data.timeSent.toLocaleString()}
-${d.data.sagaName} +${d.data.sagaName}
`; }) From 8936d83f1ffa4ed8ec57a7a322fdd775a799c777 Mon Sep 17 00:00:00 2001 From: sergioc Date: Mon, 11 Jul 2022 16:18:11 +0200 Subject: [PATCH 16/28] style tweaks --- src/ServicePulse.Host/app/css/particular.css | 88 ++++++++++++++++---- 1 file changed, 70 insertions(+), 18 deletions(-) diff --git a/src/ServicePulse.Host/app/css/particular.css b/src/ServicePulse.Host/app/css/particular.css index a16aa059a4..b2671b4499 100644 --- a/src/ServicePulse.Host/app/css/particular.css +++ b/src/ServicePulse.Host/app/css/particular.css @@ -3372,24 +3372,6 @@ section[name="platformconnection"] li { margin-bottom: 15px; } -.node rect { - fill: #fff; - stroke: steelblue; - stroke-width: 3px; -} - -.node rect.error { - stroke: red; -} - -.node text { - font: 12px sans-serif; -} - -.node .time-sent { - color: #777f7f; -} - .link { fill: none; stroke: #ccc; @@ -3429,4 +3411,74 @@ section[name="platformconnection"] li { background-repeat: no-repeat; height: 15px; width: 15px; +} + +.node rect { + fill: #fff; + stroke: #cccbcc; + stroke-width: 3px; +} + +.node rect.error { + stroke: red; +} + +.node text { + font: 12px sans-serif; +} + +.node .time-sent { + color: #777f7f; +} + +.node-text { + padding: 8px; +} + +.node-text i { + display: inline-block; + position: relative; + top: 3px; + margin-right: 3px; + filter: brightness(0) saturate(100%) invert(0%) sepia(0%) saturate(0%) hue-rotate(346deg) brightness(104%) contrast(104%); +} + +.node-text .lead { + display: inline; +} + +.node-text .time-sent { +display: block; +} + +g.current-message rect { + stroke: #be514a; + fill: #be514a !important; +} + +g.current-message .node-text { + color: #fff; +} + +g.current-message .node-text i { + color: #fff; + filter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(7475%) hue-rotate(21deg) brightness(100%) contrast(106%); +} + +g.current-message .node-text b { + color: #fff; + text-decoration: underline +} + +g.current-message .node-text .time-sent { + color: #ffcecb; +} + +g.error rect { + stroke: #be514a; +} + +g.error .node-text a { + color: #000; + border-bottom: 1px solid #000; } \ No newline at end of file From 376ad95e1d42fb1b32cb59def0c20d9388e42123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Tue, 12 Jul 2022 08:52:22 +0200 Subject: [PATCH 17/28] Adding agreed changes to saga information --- .../app/js/directives/ui.particular.flow-diagram.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js index bffe78f721..f6dda32597 100644 --- a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js +++ b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js @@ -162,9 +162,9 @@ return `
-
${(d.data.isError ? `${d.data.nodeName}` : d.data.nodeName)}
-${d.data.timeSent.toLocaleString()}
-${d.data.sagaName} +
${(d.data.isError ? `${d.data.nodeName}` : d.data.nodeName)}
+${d.data.timeSent.toLocaleString()} +${!!d.data.sagaName ? `
${d.data.sagaName}
` : ''}
`; }) From d6480772546a07a1f80eb0a0739915434e33b72d Mon Sep 17 00:00:00 2001 From: sergioc Date: Tue, 12 Jul 2022 10:26:20 +0200 Subject: [PATCH 18/28] more style tweaks --- src/ServicePulse.Host/app/css/particular.css | 28 +++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/ServicePulse.Host/app/css/particular.css b/src/ServicePulse.Host/app/css/particular.css index b2671b4499..02ca71f1b0 100644 --- a/src/ServicePulse.Host/app/css/particular.css +++ b/src/ServicePulse.Host/app/css/particular.css @@ -3428,27 +3428,30 @@ section[name="platformconnection"] li { } .node .time-sent { + display: block; color: #777f7f; + margin-left: 19px; } .node-text { - padding: 8px; + padding: 3px 8px 1px; } .node-text i { display: inline-block; - position: relative; - top: 3px; margin-right: 3px; filter: brightness(0) saturate(100%) invert(0%) sepia(0%) saturate(0%) hue-rotate(346deg) brightness(104%) contrast(104%); } .node-text .lead { - display: inline; + display: inline-block; + width: 206px; + position: relative; + top: 4px; } -.node-text .time-sent { -display: block; +.node-text .saga { + font-weight: normal; } g.current-message rect { @@ -3456,8 +3459,8 @@ g.current-message rect { fill: #be514a !important; } -g.current-message .node-text { - color: #fff; +g.current-message .node-text, g.current-message .node-text .lead { + color: #fff !important; } g.current-message .node-text i { @@ -3465,9 +3468,8 @@ g.current-message .node-text i { filter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(7475%) hue-rotate(21deg) brightness(100%) contrast(106%); } -g.current-message .node-text b { +g.current-message .node-text strong { color: #fff; - text-decoration: underline } g.current-message .node-text .time-sent { @@ -3481,4 +3483,10 @@ g.error rect { g.error .node-text a { color: #000; border-bottom: 1px solid #000; + padding-bottom: 1px; +} + +g.error .node-text a:hover { + text-decoration: none; + border-bottom: 2px solid #000; } \ No newline at end of file From 4ff6e987ca9302c0c2ffb5d133daa92d3eb0685b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=C3=B3jcik?= Date: Tue, 12 Jul 2022 10:40:51 +0200 Subject: [PATCH 19/28] Adding tooltips, human readable date and variable size --- .../directives/ui.particular.flow-diagram.js | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js index f6dda32597..8364815c18 100644 --- a/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js +++ b/src/ServicePulse.Host/app/js/directives/ui.particular.flow-diagram.js @@ -2,7 +2,7 @@ 'use strict'; - function controller($scope, serviceControlService, $routeParams) { + function controller($scope, serviceControlService, $routeParams, $compile) { function createTreeStructure(messages) { var map = {}, node, roots = [], i; @@ -142,7 +142,9 @@ .attr('rx', 6) .attr('ry', 6) .attr('width', rectNode.width) - .attr('height', rectNode.height) + .attr('height', function(d) { + return !d.data.sagaName ? rectNode.height - 10 : rectNode.height; + }) .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }); @@ -154,19 +156,25 @@ return (rectNode.width - rectNode.textMargin * 2) < 0 ? 0 : (rectNode.width - rectNode.textMargin * 2) }) - .attr('height', function() { - return (rectNode.height - rectNode.textMargin * 2) < 0 ? 0 - : (rectNode.height - rectNode.textMargin * 2) + .attr('height', function(d) { + var height = rectNode.height; + if(!d.data.sagaName){ + height -= 10; + } + return (height - rectNode.textMargin * 2) < 0 ? 0 + : (height - rectNode.textMargin * 2) }) .append('xhtml').html(function(d) { return `
-
${(d.data.isError ? `${d.data.nodeName}` : d.data.nodeName)}
-${d.data.timeSent.toLocaleString()} -${!!d.data.sagaName ? `
${d.data.sagaName}
` : ''} + ${(rectNode.height - rectNode.textMargin * 2)} px;" class="node-text wordwrap"> +
${(d.data.isError ? `${d.data.nodeName}` : d.data.nodeName)}
+ +${!!d.data.sagaName ? `
${d.data.sagaName}
` : ''}
`; - }) + }).each(function(){ + $compile(this)($scope); + }); // UPDATE var nodeUpdate = nodeEnter.merge(node); @@ -245,8 +253,8 @@ ${!!d.data.sagaName ? `
x.key === 'NServiceBus.DeliverAt') > -1 ? 'Delay' : message.headers.find(x => x.key === 'NServiceBus.MessageIntent').value === 'Publish' ? 'Event' : 'Command', + "type": message.headers.findIndex(x => x.key === 'NServiceBus.DeliverAt') > -1 ? 'Timeout message' : message.headers.find(x => x.key === 'NServiceBus.MessageIntent').value === 'Publish' ? 'Event message' : 'Command message', "isError": message.headers.findIndex(x => x.key === 'NServiceBus.ExceptionInfo.ExceptionType') > -1, "sagaName": saga, "link" : { @@ -143,7 +143,7 @@ .attr('ry', 6) .attr('width', rectNode.width) .attr('height', function(d) { - return !d.data.sagaName ? rectNode.height - 10 : rectNode.height; + return !d.data.sagaName ? rectNode.height - 22 : rectNode.height; }) .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; @@ -253,8 +253,8 @@ ${!!d.data.sagaName ? `
x.key === 'NServiceBus.DeliverAt') > -1 ? 'Timeout message' : message.headers.find(x => x.key === 'NServiceBus.MessageIntent').value === 'Publish' ? 'Event message' : 'Command message', - "isError": message.headers.findIndex(x => x.key === 'NServiceBus.ExceptionInfo.ExceptionType') > -1, + "type": message.headers.findIndex(function(x) { return x.key === 'NServiceBus.DeliverAt'; }) > -1 ? 'Timeout message' : message.headers.find(function(x) { return x.key === 'NServiceBus.MessageIntent'; }).value === 'Publish' ? 'Event message' : 'Command message', + "isError": message.headers.findIndex(function(x) { x.key === 'NServiceBus.ExceptionInfo.ExceptionType'; }) > -1, "sagaName": saga, "link" : { "name" : "Link "+message.id, @@ -53,8 +53,8 @@ // Set the dimensions and margins of the diagram var margin = {top: 20, right: 90, bottom: 30, left: 90}, - width = 3600 - margin.left - margin.right, - height = 1500 - margin.top - margin.bottom; + width = 3600 - margin.left - margin.right; + var rectNode = { width : 250, height : 90, textMargin : 5 } @@ -69,8 +69,7 @@ root = d3.hierarchy(treeData, function (d) { return d.children; }); - - height = 200 * (root.height + 1); + // append the svg object to the body of the page // appends a 'group' element to 'svg' // moves the 'group' element to the top left margin @@ -131,9 +130,9 @@ // Enter any new modes at the parent's previous position. var nodeEnter = node.enter().append('g') - .attr('class', function(d){return `node ${d.data.type.toLowerCase()} ${d.data.isError ? 'error' : ''} ${d.data.id === currentMessageId ? 'current-message' : ''}`;}) - .attr("transform", function(d) { - return "translate(" + source.x0 + "," + source.y0 + ")"; + .attr('class', function(d){ return 'node ' + d.data.type.toLowerCase() + ' ' + (d.data.isError ? 'error' : '') + ' ' + (d.data.id === currentMessageId ? 'current-message' : '');}) + .attr('transform', function(d) { + return 'translate(' + source.x0 + "," + source.y0 + ')'; }) .on('click', click); @@ -146,7 +145,7 @@ return !d.data.sagaName ? rectNode.height - 22 : rectNode.height; }) .style("fill", function(d) { - return d._children ? "lightsteelblue" : "#fff"; + return d._children ? 'lightsteelblue' : '#fff'; }); nodeEnter.append('foreignObject') @@ -165,13 +164,13 @@ : (height - rectNode.textMargin * 2) }) .append('xhtml').html(function(d) { - return `
-
${(d.data.isError ? `${d.data.nodeName}` : d.data.nodeName)}
- -${!!d.data.sagaName ? `
${d.data.sagaName}
` : ''} -
`; + return '
' + + '
' + (d.data.isError ? '' + d.data.nodeName + '' : d.data.nodeName) + '
' + + '' + + (d.data.sagaName ? '
' + d.data.sagaName + '
' : '') + +'
'; }).each(function(){ $compile(this)($scope); }); @@ -236,7 +235,7 @@ ${!!d.data.sagaName ? `