Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions robotframework_dashboard/css/styling.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
#overview,
#dashboard,
#unified,
#compare,
#tables {
margin-bottom: 35vh;
}
/* LIGHT MODE STYLING */
body {
background-color: #eee;
Expand Down
74 changes: 71 additions & 3 deletions robotframework_dashboard/js/eventlisteners.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
import {
create_overview_statistics_graphs,
update_overview_statistics_heading,
update_projectbar_visibility,
set_filter_show_current_project,
set_filter_show_current_version,
} from "./graph_creation/overview.js";
Expand Down Expand Up @@ -722,8 +723,13 @@ function setup_graph_view_buttons() {
}

// function to setup collapse buttons and icons
function setup_collapsables(elementToSearch = document) {
elementToSearch.querySelectorAll(".collapse-icon").forEach(icon => {
function setup_collapsables() {
document.querySelectorAll(".collapse-icon").forEach(origIcon => {
// Replace the element with a clone to remove existing listeners
// required to readd collapsables for overview project sections
const icon = origIcon.cloneNode(true);
origIcon.replaceWith(icon);

const sectionId = icon.id.replace("collapse", "");
const update_icon = () => {
const section = document.getElementById(sectionId);
Expand All @@ -748,11 +754,73 @@ function attach_run_card_version_listener(versionElement, projectName, projectVe
});
}

function setup_overview_order_filters() {
const parseProjectId = (selectId) => selectId.replace(/SectionOrder$/i, "");
const parseRunStatsFromCard = (cardEl) => {
const text = cardEl.innerText || "";
const runMatch = text.match(/#\s*(\d+)/); // e.g., "#8"
const passedMatch = text.match(/Passed:\s*(\d+)/i);
const failedMatch = text.match(/Failed:\s*(\d+)/i);
const skippedMatch = text.match(/Skipped:\s*(\d+)/i);
return {
runNumber: runMatch ? parseInt(runMatch[1]) : 0,
passed: passedMatch ? parseInt(passedMatch[1]) : 0,
failed: failedMatch ? parseInt(failedMatch[1]) : 0,
skipped: skippedMatch ? parseInt(skippedMatch[1]) : 0,
};
};

const reorderProjectCards = (projectId, order) => {
// Determine correct container for both overview and project sections
const containerId = `${projectId}RunCardsContainer`;
const container = document.getElementById(containerId);
if (!container) return; // guard against missing containers
const cards = Array.from(container.querySelectorAll('.overview-card'));
if (cards.length === 0) return;
const enriched = cards.map(card => ({ el: card, stats: parseRunStatsFromCard(card) }));

const cmpDesc = (a, b, key) => (b.stats[key] - a.stats[key]);
const cmpAsc = (a, b, key) => (a.stats[key] - b.stats[key]);

if (order === "oldest" || order.toLowerCase() === "oldest run") {
enriched.sort((a, b) => cmpAsc(a, b, 'runNumber'));
} else if (order === "most failed") {
enriched.sort((a, b) => cmpDesc(a, b, 'failed'));
} else if (order === "most skipped") {
enriched.sort((a, b) => cmpDesc(a, b, 'skipped'));
} else if (order === "most passed") {
enriched.sort((a, b) => cmpDesc(a, b, 'passed'));
} else {
enriched.sort((a, b) => cmpDesc(a, b, 'runNumber'));
}
const fragment = document.createDocumentFragment();
enriched.forEach(item => fragment.appendChild(item.el));
container.innerHTML = '';
container.appendChild(fragment);
};

document.querySelectorAll('.section-order-filter').forEach(select => {
const selectId = select.id;
if (selectId === "overviewStatisticsSectionOrder") {
select.addEventListener('change', (e) => {
create_overview_statistics_graphs();
});
} else {
const projectId = parseProjectId(selectId);
select.addEventListener('change', (e) => {
const order = (e.target.value || '').toLowerCase();
reorderProjectCards(projectId, order);
});
}
});
}

export {
setup_filter_modal,
setup_settings_modal,
setup_sections_filters,
setup_graph_view_buttons,
setup_collapsables,
attach_run_card_version_listener
attach_run_card_version_listener,
setup_overview_order_filters
};
2 changes: 1 addition & 1 deletion robotframework_dashboard/js/graph_creation/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import {
function setup_dashboard_graphs() {
if (settings.menu.overview) {
create_overview_statistics_graphs();
update_donut_charts("projectOverviewData");
update_donut_charts();
} else if (settings.menu.dashboard) {
create_run_statistics_graph();
create_run_donut_graph();
Expand Down
63 changes: 48 additions & 15 deletions robotframework_dashboard/js/graph_creation/overview.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
import { update_menu } from '../menu.js';
import {
setup_collapsables,
setup_filter_checkbox_handler_listeners,
attach_run_card_version_listener
} from '../eventlisteners.js';
import { clockDarkSVG, clockLightSVG, arrowRight } from '../variables/svg.js';
Expand All @@ -32,10 +31,11 @@ import {
areGroupedProjectsPrepared
} from '../variables/globals.js';
import { runs, use_logs } from '../variables/data.js';
import { clear_all_filters, update_filter_active_indicator } from '../filter.js';
import { clear_all_filters, update_filter_active_indicator, setup_filter_checkbox_handler_listeners } from '../filter.js';

// function to create overview statistics blocks in the overview section
function create_overview_statistics_graphs(preFilteredRuns = null) {
const order = document.getElementById("overviewStatisticsSectionOrder").value;
const overviewCardsContainer = document.getElementById("overviewRunCardsContainer");
overviewCardsContainer.innerHTML = '';
const allProjects = { ...projects_by_name, ...projects_by_tag };
Expand All @@ -44,13 +44,32 @@ function create_overview_statistics_graphs(preFilteredRuns = null) {
const durations = projectRuns.map(r => r.elapsed_s);
durationsByProject[projectName] = durations;
}
const latestRunByProject = {};
let latestRunByProject = {};
if (!preFilteredRuns) {
settings.switch.runName && Object.assign(latestRunByProject, latestRunByProjectName);
settings.switch.runTags && Object.assign(latestRunByProject, latestRunByProjectTag);
} else { // if called by version filter listener
Object.assign(latestRunByProject, preFilteredRuns);
}
// default order by newest (keep current insertion order)
if (order === 'oldest') {
// Reverse current order while preserving the same key->value pairs
latestRunByProject = Object.fromEntries(
Object.entries(latestRunByProject).reverse()
);
} else if (order === 'most failed') {
latestRunByProject = Object.fromEntries(
Object.entries(latestRunByProject).sort(([, runA], [, runB]) => runB.failed - runA.failed)
);
} else if (order === 'most skipped') {
latestRunByProject = Object.fromEntries(
Object.entries(latestRunByProject).sort(([, runA], [, runB]) => runB.skipped - runA.skipped)
);
} else if (order === 'most passed') {
latestRunByProject = Object.fromEntries(
Object.entries(latestRunByProject).sort(([, runA], [, runB]) => runB.passed - runA.passed)
);
}
for (const [projectName, latestRun] of Object.entries(latestRunByProject)) {
const projectRuns = allProjects[projectName];
const totalRunsAmount = projectRuns.length;
Expand All @@ -71,9 +90,8 @@ function create_overview_statistics_graphs(preFilteredRuns = null) {
}
}

function update_donut_charts(scopeElement) {
const donutContainer = document.getElementById(scopeElement);
donutContainer.querySelectorAll(".overview-canvas").forEach(canvas => {
function update_donut_charts() {
document.querySelectorAll(".overview-canvas").forEach(canvas => {
const chart = canvas.querySelector("canvas").chartInstance;
if (chart) chart.update();
});
Expand Down Expand Up @@ -123,15 +141,13 @@ function prepare_projects_grouped_data() {
}

function create_project_overview() {
const projectOverviewData = document.getElementById("projectOverviewData");
projectOverviewData.innerHTML = "";
const projectData = { ...projects_by_name, ...projects_by_tag };
// create run cards for each project
Object.keys(projectData).sort().forEach(projectName => {
create_project_cards_container(projectName, projectData[projectName]);
});
// setup collapsables specifically for overview project sections
setup_collapsables(projectOverviewData);
setup_collapsables();
// set project bar visibility based on switch settings
update_projectbar_visibility();
}
Expand Down Expand Up @@ -164,7 +180,7 @@ function create_project_bar(projectName, projectRuns, totalRunsAmount, passRate)
- Clicking on the run card applies a filter for that project and switches to dashboard
- Clicking on the version element within the run card additionally applies a filter for that version`;
const projectCard = `
<div class="card mb-3" id="${projectName}Card">
<div class="card mb-3 overview-bar overview-project-card" id="${projectName}Section">
<div class="card-header">
<div class="row">
<div class="col-auto align-self-center">
Expand All @@ -187,7 +203,7 @@ function create_project_bar(projectName, projectRuns, totalRunsAmount, passRate)
</select>
</div>
</div>
<div class="col-auto">
<div class="col-auto me-2">
<div class="btn-group">
<div id="${projectName}VersionFilterDropDown" class="dropdown" >
<button class="btn btn-sm btn-outline-dark dropdown-toggle"
Expand All @@ -205,9 +221,27 @@ function create_project_bar(projectName, projectRuns, totalRunsAmount, passRate)
<input type="text" class="form-control form-control-sm" id="${projectName}VersionFilterSearch" placeholder="Version Filter...">
</div>
</div>
<div class="col-auto me-1">
<div class="btn-group">
<label class="form-label mb-0" for="${projectName}SectionOrder">Sort</label>
</div>
<div class="btn-group">
<select class="form-select form-select-sm section-order-filter" id="${projectName}SectionOrder">
<option value="newest" selected>Most Recent</option>
<option value="oldest">Oldest</option>
<option value="most failed">Most Failed</option>
<option value="most skipped">Most Skipped</option>
<option value="most passed">Most Passed</option>
</select>
</div>
</div>
<div class="col-auto">
<a type="button" class="information information-icon ms-2" id="${projectName}Information" data-title="${projectInformation}"></a>
</div>
<div class="col-auto ms-auto">
<a class="move-up-section information" id="${projectName}SectionMoveUp" hidden></a>
<a class="move-down-section information" id="${projectName}SectionMoveDown" hidden></a>
</div>
</div>
</div>
</div>
Expand All @@ -216,7 +250,8 @@ function create_project_bar(projectName, projectRuns, totalRunsAmount, passRate)
</div>
</div>
`;
projectOverviewData.appendChild(document.createRange().createContextualFragment(projectCard));
const overview = document.getElementById("overview")
overview.appendChild(document.createRange().createContextualFragment(projectCard));
// percentage selector
const projectPercentageSelector = document.getElementById(`${projectName}DurationPercentage`);
projectPercentageSelector.addEventListener('change', () => {
Expand Down Expand Up @@ -374,9 +409,7 @@ function create_overview_run_donut(run, chartElementPostfix, projectName) {

// hide project bars based on switch config
function update_projectbar_visibility() {
const container = document.getElementById("projectOverviewData");
if (!container) return;
const bars = container.querySelectorAll('[id$="Card"]');
const bars = document.querySelectorAll('.overview-project-card');
const tagged = [];
const untagged = [];
for (const el of bars) {
Expand Down
Loading