diff --git a/robotframework_dashboard/js/eventlisteners.js b/robotframework_dashboard/js/eventlisteners.js index 5664b6c4..aebb5e11 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,8 @@ import { set_filter_show_current_project, set_filter_show_current_version, update_overview_filter_visibility, + update_duration_comparison_for_all_projects, + update_overview_version_select_list, } from "./graph_creation/overview.js"; import { update_run_donut_total_graph, update_run_heatmap_graph } from "./graph_creation/run.js"; import { @@ -158,6 +159,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 +326,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 +412,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 +518,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,7 +779,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 () { @@ -808,9 +829,9 @@ 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); + 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("switchSortFilters").addEventListener("click", function () { @@ -818,11 +839,6 @@ function setup_sections_filters() { 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 c16d776f..c5cd9469 100644 --- a/robotframework_dashboard/js/filter.js +++ b/robotframework_dashboard/js/filter.js @@ -902,69 +902,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, @@ -1044,6 +981,10 @@ function clear_all_filters() { document.getElementById("amount").value = filteredAmountDefault; document.getElementById("metadata").value = "All"; setup_lowest_highest_dates(); + const runsIndicator = document.getElementById("filterRunSelectedIndicator"); + if (runsIndicator) runsIndicator.style.display = "none"; + const filtersActiveIndicator = document.getElementById("filtersActiveIndicator"); + if (filtersActiveIndicator) filtersActiveIndicator.style.display = "none"; } function clear_suite_path_filter() { @@ -1383,6 +1324,11 @@ function apply_filter_profile(profile, name) { if (modeEl) modeEl.value = mode; } } + const runsIndicator = document.getElementById("filterRunSelectedIndicator"); + const runsVal = document.getElementById("runs")?.value; + if (runsIndicator) + runsIndicator.style.display = + runsVal && runsVal !== "All" ? "inline-block" : "none"; } // Load filter profiles from settings (cumulative — never deletes existing ones) @@ -1608,7 +1554,6 @@ export { setup_suite_path_navigator, setup_custom_filters_in_select_filter_buttons, setup_filter_checkbox_handler_listeners, - update_overview_version_select_list, clear_all_filters, set_filter_show_current_version, generate_version_filter_list_item_html, diff --git a/robotframework_dashboard/js/graph_creation/all.js b/robotframework_dashboard/js/graph_creation/all.js index 88297dbd..e0ab7c57 100644 --- a/robotframework_dashboard/js/graph_creation/all.js +++ b/robotframework_dashboard/js/graph_creation/all.js @@ -1,9 +1,10 @@ import { settings } from "../variables/settings.js"; -import { +import { create_overview_latest_graphs, create_overview_total_graphs, - update_donut_charts + update_donut_charts, + update_grouped_data_for_filter } from "./overview.js"; import { create_run_statistics_graph, @@ -102,6 +103,7 @@ import { update_custom_stat_widgets } from "../statwidgets.js"; // function that creates all graphs from scratch - used on first load of each tab function create_dashboard_graphs() { if (settings.menu.overview) { + update_grouped_data_for_filter(); create_overview_latest_graphs(); create_overview_total_graphs(); update_donut_charts(); @@ -156,6 +158,7 @@ function create_dashboard_graphs() { // each update function falls back to create if the chart doesn't exist yet function update_dashboard_graphs() { if (settings.menu.overview) { + update_grouped_data_for_filter(); create_overview_latest_graphs(); create_overview_total_graphs(); update_donut_charts(); diff --git a/robotframework_dashboard/js/graph_creation/overview.js b/robotframework_dashboard/js/graph_creation/overview.js index dc0d6a84..54ae1b0a 100644 --- a/robotframework_dashboard/js/graph_creation/overview.js +++ b/robotframework_dashboard/js/graph_creation/overview.js @@ -23,7 +23,6 @@ import { } from '../variables/chartconfig.js'; import { settings } from '../variables/settings.js'; import { - DEFAULT_DURATION_PERCENTAGE, projects_by_tag, projects_by_name, selectedRunSetting, @@ -31,7 +30,8 @@ import { versionsByProject, latestRunByProjectName, latestRunByProjectTag, - areGroupedProjectsPrepared + areGroupedProjectsPrepared, + filteredRuns, } from '../variables/globals.js'; import { runs, use_logs } from '../variables/data.js'; import { @@ -43,7 +43,7 @@ import { // Data prep/aggregation function prepare_projects_grouped_data() { - for (const run of runs) { + for (const run of filteredRuns) { const tags = run.tags.split(","); const project_tags = tags.filter(tag => tag.toLowerCase().startsWith("project_")); for (const project of project_tags) { @@ -60,11 +60,11 @@ function prepare_projects_grouped_data() { areGroupedProjectsPrepared = true; } -// versionByProject = {projectName:{version:amount}} +// versionsByProject = {projectName: {version: amount}} function prepare_projects_version_counts_map(projects) { - for (const [project, runs] of Object.entries(projects)) { + for (const [project, projectRuns] of Object.entries(projects)) { const versionCounts = {}; - for (const run of runs) { + for (const run of projectRuns) { const projectVersion = run.project_version ?? "None"; versionCounts[projectVersion] = (versionCounts[projectVersion] || 0) + 1; } @@ -154,12 +154,15 @@ function generate_overview_card_html( compares = ''; } // for project bars - const versionsForProject = Object.keys(versionsByProject[projectName]); - const projectHasVersions = !(versionsForProject.length === 1 && versionsForProject[0] === "None"); + const runsForProject = projects_by_name[projectName] ?? projects_by_tag[projectName] ?? []; + const projectHasVersions = runsForProject.some(r => r.project_version != null && r.project_version !== "None"); // for overview statistics // Preserve the original project name (used for logic like tag-detection), // but compute a display name that omits the 'project_' prefix when prefixes are hidden. const originalProjectName = projectName; + const projectEverHasVersions = runs + .filter(r => r.name === originalProjectName) + .some(r => r.project_version != null && r.project_version !== "None"); const displayProjectName = settings.show.prefixes ? projectName : projectName.replace(/^project_/, ''); projectName = displayProjectName; let cardTitle = ` @@ -172,7 +175,7 @@ function generate_overview_card_html( cardTitle = `
${stats[5]}, Version: ${normalizedProjectVersion}
`; - } else if (projectHasVersions) { + } else if (projectHasVersions || projectEverHasVersions) { // Non-tagged projects with versions: interactive version title cardTitle = `
- `` - ).join(''); +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(([, run]) => selectedOptions.includes(run.project_version ?? "None")) + ); + create_overview_latest_graphs(filteredLatestRunByProject); + } + update_overview_latest_heading(); + hide_loading_overlay(); + }); + }); +} + +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 => generate_version_filter_list_item_html(version, "overviewLatest", "", 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) } + ); + } + } +} +function create_overview_latest_runs_section() { const filtersHtml = ` -
-
- -
-
- -
-
@@ -347,29 +389,13 @@ function create_overview_latest_runs_section() { create_overview_latest_graphs(); update_overview_latest_heading(); - - // Setup event listeners for filters - const percentageSelector = document.getElementById("overviewLatestDurationPercentage"); - if (percentageSelector) { - percentageSelector.addEventListener('change', () => { - show_loading_overlay(); - requestAnimationFrame(() => { - requestAnimationFrame(() => { - create_overview_latest_graphs(); - hide_loading_overlay(); - }); - }); - }); - } + update_overview_version_select_list(); const versionFilterSearch = document.getElementById("overviewLatestVersionFilterSearch"); if (versionFilterSearch) { - const handle_version_filter_input = () => { - apply_overview_latest_version_text_filter(); - }; const maxDelay = 50; - const delayScaledByRunAmount = Math.min(runs.length / 100, maxDelay); - versionFilterSearch.addEventListener('input', debounce(handle_version_filter_input, delayScaledByRunAmount)); + const delayScaledByRunAmount = Math.min(filteredRuns.length / 100, maxDelay); + versionFilterSearch.addEventListener('input', debounce(apply_overview_latest_version_text_filter, delayScaledByRunAmount)); } } @@ -388,7 +414,7 @@ function create_overview_total_stats_section() { // create project bar (the collapsables below overview statistic) in overview function create_project_bar(projectName, projectRuns, totalRunsAmount, passRate) { const projectVersions = new Set( - Object.keys(versionsByProject[projectName]) + Object.keys(versionsByProject[projectName] || {}) .sort() .reverse() ); @@ -401,9 +427,6 @@ function create_project_bar(projectName, projectRuns, totalRunsAmount, passRate) return generate_version_filter_list_item_html(version, projectName, "", runAmount, "run"); }) .join(''); - const percentageSelectHtml = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100].map(val => - `` - ).join(''); // added here instead of adding to informationMap, since the dynamic IDs don't work with the map const displayProjectName = (!settings.show.prefixes && projectName.startsWith('project_')) ? projectName.replace(/^project_/, '') @@ -430,27 +453,17 @@ See Settings > Overview for more options.`;
Total Runs: ${totalRunsAmount} | Passed Runs: ${passRate}%
-
-
- -
-
- -
-
-