profoundjs
Version:
Profound.js Framework and Server
216 lines (183 loc) • 7.04 kB
JavaScript
;
document.addEventListener("DOMContentLoaded", () => {
if (!sessionStorage["atrium-token"]) {
const currentUrl = window.location.pathname + window.location.search;
window.location.href = "/auto-testing/login?redirect=" + encodeURIComponent(currentUrl);
return;
}
const reportContainer = document.getElementById("report-container");
const refreshButton = document.getElementById("refresh-button");
const urlParams = new URLSearchParams(window.location.search);
const runIds = urlParams.getAll("runId");
if (runIds.length < 2) {
reportContainer.innerHTML = '<div class="error-message">Please provide at least 2 run IDs as query parameters (e.g. ?runId=A&runId=B).</div>';
return;
}
function formatTime(ms) {
if (ms == null || ms === "" || ms === "0" || ms === 0) return "--";
const num = parseFloat(ms);
if (isNaN(num)) return "--";
if (num >= 1000) {
return (num / 1000).toFixed(2) + "s";
}
return Math.round(num) + "ms";
}
function formatDiff(diff) {
if (diff === 0) return "";
const sign = diff > 0 ? "+" : "";
if (Math.abs(diff) >= 1000) {
return sign + (diff / 1000).toFixed(2) + "s";
}
return sign + Math.round(diff) + "ms";
}
function fetchPerformanceData() {
reportContainer.innerHTML = '<div class="spinner"></div>';
const headers = {
"Content-Type": "application/json"
};
if (sessionStorage["atrium-token"]) {
headers["Authorization"] = "Basic " + sessionStorage["atrium-token"];
}
fetch("/auto-testing/performance-data", {
method: "POST",
headers,
body: JSON.stringify({ runIds: runIds })
})
.then(res => res.json())
.then(data => {
if (data.error) {
reportContainer.innerHTML = '<div class="error-message">' + data.error + "</div>";
return;
}
renderReport(data);
})
.catch(err => {
console.error("Error fetching performance data:", err);
reportContainer.innerHTML = '<div class="error-message">Unable to fetch performance data. Please check your connection.</div>';
});
}
function formatRunTimestamp(arstart) {
if (!arstart) return "";
const arstartParts = arstart.split("-");
const arstartTimeParts = arstartParts[3].split(".");
const d = new Date(`${arstartParts[0]}-${arstartParts[1]}-${arstartParts[2]} ${arstartTimeParts[0]}:${arstartTimeParts[1]}:${arstartTimeParts[2]}`);
if (isNaN(d.getTime())) return "";
return d.toLocaleString();
}
function renderReport(data) {
const runs = data.runs;
const orderedRunIds = data.runIds.filter(id => runs[id]);
if (orderedRunIds.length === 0) {
reportContainer.innerHTML = '<div class="error-message">No run data found.</div>';
return;
}
// Get step count from the first run
const firstRun = runs[orderedRunIds[0]];
const stepCount = firstRun.steps.length;
// Build header
let html = '<table id="perf-table">';
html += "<thead><tr>";
html += "<th>Step</th>";
html += "<th>Step Name</th>";
for (const runId of orderedRunIds) {
const run = runs[runId];
const timestamp = formatRunTimestamp(run.arstart);
html += '<th class="run-header">' + (run.environment || "N/A");
if (timestamp) {
html += '<br><span class="run-timestamp">' + timestamp + "</span>";
}
html += '<br><span class="run-id">' + runId + "</span>";
html += "</th>";
}
html += "</tr></thead>";
html += "<tbody>";
// Collect totals per run
const totals = {};
for (const runId of orderedRunIds) {
totals[runId] = 0;
}
// Build rows for each step
for (let i = 0; i < stepCount; i++) {
const stepName = firstRun.steps[i].name || ("Step " + firstRun.steps[i].seq);
// Collect times for this step across all runs
const times = {};
for (const runId of orderedRunIds) {
const step = runs[runId].steps[i];
const time = step ? parseFloat(step.runtime) : NaN;
times[runId] = isNaN(time) ? null : time;
if (times[runId] != null && times[runId] > 0) {
totals[runId] += times[runId];
}
}
// Find min and max among valid times (> 0)
const validTimes = orderedRunIds
.map(id => times[id])
.filter(t => t != null && t > 0);
const minTime = validTimes.length > 0 ? Math.min(...validTimes) : null;
const maxTime = validTimes.length > 0 ? Math.max(...validTimes) : null;
// Compute the reference (fastest) time for diffs
const refTime = minTime;
html += "<tr>";
html += "<td>" + (i + 1) + "</td>";
html += '<td class="step-name">' + escapeHtml(stepName) + "</td>";
for (const runId of orderedRunIds) {
const t = times[runId];
if (t == null || t === 0) {
html += '<td class="time-cell">--</td>';
continue;
}
let cssClass = "time-cell";
// Only color-code if there are at least 2 valid times and they differ
if (validTimes.length >= 2 && minTime !== maxTime) {
if (t === minTime) cssClass += " fastest";
else if (t === maxTime) cssClass += " slowest";
}
let diffHtml = "";
if (refTime != null && refTime > 0 && t !== refTime) {
const diff = t - refTime;
diffHtml = ' <span class="time-diff">(' + formatDiff(diff) + ")</span>";
}
html += '<td class="' + cssClass + '">' + formatTime(t) + diffHtml + "</td>";
}
html += "</tr>";
}
// Total row
const totalValues = orderedRunIds
.map(id => totals[id])
.filter(t => t > 0);
const minTotal = totalValues.length > 0 ? Math.min(...totalValues) : null;
const maxTotal = totalValues.length > 0 ? Math.max(...totalValues) : null;
const refTotal = minTotal;
html += '<tr class="total-row">';
html += "<td></td>";
html += '<td class="step-name">Total</td>';
for (const runId of orderedRunIds) {
const t = totals[runId];
if (t === 0) {
html += '<td class="time-cell">--</td>';
continue;
}
let cssClass = "time-cell";
if (totalValues.length >= 2 && minTotal !== maxTotal) {
if (t === minTotal) cssClass += " fastest";
else if (t === maxTotal) cssClass += " slowest";
}
let diffHtml = "";
if (refTotal != null && refTotal > 0 && t !== refTotal) {
const diff = t - refTotal;
diffHtml = ' <span class="time-diff">(' + formatDiff(diff) + ")</span>";
}
html += '<td class="' + cssClass + '">' + formatTime(t) + diffHtml + "</td>";
}
html += "</tr>";
html += "</tbody></table>";
reportContainer.innerHTML = html;
}
function escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
refreshButton.addEventListener("click", fetchPerformanceData);
fetchPerformanceData();
});