diff --git a/docs/filtering.md b/docs/filtering.md
index c138297b..9aeeaa33 100644
--- a/docs/filtering.md
+++ b/docs/filtering.md
@@ -10,12 +10,13 @@ RobotFramework Dashboard provides flexible filtering options across different pa
## Overview Page
-The **Overview** page does not have global filters. However, it offers a few **display settings**:
+The **Overview** page uses the same **global filters** as the Dashboard page (see below): open the filter modal from the top navigation bar and every project bar, the Latest Runs bar and the Total Stats bar reflect the filtered set of runs. A dot on the filter icon indicates a filter is active.
-- **Display By Name** – Toggle whether to display the names of projects in the statistics.
-- **Display By Tag** – Toggle whether to use custom project tags if defined in your test run metadata. (See [Advanced CLI & Examples](advanced-cli-examples.md#project-tagging) for more information on Tags!)
-- **Duration Percentage Threshold** – Adjust the percentage threshold used to color-code durations (faster/slower runs).
-- **Select Versions** – Filter the displayed runs by their associated versions.
+In addition, the Overview offers a few **display settings** under **Settings > Overview**:
+
+- **Projects by Name** – Toggle whether to display the names of projects in the statistics.
+- **Projects by Tag** – Toggle whether to use custom project tags if defined in your test run metadata. (See [Advanced CLI & Examples](advanced-cli-examples.md#project-tagging) for more information on Tags!)
+- **Duration comparison percentage** – Adjust the percentage threshold used to color-code durations (faster/slower runs).
> These settings affect only the way the statistics are presented on the Overview page.
@@ -52,7 +53,8 @@ Global filters are applied to the entire dashboard, affecting all sections and g
- **All** (ticked by default) means no version filter is applied.
- **None** covers runs that have no version label set.
- A dot next to the label indicates the filter is active.
-- Use the search box inside the dropdown to quickly find a version by name.
+- Typing in the search box inside the dropdown selects every matching version and unselects the rest (e.g. `1.` selects all `1.x` versions in one go).
+- Click the **X** in the search box to clear it while keeping the current selection. Deleting characters one by one instead re-runs the matching on every keystroke.
#### 4. From Date / From Time
@@ -214,7 +216,7 @@ The **Tables** page allows for detailed inspection of raw test data and uses the
| Page | Filter support |
|------|---------------|
-| **Overview** | Display-only settings (name, tag, duration threshold, versions) |
+| **Overview** | Same global filters as Dashboard + display settings (name, tag, duration threshold) |
| **Dashboard** | Full global filters + section-specific filters + Filter Profiles |
| **Compare** | Run selection dropdowns + suite paths toggle |
| **Tables** | Same global filters as Dashboard |
diff --git a/docs/graphs-tables.md b/docs/graphs-tables.md
index f42a2c66..8806c118 100644
--- a/docs/graphs-tables.md
+++ b/docs/graphs-tables.md
@@ -32,8 +32,7 @@ The Overview page supports several display toggles (configured in [Settings - Ov
| **Projects by Name** | Groups runs by their project name. |
| **Projects by Tag** | Groups runs by `project_` tags instead of name. See [Project Tagging](/advanced-cli-examples#project-tagging). |
| **Prefixes** | Shows or hides the `project_` prefix text on tag-based names. |
-| **Percentage Filters** | Enables the duration percentage threshold control for color-coding. |
-| **Version Filters** | Enables per-project version filtering with checkbox selectors. |
+| **Duration comparison percentage** | Threshold used to color-code run durations (faster/slower than average). |
| **Sort Filters** | Enables sort controls on the Overview page. |
## Graph Switch Persistence
diff --git a/docs/settings.md b/docs/settings.md
index 014f6776..658c6ea9 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -95,7 +95,7 @@ This allows you to include or exclude specific libraries based on your dashboard
## Overview Settings (Overview Tab)
-The **Overview** tab controls which sections and filters are visible on the Overview page. These toggles let you tailor the Overview layout to your needs.
+The **Overview** tab controls which sections are visible on the Overview page and how projects are grouped and color-coded. These toggles let you tailor the Overview layout to your needs.
### Details
@@ -106,8 +106,7 @@ The **Overview** tab controls which sections and filters are visible on the Over
| **Projects by Name** | On | Group and display projects by their run name on the Overview. |
| **Projects by Tag** | Off | Group and display projects by custom `project_` tags. See [Project Tagging](/advanced-cli-examples#project-tagging). |
| **Display Prefixes** | On | Show the `project_` prefix text on tag-based project names. |
-| **Percentage Filters** | On | Show the duration percentage threshold filter for color-coding run durations. |
-| **Version Filters** | On | Show the version filter allowing per-project version selection. |
+| **Duration comparison percentage** | 20% | Threshold for color-coding run durations on all project bars: green if a run is at least X% faster than average, red if X% slower. |
| **Sort Filters** | On | Show the sort filter controls on the Overview. |
### Saving Overview Settings
diff --git a/robotframework_dashboard/css/components.css b/robotframework_dashboard/css/components.css
index ddec1228..4106097b 100644
--- a/robotframework_dashboard/css/components.css
+++ b/robotframework_dashboard/css/components.css
@@ -115,6 +115,37 @@
display: block;
}
+.filter-search-wrapper {
+ position: relative;
+}
+
+.filter-search-wrapper .form-control {
+ padding-right: 1.5rem;
+}
+
+.filter-search-clear {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ width: 1.5rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 0 0.15rem 0;
+ border: none;
+ background: none;
+ color: inherit;
+ opacity: 0.6;
+ font-size: 1.4rem;
+ line-height: 1;
+ cursor: pointer;
+}
+
+.filter-search-clear:hover {
+ opacity: 1;
+}
+
.filter-profile-bar {
border-bottom: 1px solid var(--bs-border-color);
}
diff --git a/robotframework_dashboard/js/eventlisteners.js b/robotframework_dashboard/js/eventlisteners.js
index 5664b6c4..e6c3e87e 100644
--- a/robotframework_dashboard/js/eventlisteners.js
+++ b/robotframework_dashboard/js/eventlisteners.js
@@ -28,7 +28,6 @@ import {
setup_project_versions_in_select_filter_buttons,
setup_suite_path_navigator,
setup_custom_filters_in_select_filter_buttons,
- update_overview_version_select_list,
setup_metadata_filter,
build_profile_from_checks,
apply_filter_profile,
@@ -59,6 +58,7 @@ import {
set_filter_show_current_project,
set_filter_show_current_version,
update_overview_filter_visibility,
+ update_duration_comparison_for_all_projects,
} from "./graph_creation/overview.js";
import { update_run_donut_total_graph, update_run_heatmap_graph } from "./graph_creation/run.js";
import {
@@ -158,6 +158,14 @@ function update_merge_result_preview() {
// ---- end of merge profiles helpers ----
+function update_filters_button_indicator() {
+ const indicator = document.getElementById("filtersActiveIndicator");
+ if (!indicator) return;
+ const anyActive = [...document.querySelectorAll("#filtersModal .version-selected-dot")]
+ .some(el => el.style.display !== "none");
+ indicator.style.display = anyActive ? "inline-block" : "none";
+}
+
// function to setup filter modal eventlisteners
function setup_filter_modal() {
// eventlistener to catch the closing of the filter modal
@@ -317,6 +325,7 @@ function setup_filter_modal() {
add_alert(`Filter profile "${name}" applied`, "success");
update_profile_select_display();
populate_filter_profile_select();
+ update_filters_button_indicator();
}
}
});
@@ -402,13 +411,21 @@ function setup_filter_modal() {
add_alert(`Filter profile "${newName}" saved!`, "success");
bootstrap.Modal.getInstance(document.getElementById("mergeProfilesModal")).hide();
});
- // Listen for filter changes to update the profile display
+ // Show indicator dot when a specific run is selected in the dropdown
+ document.getElementById("runs").addEventListener("change", function () {
+ const indicator = document.getElementById("filterRunSelectedIndicator");
+ if (indicator) indicator.style.display = this.value !== "All" ? "inline-block" : "none";
+ update_filters_button_indicator();
+ });
+ // Listen for filter changes to update the profile display and the navbar dot
const filterModal = document.getElementById("filtersModal");
filterModal.addEventListener("change", function () {
update_profile_select_display();
+ update_filters_button_indicator();
});
filterModal.addEventListener("input", function () {
update_profile_select_display();
+ update_filters_button_indicator();
});
}
@@ -500,11 +517,15 @@ function setup_settings_modal() {
{ key: "show.convertTimezone", elementId: "toggleTimezone" },
{ key: "show.suitesSelectionInSuiteStats", elementId: "toggleSuitesSelectionInSuiteStats", datatype: "string", event: "change" },
{ key: "show.suitesSelectionInTestStats", elementId: "toggleSuitesSelectionInTestStats", datatype: "string", event: "change" },
+ { key: "show.overviewDurationPercentage", elementId: "overviewDurationPercentage", datatype: "number", event: "change" },
].forEach(def => {
const handler = create_toggle_handler(def);
handler(true);
document.getElementById(def.elementId).addEventListener(def.event || "click", () => handler());
});
+ document.getElementById("overviewDurationPercentage").addEventListener("change", () => {
+ if (settings.menu.overview) update_duration_comparison_for_all_projects();
+ });
// Re-populate the date filter pickers when either timezone toggle changes,
// since the displayed timestamps change and the defaults need to match.
document.getElementById("toggleTimezone").addEventListener("click", () => setup_lowest_highest_dates());
@@ -757,8 +778,6 @@ function setup_sections_filters() {
update_switch_local_storage("switch.runName", settings.switch.runName, true);
update_switch_local_storage("switch.totalStats", settings.switch.totalStats, true);
update_switch_local_storage("switch.latestRuns", settings.switch.latestRuns, true);
- update_switch_local_storage("switch.percentageFilters", settings.switch.percentageFilters, true);
- update_switch_local_storage("switch.versionFilters", settings.switch.versionFilters, true);
update_switch_local_storage("switch.sortFilters", settings.switch.sortFilters, true);
document.getElementById("switchRunTags").addEventListener("click", function () {
settings.switch.runTags = !settings.switch.runTags
@@ -773,7 +792,6 @@ function setup_sections_filters() {
update_overview_total_heading();
update_overview_sections_visibility();
// update all tagged bars
- update_overview_version_select_list();
update_projectbar_visibility();
hide_loading_overlay();
});
@@ -792,7 +810,6 @@ function setup_sections_filters() {
update_overview_total_heading();
update_overview_sections_visibility();
// update all named project bars
- update_overview_version_select_list();
update_projectbar_visibility();
hide_loading_overlay();
});
@@ -808,21 +825,11 @@ function setup_sections_filters() {
update_switch_local_storage("switch.totalStats", settings.switch.totalStats);
update_overview_sections_visibility();
});
- document.getElementById("switchPercentageFilters").addEventListener("click", function () {
- settings.switch.percentageFilters = !settings.switch.percentageFilters
- update_switch_local_storage("switch.percentageFilters", settings.switch.percentageFilters);
- update_overview_filter_visibility();
- });
document.getElementById("switchSortFilters").addEventListener("click", function () {
settings.switch.sortFilters = !settings.switch.sortFilters
update_switch_local_storage("switch.sortFilters", settings.switch.sortFilters);
update_overview_filter_visibility();
});
- document.getElementById("switchVersionFilters").addEventListener("click", function () {
- settings.switch.versionFilters = !settings.switch.versionFilters
- update_switch_local_storage("switch.versionFilters", settings.switch.versionFilters);
- update_overview_filter_visibility();
- });
document.getElementById("suiteSelectSuites").addEventListener("change", () => {
const mostGraphIds = settings.switch.sectionFiltersApplySuite
? ["suiteMostFailedGraph", "suiteMostTimeConsumingGraph"]
diff --git a/robotframework_dashboard/js/filter.js b/robotframework_dashboard/js/filter.js
index 79b1a102..f93c4c87 100644
--- a/robotframework_dashboard/js/filter.js
+++ b/robotframework_dashboard/js/filter.js
@@ -705,7 +705,7 @@ function setup_project_versions_in_select_filter_buttons() {
allVersionsCheckBox.checked = true;
const filterVersionSelectedIndicatorId = "filterVersionSelectedIndicator";
setup_filter_active_indicator(allVersionsCheckBox, filterVersionSelectedIndicatorId);
- setup_filter_checkbox_subfilter("projectVersionCheckBoxes");
+ setup_filter_checkbox_subfilter("projectVersionCheckBoxes", true);
setup_filter_checkbox_handler_listeners(projectVersionList, allVersionsCheckBox, filterVersionSelectedIndicatorId);
}
@@ -903,69 +903,6 @@ function unselect_checkboxes(checkBoxesToUnselect) {
}
}
-function handle_overview_latest_version_selection(overviewVersionSelectorList, latestRunByProject) {
- show_loading_overlay();
- requestAnimationFrame(() => {
- requestAnimationFrame(() => {
- const selectedOptions = Array.from(
- overviewVersionSelectorList.querySelectorAll("input:checked")
- ).map(inputElement => inputElement.value);
- if (selectedOptions.includes("All")) {
- create_overview_latest_graphs(latestRunByProject);
- } else {
- const filteredLatestRunByProject = Object.fromEntries(
- Object.entries(latestRunByProject)
- .filter(([projectName, run]) => selectedOptions.includes(run.project_version ?? "None"))
- );
- create_overview_latest_graphs(filteredLatestRunByProject);
- }
- update_overview_latest_heading();
- hide_loading_overlay();
- });
- });
-}
-
-// this function updates the version select list in the latest runs bar
-function update_overview_version_select_list() {
- const overviewLatestVersionSelectorList = document.getElementById("overviewLatestVersionSelectorList");
- if (overviewLatestVersionSelectorList) {
- overviewLatestVersionSelectorList.innerHTML = '';
- if (settings.switch.runName || settings.switch.runTags) {
- const filteredLatestRunByProject = {};
- settings.switch.runName && Object.assign(filteredLatestRunByProject, latestRunByProjectName);
- settings.switch.runTags && Object.assign(filteredLatestRunByProject, latestRunByProjectTag);
- const runAmountByVersion = {};
- for (const run of Object.values(filteredLatestRunByProject)) {
- const projectVersion = run.project_version ?? "None";
- runAmountByVersion[projectVersion] ??= 0;
- runAmountByVersion[projectVersion]++;
- }
- const allVersionAmountInFilter = Object.keys(runAmountByVersion).length;
- const versionFilterListItemAllHtml = generate_version_filter_list_item_html("All", "overviewLatest", "checked", allVersionAmountInFilter, "version");
- const specificVersionFilterListItemHtml = Object.keys(runAmountByVersion)
- .sort()
- .reverse()
- .map(version => {
- return generate_version_filter_list_item_html(
- version,
- "overviewLatest",
- "", //not checked
- runAmountByVersion[version],
- "run"
- )
- }).join('');
- overviewLatestVersionSelectorList.innerHTML = versionFilterListItemAllHtml + specificVersionFilterListItemHtml;
- const allCheckBox = document.getElementById("overviewLatestVersionFilterListItemAllInput");
- setup_filter_checkbox_handler_listeners(
- overviewLatestVersionSelectorList,
- allCheckBox,
- "overviewLatestVersionSelectedIndicator",
- () => { handle_overview_latest_version_selection(overviewLatestVersionSelectorList, filteredLatestRunByProject) }
- );
- }
- }
-}
-
// for runTag/version filter popup and overview
function setup_filter_checkbox_handler_listeners(
checkBoxContainerElement,
@@ -1002,39 +939,36 @@ function setup_filter_checkbox_handler_listeners(
}
}
-function setup_filter_checkbox_subfilter(parentElementId) {
+// autoSelectMatches: while the search box has text, check every matching checkbox and
+// uncheck the rest (e.g. typing "1." selects every 1.x version in one go) instead of only
+// narrowing which rows are visible. Clearing the search box leaves the selection as-is.
+// An optional ".filter-search-clear" button next to the search box empties it in one step
+// (unlike deleting characters one by one, which re-runs the auto-select on every keystroke).
+function setup_filter_checkbox_subfilter(parentElementId, autoSelectMatches = false) {
const container = document.getElementById(parentElementId);
const searchBar = container.querySelector("input.form-control");
+ const clearButton = container.querySelector(".filter-search-clear");
const checkBoxRows = container.querySelectorAll("li.list-group-item-action");
- searchBar.addEventListener("input", () => {
+ const apply_search = () => {
const filterText = searchBar.value.toLowerCase();
+ if (clearButton) clearButton.hidden = !filterText;
checkBoxRows.forEach(row => {
const checkbox = row.querySelector("input.form-check-input");
const rowValue = checkbox.value.toLowerCase();
- const shouldHide = !rowValue.includes(filterText);
- row.classList.toggle("d-none", shouldHide);
+ const matches = rowValue.includes(filterText);
+ row.classList.toggle("d-none", !matches);
+ if (autoSelectMatches && filterText && checkbox.value !== "All" && checkbox.checked !== matches) {
+ checkbox.checked = matches;
+ checkbox.dispatchEvent(new Event('change', { bubbles: true }));
+ }
});
- });
-}
-
-function generate_version_filter_list_item_html(version, projectName, checked, amount, amountType) {
- function versionFilterListItemInput(version, id, checked) {
- return ``
};
- function versionFilterListItemLabel(forId, version, amount, amountType, pluralPostfix) {
- return ``
- };
- const listItemId = `${projectName}VersionFilterListItem`;
- const listItemInputId = `${listItemId}${version}Input`;
- const pluralPostfix = amount === 1 ? '' : 's';
- return `
-