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
10 changes: 6 additions & 4 deletions __tests__/integration/ai-review.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1703,16 +1703,18 @@ describe('ai-review', () => {
storeReview({ repo: 'owner/repo', prNumber: 1, status: 'open' });
storeReview({ repo: 'owner/repo', prNumber: 2, status: 'open' });
updateReviewStatus('owner/repo', 1, 'merged');
expect(getReviews()[0].status).toBe('merged');
expect(getReviews()[1].status).toBe('open');
// getReviews() returns newest-first, so [0] is prNumber:2, [1] is prNumber:1
expect(getReviews()[0].status).toBe('open');
expect(getReviews()[1].status).toBe('merged');
});

it('does not update reviews for different repo', () => {
storeReview({ repo: 'owner/repo-a', prNumber: 1, status: 'open' });
storeReview({ repo: 'owner/repo-b', prNumber: 1, status: 'open' });
updateReviewStatus('owner/repo-a', 1, 'closed');
expect(getReviews()[0].status).toBe('closed');
expect(getReviews()[1].status).toBe('open');
// getReviews() returns newest-first, so [0] is repo-b, [1] is repo-a
expect(getReviews()[0].status).toBe('open');
expect(getReviews()[1].status).toBe('closed');
});

it('returns 0 when no reviews match', () => {
Expand Down
25 changes: 23 additions & 2 deletions src/ai-review.js
Original file line number Diff line number Diff line change
Expand Up @@ -427,8 +427,28 @@ function storeReview(entry) {
saveReviews();
}

function getReviews() {
return _reviews;
function getReviews(opts = {}) {
const { limit, offset = 0 } = typeof opts === 'object' ? opts : {};
// Newest-first
const sorted = [..._reviews].reverse();
if (limit !== undefined && limit !== null) return sorted.slice(offset, offset + limit);
return sorted;
}

function getReviewStats() {
const now = Date.now();
const weekAgo = now - 7 * 24 * 60 * 60 * 1000;
let total = 0, approve = 0, minor = 0, major = 0, unknown = 0, thisWeek = 0, totalFindings = 0;
for (const r of _reviews) {
total++;
if (r.assessment === 'approve') approve++;
else if (r.assessment === 'minor') minor++;
else if (r.assessment === 'major') major++;
else unknown++;
if (r.timestamp && new Date(r.timestamp).getTime() > weekAgo) thisWeek++;
totalFindings += r.findings || 0;
}
return { total, approve, minor, major, unknown, thisWeek, avgFindings: total ? +(totalFindings / total).toFixed(1) : 0 };
}

/**
Expand Down Expand Up @@ -470,6 +490,7 @@ export {
supersedePreviousReviews,
storeReview,
getReviews,
getReviewStats,
updateReviewStatus,
_reviewTimestamps,
_resetReviews,
Expand Down
50 changes: 34 additions & 16 deletions src/dashboard-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,13 @@ function toggleInsightsView(showInsights) {
}

function toggleReviewDetail(id) {
var el = document.getElementById("review-detail-" + id);
if (el) el.classList.toggle("open");
// Close any previously open detail row (accordion)
var prev = document.querySelector('tr.review-detail-row[style*="table-row"]');
if (prev && prev.getAttribute("data-pair-of") !== id) {
prev.style.display = "none";
}
var el = document.querySelector('tr.review-detail-row[data-pair-of="' + id + '"]');
if (el) el.style.display = el.style.display === "table-row" ? "none" : "table-row";
}

function runCmd(cmd, resultId) {
Expand Down Expand Up @@ -137,14 +142,22 @@ document.body.addEventListener("click", function(e) {
return;
}

// ── Review table row click → expand detail ──
var reviewRow = e.target.closest("tr.review-row");
if (reviewRow && !e.target.closest("a")) {
var rid = reviewRow.getAttribute("data-review-id");
if (rid) toggleReviewDetail(rid);
return;
}

// ── Table sorting ──
var th = e.target.closest("th[data-sort]");
if (!th) return;
var table = th.closest("table");
var idx = Array.from(th.parentElement.children).indexOf(th);
var tbody = table.querySelector("tbody");
if (!tbody) return;
var rows = Array.from(tbody.querySelectorAll("tr"));
var rows = Array.from(tbody.querySelectorAll("tr:not([data-pair-of])"));
var asc = th.getAttribute("data-sort") !== "asc";
th.parentElement.querySelectorAll("th[data-sort]").forEach(function(h) { h.setAttribute("data-sort",""); });
th.setAttribute("data-sort", asc ? "asc" : "desc");
Expand All @@ -153,7 +166,15 @@ document.body.addEventListener("click", function(e) {
var bt = (b.children[idx]||{}).textContent || "";
return (asc ? 1 : -1) * at.localeCompare(bt, undefined, {numeric:true});
});
rows.forEach(function(r) { tbody.appendChild(r); });
rows.forEach(function(r) {
tbody.appendChild(r);
// Re-pair detail row after its data row
var pairId = r.getAttribute("data-review-id");
if (pairId) {
var detail = tbody.querySelector('tr[data-pair-of="' + pairId + '"]');
if (detail) tbody.appendChild(detail);
}
});
});

// ── Table filtering ──
Expand All @@ -163,18 +184,15 @@ document.body.addEventListener("input", function(e) {
var id = e.target.getAttribute("data-filter");
var tbody = document.querySelector("#" + id + " tbody");
if (!tbody) return;
Array.from(tbody.querySelectorAll("tr")).forEach(function(row) {
row.style.display = row.textContent.toLowerCase().indexOf(q) !== -1 ? "" : "none";
});
});

// ── Reviews filter ──
document.body.addEventListener("input", function(e) {
if (e.target.id !== "reviews-filter") return;
var q = e.target.value.toLowerCase();
document.querySelectorAll(".review-entry").forEach(function(entry) {
var filterText = entry.getAttribute("data-review-filter") || "";
entry.style.display = filterText.toLowerCase().indexOf(q) !== -1 ? "" : "none";
Array.from(tbody.querySelectorAll("tr:not([data-pair-of])")).forEach(function(row) {
var match = row.textContent.toLowerCase().indexOf(q) !== -1;
row.style.display = match ? "" : "none";
// Cascade: hide/close paired detail row when data row is hidden
var pairId = row.getAttribute("data-review-id");
if (pairId) {
var detail = tbody.querySelector('tr[data-pair-of="' + pairId + '"]');
if (detail) detail.style.display = match ? detail.style.display : "none";
}
});
});

Expand Down
Loading
Loading