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
5 changes: 5 additions & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
@@ -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.
71 changes: 64 additions & 7 deletions microcalibration-dashboard/src/components/ComparisonDataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -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;
}
Expand All @@ -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);

Expand Down Expand Up @@ -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 <span className="text-gray-400 font-mono text-xs">—</span>;
}

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 (
<span className={`font-mono text-xs font-semibold ${colorClass}`} title={`B: ${(secondValue * 100).toFixed(2)}% - A: ${(firstValue * 100).toFixed(2)}% = ${sign}${percentageDiff}%`}>
{sign}{percentageDiff}%
</span>
);
};

return (
<div className="bg-white border border-gray-300 p-6 rounded-lg shadow-sm">
<div className="flex justify-between items-center mb-4">
Expand Down Expand Up @@ -347,6 +392,7 @@ export default function ComparisonDataTable({
<col className="w-20" />
<col className="w-20" />
<col className="w-20" />
<col className="w-24" />
</colgroup>
<thead>
<tr className="border-b border-gray-300 bg-gray-50">
Expand All @@ -368,6 +414,9 @@ export default function ComparisonDataTable({
<th className="text-center py-2 px-4 font-semibold text-gray-700 border-b border-gray-200" colSpan={2}>
Rel abs error %
</th>
<th className="text-center py-2 px-4 font-semibold text-gray-700 border-b border-gray-200">
Difference
</th>
</tr>
<tr className="border-b border-gray-300 bg-gray-50">
<th className="text-center py-2 px-2 text-xs font-medium text-blue-600">A</th>
Expand All @@ -388,6 +437,9 @@ export default function ComparisonDataTable({
B
</DatasetSortButton>
</th>
<th className="text-center py-2 px-5">
<SortButton field="difference">(B-A)</SortButton>
</th>
</tr>
</thead>
<tbody>
Expand Down Expand Up @@ -460,6 +512,11 @@ export default function ComparisonDataTable({
<td className="py-3 px-2 text-right">
{renderDataCell(row.second?.rel_abs_error, false, true)}
</td>

{/* Difference column */}
<td className="py-3 px-2 text-center">
{renderDifferenceCell(row.first?.rel_abs_error, row.second?.rel_abs_error)}
</td>
</tr>
))}
</tbody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,11 @@ export default function ComparisonQualitySummary({
let qualityIcon: React.ReactNode;

if (overallQualityChange > 0.1) {
qualityAssessment = "Significantly Better";
qualityAssessment = "Improved";
qualityColor = "green";
qualityIcon = <TrendingUp className="w-6 h-6" />;
} else if (overallQualityChange < -0.1) {
qualityAssessment = "Significantly worse";
qualityAssessment = "Worsened";
qualityColor = "red";
qualityIcon = <TrendingDown className="w-6 h-6" />;
} else {
Expand All @@ -145,7 +145,7 @@ export default function ComparisonQualitySummary({
</p>

{/* Overall Quality Assessment */}
<div className={`mb-6 bg-${qualityColor}-50 border-2 border-${qualityColor}-200 rounded-lg p-6`}>
<div className={`mb-6 border-2 border-${qualityColor}-500 rounded-lg p-6`}>
<div className="text-center">
<div className={`flex items-center justify-center mb-2 text-${qualityColor}-600`}>
{qualityIcon}
Expand Down
60 changes: 44 additions & 16 deletions microcalibration-dashboard/src/components/FileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down