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