From 175bdc39e1bb13dd76cfbdf585ffceeaf8c616d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20R=C3=B6der?= Date: Mon, 23 Feb 2026 14:06:30 +0100 Subject: [PATCH 1/2] #659: Allow filtering report tables by label --- config/testreport/css/default.css | 4 +- config/testreport/js/table.js | 2 +- config/testreport/js/xlt.js | 121 +++++++++++------- .../xsl/diffreport/sections/custom-values.xsl | 2 +- config/xsl/diffreport/sections/web-vitals.xsl | 2 +- config/xsl/diffreport/util/timer-section.xsl | 4 +- config/xsl/diffreport/util/timer-table.xsl | 2 +- config/xsl/loadreport/sections/agents.xsl | 2 +- .../xsl/loadreport/sections/custom-values.xsl | 2 +- config/xsl/loadreport/sections/web-vitals.xsl | 2 +- .../util/slowest-requests-table.xsl | 2 +- config/xsl/loadreport/util/timer-section.xsl | 24 +++- config/xsl/loadreport/util/timer-table.xsl | 11 +- config/xsl/trendreport/util/timer-chart.xsl | 5 +- config/xsl/trendreport/util/timer-table.xsl | 4 +- 15 files changed, 118 insertions(+), 71 deletions(-) diff --git a/config/testreport/css/default.css b/config/testreport/css/default.css index 7018250c8..a07df3a25 100644 --- a/config/testreport/css/default.css +++ b/config/testreport/css/default.css @@ -1013,11 +1013,11 @@ table * { } table th.table-sorted-asc { - background-image: url('../images/caret-down-fill.svg'); + background-image: url('../images/caret-up-fill.svg'); } table th.table-sorted-desc { - background-image: url('../images/caret-up-fill.svg'); + background-image: url('../images/caret-down-fill.svg'); } table tfoot td.colgroup1 { diff --git a/config/testreport/js/table.js b/config/testreport/js/table.js index c0c9fea0f..a34d02f49 100644 --- a/config/testreport/js/table.js +++ b/config/testreport/js/table.js @@ -520,7 +520,7 @@ var Table = (function(){ removeClass(cell,this.SortedDescendingClassName); // If the computed colIndex of the cell equals the sorted colIndex, flag it as sorted if (tdata.col==table.getActualCellIndex(cell) && (classValue(cell,table.SortableClassName))) { - addClass(cell,tdata.desc?this.SortedAscendingClassName:this.SortedDescendingClassName); + addClass(cell,tdata.desc?this.SortedDescendingClassName:this.SortedAscendingClassName); } } } diff --git a/config/testreport/js/xlt.js b/config/testreport/js/xlt.js index 8200a69c1..ca954a283 100644 --- a/config/testreport/js/xlt.js +++ b/config/testreport/js/xlt.js @@ -347,17 +347,13 @@ function filter (input) { var $input = $(input), - table = $input.parents("table:not(.cluetip-table)"), // get the target/foreground table + $table = $input.parents("table:not(.cluetip-table)"), // get the target/foreground table filterPhrase = $input.val(); + filterId = $input.attr('data-filter-id'); - var filterFunc = function(value) { return doFilter(value, filterPhrase) }; - - // actually perform filtering a table by a filter phrase - var filterTable = function(table) { - table.find("input.filter").each(function() { - Table.filter(this, { 'filter': filterFunc }); - }); - }; + var filter = {}; + filter.filter = function(value) { return doFilter(value, filterPhrase) }; + filter.col = $input.attr('data-col-index'); // input should provide the index of the column to filter // shows/hides the footer row of a table var showTableFooter = function(table, footerVisible) { @@ -383,29 +379,34 @@ var footerVisible; - // let the table filter the rows - filterTable(table); + // let the table filter the rows; the filter logic stores all previously applied filters (e.g. filters for other + // columns), so we only need to provide the input field that got updated here + Table.filter(input, filter); // show the table footer only if no body rows have been filtered out - footerVisible = table.find('> tbody > tr:hidden').length == 0; + footerVisible = $table.find('> tbody > tr:hidden').length == 0; - showTableFooter(table, footerVisible); + showTableFooter($table, footerVisible); - // now process any hidden table (Requests page only) - $('table:hidden').each(function() { + // now process any hidden tables (Requests page only) or all tables on the page for trend reports; don't process + // the initial table again + $('table:hidden,table.trend').not($table).each(function() { var $this = $(this); - filterTable($this); + var $input = $this.find('input[data-filter-id="' + filterId + '"]'); + Table.filter($input[0], filter); showTableFooter($this, footerVisible); // set the current filter phrase as the filter input's value - $this.find('input.filter').val(filterPhrase); + $input.val(filterPhrase); }); // now filter the charts $('.charts:not(.overview) .chart-group.no-print').each(function() { - var value = this.getAttribute('data-name'); - var visible = filterFunc(value); - - $(this).toggle(visible); + var chartId = this.getAttribute('id'); + // find the table row for the current chart; the chart is only visible if the matching table row is visible + var $row = $table.find('tr a[href="#' + chartId + '"]'); + if($row.length > 0){ + $(this).toggle($row.is(':visible')); + } }); } @@ -513,6 +514,7 @@ event.stopPropagation(); $input.val(""); throttleFilter(input); + updateFilterHash(input); }); }); })(); @@ -987,9 +989,9 @@ newHashObj.sort = oldHashObj.sort; } - // hashes might contain a filter - if(oldHashObj.filter != undefined && newHashObj.filter == undefined){ - newHashObj.filter = oldHashObj.filter; + // hashes might contain filters + if(oldHashObj.filters.length > 0 && newHashObj.filters.length == 0){ + newHashObj.filters = oldHashObj.filters; } // sometimes sorting must be triggered from the update hash function. For example, when a user directly changes the sorting option (navbar) in the url @@ -1012,9 +1014,11 @@ } } - // splits the given hash - automatically tries to detect the current format. the returned hash object might contain a "navigation", "sort" and "filter" option + // splits the given hash - automatically tries to detect the current format. the returned hash object might contain a "navigation" and "sort" option, as well + // as multiple "filter" options function splitHash(hash){ var hashObj = {}; + hashObj.filters = []; if(hash !== ""){ // hash format: http://...#abc @@ -1033,7 +1037,11 @@ hashObj.sort = param; } else if(param.startsWith('filter')){ - hashObj.filter = param; + // filter params have the format "filterByX=value" + var filterId = param.split('=')[0]; + // order filters based on their column index, so the order in the hash is consistent + var index = $('input[data-filter-id="' + filterId + '"]').attr('data-col-index'); + hashObj.filters[index] = param; } else{ hashObj.navigation = '#' + param; @@ -1059,8 +1067,13 @@ newHash.push(updatedHashObj.sort); } - if(updatedHashObj.filter != undefined){ - newHash.push(updatedHashObj.filter); + if(updatedHashObj.filters != undefined){ + for(var i = 0; i < updatedHashObj.filters.length; i++){ + // the "filters" array might contain undefined values if not all filters are set; we ignore those here + if(updatedHashObj.filters[i]){ + newHash.push(updatedHashObj.filters[i]); + } + } } // check if we have a hash to process @@ -1081,13 +1094,13 @@ // eventlistener that fires if a sortable table row gets clicked function updateHashAfterSort(sortingEvent){ - if(sortingEvent.target.classList.contains('table-sortable') && sortingEvent.target.id != undefined){ - var sortingRule = sortingEvent.target.classList.contains('table-sorted-asc') ? 'asc' : 'desc'; + if(sortingEvent.currentTarget.classList.contains('table-sortable') && sortingEvent.currentTarget.id != undefined){ + var sortingRule = sortingEvent.currentTarget.classList.contains('table-sorted-asc') ? 'asc' : 'desc'; // Get the current hash (if one exists) var hashObj = splitHash(window.location.hash); // update the sorting option of the hash - hashObj.sort = sortingEvent.target.id + '=' + sortingRule; + hashObj.sort = sortingEvent.currentTarget.id + '=' + sortingRule; // After sorting we update the hash manually, so we disable executing the next event ignoreNextHashChange = true; @@ -1140,12 +1153,21 @@ // method that gets triggered when the user enters some input to apply a filter function updateHashAfterFilter(filterEvent){ - var filter = filterEvent.target.value; - var encodedFilter = encodeURIComponent(filterEvent.target.value); + updateFilterHash(filterEvent.target); + } + + // updates the current hash with information from the given filter input + function updateFilterHash(filterInput){ + var filter = filterInput.value; + var encodedFilter = encodeURIComponent(filter); // console.log('filter change:' + filter + ' to ' + encodedFilter); + var filterId = filterInput.getAttribute('data-filter-id'); + var index = filterInput.getAttribute('data-col-index'); + var newHashObj = splitHash(window.location.hash); - newHashObj.filter = 'filter=' + encodedFilter; + + newHashObj.filters[index] = filterId + '=' + encodedFilter; updateHash(newHashObj); } @@ -1164,20 +1186,23 @@ sort(sortingElem, sortingRule); } - // check for existing filter to apply - if(hashObj.filter != undefined){ - // Apply initial filter - var filterParam = hashObj.filter.split('='); - var encodedFilter = filterParam[1]; - - if(encodedFilter.length > 0){ - var decodedFilter = decodeURIComponent(encodedFilter); - // console.log('filter value: ' + decodedFilter); - var filterInputFields = document.querySelectorAll('input.filter'); - for(var i = 0; i < filterInputFields.length; i++){ - var filterField = filterInputFields[i]; - filterField.value = decodedFilter; - filter(filterField); + // check for existing filters to apply + for(var i = 0; i < hashObj.filters.length; i++){ + // skip undefined values in the "filters" array + if(hashObj.filters[i]){ + // Apply initial filters + var filterParam = hashObj.filters[i].split('='); + var encodedFilter = filterParam[1]; + + if(encodedFilter.length > 0){ + var decodedFilter = decodeURIComponent(encodedFilter); + // console.log('filter value: ' + decodedFilter); + + // apply filter to the first visible matching filter input field; the filter logic will propagate + // the filter value to all other matching input fields automatically + var filterInputField = $('input:visible[data-filter-id="' + filterParam[0] + '"]')[0]; + filterInputField.value = decodedFilter; + filter(filterInputField); } } } diff --git a/config/xsl/diffreport/sections/custom-values.xsl b/config/xsl/diffreport/sections/custom-values.xsl index 925e7fe97..fe515b373 100644 --- a/config/xsl/diffreport/sections/custom-values.xsl +++ b/config/xsl/diffreport/sections/custom-values.xsl @@ -18,7 +18,7 @@ sortByName Value Name
- + Count diff --git a/config/xsl/diffreport/sections/web-vitals.xsl b/config/xsl/diffreport/sections/web-vitals.xsl index f0b5d4bd5..f03e31806 100644 --- a/config/xsl/diffreport/sections/web-vitals.xsl +++ b/config/xsl/diffreport/sections/web-vitals.xsl @@ -18,7 +18,7 @@ sortByName Action Name
- + First Contentful Paint
(FCP) diff --git a/config/xsl/diffreport/util/timer-section.xsl b/config/xsl/diffreport/util/timer-section.xsl index 406497341..ede75c045 100644 --- a/config/xsl/diffreport/util/timer-section.xsl +++ b/config/xsl/diffreport/util/timer-section.xsl @@ -35,10 +35,10 @@ - diff --git a/config/xsl/diffreport/util/timer-table.xsl b/config/xsl/diffreport/util/timer-table.xsl index b6ed2993b..65575de75 100644 --- a/config/xsl/diffreport/util/timer-table.xsl +++ b/config/xsl/diffreport/util/timer-table.xsl @@ -15,7 +15,7 @@ sortByName
- + diff --git a/config/xsl/loadreport/sections/agents.xsl b/config/xsl/loadreport/sections/agents.xsl index b5ac57f8e..d4ca4b777 100644 --- a/config/xsl/loadreport/sections/agents.xsl +++ b/config/xsl/loadreport/sections/agents.xsl @@ -25,7 +25,7 @@
diff --git a/config/xsl/loadreport/sections/custom-values.xsl b/config/xsl/loadreport/sections/custom-values.xsl index e2c8a44f6..092bec9a1 100644 --- a/config/xsl/loadreport/sections/custom-values.xsl +++ b/config/xsl/loadreport/sections/custom-values.xsl @@ -17,7 +17,7 @@ diff --git a/config/xsl/loadreport/sections/web-vitals.xsl b/config/xsl/loadreport/sections/web-vitals.xsl index b04fcfc9b..feeb6c12a 100644 --- a/config/xsl/loadreport/sections/web-vitals.xsl +++ b/config/xsl/loadreport/sections/web-vitals.xsl @@ -16,7 +16,7 @@ diff --git a/config/xsl/loadreport/util/slowest-requests-table.xsl b/config/xsl/loadreport/util/slowest-requests-table.xsl index ee36d19fe..e6d309c35 100644 --- a/config/xsl/loadreport/util/slowest-requests-table.xsl +++ b/config/xsl/loadreport/util/slowest-requests-table.xsl @@ -12,7 +12,7 @@ Request Name
- + diff --git a/config/xsl/loadreport/util/timer-section.xsl b/config/xsl/loadreport/util/timer-section.xsl index b38d2d6fb..a865b7150 100644 --- a/config/xsl/loadreport/util/timer-section.xsl +++ b/config/xsl/loadreport/util/timer-section.xsl @@ -8,7 +8,7 @@ -
+
@@ -52,10 +52,17 @@
- + @@ -274,10 +281,17 @@ - + diff --git a/config/xsl/loadreport/util/timer-table.xsl b/config/xsl/loadreport/util/timer-table.xsl index e88f5920c..9917c4838 100644 --- a/config/xsl/loadreport/util/timer-table.xsl +++ b/config/xsl/loadreport/util/timer-table.xsl @@ -31,12 +31,19 @@
- + -
+ diff --git a/config/xsl/trendreport/util/timer-chart.xsl b/config/xsl/trendreport/util/timer-chart.xsl index 809d7ec9c..1b82a26fc 100644 --- a/config/xsl/trendreport/util/timer-chart.xsl +++ b/config/xsl/trendreport/util/timer-chart.xsl @@ -13,13 +13,14 @@ - timerchart- + This is a placeholder for the anchor. -
+
+ timerchart-
+
- +
Bytes Sent Agent Name
- +
Transactions Value Name
- +
Count Action Name
- +
First Contentful Paint
(FCP)

- +
Labels + Labels +
+
+ + +
+
Bytes Sent Bytes Received

- +
Labels + Labels +
+
+ + +
+
DNS Time [ms] Connect Time [ms] Send Time [ms]Labels + Labels +
+
+ + +
+
+
From 9feb355780253a5171f6adaa72643208556dfa9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20R=C3=B6der?= Date: Mon, 23 Feb 2026 14:23:45 +0100 Subject: [PATCH 2/2] fix filtering on slowest requests page --- config/testreport/js/xlt.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/testreport/js/xlt.js b/config/testreport/js/xlt.js index ca954a283..15cd65414 100644 --- a/config/testreport/js/xlt.js +++ b/config/testreport/js/xlt.js @@ -389,8 +389,8 @@ showTableFooter($table, footerVisible); // now process any hidden tables (Requests page only) or all tables on the page for trend reports; don't process - // the initial table again - $('table:hidden,table.trend').not($table).each(function() { + // the initial table again; ignore cluetip tables + $('table:not(.cluetip-table):hidden,table.trend').not($table).each(function() { var $this = $(this); var $input = $this.find('input[data-filter-id="' + filterId + '"]'); Table.filter($input[0], filter);
sortByName
- +