github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/static/js/dependency-graph.js (about) 1 /* 2 Copyright 2023. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 "use strict"; 18 19 const colorArray = [ 20 "#6347D9", 21 "#01BFB3", 22 "#E9DC6E", 23 "#F2A52B", 24 "#4BAE7F", 25 "#9178C5", 26 "#23A9E2", 27 "#8C706B", 28 "#22589D", 29 "#B33B97", 30 "#9FBF46", 31 "#BF9A68", 32 "#DC756F", 33 "#E55D9A", 34 "#597C53", 35 ]; 36 37 let svgWidth; 38 let svgHeight; 39 40 $(document).ready(() => { 41 if (Cookies.get("theme")) { 42 theme = Cookies.get("theme"); 43 $("body").attr("data-theme", theme); 44 } 45 $(".theme-btn").on("click", themePickerHandler); 46 47 svgWidth = $("#dependency-graph-container").width(); 48 svgHeight = $("#dependency-graph-container").height(); 49 50 $("#error-msg-container, #dependency-info").hide(); 51 getServiceDependencyData(); 52 53 $("#dependency-info").tooltip({ 54 delay: { show: 0, hide: 300 }, 55 trigger: 'click' 56 }); 57 58 $('#dependency-info').on('click', function (e) { 59 $('#dependency-info').tooltip('show'); 60 }); 61 62 $(document).mouseup(function (e) { 63 if ($(e.target).closest(".tooltip-inner").length === 0) { 64 $('#dependency-info').tooltip('hide'); 65 } 66 }); 67 }); 68 69 function getServiceDependencyData() { 70 $.ajax({ 71 method: "GET", 72 url: "api/traces/dependencies", 73 headers: { 74 "Content-Type": "application/json; charset=utf-8", 75 Accept: "*/*", 76 }, 77 dataType: "json", 78 crossDomain: true, 79 success: function (res) { 80 if ($.isEmptyObject(res)) { 81 $("#dependency-graph-container").hide(); 82 $("#error-msg-container").show() 83 } else { 84 $("#dependency-graph-container,#dependency-info").show(); 85 $("#error-msg-container").hide(); 86 createDependencyMatrix(res); 87 var lastRunTimestamp =moment(res.timestamp); 88 var formattedDate = lastRunTimestamp.format("DD-MMM-YYYY hh:mm:ss"); 89 $('#last-run-timestamp').text(formattedDate); 90 } 91 }, 92 error: function () { 93 $("#dependency-graph-container").hide(); 94 $("#error-msg-container").show(); 95 }, 96 }); 97 } 98 99 function createDependencyMatrix(res) { 100 const data = {}; 101 const nodes = []; 102 const links = []; 103 104 for (const key in res) { 105 if (key !== "_index" && key !== "timestamp") { 106 data[key] = res[key]; 107 } 108 } 109 110 Object.keys(data).forEach((parentNode) => { 111 if (!nodes.some((node) => node.id === parentNode)) { 112 nodes.push({ id: parentNode }); 113 } 114 // Iterate through parent node 115 Object.keys(data[parentNode]).forEach((childNode) => { 116 if (!nodes.some((node) => node.id === childNode)) { 117 nodes.push({ id: childNode }); 118 } 119 // Add link 120 links.push({ 121 source: parentNode, 122 target: childNode, 123 value: data[parentNode][childNode], 124 }); 125 }); 126 }); 127 128 displayDependencyGraph(nodes, links); 129 } 130 131 function displayDependencyGraph(nodes, links) { 132 const svg = d3 133 .select("#dependency-graph-container") 134 .append("svg") 135 .attr("width", svgWidth) 136 .attr("height", svgHeight) 137 .call( 138 d3.zoom().on("zoom", (event) => { 139 svg.attr("transform", event.transform); 140 }) 141 ) 142 .append("g"); 143 144 const simulation = d3 145 .forceSimulation(nodes) 146 .force( 147 "link", 148 d3 149 .forceLink(links) 150 .id((d) => d.id) 151 .distance(200) 152 .strength(0.5) 153 ) 154 .force("charge", d3.forceManyBody().strength(-300)) 155 .force("center", d3.forceCenter(svgWidth / 2, svgHeight)) 156 .force( 157 "radial", 158 d3 159 .forceRadial( 160 Math.min(svgWidth, svgHeight) / 2, 161 svgWidth / 2, 162 svgHeight / 2 163 ) 164 .strength(0.1) 165 ); 166 167 svg 168 .append("defs") 169 .append("marker") 170 .attr("id", "arrowhead") 171 .attr("viewBox", "-0 -5 10 10") 172 .attr("refX", 23) 173 .attr("refY", 0) 174 .attr("orient", "auto") 175 .attr("markerWidth", 12) 176 .attr("markerHeight", 12) 177 .attr("xoverflow", "visible") 178 .append("svg:path") 179 .attr("d", "M 0,-5 L 10 ,0 L 0,5"); 180 181 const link = svg 182 .selectAll(".links") 183 .data(links) 184 .enter() 185 .append("line") 186 .attr("class", "links line") 187 .attr("marker-end", "url(#arrowhead)"); 188 189 const node = svg 190 .selectAll("circle") 191 .data(nodes) 192 .enter() 193 .append("circle") 194 .attr("r", 20) 195 .attr("fill", (d, i) => colorArray[i % colorArray.length]) 196 .call( 197 d3 198 .drag() 199 .on("start", dragstarted) 200 .on("drag", dragged) 201 .on("end", dragended) 202 ); 203 204 const label = svg 205 .selectAll(".label") 206 .data(nodes) 207 .enter() 208 .append("text") 209 .text((d) => d.id) 210 .attr("class", "label"); 211 212 const linkLabel = svg 213 .selectAll(".link-label") 214 .data(links) 215 .enter() 216 .append("text") 217 .text((d) => d.value) 218 .attr("class", "link-label") 219 .attr("dy", -10) 220 .attr("dx", -10); 221 222 simulation.on("tick", () => { 223 link 224 .attr("x1", (d) => d.source.x) 225 .attr("y1", (d) => d.source.y) 226 .attr("x2", (d) => d.target.x) 227 .attr("y2", (d) => d.target.y); 228 229 node.attr("cx", (d) => d.x).attr("cy", (d) => d.y); 230 231 label.attr("x", (d) => d.x - 10).attr("y", (d) => d.y - 25); 232 233 linkLabel 234 .attr("x", (d) => (d.source.x + d.target.x) / 2) 235 .attr("y", (d) => (d.source.y + d.target.y) / 2); 236 }); 237 238 function dragstarted(event, d) { 239 if (!event.active) simulation.alphaTarget(0.3).restart(); 240 d.fx = d.x; 241 d.fy = d.y; 242 } 243 244 function dragged(event, d) { 245 d.fx = event.x; 246 d.fy = event.y; 247 } 248 249 function dragended(event, d) { 250 if (!event.active) simulation.alphaTarget(0); 251 d.fx = null; 252 d.fy = null; 253 } 254 } 255