Skip to content

Commit c07bf71

Browse files
rohanbalkondekarXoRohan
authored andcommitted
Add downloadable table feature with CSV, JSON, and Excel export
Users can now download the leaderboard table data via a Download button next to Clear Filters. A two-column panel lets users choose scope (Current View or Full Data) and format (CSV, JSON, Excel - multi-select). - Export values are numeric (not formatted strings) for proper use in spreadsheets and programmatic consumption - Full Data export includes all models even without metadata - xlsx dependency added for Excel export via SheetJS
1 parent cf5c504 commit c07bf71

5 files changed

Lines changed: 393 additions & 7 deletions

File tree

package-lock.json

Lines changed: 105 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"react-router-dom": "^6.23.1",
1616
"react-scripts": "^5.0.1",
1717
"react-select": "^5.8.3",
18-
"web-vitals": "^2.1.4"
18+
"web-vitals": "^2.1.4",
19+
"xlsx": "^0.18.5"
1920
},
2021
"scripts": {
2122
"start": "react-scripts start",

src/App.css

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,16 +242,97 @@ td {
242242
}
243243

244244
.other-controls {
245+
margin-bottom: 0.5rem;
246+
}
247+
248+
.action-buttons {
249+
display: flex;
250+
align-items: center;
251+
justify-content: center;
252+
width: 100%;
253+
gap: 0.5rem;
245254
margin-bottom: 1rem;
255+
overflow: visible;
256+
}
257+
258+
.clear-filters-button,
259+
.download-button {
260+
background-color: #007bff;
261+
color: #fff;
262+
border: none;
263+
padding: 0.3rem 0.75rem;
264+
font-size: 0.85rem;
265+
border-radius: 3px;
266+
cursor: pointer;
267+
}
268+
269+
.clear-filters-button:hover,
270+
.download-button:hover {
271+
background-color: #0056b3;
272+
}
273+
274+
.download-dropdown {
275+
position: relative;
276+
display: inline-block;
277+
}
278+
279+
.download-panel {
280+
position: fixed;
281+
z-index: 1000;
282+
background: #fff;
283+
border: 1px solid #ccc;
284+
border-radius: 4px;
285+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
286+
padding: 0.8rem;
287+
min-width: 280px;
288+
}
289+
290+
.download-panel-body {
291+
display: flex;
292+
gap: 1.5rem;
246293
}
247294

248-
.clear-filters-button {
295+
.download-panel-heading {
296+
font-size: 0.75rem;
297+
font-weight: 600;
298+
color: #666;
299+
text-transform: uppercase;
300+
letter-spacing: 0.05em;
301+
margin-bottom: 0.5rem;
302+
}
303+
304+
.download-panel-scope label,
305+
.download-panel-formats label {
306+
display: flex;
307+
align-items: center;
308+
gap: 0.4rem;
309+
padding: 0.25rem 0;
310+
cursor: pointer;
311+
font-size: 0.9rem;
312+
color: #333;
313+
white-space: nowrap;
314+
}
315+
316+
.download-panel-action {
317+
display: block;
318+
width: 100%;
319+
margin-top: 0.8rem;
320+
padding: 0.45rem 0;
249321
background-color: #007bff;
250322
color: #fff;
251323
border: none;
252-
padding: 0.5rem 1rem;
324+
border-radius: 3px;
253325
cursor: pointer;
254-
margin-left: 1rem;
326+
font-size: 0.9rem;
327+
}
328+
329+
.download-panel-action:hover:not(:disabled) {
330+
background-color: #0056b3;
331+
}
332+
333+
.download-panel-action:disabled {
334+
background-color: #ccc;
335+
cursor: not-allowed;
255336
}
256337

257338
.section {

src/Table/CSVTable.jsx

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
// src/Table/CSVTable.jsx
2-
import React, { useState, useEffect, useMemo, useCallback } from 'react';
2+
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
33
import Papa from 'papaparse';
44
import { calculateAverage, getGlobalAverage } from './Averaging';
55
import { useTable } from "./SortTable";
66
import { getModelInfo, getVariantGroup } from './modelLinks';
77
import { useSearchParams } from 'react-router-dom';
88
import Select from 'react-select';
9+
import { buildCurrentViewData, buildFullData, downloadCSV, downloadJSON, downloadExcel } from './downloadTable';
910

1011

1112
const CSVTable = ({dateStr}) => {
@@ -24,7 +25,25 @@ const CSVTable = ({dateStr}) => {
2425
const [showVariants, setShowVariants] = useState(false);
2526
const [showHighUnseenBias, setShowHighUnseenBias] = useState(true);
2627

27-
const updateURL = (checkedCategories, newFilter, newSortField = null, newSortOrder = null, newShowProvider = null, newShowApiName = null, newShowReasoners = null, newShowOpenWeights = null, newShowVariants = null, newShowHighUnseenBias = null, newSearchQuery = null) => {
28+
const [showDownloadMenu, setShowDownloadMenu] = useState(false);
29+
const [downloadScope, setDownloadScope] = useState('current');
30+
const [downloadFormats, setDownloadFormats] = useState({ csv: false, json: false, xlsx: false });
31+
const [panelPos, setPanelPos] = useState({ top: 0, left: 0 });
32+
const downloadMenuRef = useRef(null);
33+
const downloadBtnRef = useRef(null);
34+
35+
useEffect(() => {
36+
if (!showDownloadMenu) return;
37+
const handleClickOutside = (e) => {
38+
if (downloadMenuRef.current && !downloadMenuRef.current.contains(e.target)) {
39+
setShowDownloadMenu(false);
40+
}
41+
};
42+
document.addEventListener('mousedown', handleClickOutside);
43+
return () => document.removeEventListener('mousedown', handleClickOutside);
44+
}, [showDownloadMenu]);
45+
46+
const updateURL =(checkedCategories, newFilter, newSortField = null, newSortOrder = null, newShowProvider = null, newShowApiName = null, newShowReasoners = null, newShowOpenWeights = null, newShowVariants = null, newShowHighUnseenBias = null, newSearchQuery = null) => {
2847
const params = new URLSearchParams();
2948

3049
let allAverages = true;
@@ -426,6 +445,18 @@ const CSVTable = ({dateStr}) => {
426445
updateURL(defaultCategories, {}, 'ga', 'desc', true, false, true, false, false, false, '');
427446
}
428447

448+
const handleDownload = () => {
449+
const rows = downloadScope === 'current'
450+
? buildCurrentViewData(displayedData, checkedCategories, categories, { showProvider, showApiName, displayNameCounts })
451+
: buildFullData(data, categories);
452+
const filename = `livebench_${downloadScope === 'current' ? 'current_view' : 'full_data'}`;
453+
if (downloadFormats.csv) downloadCSV(rows, `${filename}.csv`);
454+
if (downloadFormats.json) downloadJSON(rows, `${filename}.json`);
455+
if (downloadFormats.xlsx) downloadExcel(rows, `${filename}.xlsx`);
456+
setDownloadFormats({ csv: false, json: false, xlsx: false });
457+
setShowDownloadMenu(false);
458+
};
459+
429460
// Utility to compute class for sorting
430461
const getSortClass = (accessor) => {
431462
return sortField === accessor ? (sortOrder === "asc" ? "up" : "down") : "default";
@@ -559,7 +590,58 @@ const CSVTable = ({dateStr}) => {
559590
<input type="checkbox" checked={showHighUnseenBias} onChange={() => setShowHighUnseenBias(!showHighUnseenBias)} id="showHighUnseenBias" />
560591
<span style={{marginLeft: '0.5rem'}}>Show High Unseen Question Bias Models</span>
561592
</label>
593+
</div>
594+
<div className="action-buttons">
562595
<button onClick={handleResetFilters} className="clear-filters-button">Clear Filters</button>
596+
<div className="download-dropdown" ref={downloadMenuRef}>
597+
<button ref={downloadBtnRef} onClick={() => {
598+
if (!showDownloadMenu && downloadBtnRef.current) {
599+
const rect = downloadBtnRef.current.getBoundingClientRect();
600+
setPanelPos({ top: rect.bottom + 4, left: rect.left });
601+
}
602+
setShowDownloadMenu(!showDownloadMenu);
603+
}} className="download-button">
604+
<i className="fa fa-download" style={{marginRight: '0.4rem'}}></i>Download
605+
</button>
606+
{showDownloadMenu && (
607+
<div className="download-panel" style={{ top: panelPos.top, left: panelPos.left }}>
608+
<div className="download-panel-body">
609+
<div className="download-panel-scope">
610+
<div className="download-panel-heading">Data</div>
611+
<label>
612+
<input type="radio" name="downloadScope" checked={downloadScope === 'current'} onChange={() => setDownloadScope('current')} />
613+
<span>Current View</span>
614+
</label>
615+
<label>
616+
<input type="radio" name="downloadScope" checked={downloadScope === 'full'} onChange={() => setDownloadScope('full')} />
617+
<span>Full Data</span>
618+
</label>
619+
</div>
620+
<div className="download-panel-formats">
621+
<div className="download-panel-heading">Format</div>
622+
<label>
623+
<input type="checkbox" checked={downloadFormats.csv} onChange={() => setDownloadFormats(f => ({...f, csv: !f.csv}))} />
624+
<span>CSV</span>
625+
</label>
626+
<label>
627+
<input type="checkbox" checked={downloadFormats.json} onChange={() => setDownloadFormats(f => ({...f, json: !f.json}))} />
628+
<span>JSON</span>
629+
</label>
630+
<label>
631+
<input type="checkbox" checked={downloadFormats.xlsx} onChange={() => setDownloadFormats(f => ({...f, xlsx: !f.xlsx}))} />
632+
<span>Excel (.xlsx)</span>
633+
</label>
634+
</div>
635+
</div>
636+
<button
637+
className="download-panel-action"
638+
disabled={!downloadFormats.csv && !downloadFormats.json && !downloadFormats.xlsx}
639+
onClick={handleDownload}>
640+
Download
641+
</button>
642+
</div>
643+
)}
644+
</div>
563645
</div>
564646
<div className="search-bar">
565647
<input

0 commit comments

Comments
 (0)