diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29..69f9b7f 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,5 @@ +- bump: minor + changes: + added: + - Adding column to sort by calibration difference to dashboard comparisons page. + - Ensure pagination so all repo branches are visible in the github loading. diff --git a/microcalibration-dashboard/src/components/ComparisonDataTable.tsx b/microcalibration-dashboard/src/components/ComparisonDataTable.tsx index 64e6a36..8694774 100644 --- a/microcalibration-dashboard/src/components/ComparisonDataTable.tsx +++ b/microcalibration-dashboard/src/components/ComparisonDataTable.tsx @@ -18,7 +18,7 @@ interface ComparisonRow { second?: CalibrationDataPoint; } -type SortField = keyof CalibrationDataPoint | 'random'; +type SortField = keyof CalibrationDataPoint | 'random' | 'difference'; type SortDataset = 'first' | 'second' | null; type SortDirection = 'asc' | 'desc'; @@ -97,6 +97,29 @@ export default function ComparisonDataTable({ return sortDirection === 'asc' ? result : -result; } + if (sortField === 'difference') { + // Calculate differences for sorting + const aDiff = (a.first?.rel_abs_error !== undefined && a.second?.rel_abs_error !== undefined) + ? a.second.rel_abs_error - a.first.rel_abs_error + : null; + const bDiff = (b.first?.rel_abs_error !== undefined && b.second?.rel_abs_error !== undefined) + ? b.second.rel_abs_error - b.first.rel_abs_error + : null; + + // Handle undefined/null values - put them at the end regardless of sort direction + if (aDiff === null && bDiff === null) { + return 0; + } + if (aDiff === null) { + return 1; + } + if (bDiff === null) { + return -1; + } + + return sortDirection === 'asc' ? aDiff - bDiff : bDiff - aDiff; + } + // Sort by values based on selected dataset let aVal, bVal; if (sortDataset === 'first') { @@ -150,15 +173,15 @@ export default function ComparisonDataTable({ const totalPages = Math.ceil(sortedData.length / itemsPerPage); - const handleSort = (field: keyof CalibrationDataPoint, dataset?: 'first' | 'second') => { - // For target_name, don't use dataset-specific sorting - if (field === 'target_name') { + const handleSort = (field: keyof CalibrationDataPoint | 'difference', dataset?: 'first' | 'second') => { + // For target_name and difference, don't use dataset-specific sorting + if (field === 'target_name' || field === 'difference') { if (sortField === field && sortDataset === null) { setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); } else { setSortField(field); setSortDataset(null); - setSortDirection('asc'); + setSortDirection(field === 'difference' ? 'desc' : 'asc'); // Start with desc for difference to show biggest improvements first } return; } @@ -176,11 +199,11 @@ export default function ComparisonDataTable({ }; const SortButton = ({ field, children, dataset }: { - field: keyof CalibrationDataPoint, + field: keyof CalibrationDataPoint | 'difference', children: React.ReactNode, dataset?: 'first' | 'second' }) => { - const isActive = field === 'target_name' + const isActive = (field === 'target_name' || field === 'difference') ? (sortField === field && sortDataset === null) : (sortField === field && sortDataset === dataset); @@ -283,6 +306,28 @@ export default function ComparisonDataTable({ ); }; + const renderDifferenceCell = (firstValue: number | undefined | null, secondValue: number | undefined | null) => { + if (firstValue === undefined || firstValue === null || secondValue === undefined || secondValue === null) { + return ; + } + + const difference = secondValue - firstValue; + const percentageDiff = (difference * 100).toFixed(2); + const sign = difference > 0 ? '+' : ''; + + // Color coding: negative difference (B better than A) = green, positive (B worse than A) = red + let colorClass = 'text-gray-600'; + if (Math.abs(difference) > 0.001) { // Only color if difference is meaningful + colorClass = difference < 0 ? 'text-green-600' : 'text-red-600'; + } + + return ( + + {sign}{percentageDiff}% + + ); + }; + return (
@@ -347,6 +392,7 @@ export default function ComparisonDataTable({ + @@ -368,6 +414,9 @@ export default function ComparisonDataTable({ Rel abs error % + + Difference + A @@ -388,6 +437,9 @@ export default function ComparisonDataTable({ B + + (B-A) + @@ -460,6 +512,11 @@ export default function ComparisonDataTable({ {renderDataCell(row.second?.rel_abs_error, false, true)} + + {/* Difference column */} + + {renderDifferenceCell(row.first?.rel_abs_error, row.second?.rel_abs_error)} + ))} diff --git a/microcalibration-dashboard/src/components/ComparisonQualitySummary.tsx b/microcalibration-dashboard/src/components/ComparisonQualitySummary.tsx index 2b0e213..4851111 100644 --- a/microcalibration-dashboard/src/components/ComparisonQualitySummary.tsx +++ b/microcalibration-dashboard/src/components/ComparisonQualitySummary.tsx @@ -120,11 +120,11 @@ export default function ComparisonQualitySummary({ let qualityIcon: React.ReactNode; if (overallQualityChange > 0.1) { - qualityAssessment = "Significantly Better"; + qualityAssessment = "Improved"; qualityColor = "green"; qualityIcon = ; } else if (overallQualityChange < -0.1) { - qualityAssessment = "Significantly worse"; + qualityAssessment = "Worsened"; qualityColor = "red"; qualityIcon = ; } else { @@ -145,7 +145,7 @@ export default function ComparisonQualitySummary({

{/* Overall Quality Assessment */} -
+
{qualityIcon} diff --git a/microcalibration-dashboard/src/components/FileUpload.tsx b/microcalibration-dashboard/src/components/FileUpload.tsx index 3d5cab8..17ece15 100644 --- a/microcalibration-dashboard/src/components/FileUpload.tsx +++ b/microcalibration-dashboard/src/components/FileUpload.tsx @@ -568,28 +568,56 @@ export default function FileUpload({ setError(''); try { - // Use authenticated requests to avoid rate limiting - const response = await fetch(`https://api.github.com/repos/${githubRepo}/branches`, { - headers: { - 'Authorization': `Bearer ${githubToken}`, - 'Accept': 'application/vnd.github.v3+json', - 'User-Agent': 'PolicyEngine-Dashboard/1.0' + // Fetch all branches with pagination support + const allBranches: GitHubBranch[] = []; + let page = 1; + const perPage = 100; // Maximum allowed by GitHub API + + while (true) { + const response = await fetch(`https://api.github.com/repos/${githubRepo}/branches?per_page=${perPage}&page=${page}`, { + headers: { + 'Authorization': `Bearer ${githubToken}`, + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'PolicyEngine-Dashboard/1.0' + } + }); + + if (!response.ok) { + if (response.status === 404) { + throw new Error('Repository not found. Please check the repository name and ensure it is accessible.'); + } else if (response.status === 403) { + throw new Error('Access forbidden. Please check your GitHub token permissions or repository access.'); + } + throw new Error(`Failed to fetch branches: ${response.status} ${response.statusText}`); } - }); - if (!response.ok) { - if (response.status === 404) { - throw new Error('Repository not found. Please check the repository name and ensure it is accessible.'); - } else if (response.status === 403) { - throw new Error('Access forbidden. Please check your GitHub token permissions or repository access.'); + + const branches: GitHubBranch[] = await response.json(); + + if (branches.length === 0) { + // No more branches to fetch + break; + } + + allBranches.push(...branches); + + // If we got fewer branches than requested, we've reached the end + if (branches.length < perPage) { + break; + } + + page++; + + // Safety check to prevent infinite loops (GitHub repos rarely have more than 1000 branches) + if (page > 10) { + console.warn('Stopped fetching branches after 10 pages (1000 branches) to prevent excessive API calls'); + break; } - throw new Error(`Failed to fetch branches: ${response.status} ${response.statusText}`); } - const branches: GitHubBranch[] = await response.json(); - setGithubBranches(branches); + setGithubBranches(allBranches); // Auto-select main/master branch if available - const defaultBranch = branches.find(b => b.name === 'main' || b.name === 'master'); + const defaultBranch = allBranches.find(b => b.name === 'main' || b.name === 'master'); if (defaultBranch) { setSelectedBranch(defaultBranch.name); await fetchGithubCommits(defaultBranch.name);