Skip to content

Commit a8f23a3

Browse files
author
Dylan Huang
committed
allow evaluationtable to be sorted
1 parent cfb4b67 commit a8f23a3

File tree

3 files changed

+312
-14
lines changed

3 files changed

+312
-14
lines changed

vite-app/src/GlobalState.tsx

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ const DEFAULT_PAGINATION_CONFIG = {
2222
pageSize: 25,
2323
};
2424

25+
// Default sort configuration
26+
const DEFAULT_SORT_CONFIG = {
27+
sortField: "created_at",
28+
sortDirection: "desc" as "asc" | "desc",
29+
};
30+
2531
export class GlobalState {
2632
isConnected: boolean = false;
2733
// rollout_id -> EvaluationRow
@@ -37,6 +43,9 @@ export class GlobalState {
3743
// Pagination configuration
3844
currentPage: number;
3945
pageSize: number;
46+
// Sort configuration
47+
sortField: string;
48+
sortDirection: "asc" | "desc";
4049
// Loading state
4150
isLoading: boolean = true;
4251

@@ -64,6 +73,10 @@ export class GlobalState {
6473
const paginationConfig = this.loadPaginationConfig();
6574
this.currentPage = paginationConfig.currentPage;
6675
this.pageSize = paginationConfig.pageSize;
76+
// Load sort config from localStorage or use defaults
77+
const sortConfig = this.loadSortConfig();
78+
this.sortField = sortConfig.sortField;
79+
this.sortDirection = sortConfig.sortDirection;
6780
makeAutoObservable(this);
6881
}
6982

@@ -114,6 +127,21 @@ export class GlobalState {
114127
return DEFAULT_PAGINATION_CONFIG;
115128
}
116129

130+
// Load sort configuration from localStorage
131+
private loadSortConfig() {
132+
try {
133+
const stored = localStorage.getItem("sortConfig");
134+
if (stored) {
135+
const parsed = JSON.parse(stored);
136+
// Merge with defaults to handle any missing properties
137+
return { ...DEFAULT_SORT_CONFIG, ...parsed };
138+
}
139+
} catch (error) {
140+
console.warn("Failed to load sort config from localStorage:", error);
141+
}
142+
return DEFAULT_SORT_CONFIG;
143+
}
144+
117145
// Save pivot configuration to localStorage
118146
private savePivotConfig() {
119147
if (this.savePivotConfigTimer) clearTimeout(this.savePivotConfigTimer);
@@ -160,6 +188,24 @@ export class GlobalState {
160188
}, 200);
161189
}
162190

191+
// Save sort configuration to localStorage
192+
private saveSortConfig() {
193+
if (this.saveFilterConfigTimer) clearTimeout(this.saveFilterConfigTimer);
194+
this.saveFilterConfigTimer = setTimeout(() => {
195+
try {
196+
localStorage.setItem(
197+
"sortConfig",
198+
JSON.stringify({
199+
sortField: this.sortField,
200+
sortDirection: this.sortDirection,
201+
})
202+
);
203+
} catch (error) {
204+
console.warn("Failed to save sort config to localStorage:", error);
205+
}
206+
}, 200);
207+
}
208+
163209
// Update pivot configuration and save to localStorage
164210
updatePivotConfig(updates: Partial<PivotConfig>) {
165211
Object.assign(this.pivotConfig, updates);
@@ -191,6 +237,29 @@ export class GlobalState {
191237
this.savePaginationConfig();
192238
}
193239

240+
// Update sort configuration and save to localStorage
241+
updateSortConfig(
242+
updates: Partial<{ sortField: string; sortDirection: "asc" | "desc" }>
243+
) {
244+
Object.assign(this, updates);
245+
// Reset to first page when sorting changes
246+
this.currentPage = 1;
247+
this.saveSortConfig();
248+
}
249+
250+
// Handle sort field click - toggle direction if same field, set to asc if new field
251+
handleSortFieldClick(field: string) {
252+
if (this.sortField === field) {
253+
// Toggle direction for same field
254+
this.sortDirection = this.sortDirection === "asc" ? "desc" : "asc";
255+
} else {
256+
// New field, set to ascending
257+
this.sortField = field;
258+
this.sortDirection = "asc";
259+
}
260+
this.saveSortConfig();
261+
}
262+
194263
// Reset pivot configuration to defaults
195264
resetPivotConfig() {
196265
this.pivotConfig = { ...DEFAULT_PIVOT_CONFIG };
@@ -211,6 +280,13 @@ export class GlobalState {
211280
this.savePaginationConfig();
212281
}
213282

283+
// Reset sort configuration to defaults
284+
resetSortConfig() {
285+
this.sortField = DEFAULT_SORT_CONFIG.sortField;
286+
this.sortDirection = DEFAULT_SORT_CONFIG.sortDirection;
287+
this.saveSortConfig();
288+
}
289+
214290
// Set current page
215291
setCurrentPage(page: number) {
216292
this.currentPage = page;
@@ -286,9 +362,48 @@ export class GlobalState {
286362

287363
// Computed values following MobX best practices
288364
get sortedIds() {
289-
return Object.keys(this.dataset).sort(
290-
(a, b) => (this.createdAtMsById[b] ?? 0) - (this.createdAtMsById[a] ?? 0)
291-
);
365+
const ids = Object.keys(this.dataset);
366+
367+
if (this.sortField === "created_at") {
368+
// Special case for created_at - use cached timestamp
369+
return ids.sort((a, b) => {
370+
const aTime = this.createdAtMsById[a] ?? 0;
371+
const bTime = this.createdAtMsById[b] ?? 0;
372+
return this.sortDirection === "asc" ? aTime - bTime : bTime - aTime;
373+
});
374+
}
375+
376+
// For other fields, sort by flattened data
377+
return ids.sort((a, b) => {
378+
const aFlat = this.flattenedById[a];
379+
const bFlat = this.flattenedById[b];
380+
381+
if (!aFlat || !bFlat) return 0;
382+
383+
const aValue = aFlat[this.sortField];
384+
const bValue = bFlat[this.sortField];
385+
386+
// Handle undefined values
387+
if (aValue === undefined && bValue === undefined) return 0;
388+
if (aValue === undefined) return this.sortDirection === "asc" ? -1 : 1;
389+
if (bValue === undefined) return this.sortDirection === "asc" ? 1 : -1;
390+
391+
// Handle different types
392+
if (typeof aValue === "string" && typeof bValue === "string") {
393+
const comparison = aValue.localeCompare(bValue);
394+
return this.sortDirection === "asc" ? comparison : -comparison;
395+
}
396+
397+
if (typeof aValue === "number" && typeof bValue === "number") {
398+
return this.sortDirection === "asc" ? aValue - bValue : bValue - aValue;
399+
}
400+
401+
// Fallback to string comparison
402+
const aStr = String(aValue);
403+
const bStr = String(bValue);
404+
const comparison = aStr.localeCompare(bStr);
405+
return this.sortDirection === "asc" ? comparison : -comparison;
406+
});
292407
}
293408

294409
get sortedDataset() {

vite-app/src/components/EvaluationTable.tsx

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
TableHeader,
99
TableHead,
1010
TableBody as TableBodyBase,
11+
SortableTableHeader,
1112
} from "./TableContainer";
1213

1314
const TableBody = observer(
@@ -51,6 +52,10 @@ export const EvaluationTable = observer(() => {
5152
state.updateFilterConfig(filters);
5253
};
5354

55+
const handleSort = (field: string) => {
56+
state.handleSortFieldClick(field);
57+
};
58+
5459
return (
5560
<div className="bg-white border border-gray-200">
5661
{/* Filter Controls */}
@@ -160,17 +165,94 @@ export const EvaluationTable = observer(() => {
160165
<TableHead>
161166
<tr>
162167
<TableHeader className="w-8">&nbsp;</TableHeader>
163-
<TableHeader>Name</TableHeader>
164-
<TableHeader>Eval Status</TableHeader>
165-
<TableHeader>Rollout Status</TableHeader>
166-
<TableHeader>Invocation ID</TableHeader>
167-
<TableHeader>Experiment ID</TableHeader>
168-
<TableHeader>Run ID</TableHeader>
169-
<TableHeader>Row ID</TableHeader>
170-
<TableHeader>Rollout ID</TableHeader>
171-
<TableHeader>Model</TableHeader>
172-
<TableHeader>Score</TableHeader>
173-
<TableHeader>Created</TableHeader>
168+
<SortableTableHeader
169+
sortField="$.eval_metadata.name"
170+
currentSortField={state.sortField}
171+
currentSortDirection={state.sortDirection}
172+
onSort={handleSort}
173+
>
174+
Name
175+
</SortableTableHeader>
176+
<SortableTableHeader
177+
sortField="$.eval_metadata.status.code"
178+
currentSortField={state.sortField}
179+
currentSortDirection={state.sortDirection}
180+
onSort={handleSort}
181+
>
182+
Eval Status
183+
</SortableTableHeader>
184+
<SortableTableHeader
185+
sortField="$.rollout_status.code"
186+
currentSortField={state.sortField}
187+
currentSortDirection={state.sortDirection}
188+
onSort={handleSort}
189+
>
190+
Rollout Status
191+
</SortableTableHeader>
192+
<SortableTableHeader
193+
sortField="$.execution_metadata.invocation_id"
194+
currentSortField={state.sortField}
195+
currentSortDirection={state.sortDirection}
196+
onSort={handleSort}
197+
>
198+
Invocation ID
199+
</SortableTableHeader>
200+
<SortableTableHeader
201+
sortField="$.execution_metadata.experiment_id"
202+
currentSortField={state.sortField}
203+
currentSortDirection={state.sortDirection}
204+
onSort={handleSort}
205+
>
206+
Experiment ID
207+
</SortableTableHeader>
208+
<SortableTableHeader
209+
sortField="$.execution_metadata.run_id"
210+
currentSortField={state.sortField}
211+
currentSortDirection={state.sortDirection}
212+
onSort={handleSort}
213+
>
214+
Run ID
215+
</SortableTableHeader>
216+
<SortableTableHeader
217+
sortField="$.input_metadata.row_id"
218+
currentSortField={state.sortField}
219+
currentSortDirection={state.sortDirection}
220+
onSort={handleSort}
221+
>
222+
Row ID
223+
</SortableTableHeader>
224+
<SortableTableHeader
225+
sortField="$.execution_metadata.rollout_id"
226+
currentSortField={state.sortField}
227+
currentSortDirection={state.sortDirection}
228+
onSort={handleSort}
229+
>
230+
Rollout ID
231+
</SortableTableHeader>
232+
<SortableTableHeader
233+
sortField="$.input_metadata.completion_params.model"
234+
currentSortField={state.sortField}
235+
currentSortDirection={state.sortDirection}
236+
onSort={handleSort}
237+
>
238+
Model
239+
</SortableTableHeader>
240+
<SortableTableHeader
241+
sortField="$.evaluation_result.score"
242+
currentSortField={state.sortField}
243+
currentSortDirection={state.sortDirection}
244+
onSort={handleSort}
245+
>
246+
Score
247+
</SortableTableHeader>
248+
<SortableTableHeader
249+
sortField="created_at"
250+
currentSortField={state.sortField}
251+
currentSortDirection={state.sortDirection}
252+
onSort={handleSort}
253+
>
254+
Created
255+
</SortableTableHeader>
174256
</tr>
175257
</TableHead>
176258

0 commit comments

Comments
 (0)