github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/static/js/search.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  
    20   function wsURL(path) {
    21       var protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
    22       var url = protocol + location.host;
    23       return url + path;
    24   }
    25   
    26   function doCancel(data) {
    27       socket.send(JSON.stringify(data));
    28       $('body').css('cursor', 'default');
    29       $('#run-filter-btn').html(' ');
    30       $("#run-filter-btn").removeClass("cancel-search");
    31       $('#run-filter-btn').removeClass('active');
    32       $("#query-builder-btn").html(" ");
    33       $("#query-builder-btn").removeClass("cancel-search");
    34       $("#query-builder-btn").removeClass("active");
    35       $('#progress-div').html(``);
    36   }
    37    function doLiveTailCancel(data) {
    38      $("body").css("cursor", "default");
    39      $("#live-tail-btn").html("Live Tail");
    40      $("#live-tail-btn").removeClass("active");
    41      $("#progress-div").html(``);
    42    }
    43   function resetDataTable(firstQUpdate) {
    44       if (firstQUpdate) {
    45           $('#empty-response').hide();
    46           $("#custom-chart-tab").show();
    47           let currentTab = $("#custom-chart-tab").tabs("option", "active");
    48           if (currentTab == 0) {
    49             $("#logs-view-controls").show();
    50           } else {
    51             $("#logs-view-controls").hide();
    52           }
    53           $("#agg-result-container").hide();
    54           $("#data-row-container").hide();
    55           hideError();
    56       }
    57   }
    58   
    59   function resetLogsGrid(firstQUpdate){
    60       if (firstQUpdate){
    61           resetAvailableFields();
    62       }
    63   }
    64   
    65   function doSearch(data) {
    66       startQueryTime = (new Date()).getTime();
    67       newUri = wsURL("/api/search/ws");
    68       socket = new WebSocket(newUri);
    69       let timeToFirstByte = 0;
    70       let firstQUpdate = true;
    71       let lastKnownHits = 0;
    72       socket.onopen = function (e) {
    73           console.time("socket timing");
    74           $('body').css('cursor', 'progress');
    75           $("#run-filter-btn").addClass("cancel-search");
    76           $('#run-filter-btn').addClass('active');
    77           $("#query-builder-btn").html("   ");
    78           $("#query-builder-btn").addClass("cancel-search");
    79           $("#query-builder-btn").addClass("active");
    80           socket.send(JSON.stringify(data));
    81       };
    82   
    83       socket.onmessage = function (event) {
    84           let jsonEvent = JSON.parse(event.data);
    85           let eventType = jsonEvent.state;
    86           let totalEventsSearched = jsonEvent.total_events_searched
    87           let totalTime = (new Date()).getTime() - startQueryTime;
    88           switch (eventType) {
    89               case "RUNNING":
    90                   console.time("RUNNING");
    91                   console.timeEnd("RUNNING");
    92                   break;
    93               case "QUERY_UPDATE":
    94                   console.time("QUERY_UPDATE");
    95                   if (timeToFirstByte === 0) {
    96                       timeToFirstByte = Number(totalTime).toLocaleString();
    97                   }
    98                   let totalHits;
    99   
   100                   if (jsonEvent && jsonEvent.hits && jsonEvent.hits.totalMatched) {
   101                       totalHits = jsonEvent.hits.totalMatched
   102                       totalMatchLogs = totalHits;
   103                       lastKnownHits = totalHits;
   104                   } else {
   105                       // we enter here only because backend sent null hits/totalmatched
   106                       totalHits = lastKnownHits
   107                   }
   108                   resetDataTable(firstQUpdate);
   109                   processQueryUpdate(jsonEvent, eventType, totalEventsSearched, timeToFirstByte, totalHits);
   110                   console.timeEnd("QUERY_UPDATE");
   111                   firstQUpdate = false
   112                   break;
   113               case "COMPLETE":
   114                   let eqRel = "eq";
   115                   if (jsonEvent.totalMatched != null && jsonEvent.totalMatched.relation != null) {
   116                       eqRel = jsonEvent.totalMatched.relation;
   117                   }
   118                   console.time("COMPLETE");
   119                   canScrollMore = jsonEvent.can_scroll_more;
   120                   scrollFrom = jsonEvent.total_rrc_count;
   121                   processCompleteUpdate(jsonEvent, eventType, totalEventsSearched, timeToFirstByte, eqRel);
   122                   console.timeEnd("COMPLETE");
   123                   socket.close(1000);
   124                   break;
   125               case "TIMEOUT":
   126                   console.time("TIMEOUT");
   127                   console.log(`[message] Timeout state received from server: ${jsonEvent}`);
   128                   processTimeoutUpdate(jsonEvent);
   129                   console.timeEnd("TIMEOUT");
   130                   break;
   131               case "ERROR":
   132                   console.time("ERROR");
   133                   console.log(`[message] Error state received from server: ${jsonEvent}`);
   134                   processErrorUpdate(jsonEvent);
   135                   console.timeEnd("ERROR");
   136                   break;
   137               default:
   138                   console.log(`[message] Unknown state received from server: `+ JSON.stringify(jsonEvent));
   139                   if (jsonEvent.message.includes("expected")){
   140                      jsonEvent.message = "Your query contains syntax error"
   141                   } else if (jsonEvent.message.includes("not present")){
   142                      jsonEvent['no_data_err'] = "No data found for the query"
   143                   }
   144                   processSearchErrorLog(jsonEvent);
   145           }
   146       };
   147   
   148       socket.onclose = function (event) {
   149           if (event.wasClean) {
   150               console.log(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
   151           } else {
   152               console.log(`Connection close not clean=${event} code=${event.code} reason=${event.reason} `);
   153           }
   154           console.timeEnd("socket timing");
   155       };
   156   
   157       socket.addEventListener('error', (event) => {
   158           console.log('WebSocket error: ', event);
   159       });
   160   }
   161   
   162    function reconnect() {
   163      if (lockReconnect) {
   164        return;
   165      }
   166      lockReconnect = true;
   167      //keep reconnect,set delay to avoid much request, set tt, cancel first, then reset
   168      clearInterval(tt);
   169      tt = setInterval(function () {
   170        if (!liveTailState) {
   171          lockReconnect = false;
   172          return;
   173        }
   174        data = getLiveTailFilter(false, false, 30);
   175        createLiveTailSocket(data);
   176        lockReconnect = false;
   177    }, refreshInterval);
   178    }
   179  
   180    function createLiveTailSocket(data) {
   181      try {
   182        if (!liveTailState) return;
   183        startQueryTime = new Date().getTime();
   184        newUri = wsURL("/api/search/live_tail");
   185        socket = new WebSocket(newUri);
   186        doLiveTailSearch(data);
   187      } catch (e) {
   188        console.log("live tail connect websocket catch: " + e);
   189        reconnect();
   190      }
   191    }
   192     function doLiveTailSearch(data) {
   193       let timeToFirstByte = 0;
   194       let firstQUpdate = true;
   195       let lastKnownHits = 0;
   196       socket.onopen = function (e) {
   197         //  console.time("socket timing");
   198         $("body").css("cursor", "progress");
   199         $("#live-tail-btn").html("Cancel Live Tail");
   200         $("#live-tail-btn").addClass("active");
   201         socket.send(JSON.stringify(data));
   202       };
   203  
   204       socket.onmessage = function (event) {
   205         let jsonEvent = JSON.parse(event.data);
   206         let eventType = jsonEvent.state;
   207         if (
   208           jsonEvent &&
   209           jsonEvent.total_events_searched &&
   210           jsonEvent.total_events_searched != 0
   211         ) {
   212           total_liveTail_searched = jsonEvent.total_events_searched;
   213         }
   214         let totalEventsSearched = total_liveTail_searched;
   215         let totalTime = new Date().getTime() - startQueryTime;
   216         switch (eventType) {
   217           case "RUNNING":
   218             console.time("RUNNING");
   219             console.timeEnd("RUNNING");
   220             break;
   221           case "QUERY_UPDATE":
   222             console.time("QUERY_UPDATE");
   223             if (timeToFirstByte === 0) {
   224               timeToFirstByte = Number(totalTime).toLocaleString();
   225             }
   226             let totalHits;
   227  
   228             if (jsonEvent && jsonEvent.hits && jsonEvent.hits.totalMatched) {
   229               totalHits = jsonEvent.hits.totalMatched;
   230               lastKnownHits = totalHits;
   231             } else {
   232               // we enter here only because backend sent null hits/totalmatched
   233               totalHits = lastKnownHits;
   234             }
   235             resetDataTable(firstQUpdate);
   236             processLiveTailQueryUpdate(
   237               jsonEvent,
   238               eventType,
   239               totalEventsSearched,
   240               timeToFirstByte,
   241               totalHits
   242             );
   243             //  console.timeEnd("QUERY_UPDATE");
   244             firstQUpdate = false;
   245             break;
   246           case "COMPLETE":
   247             let eqRel = "eq";
   248             if (
   249               jsonEvent.totalMatched != null &&
   250               jsonEvent.totalMatched.relation != null
   251             ) {
   252               eqRel = jsonEvent.totalMatched.relation;
   253             }
   254             console.time("COMPLETE");
   255             console.log(new Date().getTime());
   256             canScrollMore = jsonEvent.can_scroll_more;
   257             scrollFrom = jsonEvent.total_rrc_count;
   258             processLiveTailCompleteUpdate(
   259               jsonEvent,
   260               eventType,
   261               totalEventsSearched,
   262               timeToFirstByte,
   263               eqRel
   264             );
   265             console.timeEnd("COMPLETE");
   266             socket.close(1000);
   267             break;
   268           case "TIMEOUT":
   269             console.time("TIMEOUT");
   270             console.log(
   271               `[message] Timeout state received from server: ${jsonEvent}`
   272             );
   273             processTimeoutUpdate(jsonEvent);
   274             console.timeEnd("TIMEOUT");
   275             break;
   276           case "ERROR":
   277             console.time("ERROR");
   278             console.log(
   279               `[message] Error state received from server: ${jsonEvent}`
   280             );
   281             processErrorUpdate(jsonEvent);
   282             console.timeEnd("ERROR");
   283             break;
   284           default:
   285             console.log(
   286               `[message] Unknown state received from server: ` +
   287                 JSON.stringify(jsonEvent)
   288             );
   289             if (jsonEvent.message.includes("expected")) {
   290               jsonEvent.message = "Your query contains syntax error";
   291             } else if (jsonEvent.message.includes("not present")) {
   292               jsonEvent["no_data_err"] = "No data found for the query";
   293             }
   294             processSearchErrorLog(jsonEvent);
   295         }
   296       };
   297  
   298       socket.onclose = function (event) {
   299         if (liveTailState) {
   300           reconnect();
   301           console.log("live tail reconnect websocket");
   302         } else {
   303           console.log("stop reconnect live tail");
   304           if (event.wasClean) {
   305             console.log(
   306               `[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`
   307             );
   308           } else {
   309             console.log(
   310               `Connection close not clean=${event} code=${event.code} reason=${event.reason} `
   311             );
   312           }
   313           console.timeEnd("socket timing");
   314         }
   315       };
   316  
   317       socket.addEventListener("error", (event) => {
   318         console.log("WebSocket error: ", event);
   319       });
   320     }
   321   function getInitialSearchFilter(skipPushState, scrollingTrigger) {
   322       let queryParams = new URLSearchParams(window.location.search);
   323       let stDate = queryParams.get("startEpoch") || Cookies.get('startEpoch') || "now-15m";
   324       let endDate = queryParams.get("endEpoch") || Cookies.get('endEpoch') || "now";
   325       let selIndexName = queryParams.get('indexName');
   326       let queryLanguage = queryParams.get("queryLanguage") ||$('#query-language-btn span').html();
   327       queryLanguage = queryLanguage.replace('"', '');
   328       $("#query-language-btn span").html(queryLanguage);
   329      $(".query-language-option").removeClass("active");
   330       if (queryLanguage == "SQL") {
   331         $("#option-1").addClass("active");
   332       } else if (queryLanguage == "Log QL") {
   333         $("#option-2").addClass("active");
   334       } else if (queryLanguage == "Splunk QL") {
   335         $("#option-3").addClass("active");
   336       }
   337       let filterTab = queryParams.get("filterTab");
   338       let filterValue = queryParams.get('searchText');
   339       if(filterTab == "0" || filterTab == null){
   340        if(filterValue != "*"){
   341          if(filterValue.indexOf("|") != -1){
   342            firstBoxSet = new Set(filterValue.split(" | ")[0].split(" "));
   343            secondBoxSet = new Set(
   344              filterValue
   345                .split("stats ")[1]
   346                .split(" BY")[0]
   347                .split(/(?=[A-Z])/)
   348            );
   349            if (filterValue.includes(" BY ")) {
   350              thirdBoxSet = new Set(filterValue.split(" BY ")[1].split(","));
   351            }
   352          }else{
   353            firstBoxSet = new Set(filterValue.split(" "));
   354          }
   355          if (firstBoxSet && firstBoxSet.size > 0) {
   356            let tags = document.getElementById("tags");
   357            while (tags.firstChild) {
   358              tags.removeChild(tags.firstChild);
   359            }
   360            firstBoxSet.forEach((value, i) => {
   361              let tag = document.createElement("li");
   362              tag.innerText = value;
   363              // Add a delete button to the tag
   364              tag.innerHTML += '<button class="delete-button">x</button>';
   365              // Append the tag to the tags list
   366              tags.appendChild(tag);
   367            });
   368          }
   369          if (secondBoxSet && secondBoxSet.size > 0) {
   370            let tags = document.getElementById("tags-second");
   371            while (tags.firstChild) {
   372              tags.removeChild(tags.firstChild);
   373            }
   374            secondBoxSet.forEach((value, i) => {
   375              let tag = document.createElement("li");
   376              tag.innerText = value;
   377              // Add a delete button to the tag
   378              tag.innerHTML += '<button class="delete-button">x</button>';
   379              // Append the tag to the tags list
   380              tags.appendChild(tag);
   381            });
   382          }
   383          if (thirdBoxSet && thirdBoxSet.size > 0) {
   384            let tags = document.getElementById("tags-third");
   385            while (tags.firstChild) {
   386              tags.removeChild(tags.firstChild);
   387            }
   388            thirdBoxSet.forEach((value, i) => {
   389              let tag = document.createElement("li");
   390              tag.innerText = value;
   391              // Add a delete button to the tag
   392              tag.innerHTML += '<button class="delete-button">x</button>';
   393              // Append the tag to the tags list
   394              
   395              tags.appendChild(tag);
   396            });
   397          }
   398        }
   399          $("#query-input").val(filterValue);
   400       }else{
   401          $("#custom-code-tab").tabs("option", "active", 1);
   402          if (filterValue === "*") {
   403            $("#filter-input").val("").change();
   404          } else {
   405            $("#filter-input").val(filterValue).change();
   406          }
   407       }
   408       let sFrom = 0;
   409       if(selIndexName!==null){
   410           if (selIndexName.length === 0){
   411               selIndexName = "*"
   412           }
   413       }else{
   414           selIndexName = "*"
   415       }
   416       selIndexName.split(',').forEach(function(searchVal){
   417           $(`.index-dropdown-item[data-index="${searchVal}"]`).toggleClass('active');
   418       });
   419   
   420       selectedSearchIndex = selIndexName.split(",").join(",");
   421       Cookies.set('IndexList', selIndexName.split(",").join(","));
   422   
   423       if (!isNaN(stDate)) {
   424           stDate = Number(stDate);
   425           endDate = Number(endDate);
   426           datePickerHandler(stDate, endDate, "custom");
   427           loadCustomDateTimeFromEpoch(stDate,endDate);
   428       } else if (stDate !== "now-15m") {
   429           datePickerHandler(stDate, endDate, stDate);
   430       } else {
   431           datePickerHandler(stDate, endDate, "");
   432       }
   433  
   434       selectedSearchIndex = selIndexName;
   435       if (!skipPushState) {
   436           addQSParm("searchText", filterValue);
   437           addQSParm("startEpoch", stDate);
   438           addQSParm("endEpoch", endDate);
   439           addQSParm("indexName", selIndexName);
   440           addQSParm("queryLanguage", queryLanguage);
   441           window.history.pushState({ path: myUrl }, '', myUrl);
   442       }
   443   
   444       if (scrollingTrigger){
   445           sFrom = scrollFrom;
   446       }
   447  
   448  
   449       return {
   450           'state': 'query',
   451           'searchText': filterValue,
   452           'startEpoch': stDate,
   453           'endEpoch': endDate,
   454           'indexName': selIndexName,
   455           'from' : sFrom,
   456           'queryLanguage' : queryLanguage,
   457       };
   458   }
   459    function getLiveTailFilter(skipPushState, scrollingTrigger, startTime) {
   460      let filterValue = $("#filter-input").val().trim() || "*";
   461      let endDate = "now";
   462      let date = new Date();
   463      let stDate = new Date(date.getTime() - startTime * 1000).getTime();
   464      if (startTime == 1800) stDate = "now-1h";
   465      let selIndexName = selectedSearchIndex;
   466      let sFrom = 0;
   467      let queryLanguage = $("#query-language-btn span").html();
   468  
   469      selIndexName.split(",").forEach(function (searchVal) {
   470        $(`.index-dropdown-item[data-index="${searchVal}"]`).toggleClass(
   471          "active"
   472        );
   473      });
   474  
   475      selectedSearchIndex = selIndexName.split(",").join(",");
   476      Cookies.set("IndexList", selIndexName.split(",").join(","));
   477  
   478      addQSParm("searchText", filterValue);
   479      addQSParm("startEpoch", stDate);
   480      addQSParm("endEpoch", endDate);
   481      addQSParm("indexName", selIndexName);
   482      addQSParm("queryLanguage", queryLanguage);
   483  
   484      window.history.pushState({ path: myUrl }, "", myUrl);
   485  
   486      if (scrollingTrigger) {
   487        sFrom = scrollFrom;
   488      }
   489  
   490      return {
   491        state: wsState,
   492        searchText: filterValue,
   493        startEpoch: stDate,
   494        endEpoch: endDate,
   495        indexName: selIndexName,
   496        from: sFrom,
   497        queryLanguage: queryLanguage,
   498      };
   499    }
   500    let filterTextQB = "";
   501    /**
   502     * get real time search text
   503     * @returns real time search text
   504     */
   505    function getQueryBuilderCode() {
   506     let filterValue = "";
   507       //concat the first input box
   508       let index = 0;
   509       if (firstBoxSet && firstBoxSet.size > 0) {
   510         firstBoxSet.forEach((value, i) => {
   511           if (index != firstBoxSet.size - 1) filterValue += value + " ";
   512           else filterValue += value;
   513           index++;
   514         });
   515       }else{
   516        filterValue = '*';
   517       }
   518       index = 0;
   519       let bothRight = 0;
   520       let showError = false;
   521       //concat the second input box
   522       if (secondBoxSet && secondBoxSet.size > 0) {
   523         bothRight++;
   524         filterValue += " | stats";
   525         secondBoxSet.forEach((value, i) => {
   526           if (index != secondBoxSet.size - 1) filterValue += " " + value + ",";
   527           else filterValue += " " + value;
   528           index++;
   529         });
   530       }
   531       index = 0;
   532       if (thirdBoxSet && thirdBoxSet.size > 0) {
   533        if(bothRight == 0) showError = true;
   534         //concat the third input box
   535         filterValue += " BY";
   536         thirdBoxSet.forEach((value, i) => {
   537           if (index != thirdBoxSet.size - 1) filterValue += " " + value + ",";
   538           else filterValue += " " + value;
   539           index++;
   540         });
   541       }
   542       if (filterValue == "") filterValue = "*";
   543       $("#query-input").val(filterValue);
   544       if(thirdBoxSet && thirdBoxSet.size > 0 && (secondBoxSet == null || secondBoxSet.size == 0)) $("#query-builder-btn").addClass("stop-search").prop('disabled', true);
   545       else $("#query-builder-btn").removeClass("stop-search").prop('disabled', false);
   546     return showError ? "Searches with a Search Criteria must have an Aggregate Attribute" : filterValue;
   547    }
   548   function getSearchFilter(skipPushState, scrollingTrigger) {
   549     let currentTab = $("#custom-code-tab").tabs("option", "active");
   550     let endDate = filterEndDate || "now";
   551     let stDate = filterStartDate || "now-15m";
   552     let selIndexName = selectedSearchIndex;
   553     let sFrom = 0;
   554     let queryLanguage = $("#query-language-btn span").html();
   555    
   556     selIndexName.split(",").forEach(function (searchVal) {
   557       $(`.index-dropdown-item[data-index="${searchVal}"]`).toggleClass("active");
   558     });
   559  
   560     selectedSearchIndex = selIndexName.split(",").join(",");
   561     Cookies.set("IndexList", selIndexName.split(",").join(","));
   562  
   563     if (!isNaN(stDate)) {
   564       datePickerHandler(Number(stDate), Number(endDate), "custom");
   565     } else if (stDate !== "now-15m") {
   566       datePickerHandler(stDate, endDate, stDate);
   567     } else {
   568       datePickerHandler(stDate, endDate, "");
   569     }
   570     let filterValue = "";
   571     if(currentTab == 0){
   572      queryLanguage = "Splunk QL";
   573       //concat the 3 input boxes
   574       filterValue = getQueryBuilderCode();
   575       isQueryBuilderSearch = true;
   576     }else{
   577      filterValue = $("#filter-input").val().trim() || "*";
   578      isQueryBuilderSearch = false;
   579     }
   580     addQSParm("searchText", filterValue);
   581     addQSParm("startEpoch", stDate);
   582     addQSParm("endEpoch", endDate);
   583     addQSParm("indexName", selIndexName);
   584     addQSParm("queryLanguage", queryLanguage);
   585  
   586     window.history.pushState({ path: myUrl }, "", myUrl);
   587  
   588     if (scrollingTrigger) {
   589       sFrom = scrollFrom;
   590     }
   591  
   592     filterTextQB = filterValue;
   593     return {
   594       state: wsState,
   595       searchText: filterValue,
   596       startEpoch: stDate,
   597       endEpoch: endDate,
   598       indexName: selIndexName,
   599       from: sFrom,
   600       queryLanguage: queryLanguage,
   601     };
   602   }
   603   
   604   function getSearchFilterForSave(qname, qdesc) {
   605       let filterValue = filterTextQB.trim() || "*";
   606        let currentTab = $("#custom-code-tab").tabs("option", "active");
   607       return {
   608           'queryName': qname,
   609           'queryDescription': qdesc || "",
   610           'searchText': filterValue,
   611           'indexName': selectedSearchIndex,
   612           'filterTab': currentTab.toString(),
   613           'queryLanguage': $("#query-language-btn span").html()
   614       };
   615   }
   616    function processLiveTailQueryUpdate(
   617      res,
   618      eventType,
   619      totalEventsSearched,
   620      timeToFirstByte,
   621      totalHits
   622    ) {
   623      if (
   624        res.hits &&
   625        res.hits.records !== null &&
   626        res.hits.records.length >= 1 &&
   627        res.qtype === "logs-query"
   628      ) {
   629        let columnOrder = _.uniq(
   630          _.concat(
   631            // make timestamp the first column
   632            "timestamp",
   633            // make logs the second column
   634            "logs",
   635            res.allColumns
   636          )
   637        );
   638        allLiveTailColumns = res.allColumns;
   639        renderAvailableFields(columnOrder);
   640        renderLogsGrid(columnOrder, res.hits.records);
   641  
   642        if (res && res.hits && res.hits.totalMatched) {
   643          totalHits = res.hits.totalMatched;
   644        }
   645      } else if (logsRowData.length > 0) {
   646        let columnOrder = _.uniq(
   647          _.concat(
   648            // make timestamp the first column
   649            "timestamp",
   650            // make logs the second column
   651            "logs",
   652            allLiveTailColumns
   653          )
   654        );
   655        renderAvailableFields(columnOrder);
   656        renderLogsGrid(columnOrder, logsRowData);
   657        totalHits = logsRowData.length;
   658      } else if (
   659        res.measure &&
   660        (res.qtype === "aggs-query" || res.qtype === "segstats-query")
   661      ) {
   662        if (res.groupByCols) {
   663          columnOrder = _.uniq(_.concat(res.groupByCols));
   664        }
   665        let columnOrder = [];
   666        if (res.measureFunctions) {
   667          columnOrder = _.uniq(_.concat(columnOrder, res.measureFunctions));
   668        }
   669  
   670        aggsColumnDefs = [];
   671        segStatsRowData = [];
   672        renderMeasuresGrid(columnOrder, res.measure);
   673      }
   674      let totalTime = new Date().getTime() - startQueryTime;
   675      let percentComplete = res.percent_complete;
   676      renderTotalHits(
   677        totalHits,
   678        totalTime,
   679        percentComplete,
   680        eventType,
   681        totalEventsSearched,
   682        timeToFirstByte,
   683        "",
   684        res.qtype
   685      );
   686      $("body").css("cursor", "default");
   687    }
   688   function processQueryUpdate(res, eventType, totalEventsSearched, timeToFirstByte, totalHits) {
   689       if (res.hits && res.hits.records!== null && res.hits.records.length >= 1 && res.qtype === "logs-query") {
   690           let columnOrder = _.uniq(_.concat(
   691               // make timestamp the first column
   692               'timestamp',
   693               // make logs the second column
   694               'logs',
   695               res.allColumns));
   696               
   697           // for sort function display
   698           sortByTimestampAtDefault = res.sortByTimestampAtDefault; 
   699  
   700           renderAvailableFields(columnOrder);
   701           renderLogsGrid(columnOrder, res.hits.records);
   702  
   703          $("#logs-result-container").show();
   704          $("#agg-result-container").hide();
   705           
   706           if (res && res.hits && res.hits.totalMatched) {
   707               totalHits = res.hits.totalMatched
   708           }
   709       } else if (res.measure && (res.qtype === "aggs-query" || res.qtype === "segstats-query")) {
   710           if (res.groupByCols ) {
   711               columnOrder = _.uniq(_.concat(
   712                   res.groupByCols));
   713           }
   714           let columnOrder =[]
   715           if (res.measureFunctions ) {
   716               columnOrder = _.uniq(_.concat(
   717                   columnOrder,res.measureFunctions));
   718           }
   719   
   720           aggsColumnDefs=[];
   721           segStatsRowData=[]; 
   722           renderMeasuresGrid(columnOrder, res.measure);
   723   
   724       }
   725       let totalTime = (new Date()).getTime() - startQueryTime;
   726       let percentComplete = res.percent_complete;
   727       renderTotalHits(totalHits, totalTime, percentComplete, eventType, totalEventsSearched, timeToFirstByte, "", res.qtype);
   728       $('body').css('cursor', 'default');
   729   }
   730   
   731   function processEmptyQueryResults() {
   732       $("#logs-result-container").hide();
   733      $("#custom-chart-tab").hide();
   734       $("#agg-result-container").hide();
   735       $("#data-row-container").hide();
   736       $('#corner-popup').hide();
   737       $('#empty-response').show();
   738       $('#logs-view-controls').hide();
   739       let el = $('#empty-response');
   740       $('#empty-response').empty();
   741       el.append('<span>Your query returned no data, adjust your query.</span>')
   742   }
   743    function processLiveTailCompleteUpdate(
   744      res,
   745      eventType,
   746      totalEventsSearched,
   747      timeToFirstByte,
   748      eqRel
   749    ) {
   750      let columnOrder = [];
   751      let totalHits = res.totalMatched.value + logsRowData.length;
   752      if (res.totalMatched.value + logsRowData.length > 500) totalHits = 500;
   753      if (
   754        logsRowData.length == 0 &&
   755        res.totalMatched.value === 0 &&
   756        res.measure === undefined
   757      ) {
   758        processEmptyQueryResults();
   759      }
   760      if (res.measure) {
   761        if (res.groupByCols) {
   762          columnOrder = _.uniq(_.concat(res.groupByCols));
   763        }
   764        if (res.measureFunctions) {
   765          columnOrder = _.uniq(_.concat(columnOrder, res.measureFunctions));
   766        }
   767        resetDashboard();
   768        $("#logs-result-container").hide();
   769        $("#custom-chart-tab").show();
   770        $("#agg-result-container").show();
   771        aggsColumnDefs = [];
   772        segStatsRowData = [];
   773        renderMeasuresGrid(columnOrder, res.measure);
   774        if (
   775          (res.qtype === "aggs-query" || res.qtype === "segstats-query") &&
   776          res.bucketCount
   777        ) {
   778          totalHits = res.bucketCount;
   779        }
   780      }
   781  
   782      let totalTime = new Date().getTime() - startQueryTime;
   783      let percentComplete = res.percent_complete;
   784      if (res.total_rrc_count > 0) {
   785        totalRrcCount += res.total_rrc_count;
   786      }
   787      renderTotalHits(
   788        totalHits,
   789        totalTime,
   790        percentComplete,
   791        eventType,
   792        totalEventsSearched,
   793        timeToFirstByte,
   794        eqRel,
   795        res.qtype
   796      );
   797      $("#run-filter-btn").html(" ");
   798      $("#run-filter-btn").removeClass("cancel-search");
   799      $("#run-filter-btn").removeClass("active");
   800      $("#query-builder-btn").html(" ");
   801      $("#query-builder-btn").removeClass("cancel-search");
   802      $("#query-builder-btn").removeClass("active");
   803      wsState = "query";
   804      if (canScrollMore === false) {
   805        scrollFrom = 0;
   806      }
   807    }
   808   function processCompleteUpdate(res, eventType, totalEventsSearched, timeToFirstByte, eqRel) {
   809       let columnOrder =[]
   810       let totalHits = res.totalMatched.value;
   811       if ((res.totalMatched == 0 || res.totalMatched.value === 0) && res.measure ===undefined) {
   812           processEmptyQueryResults();
   813       }
   814       if (res.measureFunctions && res.measureFunctions.length > 0) {
   815         measureFunctions = res.measureFunctions;
   816       }
   817       if (res.measure) {
   818           measureInfo = res.measure;
   819           if (res.groupByCols) {
   820               columnOrder = _.uniq(_.concat(
   821                   res.groupByCols));
   822           }
   823           if (res.measureFunctions) {
   824               columnOrder = _.uniq(_.concat(
   825                   columnOrder,res.measureFunctions));
   826           }
   827           resetDashboard();
   828           $("#logs-result-container").hide();
   829           $("#custom-chart-tab").show();
   830           $("#agg-result-container").show();
   831           aggsColumnDefs=[];
   832           segStatsRowData=[];
   833           renderMeasuresGrid(columnOrder, res.measure);
   834           if ((res.qtype ==="aggs-query" || res.qtype === "segstats-query") && res.bucketCount){
   835               totalHits = res.bucketCount;
   836           }
   837       }else{
   838        measureInfo = [];
   839       }
   840      isTimechart = res.isTimechart;
   841      const currentUrl = window.location.href;
   842      if (currentUrl.includes("index.html"))
   843        timeChart();
   844       let totalTime = (new Date()).getTime() - startQueryTime;
   845       let percentComplete = res.percent_complete;
   846       if (res.total_rrc_count > 0){
   847           totalRrcCount += res.total_rrc_count;
   848       }
   849       renderTotalHits(totalHits, totalTime, percentComplete, eventType, totalEventsSearched,
   850           timeToFirstByte, eqRel, res.qtype);
   851       $('#run-filter-btn').html(' ');
   852       $("#run-filter-btn").removeClass("cancel-search");
   853       $('#run-filter-btn').removeClass('active');
   854       $("#query-builder-btn").html(" ");
   855       $("#query-builder-btn").removeClass("cancel-search");
   856       $("#query-builder-btn").removeClass("active");
   857       wsState = 'query'
   858       if (canScrollMore === false){
   859           scrollFrom = 0;
   860       }
   861   }
   862   
   863   function processTimeoutUpdate(res) {
   864       showError(`Query ${res.qid} reached the timeout limit of ${res.timeoutSeconds} seconds`);
   865   }
   866  
   867   function processErrorUpdate(res) {
   868       showError(`Message: ${res.message}`);
   869   }
   870   
   871   function processSearchError(res) {
   872       if (res.can_scroll_more === false){
   873           showInfo(`You've reached maximum scroll limit (10,000).`);
   874       } else if (res.message != "") {
   875           showError(`Message: ${res.message}`);
   876           resetDashboard();
   877       }
   878   }
   879  
   880   function processSearchErrorLog(res){
   881      if (res.can_scroll_more === false){
   882          showInfo(`You've reached maximum scroll limit (10,000).`);
   883      } else if (res.message != "") {
   884          showErrorResponse(`Message: ${res.message}`,res);
   885          resetDashboard();
   886      }
   887   }
   888  
   889   function showErrorResponse(errorMsg,res){
   890      $("#logs-result-container").hide();
   891       $("#agg-result-container").hide();
   892       $("#data-row-container").hide();
   893       $('#corner-popup').hide();
   894       $('#empty-response').show();
   895       $('#logs-view-controls').hide();
   896      $("#custom-chart-tab").hide();
   897       let el = $('#empty-response');
   898       $('#empty-response').empty();
   899       if (res && res.no_data_err && res.no_data_err.includes("No data found")){
   900          el.html(`${res.no_data_err} <br> `+ errorMsg);
   901      }else{
   902          el.html(errorMsg);
   903      }
   904      $('body').css('cursor', 'default');
   905      $('#run-filter-btn').html(' ');
   906      $("#run-filter-btn").removeClass("cancel-search");
   907      $('#run-filter-btn').removeClass('active');
   908      $("#query-builder-btn").html(" ");
   909      $("#query-builder-btn").removeClass("cancel-search");
   910      $("#query-builder-btn").removeClass("active");
   911      $('#run-metrics-query-btn').removeClass('active');
   912  
   913      wsState = 'query';
   914   }
   915  
   916  
   917   
   918   function renderTotalHits(totalHits, elapedTimeMS, percentComplete, eventType, totalEventsSearched, timeToFirstByte, eqRel, qtype) {
   919       //update chart title
   920       console.log(`rendering total hits: ${totalHits}. elapedTimeMS: ${elapedTimeMS}`);
   921       let startDate = displayStart ;
   922       let endDate = displayEnd;
   923       // Check if totalHits is undefined and set it to 0
   924       let totalHitsFormatted = Number(totalHits || 0).toLocaleString();
   925   
   926       if (eventType === "QUERY_UPDATE") {
   927        if (totalHits > 0){
   928               $('#hits-summary').html(`
   929               <div><span class="total-hits">${totalHitsFormatted} </span><span>of ${totalEventsSearched} Records Matched</span> </div>
   930   
   931               <div class="text-center">${dateFns.format(startDate, timestampDateFmt)} &mdash; ${dateFns.format(endDate, timestampDateFmt)}</div>
   932               <div class="text-end">Response: ${timeToFirstByte} ms</div>
   933           `);
   934           } else{
   935               $('#hits-summary').html(`<div><span> ${totalEventsSearched} Records Searched</span> </div>
   936   
   937               <div class="text-center">${dateFns.format(startDate, timestampDateFmt)} &mdash; ${dateFns.format(endDate, timestampDateFmt)}</div>
   938               <div class="text-end">Response: ${timeToFirstByte} ms</div>
   939           `);
   940           }
   941           $('#progress-div').html(`
   942               <progress id="percent-complete" value=${percentComplete} max="100">${percentComplete}</progress>
   943               <div id="percent-value">${parseInt(percentComplete)}%</div>
   944           `);
   945       }
   946       else if (eventType === "COMPLETE") {
   947           let operatorSign = '';
   948           if (eqRel === "gte") {
   949               operatorSign = '>=';
   950           }
   951           if (qtype == "aggs-query" || qtype === "segstats-query") {
   952             let bucketGrammer = totalHits == 1 ? "bucket was" : "buckets were";
   953             $("#hits-summary").html(`
   954               <div><b>Response: ${timeToFirstByte} ms</b></div>
   955               <div><span class="total-hits"><b>${operatorSign} ${totalHitsFormatted}</b></span><span> ${bucketGrammer} created from <b>${totalEventsSearched}</b> records.</span></div>
   956               <div>${dateFns.format(
   957                 startDate,
   958                 timestampDateFmt
   959               )} &mdash; ${dateFns.format(endDate, timestampDateFmt)}</div>
   960           `);
   961           } else if (totalHits > 0) {
   962             $("#hits-summary").html(`
   963               <div><b>Response: ${timeToFirstByte} ms</b></div>
   964               <div><span class="total-hits"><b>${operatorSign} ${totalHitsFormatted}</b></span><span> of <b>${totalEventsSearched}</b> Records Matched</span></div>
   965               <div>${dateFns.format(
   966                 startDate,
   967                 timestampDateFmt
   968               )} &mdash; ${dateFns.format(endDate, timestampDateFmt)}</div>
   969           `);
   970           } else {
   971             $("#hits-summary").html(`
   972               <div><b>Response: ${timeToFirstByte} ms</b></div>
   973               <div><span><b> ${totalEventsSearched} </b>Records Searched</span></div>
   974               <div>${dateFns.format(
   975                 startDate,
   976                 timestampDateFmt
   977               )} &mdash; ${dateFns.format(endDate, timestampDateFmt)}</div>
   978           `);
   979           }
   980           $('#progress-div').html(``)
   981       }
   982   }
   983  
   984  // LiveTail Refresh Duration
   985  let refreshInterval = 10000;
   986  
   987  $('.refresh-range-item').on('click', refreshRangeItemHandler);
   988  
   989  function refreshRangeItemHandler(evt){
   990    $.each($(".refresh-range-item.active"), function () {
   991        $(this).removeClass('active');
   992    });
   993    $(evt.currentTarget).addClass('active');
   994    let interval = $(evt.currentTarget).attr('id');
   995    $('#refresh-picker-btn span').html(interval);
   996  
   997    refreshInterval = parseInterval(interval); // Parsing interval
   998    if(liveTailState) 
   999      reconnect();
  1000  }
  1001  
  1002  function parseInterval(interval) {
  1003    const regex = /(\d+)([smhd])/;
  1004    const match = interval.match(regex);
  1005    const value = parseInt(match[1]);
  1006    const unit = match[2];
  1007    
  1008    switch (unit) {
  1009        case 's':
  1010            return value * 1000;
  1011        case 'm':
  1012            return value * 60 * 1000;
  1013        case 'h':
  1014            return value * 60 * 60 * 1000;
  1015        case 'd':
  1016            return value * 24 * 60 * 60 * 1000;
  1017        default:
  1018            throw new Error("Invalid interval unit");
  1019    }
  1020  }