github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/static/js/query-builder.js (about)

     1  "use strict";
     2  let chart;
     3  $(function () {
     4    $("#custom-code-tab").tabs();
     5    $("#custom-chart-tab").tabs();
     6    $('#logs-view-controls').hide();
     7  });
     8  $("#custom-code-tab").tabs({
     9    activate: function (event, ui) {
    10      let currentResTab = $("#custom-chart-tab").tabs("option", "active");
    11      if(currentResTab == 1){
    12        timeChart();
    13      }
    14      let currentTab = $("#custom-code-tab").tabs("option", "active");
    15      if (currentTab == 0) {
    16        // Query Builder Tab
    17        let filterValue = $("#filter-input").val();
    18        if (filterValue != "") $("#query-input").val(filterValue);
    19        if (filterValue == "") $("#query-input").val('*')
    20        if(!isQueryBuilderSearch){
    21          // Clear input boxes of the query builder when a query is searched from the free text
    22          $(".tags-list").empty();  
    23          [firstBoxSet, secondBoxSet, thirdBoxSet] = [new Set(), new Set(), new Set()];
    24          $("#aggregations, #aggregate-attribute-text, #search-filter-text").show();
    25        }
    26        $(".query-language-option").removeClass("active");
    27        $("#query-language-options #option-3").addClass("active");
    28        $("#query-language-btn span").html("Splunk QL");
    29        displayQueryLangToolTip("3");
    30      }else{
    31        // Free Text Tab
    32        let filterValue = $("#query-input").val();
    33        if (filterValue != "") $("#filter-input").val(filterValue);
    34      }
    35    },
    36  });
    37  $("#custom-chart-tab").tabs({
    38    activate: function (event, ui) {
    39      let currentTab = $("#custom-chart-tab").tabs("option", "active");
    40      if (currentTab == 0) {
    41        $("#logs-view-controls").show();
    42      } else {
    43        $("#logs-view-controls").hide();
    44        timeChart();
    45      }
    46    },
    47  });
    48  //querybuilder and run-filter-btn text: 
    49  //search -> " "
    50  //run -> "  "
    51  //cancel -> "   "
    52  //running -> "    "
    53  $(document).ready(function () {
    54    $("#add-con").on("click", filterStart);
    55    $("#filter-box-1").on("click", filterStart);
    56    $("#add-con-second").on("click", secondFilterStart);
    57    $("#filter-box-2").on("click", secondFilterStart);
    58    $("#add-con-third").on("click", ThirdFilterStart);
    59    $("#filter-box-3").on("click", ThirdFilterStart);
    60    $("#completed").on("click", filterComplete);
    61    $("#completed-second").on("click", secondFilterComplete);
    62    $("#cancel-enter").on("click", cancelInfo);
    63    $("#cancel-enter-second").on("click", secondCancelInfo);
    64    $("#cancel-enter-third").on("click", ThirdCancelInfo);
    65  
    66    $("#add-con").show();
    67    $("#add-con-second").show();
    68    $("#completed").hide();
    69    $("#completed-second").hide();
    70    $("#cancel-enter").hide();
    71    $("#cancel-enter-second").hide();
    72    $("#add-filter").hide();
    73    $("#add-filter-second").hide();
    74    if (thirdBoxSet.size > 0) $("#aggregations").hide();
    75    else $("#aggregations").show();
    76    if (secondBoxSet.size > 0) $("#aggregate-attribute-text").hide();
    77    else $("#aggregate-attribute-text").show();
    78    if (firstBoxSet.size > 0) $("#search-filter-text").hide();
    79    else $("#search-filter-text").show();
    80    setShowColumnInfoDialog();
    81    timeChart();
    82  });
    83  
    84  const tags = document.getElementById("tags");
    85  const tagSecond = document.getElementById("tags-second");
    86  const tagThird = document.getElementById("tags-third");
    87  tags.addEventListener("click", function (event) {
    88    // If the clicked element has the class 'delete-button'
    89    if (event.target.classList.contains("delete-button")) {
    90      // Remove the parent element (the tag)
    91      let str = event.target.parentNode.textContent;
    92      firstBoxSet.delete(str.substring(0, str.length - 1));
    93      event.target.parentNode.remove();
    94      getSearchText();
    95      if (firstBoxSet.size > 0) $("#search-filter-text").hide();
    96      else $("#search-filter-text").show();
    97      cancelInfo(event);
    98    }
    99  });
   100  tagSecond.addEventListener("click", function (event) {
   101    // If the clicked element has the class 'delete-button'
   102    if (event.target.classList.contains("delete-button")) {
   103      // Remove the parent element (the tag)
   104      let str = event.target.parentNode.textContent;
   105      secondBoxSet.delete(str.substring(0, str.length - 1));
   106      event.target.parentNode.remove();
   107      getSearchText();
   108      if (secondBoxSet.size > 0) $("#aggregate-attribute-text").hide();
   109      else $("#aggregate-attribute-text").show();
   110      secondCancelInfo(event);
   111    }
   112  });
   113  tagThird.addEventListener("click", function (event) {
   114    // If the clicked element has the class 'delete-button'
   115    if (event.target.classList.contains("delete-button")) {
   116      // Remove the parent element (the tag)
   117      let str = event.target.parentNode.textContent;
   118      thirdBoxSet.delete(str.substring(0, str.length - 1));
   119      event.target.parentNode.remove();
   120      getSearchText();
   121      if (thirdBoxSet.size > 0) $("#aggregations").hide();
   122      else $("#aggregations").show();
   123      ThirdCancelInfo(event);
   124    }
   125  });
   126  $(document).mouseup(function (e) {
   127    var firstCon = $("#add-filter");
   128    var secondCon = $("#add-filter-second");
   129    var thirdCon = $("#add-filter-third");
   130    var dropInfo = $(".ui-autocomplete");
   131    if (
   132      !firstCon.is(e.target) &&
   133      firstCon.has(e.target).length === 0 &&
   134      !dropInfo.is(e.target) &&
   135      dropInfo.has(e.target).length === 0 &&
   136      !secondCon.is(e.target) &&
   137      secondCon.has(e.target).length === 0 &&
   138      !thirdCon.is(e.target) &&
   139      thirdCon.has(e.target).length === 0
   140    ) {
   141      cancelInfo(e);
   142      secondCancelInfo(e);
   143      ThirdCancelInfo(e);
   144    }
   145  });
   146  var calculations = ["min", "max", "count", "avg", "sum"];
   147  var numericColumns = [];
   148  var ifCurIsNum = false;
   149  var availSymbol = [];
   150  let valuesOfColumn = new Set();
   151  let paramFirst;
   152  let columnsNames = [];
   153  let previousStartEpoch = null;
   154  let previousEndEpoch = null;
   155  let previousIndexName = null;
   156  
   157  async function filterStart(evt) {
   158    evt.preventDefault();
   159    $("#column-first").attr("type", "text");
   160    $("#add-con").hide();
   161    $("#cancel-enter").show();
   162    $("#add-filter").show();
   163    $("#add-filter").css({ visibility: "visible" });
   164    $("#filter-box-1").addClass("select-box");
   165    // fetch columns if empty or startTime, endTime or Index is changed
   166    if (
   167      columnsNames.length === 0 ||
   168      filterStartDate !== previousStartEpoch ||
   169      filterEndDate !== previousEndEpoch ||
   170      selectedSearchIndex !== previousIndexName
   171    ) {
   172      await getColumns();
   173      previousStartEpoch = filterStartDate;
   174      previousEndEpoch = filterEndDate;
   175      previousIndexName = selectedSearchIndex;
   176    }
   177    $("#column-first")
   178      .autocomplete({
   179        source: columnsNames.sort(),
   180        minLength: 0,
   181        maxheight: 100,
   182        select: function (event, ui) {
   183          $("#symbol").attr("type", "text");
   184          //when the column name is not a number, the symbols can only be = && !=
   185          availSymbol = ["=", "!="];
   186          valuesOfColumn.clear();
   187          let curIsNum = false;
   188          for (let i = 0; i < columnsNames.length; i++) {
   189            if (ui.item.value == numericColumns[i]) {
   190              curIsNum = true;
   191              availSymbol = ["=", "!=", "<=", ">=", ">", "<"];
   192              break;
   193            }
   194          }
   195          ifCurIsNum = curIsNum;
   196          $("#symbol").val("");
   197          $("#value-first").val("");
   198          //check if complete btn can click
   199          checkFirstBox(0);
   200          let chooseColumn = ui.item.value.trim();
   201          getValuesofColumn(chooseColumn);
   202  
   203          $("#symbol")
   204            .autocomplete({
   205              source: availSymbol,
   206              minLength: 0,
   207              select: function (event, ui) {
   208                //check if complete btn can click
   209                checkFirstBox(1);
   210                $("#value-first").attr("type", "text");
   211                $("#completed").show();
   212                valuesOfColumn.clear();
   213              },
   214            })
   215            .on("focus", function () {
   216              if (!$(this).val().trim()) $(this).keydown();
   217            });
   218        },
   219      })
   220      .on("focus", function () {
   221        if (!$(this).val().trim()) $(this).keydown();
   222      });
   223      $("#column-first").focus();
   224  }
   225  function secondFilterStart(evt) {
   226    evt.preventDefault();
   227    $("#filter-box-2").addClass("select-box");
   228    $("#column-second").attr("type", "text");
   229    $("#add-con-second").hide();
   230    $("#cancel-enter-second").show();
   231    $("#add-filter-second").show();
   232    $("#add-filter-second").css({ visibility: "visible" });
   233    $("#column-second").focus();
   234  }
   235  async function ThirdFilterStart(evt) {
   236    evt.preventDefault();
   237    $("#filter-box-3").addClass("select-box");
   238    $("#column-third").attr("type", "text");
   239    $("#add-con-third").hide();
   240    $("#add-filter-third").show();
   241    $("#add-filter-third").css({ visibility: "visible" });
   242    // fetch columns if empty or startTime, endTime or index is changed
   243    if (
   244      columnsNames.length === 0 ||
   245      filterStartDate !== previousStartEpoch ||
   246      filterEndDate !== previousEndEpoch ||
   247      selectedSearchIndex !== previousIndexName
   248    ) {
   249      await getColumns();
   250      previousStartEpoch = filterStartDate;
   251      previousEndEpoch = filterEndDate;
   252      previousIndexName = selectedSearchIndex;
   253    }
   254    $("#column-third")
   255      .autocomplete({
   256        source: columnsNames.sort(),
   257        minLength: 0,
   258        maxheight: 100,
   259        select: function (event, ui) {
   260          event.preventDefault();
   261          let tag = document.createElement("li");
   262          if (ui.item.value !== "") {
   263            if (thirdBoxSet.has(ui.item.value)) {
   264              alert("Duplicate filter!");
   265              return;
   266            } else thirdBoxSet.add(ui.item.value);
   267            // Set the text content of the tag to
   268            // the trimmed value
   269            tag.innerText = ui.item.value;
   270            // Add a delete button to the tag
   271            tag.innerHTML += '<button class="delete-button">x</button>';
   272            // Append the tag to the tags list
   273            tagThird.appendChild(tag);
   274            var dom = $("#tags-third");
   275            var x = dom[0].scrollWidth;
   276            dom[0].scrollLeft = x;
   277            $("#column-third").val("");
   278            $(this).blur();
   279            getSearchText();
   280          }
   281          if(thirdBoxSet.size > 0) $("#aggregations").hide();
   282          else $("#aggregations").show();
   283          $("#column-third").focus();
   284          return false;
   285        },
   286      })
   287      .on("focus", function () {
   288        if (!$(this).val().trim()) $(this).keydown();
   289      });
   290      $("#column-third").focus();
   291  }
   292  /**
   293   * check first box
   294   * @param {*} obj 
   295   */
   296  function checkContent(obj){
   297    if($(obj).val() === '' || $(obj).val() === null){
   298      $("#completed").attr('disabled', true);
   299    }else{
   300      $("#completed").attr("disabled", false);
   301    }
   302  }
   303  function checkFirstBox(curSelect){
   304    let num = 0;
   305    if (
   306      ($("#column-first").val() == null ||
   307      $("#column-first").val().trim() == "") && curSelect != 0
   308    ) num++;
   309    if (($("#symbol").val() == null || $("#symbol").val().trim() == "") && curSelect != 1) num++;
   310    if (($("#value-first").val() == null || $("#value-first").val().trim() == "") && curSelect != 2) num++;
   311    if(num != 0){
   312        $("#completed").attr("disabled", true);
   313    } else {
   314        $("#completed").attr("disabled", false);
   315    }
   316  }
   317  function checkSecondContent(obj) {
   318    if ($(obj).val() === "" || $(obj).val() === null) {
   319      $("#completed-second").attr("disabled", true);
   320    } else {
   321      $("#completed-second").attr("disabled", false);
   322    }
   323  }
   324  /**
   325   * first box complete one filter info
   326   * @param {*} evt
   327   */
   328  function filterComplete(evt) {
   329    evt.preventDefault();
   330    let val = $("#value-first").val().trim();
   331    if (
   332      $("#column-first").val() == null ||
   333      $("#column-first").val().trim() == "" ||
   334      $("#symbol").val() == null ||
   335      $("#symbol").val().trim() == "" ||
   336      $("#value-first").val() == null ||
   337      $("#value-first").val().trim() == ""
   338    ) {
   339      alert("Please select one of the values below");
   340      return;
   341    }
   342    $("#filter-box-1").removeClass("select-box");
   343    let tagContent = $("#column-first").val().trim() + $("#symbol").val().trim();
   344    if (ifCurIsNum) tagContent += val;
   345    else tagContent += '"' + val + '"';
   346    $("#column-first").val("");
   347    $("#symbol").val("");
   348    $("#value-first").val("");
   349    $("#column-first").attr("type", "hidden");
   350    $("#symbol").attr("type", "hidden");
   351    $("#value-first").attr("type", "hidden");
   352    $("#completed").hide();
   353    $("#cancel-enter").hide();
   354    $("#add-filter").hide();
   355    $("#add-con").show();
   356    let tag = document.createElement("li");
   357    if (tagContent !== "") {
   358      if (firstBoxSet.has(tagContent)) {
   359        alert("Duplicate filter!");
   360        return;
   361      } else firstBoxSet.add(tagContent);
   362      // Set the text content of the tag to
   363      // the trimmed value
   364      tag.innerText = tagContent;
   365      // Add a delete button to the tag
   366      tag.innerHTML += '<button class="delete-button">x</button>';
   367      // Append the tag to the tags list
   368      tags.appendChild(tag);
   369      var dom = $("#tags");
   370      var x = dom[0].scrollWidth;
   371      dom[0].scrollLeft = x;
   372      getSearchText();
   373      if (firstBoxSet.size > 0) $("#search-filter-text").hide();
   374      else $("#search-filter-text").show();
   375    }
   376  }
   377  function secondFilterComplete(evt) {
   378    evt.preventDefault();
   379    if (
   380      $("#column-second").val() == null ||
   381      $("#column-second").val().trim() == "" ||
   382      $("#value-second").val() == null ||
   383      $("#value-second").val().trim() == ""
   384    ) {
   385      alert("Please select one of the values below");
   386      return;
   387    }
   388    $("#filter-box-2").removeClass("select-box");
   389    let tagContent =
   390      $("#column-second").val().trim() +
   391      "(" +
   392      $("#value-second").val().trim() +
   393      ")";
   394    $("#column-second").val("");
   395    $("#value-second").val("");
   396    $("#column-second").attr("type", "hidden");
   397    $("#value-second").attr("type", "hidden");
   398    $("#completed-second").hide();
   399    $("#cancel-enter-second").hide();
   400    $("#add-filter-second").hide();
   401    $("#add-con-second").show();
   402    let tag = document.createElement("li");
   403    if (tagContent !== "") {
   404      if (secondBoxSet.has(tagContent)) {
   405        alert("Duplicate filter!");
   406        return;
   407      } else secondBoxSet.add(tagContent);
   408      // Set the text content of the tag to
   409      // the trimmed value
   410      tag.innerText = tagContent;
   411      // Add a delete button to the tag
   412      tag.innerHTML += '<button class="delete-button">x</button>';
   413      // Append the tag to the tags list
   414      tagSecond.appendChild(tag);
   415      var dom = $("#tags-second");
   416      var x = dom[0].scrollWidth;
   417      dom[0].scrollLeft = x;
   418      getSearchText();
   419      if (secondBoxSet.size > 0) $("#aggregate-attribute-text").hide();
   420      else $("#aggregate-attribute-text").show();
   421    }
   422  }
   423  function getSearchText() {
   424    let filterValue = getQueryBuilderCode();
   425    if (filterValue != "") {
   426      $("#query-input").val(filterValue);
   427    }
   428    if (filterValue === "Searches with a Search Criteria must have an Aggregate Attribute") {
   429      $("#query-builder-btn").addClass("stop-search").prop('disabled', true);
   430    } else {
   431      $("#query-builder-btn").removeClass("stop-search").prop('disabled', false);
   432    }
   433  }
   434  
   435  function cancelInfo(evt) {
   436    evt.preventDefault();
   437    evt.stopPropagation();
   438    $("#filter-box-1").removeClass("select-box");
   439    $("#column-first").val("");
   440    $("#symbol").val("");
   441    $("#value-first").val("");
   442    $("#column-first").attr("type", "hidden");
   443    $("#symbol").attr("type", "hidden");
   444    $("#value-first").attr("type", "hidden");
   445    $("#completed").hide();
   446    $("#add-filter").hide();
   447    $("#cancel-enter").hide();
   448    $("#add-con").show();
   449  }
   450  function secondCancelInfo(evt) {
   451    evt.preventDefault();
   452    evt.stopPropagation();
   453    $("#filter-box-2").removeClass("select-box");
   454    $("#column-second").val("");
   455    $("#value-second").val("");
   456    $("#column-second").attr("type", "hidden");
   457    $("#value-second").attr("type", "hidden");
   458    $("#completed-second").hide();
   459    $("#add-filter-second").hide();
   460    $("#cancel-enter-second").hide();
   461    $("#add-con-second").show();
   462  }
   463  function ThirdCancelInfo(event) {
   464    event.preventDefault();
   465    event.stopPropagation();
   466    $("#filter-box-3").removeClass("select-box");
   467    $("#column-third").val("");
   468    $("#add-filter-third").hide();
   469    $("#column-third").attr("type", "hidden");
   470    $("#add-con-third").show();
   471  }
   472  /**
   473   * first input box
   474   */
   475  
   476  $("#column-second")
   477    .autocomplete({
   478      source: calculations.sort(),
   479      minLength: 0,
   480      maxheight: 100,
   481      select: async function (event, ui) {
   482        $("#value-second").attr("type", "text");
   483        $("#completed-second").show();
   484        $("#value-second").val("");
   485        if (columnsNames.length === 0 || numericColumns.length ==0) await getColumns();
   486        let columnInfo = columnsNames;
   487        if (ui.item.value != "count") columnInfo = numericColumns;
   488        $("#completed-second").attr("disabled", true);
   489        $("#value-second")
   490          .autocomplete({
   491            source: columnInfo.sort(),
   492            minLength: 0,
   493            select: function (event, ui) {
   494              let secVal = $("#column-second").val();
   495              if (secVal == null || secVal.trim() == "") $("#completed-second").attr("disabled", true);
   496              else $("#completed-second").attr("disabled", false);
   497            }
   498          })
   499          .on("focus", function () {
   500            if (!$(this).val().trim()) $(this).keydown();
   501          });
   502      },
   503    })
   504    .on("focus", function () {
   505      if (!$(this).val().trim()) $(this).keydown();
   506    });
   507  /**
   508   * get cur column names from back-end for first input box
   509   *
   510   */
   511  async function getColumns() {
   512    const data = {
   513      state: "query",
   514      searchText: "*",
   515      startEpoch: filterStartDate,
   516      endEpoch: filterEndDate,
   517      indexName: selectedSearchIndex,
   518      from: 0,
   519      size: 1,
   520      queryLanguage: "Splunk QL",
   521    };
   522  
   523    const res = await $.ajax({
   524      method: "post",
   525      url: "api/search/",
   526      headers: {
   527        "Content-Type": "application/json; charset=utf-8",
   528        Accept: "*/*",
   529      },
   530      crossDomain: true,
   531      dataType: "json",
   532      data: JSON.stringify(data),
   533    });
   534  
   535    if (res) {
   536      columnsNames = res.allColumns.filter(column => column !== '_index' && column !== 'timestamp'); // remove '_index' and 'timestamp' column from query builder
   537      getNumericColumns(res);
   538    }
   539  }
   540  /**
   541   * get numeric column names from back-end for second input box (aggregation)
   542   *
   543   */
   544  function getNumericColumns(data) {
   545    function areAllNumerical(values) {
   546      return values.every(value => typeof value === 'number');
   547    }
   548    numericColumns = [];
   549    for (const column of data.allColumns) {
   550      // Skip the "timestamp" column
   551      if (column === "timestamp") continue;
   552      
   553      const values = data.hits.records.map(record => record[column]);
   554      
   555      if (areAllNumerical(values)) {
   556        numericColumns.push(column);
   557      }
   558    }
   559  }
   560  /**
   561   * get values of cur column names from back-end for first input box
   562   *
   563   */
   564  function getValuesofColumn(chooseColumn) {
   565    valuesOfColumn.clear();
   566    let param = {
   567      state: "query",
   568      searchText: `SELECT DISTINCT ${chooseColumn} FROM \`${selectedSearchIndex}\``,
   569      startEpoch: filterStartDate,
   570      endEpoch: filterEndDate,
   571      indexName: selectedSearchIndex,
   572      queryLanguage: "SQL",
   573      from: 0,
   574      size: 1000,
   575    };
   576    startQueryTime = new Date().getTime();
   577    $.ajax({
   578      method: "post",
   579      url: "api/search",
   580      headers: {
   581        "Content-Type": "application/json; charset=utf-8",
   582        Accept: "*/*",
   583      },
   584      crossDomain: true,
   585      dataType: "json",
   586      data: JSON.stringify(param),
   587    }).then((res) => {
   588      if (res && res.hits && res.hits.records) {
   589        for (let i = 0; i < res.hits.records.length; i++) {
   590          let cur = res.hits.records[i][chooseColumn];
   591          if (typeof cur == "string") valuesOfColumn.add(cur);
   592          else valuesOfColumn.add(cur.toString());
   593        }
   594      }
   595      let arr = Array.from(valuesOfColumn);
   596      $("#value-first")
   597        .autocomplete({
   598          source: arr.sort(),
   599          minLength: 0,
   600          select: function (event, ui) {
   601            //check if complete btn can click
   602            checkFirstBox(2);
   603            valuesOfColumn.clear();
   604          },
   605        })
   606        .on("focus", function () {
   607          if (!$(this).val().trim()) $(this).keydown();
   608        });
   609    });
   610  }
   611  function setShowColumnInfoDialog(){
   612    $("#show-record-popup").dialog({
   613      autoOpen: false,
   614      resizable: false,
   615      title: false,
   616      maxHeight: 307,
   617      height: 307,
   618      width: 464,
   619      modal: true,
   620      position: {
   621        my: "center",
   622        at: "center",
   623        of: window,
   624      },
   625      buttons: {
   626        Cancel: {
   627          class: "cancelqButton cancel-record-btn",
   628          text: "Cancel",
   629          click: function () {
   630            $("#show-record-popup").dialog("close");
   631          },
   632        },
   633      }
   634    });
   635    $("#show-record-intro-btn").on("click", function () {
   636      $("#show-record-popup").dialog("open");
   637      $(".ui-widget-overlay").addClass("opacity-75");
   638      // return false;
   639    });
   640  }
   641  function convertTimestamp(timestampString) {  
   642    var timestamp = parseInt(timestampString); 
   643    var date = new Date(timestamp);
   644    
   645    var year = date.getFullYear(); 
   646    var month = ("0" + (date.getMonth() + 1)).slice(-2);
   647    var day = ("0" + date.getDate()).slice(-2);  
   648    
   649    var hours = ("0" + date.getHours()).slice(-2); 
   650    var minutes = ("0" + date.getMinutes()).slice(-2);  
   651    var seconds = ("0" + date.getSeconds()).slice(-2);
   652    
   653    var readableDate = year + "-" + month + "-" + day + " " + hours + ":" + minutes + ":" + seconds;  
   654    return readableDate;  
   655  }  
   656  const resizeObserver = new ResizeObserver((entries) => {
   657    if (chart != null && chart != "" && chart != undefined) {
   658      let height = document
   659        .getElementById("custom-code-tab")
   660        .getBoundingClientRect().height;
   661      let width = document
   662          .getElementById("columnChart")
   663          .getBoundingClientRect().width;
   664      chart.resize({
   665        height: window.innerHeight - height - 60,
   666        width: width - 20,
   667      });
   668    }
   669  });
   670  resizeObserver.observe(document.getElementById("columnChart"));
   671  function timeChart() {
   672    if (isTimechart) {
   673      $("#columnChart").show();
   674      $("#hideGraph").hide();
   675    }else{
   676      $("#columnChart").hide();
   677      $("#hideGraph").show();
   678      return;
   679    }
   680    // Extract data for ECharts
   681    var timestamps = measureInfo.map((item) => convertTimestamp(item.GroupByValues[0]));
   682    var seriesData = measureFunctions.map(function (measureFunction) {
   683      return {
   684        name: measureFunction,
   685        type: "bar",
   686        data: measureInfo.map(function (item) {
   687          return item.MeasureVal[measureFunction] || 0;
   688        }),
   689      };
   690    });
   691  
   692    // ECharts configuration
   693    var option = {
   694      tooltip: {
   695        trigger: "item",
   696        formatter: function (params) {
   697          return params.seriesName + ": " + params.value;
   698        },
   699      },
   700      legend: {
   701        textStyle: {
   702          color: "#6e7078",
   703          fontSize: 12,
   704        },
   705        data: measureFunctions,
   706        type: "scroll", // Enable folding functionality
   707        orient: "vertical",
   708        right: 10,
   709        top: "middle",
   710        align: "left",
   711        height: "70%",
   712        width: 150,
   713      },
   714      grid: {
   715        left: 10,
   716        right: 220,
   717        containLabel: true,
   718      },
   719      xAxis: {
   720        type: "category",
   721        data: timestamps,
   722        scale: true,
   723        splitLine: { show: false },
   724      },
   725      yAxis: {
   726        type: "value",
   727        scale: true,
   728        splitLine: { show: false },
   729      },
   730      series: seriesData,
   731    };
   732  
   733    // Initialize ECharts
   734    let charId = document.getElementById("columnChart");
   735    if (chart != null && chart != "" && chart != undefined) {
   736      echarts.dispose(chart);
   737    }
   738    chart = echarts.init(charId);
   739    // Set the configuration to the chart
   740    chart.setOption(option);
   741    let height = document
   742      .getElementById("custom-code-tab")
   743      .getBoundingClientRect().height;
   744    let width = document
   745      .getElementById("columnChart")
   746      .getBoundingClientRect().width;
   747    chart.resize({
   748      height: window.innerHeight - height - 60,
   749      width: width - 20,
   750    });
   751  }