github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/static/js/metrics.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  var lineChart;
    20  
    21  $(document).ready(() => {
    22  
    23      let stDate = "now-1h";
    24      let endDate = "now";
    25      datePickerHandler(stDate, endDate, stDate);
    26  
    27      setupMetricsEventHandlers();
    28  
    29      if (Cookies.get('theme')) {
    30          theme = Cookies.get('theme');
    31          $('body').attr('data-theme', theme);
    32      }
    33      $('.theme-btn').on('click', themePickerHandler);
    34  
    35      $("#info-icon").tooltip({
    36          delay: { show: 0, hide: 300 },
    37          trigger: 'click'
    38      });
    39  
    40      $('#info-icon').on('click', function (e) {
    41          $('#info-icon').tooltip('show');
    42      });
    43  
    44      $(document).mouseup(function (e) {
    45          if ($(e.target).closest(".tooltip-inner").length === 0) {
    46              $('#info-icon').tooltip('hide');
    47          }
    48      });
    49  });
    50  
    51  // Show clear button when there's input
    52  $("#metrics-input").on("input", function() {
    53      if ($(this).val().trim() !== "") {
    54          $("#clearMetricsInput").show();
    55      } else {
    56          $("#clearMetricsInput").hide();
    57      }
    58  });
    59  
    60  // Clear input when the clear button is clicked
    61  $("#clearMetricsInput").click(function() {
    62      $("#metrics-input").val("").focus();
    63      $(this).hide();
    64  });
    65  
    66  function setupMetricsEventHandlers() {
    67      $('#filter-metrics-input').on('keyup', filterMetricsInputHandler);
    68      $('#run-metrics-query-btn').on('click', runMetricsFilterBtnHandler);
    69      $(document).on('keyup', runMetricsFilterBtnHandler);
    70  
    71      $('#date-picker-btn').on('show.bs.dropdown', showDatePickerHandler);
    72      $('#date-picker-btn').on('hide.bs.dropdown', hideDatePickerHandler);
    73      $('#reset-timepicker').on('click', resetDatePickerHandler);
    74  
    75      $('#date-start').on('change', getStartDateHandler);
    76      $('#date-end').on('change', getEndDateHandler);
    77  
    78      $('#time-start').on('change', getStartTimeHandler);
    79      $('#time-end').on('change', getEndTimeHandler);
    80      $('#customrange-btn').on('click', customRangeHandler);
    81  
    82      $('.range-item').on('click', rangeItemHandler)
    83  
    84      $('#corner-popup').on('click', '.corner-btn-close', function(){
    85          hideError();
    86          $("#metrics-input").val('');
    87          $("#metrics-graph-container").show();
    88          if (lineChart !== undefined) {
    89              lineChart.destroy();
    90              $('#metrics-legends').empty();
    91          }
    92          $('#metrics-legends').empty();
    93          $('.metrics-response').hide();
    94          lineChart.destroy();
    95          $('#metrics-legends').empty();
    96          $('.metrics-response').hide();
    97      });
    98  
    99  }
   100  
   101  function runMetricsFilterBtnHandler(evt) {
   102      if (evt.keyCode === 13 || evt.type == "click") {
   103          $('.popover').hide();
   104          evt.preventDefault();
   105          if ($('#run-metrics-query-btn').text() === "Run Query") {
   106              data = getMetricsSearchFilter(false, false);
   107              doMetricsSearch();
   108          } else {
   109              data = getMetricsSearchFilter(false, false);
   110              doCancel(data);
   111          }
   112          $('#daterangepicker').hide();
   113      }
   114  }
   115  
   116  function filterMetricsInputHandler(evt) {
   117      evt.preventDefault();
   118  
   119      if (evt.keyCode === 13 && $('#run-metrics-query-btn').text() === "Run Query") {
   120          data = getMetricsSearchFilter(false, false);
   121          doMetricsSearch();
   122      }
   123  }
   124  
   125  function getMetricsSearchFilter(skipPushState, scrollingTrigger) {
   126      let filterValue = $('#metrics-input').val().trim() || '*';
   127      let endDate = filterEndDate || "now";
   128      let stDate = filterStartDate || "now-15m";
   129  
   130      if (!isNaN(stDate)) {
   131          stDate = Number(stDate);
   132          endDate = Number(endDate);
   133          datePickerHandler(stDate, endDate, "custom");
   134          loadCustomDateTimeFromEpoch(stDate, endDate);
   135      } else if (stDate !== "now-15m") {
   136          datePickerHandler(stDate, endDate, stDate);
   137      } else {
   138          datePickerHandler(stDate, endDate, "");
   139      }
   140  
   141      addQSParm("query", filterValue);
   142      addQSParm("start", stDate);
   143      addQSParm("end", endDate);
   144  
   145      window.history.pushState({ path: myUrl }, '', myUrl);
   146  
   147      if (scrollingTrigger) {
   148          sFrom = scrollFrom;
   149      }
   150  
   151      return {
   152          'query': filterValue,
   153          'start': stDate.toString(),
   154          'end': endDate.toString(),
   155      };
   156  }
   157  
   158  function doMetricsSearch() {
   159      let startTime = (new Date()).getTime();
   160      let data = getMetricsSearchFilter();
   161      $('body').css('cursor', 'progress');
   162      $("#run-filter-btn").html("    ").attr("disabled", true);
   163      $("#run-filter-btn").removeClass("cancel-search");
   164      $("#query-builder-btn").html("    ").attr("disabled", true);
   165      $("#query-builder-btn").removeClass("cancel-search");
   166      $.ajax({
   167          method: 'post',
   168          url: 'promql/api/ui/query',
   169          headers: {
   170              'Content-Type': 'application/json; charset=utf-8',
   171              'Accept': '*/*'
   172          },
   173          crossDomain: true,
   174          dataType: 'json',
   175          data: JSON.stringify(data)
   176      })
   177          .then((res) => processSearchResult(res, startTime))
   178          .catch((res) => processSearchError(res));
   179  }
   180  
   181  function processSearchResult(res, startTime) {
   182      if (res.aggStats && Object.keys(res.aggStats).length === 0) {
   183          processNoResults();
   184      } else {
   185          hideError();
   186          renderResponseTime(startTime);
   187          var seriesArray = [];
   188          var label = [];
   189          $.each(res, function (key, value) {
   190              var series = value;
   191              $.each(series, function (key, value) {
   192                  seriesArray.push({ seriesName: key, values: value });
   193                  label = [];
   194                  $.each(value, function (k, v) {
   195                      label.push(k);
   196                  })
   197              })
   198          })
   199          var labels = label;
   200  
   201          $('body').css('cursor', 'default');
   202          $("#run-filter-btn").html("  ").attr("disabled", false);
   203          $("#run-filter-btn").removeClass("cancel-search");
   204          $("#query-builder-btn").html("  ").attr("disabled", false);
   205          $("#query-builder-btn").removeClass("cancel-search");
   206          if (lineChart !== undefined) {
   207              lineChart.destroy();
   208              $('#metrics-legends').empty();
   209          }
   210          updateContainers();
   211          displayTable(res);
   212          lineChart = displayGraph(seriesArray, labels, -1);
   213          return lineChart;
   214      }
   215  }
   216  
   217  function renderResponseTime(startTime) {
   218      $('.metrics-response').show();
   219      let responseTime = (new Date()).getTime() - startTime;
   220      $('.metrics-response').html(`Response: <span id="response-time">${responseTime} ms</span>`);
   221  }
   222  
   223  function displayGraph(seriesArray, labels, flag) {
   224      const colors = createColorsArray();
   225      let gridLineColor;
   226      let tickColor;
   227      if ($('body').attr('data-theme') == "light") {
   228          gridLineColor = "#DCDBDF";
   229          tickColor = "#160F29";
   230      }
   231      else {
   232          gridLineColor = "#383148";
   233          tickColor = "#FFFFFF"
   234      }
   235  
   236      let datasets = [];
   237      let data = {};
   238  
   239      datasets = seriesArray.map((o, i) => ({
   240          label: o.seriesName,
   241          data: Object.values(o.values),
   242          backgroundColor: colors[i],
   243          borderColor: colors[i],
   244          color: gridLineColor,
   245          borderWidth: 2,
   246          fill: false,
   247      }));
   248  
   249      data = {
   250          labels: labels,
   251          datasets: datasets
   252      }
   253  
   254      const config = {
   255          type: 'line',
   256          data,
   257          options: {
   258              maintainAspectRatio: false,
   259              responsive: true,
   260              aspectRatio: 2,
   261              title: {
   262                  display: true,
   263              },
   264              plugins: {
   265                  legend: {
   266                      display: false
   267                  },
   268                  tooltip: {
   269                      enabled: true,
   270                  }
   271              },
   272              scales: {
   273                  x: {
   274                      grid: {
   275                          color: gridLineColor,
   276                      },
   277                      ticks: {
   278                          color: tickColor,
   279                      }
   280                  },
   281                  y: {
   282                      grid: {
   283                          color: gridLineColor,
   284                      },
   285                      ticks: {
   286                          color: tickColor,
   287                      }
   288                  },
   289              }
   290          }
   291      }
   292  
   293      var lineCanvas = $('#metrics-graph').get(0).getContext('2d');
   294  
   295      // if chart is created for the first time after response
   296      if (flag === -1) {
   297          lineChart = new Chart(lineCanvas, config);
   298          displayLegends(seriesArray, labels, colors);
   299      }
   300  
   301      const bgColor = [];
   302      const bColor = data.datasets.map(color => {
   303          bgColor.push(color.backgroundColor);
   304          return color.borderColor;
   305      })
   306  
   307      if (flag == -2) {
   308          lineChart.config.data.datasets.map((dataset, index) => {
   309              dataset.backgroundColor = bgColor[index];
   310              dataset.borderColor = bColor[index];
   311          })
   312          lineChart.update();
   313      }
   314  
   315      if (flag >= 0) {
   316          const chartDataObject = lineChart.config.data.datasets.map(dataset => {
   317              dataset.borderColor = "rgba(0,0,0,0)";
   318              dataset.backgroundColor = "rgba(0,0,0,0)";
   319          })
   320  
   321          lineChart.config.data.datasets[flag].borderColor = bColor[flag];
   322          lineChart.config.data.datasets[flag].backgroundColor = bgColor[flag];
   323          lineChart.update();
   324      }
   325      return lineChart;
   326  }
   327  
   328  function createColorsArray() {
   329      let root = document.querySelector(':root');
   330      let rootStyles = getComputedStyle(root);
   331      let colorArray = [];
   332      for (let i = 1; i <= 20; i++) {
   333          colorArray.push(rootStyles.getPropertyValue(`--graph-line-color-${i}`));
   334      }
   335      return colorArray;
   336  }
   337  
   338  function getPrevSelectedElement(el = null) {
   339      let prevEl = el;
   340      return prevEl;
   341  }
   342  
   343  function displayLegends(seriesArray, labels, colors) {
   344      $.each(seriesArray, function (k, v) {
   345          $('#metrics-legends').append(`<div class="legend-element" id="legend-${k}"><span class="legend-colors" style="background-color:` + colors[k] + '"></span>' + v.seriesName + '</div>');
   346      });
   347  
   348      let prev = null;
   349  
   350      const legends = document.querySelectorAll('.legend-element');
   351      $.each(legends, function (i, legend) {
   352          legend.addEventListener('click', (e) => {
   353              let currSelectedEl = e.target;
   354              let currSelectedElId = parseInt((e.target.id).slice(7));
   355              if (currSelectedEl.classList.value == "legend-colors") {
   356                  currSelectedEl = currSelectedEl.closest("div.legend-element")
   357                  currSelectedElId = parseInt(currSelectedEl.id.slice(7))
   358              }
   359              if (prev == null) {
   360                  currSelectedEl.classList.add("selected");
   361                  prev = currSelectedElId;
   362                  displayGraph(seriesArray, labels, currSelectedElId);
   363              } else if (prev == currSelectedElId) {
   364                  currSelectedEl.classList.remove("selected");
   365                  prev = null;
   366                  displayGraph(seriesArray, labels, -2);
   367              } else {
   368                  let prevEl = document.getElementById(`legend-${prev}`);
   369                  prevEl.classList.remove("selected");
   370                  currSelectedEl.classList.add("selected");
   371                  prev = currSelectedElId;
   372                  displayGraph(seriesArray, labels, currSelectedElId);
   373              }
   374          }
   375          )
   376      })
   377  }
   378  
   379  function showDatePickerHandler(evt) {
   380      evt.stopPropagation();
   381      $('#daterangepicker').toggle();
   382      $(evt.currentTarget).toggleClass('active');
   383  }
   384  
   385  function hideDatePickerHandler() {
   386      $('#daterangepicker').removeClass('active');
   387  }
   388  
   389  function resetDatePickerHandler(evt) {
   390      evt.stopPropagation();
   391      resetCustomDateRange();
   392      $.each($(".range-item.active"), function () {
   393          $(this).removeClass('active');
   394      });
   395  
   396  }
   397  function getStartDateHandler(evt) {
   398      let inputDate = new Date(this.value);
   399      filterStartDate = inputDate.getTime();
   400      $(this).addClass("active");
   401      Cookies.set('customStartDate', this.value);
   402  
   403  }
   404  
   405  function getEndDateHandler(evt) {
   406      let inputDate = new Date(this.value);
   407      filterEndDate = inputDate.getTime();
   408      $(this).addClass("active");
   409      Cookies.set('customEndDate', this.value);
   410  }
   411  
   412  function getStartTimeHandler() {
   413      let selectedTime = $(this).val();
   414      let temp = ((Number(selectedTime.split(':')[0]) * 60 + Number(selectedTime.split(':')[1])) * 60) * 1000;
   415      //check if filterStartDate is a number or now-*
   416      if (!isNaN(filterStartDate)) {
   417          filterStartDate = filterStartDate + temp;
   418      } else {
   419          let start = new Date();
   420          start.setUTCHours(0, 0, 0, 0);
   421          filterStartDate = start.getTime() + temp;
   422      }
   423      $(this).addClass("active");
   424      Cookies.set('customStartTime', selectedTime);
   425  }
   426  
   427  function getEndTimeHandler() {
   428      let selectedTime = $(this).val();
   429      let temp = ((Number(selectedTime.split(':')[0]) * 60 + Number(selectedTime.split(':')[1])) * 60) * 1000;
   430      if (!isNaN(filterEndDate)) {
   431          filterEndDate = filterEndDate + temp;
   432      } else {
   433          let start = new Date();
   434          start.setUTCHours(0, 0, 0, 0);
   435          filterEndDate = start.getTime() + temp;
   436      }
   437      $(this).addClass("active");
   438      Cookies.set('customEndTime', selectedTime);
   439  }
   440  
   441  function customRangeHandler(evtvt) {
   442      $.each($(".range-item.active"), function () {
   443          $(this).removeClass('active');
   444      });
   445      $('#date-picker-btn span').html("Custom");
   446  }
   447  
   448  function rangeItemHandler(evt) {
   449      resetCustomDateRange();
   450      $.each($(".range-item.active"), function () {
   451          $(this).removeClass('active');
   452      });
   453      $(evt.currentTarget).addClass('active');
   454      datePickerHandler($(this).attr('id'), "now", $(this).attr('id'))
   455  }
   456  
   457  function resetCustomDateRange() {
   458      // clear custom selections
   459      $('#date-start').val("");
   460      $('#date-end').val("");
   461      $('#time-start').val("00:00");
   462      $('#time-end').val("00:00");
   463      $('#date-start').removeClass('active');
   464      $('#date-end').removeClass('active');
   465      $('#time-start').removeClass('active');
   466      $('#time-end').removeClass('active');
   467      Cookies.remove('customStartDate');
   468      Cookies.remove('customEndDate');
   469      Cookies.remove('customStartTime');
   470      Cookies.remove('customEndTime');
   471  }
   472  
   473  function themePickerHandler(evt) {
   474      let newgridLineColor;
   475      let newTickColor;
   476  
   477      if (Cookies.get('theme')) {
   478          theme = Cookies.get('theme');
   479      } else {
   480          Cookies.set('theme', 'light');
   481          theme = 'light';
   482      }
   483  
   484      if (theme === 'light') {
   485          theme = 'dark';
   486          $(evt.currentTarget).removeClass('dark-theme');
   487          $(evt.currentTarget).addClass('light-theme');
   488          newgridLineColor = "#383148";
   489          newTickColor = "#FFFFFF";
   490      } else {
   491          theme = 'light';
   492          $(evt.currentTarget).removeClass('light-theme');
   493          $(evt.currentTarget).addClass('dark-theme');
   494          newgridLineColor = "#DCDBDF";
   495          newTickColor = "#160F29";
   496      }
   497      $('body').attr('data-theme', theme);
   498  
   499      if (lineChart !== undefined) {
   500          lineChart.config.options.scales.x.grid.color = newgridLineColor;
   501          lineChart.config.options.scales.y.grid.color = newgridLineColor;
   502          lineChart.config.options.scales.x.ticks.color = newTickColor;
   503          lineChart.config.options.scales.y.ticks.color = newTickColor;
   504          lineChart.update();
   505      }
   506  
   507  
   508      Cookies.set('theme', theme, { expires: 365 });
   509  }
   510  
   511  function processSearchError(res) {
   512      $('#metrics-graph-container').hide();
   513      $('#metrics-table-container').hide();
   514      showError(`${res.responseText}`)
   515  }
   516  
   517  function processNoResults() {
   518      $('#metrics-graph-container').hide();
   519      $('#metrics-table-container').hide();
   520      showError(`Your query returned no data, adjust your query.`);
   521  }
   522  
   523  $(".metrics-graph-btn, .metrics-table-btn").click(function() {
   524      $(".metrics-graph-btn, .metrics-table-btn").removeClass("active");
   525      $(this).addClass("active");
   526      updateContainers();
   527  });
   528  
   529  function updateContainers() {
   530      const isGraphActive = $(".metrics-graph-btn").hasClass("active");
   531      if ($("#corner-popup").is(":visible")) {
   532          $("#metrics-graph-container").hide();
   533          $("#metrics-table-container").hide();
   534      }else { 
   535      if (isGraphActive) {
   536          $("#metrics-graph-container").show();
   537          $("#metrics-table-container").hide();
   538      }else {
   539          $("#metrics-graph-container").hide();
   540          $("#metrics-table-container").show();
   541      }}
   542  }
   543  
   544  function displayTable(res) {
   545      let tableBody = $('#metrics-table-container table');
   546      tableBody.empty(); 
   547      tableBody.append('<tr><th>Metric</th><th>Value</th></tr>');
   548      $.each(res.aggStats, function(metric, timestampValues) {
   549          let metricName = metric;
   550          let timestamps = Object.keys(timestampValues);
   551          let lastValue = timestampValues[timestamps[timestamps.length - 1]];
   552  
   553          let newRow = $('<tr>');
   554          newRow.append($('<td>').text(metricName));
   555          newRow.append($('<td>').text(lastValue));
   556          tableBody.append(newRow);
   557      });
   558  }
   559  
   560  window.addEventListener('resize', function () {
   561      lineChart.resize();
   562  });