Skip to content

Commit cfa015d

Browse files
author
Dylan Huang
authored
Add invocation id to table (#79)
* "Copy" button * consolidate filter configurations * filter button works * extract tooltip into its own component * vite build * Refactor AddFilterButton layout for improved styling and structure * vite build
1 parent 54333cf commit cfa015d

File tree

14 files changed

+322
-167
lines changed

14 files changed

+322
-167
lines changed

vite-app/dist/assets/index-CpPWargc.js

Lines changed: 0 additions & 93 deletions
This file was deleted.

vite-app/dist/assets/index-CpPWargc.js.map

Lines changed: 0 additions & 1 deletion
This file was deleted.

vite-app/dist/assets/index-CpScNe1P.css

Lines changed: 0 additions & 1 deletion
This file was deleted.

vite-app/dist/assets/index-D1ErODUS.js

Lines changed: 93 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vite-app/dist/assets/index-D1ErODUS.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vite-app/dist/assets/index-D5KxcfFQ.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vite-app/dist/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<title>EP | Log Viewer</title>
77
<link rel="icon" href="/assets/favicon-BkAAWQga.png" />
8-
<script type="module" crossorigin src="/assets/index-CpPWargc.js"></script>
9-
<link rel="stylesheet" crossorigin href="/assets/index-CpScNe1P.css">
8+
<script type="module" crossorigin src="/assets/index-D1ErODUS.js"></script>
9+
<link rel="stylesheet" crossorigin href="/assets/index-D5KxcfFQ.css">
1010
</head>
1111
<body>
1212
<div id="root"></div>

vite-app/src/GlobalState.tsx

Lines changed: 41 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ const DEFAULT_PIVOT_CONFIG: PivotConfig = {
1111
selectedColumnFields: ["$.input_metadata.completion_params.model"],
1212
selectedValueField: "$.evaluation_result.score",
1313
selectedAggregator: "avg",
14-
filters: [],
1514
};
1615

17-
// Default table filter configuration
18-
const DEFAULT_TABLE_FILTER_CONFIG: FilterGroup[] = [];
16+
// Default filter configuration
17+
const DEFAULT_FILTER_CONFIG: FilterGroup[] = [];
1918

2019
// Default pagination configuration
2120
const DEFAULT_PAGINATION_CONFIG = {
@@ -31,10 +30,10 @@ export class GlobalState {
3130
expandedRows: Record<string, boolean> = {};
3231
// Pivot configuration
3332
pivotConfig: PivotConfig;
34-
// Table filter configuration
35-
tableFilterConfig: FilterGroup[];
36-
// Debounced, actually applied table filter configuration (for performance while typing)
37-
appliedTableFilterConfig: FilterGroup[];
33+
// Unified filter configuration for both pivot and table views
34+
filterConfig: FilterGroup[];
35+
// Debounced, actually applied filter configuration (for performance while typing)
36+
appliedFilterConfig: FilterGroup[];
3837
// Pagination configuration
3938
currentPage: number;
4039
pageSize: number;
@@ -49,19 +48,18 @@ export class GlobalState {
4948

5049
// Debounce timers for localStorage saves and filter application
5150
private savePivotConfigTimer: ReturnType<typeof setTimeout> | null = null;
52-
private saveTableFilterConfigTimer: ReturnType<typeof setTimeout> | null =
53-
null;
51+
private saveFilterConfigTimer: ReturnType<typeof setTimeout> | null = null;
5452
private savePaginationConfigTimer: ReturnType<typeof setTimeout> | null =
5553
null;
56-
private applyTableFilterTimer: ReturnType<typeof setTimeout> | null = null;
54+
private applyFilterTimer: ReturnType<typeof setTimeout> | null = null;
5755

5856
constructor() {
5957
// Load pivot config from localStorage or use defaults
6058
this.pivotConfig = this.loadPivotConfig();
61-
// Load table filter config from localStorage or use defaults
62-
this.tableFilterConfig = this.loadTableFilterConfig();
59+
// Load filter config from localStorage or use defaults
60+
this.filterConfig = this.loadFilterConfig();
6361
// Initialize applied filter config with current value
64-
this.appliedTableFilterConfig = this.tableFilterConfig.slice();
62+
this.appliedFilterConfig = this.filterConfig.slice();
6563
// Load pagination config from localStorage or use defaults
6664
const paginationConfig = this.loadPaginationConfig();
6765
this.currentPage = paginationConfig.currentPage;
@@ -84,21 +82,18 @@ export class GlobalState {
8482
return { ...DEFAULT_PIVOT_CONFIG };
8583
}
8684

87-
// Load table filter configuration from localStorage
88-
private loadTableFilterConfig(): FilterGroup[] {
85+
// Load filter configuration from localStorage
86+
private loadFilterConfig(): FilterGroup[] {
8987
try {
90-
const stored = localStorage.getItem("tableFilterConfig");
88+
const stored = localStorage.getItem("filterConfig");
9189
if (stored) {
9290
const parsed = JSON.parse(stored);
93-
return Array.isArray(parsed) ? parsed : DEFAULT_TABLE_FILTER_CONFIG;
91+
return Array.isArray(parsed) ? parsed : DEFAULT_FILTER_CONFIG;
9492
}
9593
} catch (error) {
96-
console.warn(
97-
"Failed to load table filter config from localStorage:",
98-
error
99-
);
94+
console.warn("Failed to load filter config from localStorage:", error);
10095
}
101-
return DEFAULT_TABLE_FILTER_CONFIG;
96+
return DEFAULT_FILTER_CONFIG;
10297
}
10398

10499
// Load pagination configuration from localStorage
@@ -116,7 +111,7 @@ export class GlobalState {
116111
error
117112
);
118113
}
119-
return { ...DEFAULT_PAGINATION_CONFIG };
114+
return DEFAULT_PAGINATION_CONFIG;
120115
}
121116

122117
// Save pivot configuration to localStorage
@@ -131,21 +126,14 @@ export class GlobalState {
131126
}, 200);
132127
}
133128

134-
// Save table filter configuration to localStorage
135-
private saveTableFilterConfig() {
136-
if (this.saveTableFilterConfigTimer)
137-
clearTimeout(this.saveTableFilterConfigTimer);
138-
this.saveTableFilterConfigTimer = setTimeout(() => {
129+
// Save filter configuration to localStorage
130+
private saveFilterConfig() {
131+
if (this.saveFilterConfigTimer) clearTimeout(this.saveFilterConfigTimer);
132+
this.saveFilterConfigTimer = setTimeout(() => {
139133
try {
140-
localStorage.setItem(
141-
"tableFilterConfig",
142-
JSON.stringify(this.tableFilterConfig)
143-
);
134+
localStorage.setItem("filterConfig", JSON.stringify(this.filterConfig));
144135
} catch (error) {
145-
console.warn(
146-
"Failed to save table filter config to localStorage:",
147-
error
148-
);
136+
console.warn("Failed to save filter config to localStorage:", error);
149137
}
150138
}, 200);
151139
}
@@ -178,15 +166,15 @@ export class GlobalState {
178166
this.savePivotConfig();
179167
}
180168

181-
// Update table filter configuration and save to localStorage
182-
updateTableFilterConfig(filters: FilterGroup[]) {
183-
this.tableFilterConfig = filters;
184-
this.saveTableFilterConfig();
169+
// Update filter configuration and save to localStorage
170+
updateFilterConfig(filters: FilterGroup[]) {
171+
this.filterConfig = filters;
172+
this.saveFilterConfig();
185173

186174
// Debounce application of filters to avoid re-filtering on every keystroke
187-
if (this.applyTableFilterTimer) clearTimeout(this.applyTableFilterTimer);
188-
this.applyTableFilterTimer = setTimeout(() => {
189-
this.appliedTableFilterConfig = this.tableFilterConfig.slice();
175+
if (this.applyFilterTimer) clearTimeout(this.applyFilterTimer);
176+
this.applyFilterTimer = setTimeout(() => {
177+
this.appliedFilterConfig = this.filterConfig.slice();
190178
}, 150);
191179
}
192180

@@ -205,18 +193,15 @@ export class GlobalState {
205193

206194
// Reset pivot configuration to defaults
207195
resetPivotConfig() {
208-
this.pivotConfig = {
209-
...DEFAULT_PIVOT_CONFIG,
210-
filters: [], // Ensure filters is an empty array of FilterGroups
211-
};
196+
this.pivotConfig = { ...DEFAULT_PIVOT_CONFIG };
212197
this.savePivotConfig();
213198
}
214199

215-
// Reset table filter configuration to defaults
216-
resetTableFilterConfig() {
217-
this.tableFilterConfig = [...DEFAULT_TABLE_FILTER_CONFIG];
218-
this.appliedTableFilterConfig = [...DEFAULT_TABLE_FILTER_CONFIG];
219-
this.saveTableFilterConfig();
200+
// Reset filter configuration to defaults
201+
resetFilterConfig() {
202+
this.filterConfig = [...DEFAULT_FILTER_CONFIG];
203+
this.appliedFilterConfig = [...DEFAULT_FILTER_CONFIG];
204+
this.saveFilterConfig();
220205
}
221206

222207
// Reset pagination configuration to defaults
@@ -315,20 +300,20 @@ export class GlobalState {
315300
}
316301

317302
get filteredFlattenedDataset() {
318-
if (this.appliedTableFilterConfig.length === 0) {
303+
if (this.appliedFilterConfig.length === 0) {
319304
return this.flattenedDataset;
320305
}
321306

322-
const filterFunction = createFilterFunction(this.appliedTableFilterConfig)!;
307+
const filterFunction = createFilterFunction(this.appliedFilterConfig)!;
323308
return this.flattenedDataset.filter(filterFunction);
324309
}
325310

326311
get filteredOriginalDataset() {
327-
if (this.appliedTableFilterConfig.length === 0) {
312+
if (this.appliedFilterConfig.length === 0) {
328313
return this.sortedDataset;
329314
}
330315

331-
const filterFunction = createFilterFunction(this.appliedTableFilterConfig)!;
316+
const filterFunction = createFilterFunction(this.appliedFilterConfig)!;
332317
return this.sortedIds
333318
.filter((id) => filterFunction(this.flattenedById[id]))
334319
.map((id) => this.dataset[id]);

vite-app/src/components/EvaluationRow.tsx

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,110 @@ import { MetadataSection } from "./MetadataSection";
55
import StatusIndicator from "./StatusIndicator";
66
import { state } from "../App";
77
import { TableCell, TableRowInteractive } from "./TableContainer";
8+
import { useState } from "react";
9+
import type { FilterGroup, FilterConfig } from "../types/filters";
10+
import { Tooltip } from "./Tooltip";
11+
12+
// Add filter button component
13+
const AddFilterButton = observer(
14+
({
15+
fieldPath,
16+
value,
17+
label,
18+
}: {
19+
fieldPath: string;
20+
value: string;
21+
label: string;
22+
}) => {
23+
const [added, setAdded] = useState(false);
24+
25+
const handleClick = (e: React.MouseEvent) => {
26+
e.stopPropagation(); // Prevent row expansion
27+
28+
// Create a new filter for this field/value
29+
const newFilter: FilterConfig = {
30+
field: fieldPath,
31+
operator: "==",
32+
value: value,
33+
type: "text",
34+
};
35+
36+
// Add the filter to the existing filter configuration
37+
const currentFilters = state.filterConfig;
38+
let newFilters: FilterGroup[];
39+
40+
if (currentFilters.length === 0) {
41+
// If no filters exist, create a new filter group
42+
newFilters = [
43+
{
44+
logic: "AND",
45+
filters: [newFilter],
46+
},
47+
];
48+
} else {
49+
// Add to the first filter group (assuming AND logic)
50+
newFilters = [...currentFilters];
51+
newFilters[0] = {
52+
...newFilters[0],
53+
filters: [...newFilters[0].filters, newFilter],
54+
};
55+
}
56+
57+
state.updateFilterConfig(newFilters);
58+
setAdded(true);
59+
60+
// Reset to "Add Filter" state after 2 seconds
61+
setTimeout(() => setAdded(false), 2000);
62+
};
63+
64+
return (
65+
<Tooltip
66+
content={added ? "Filter Added!" : `Add ${label} Filter`}
67+
position="top"
68+
className="text-gray-400 hover:text-gray-600 transition-colors"
69+
>
70+
<div className="flex items-center gap-1">
71+
<button
72+
className="cursor-pointer"
73+
onClick={handleClick}
74+
title="Add filter for this value"
75+
>
76+
{/* Icon */}
77+
{added ? (
78+
<svg
79+
className="w-3 h-3 text-green-600"
80+
fill="none"
81+
stroke="currentColor"
82+
viewBox="0 0 24 24"
83+
>
84+
<path
85+
strokeLinecap="round"
86+
strokeLinejoin="round"
87+
strokeWidth={2}
88+
d="M5 13l4 4L19 7"
89+
/>
90+
</svg>
91+
) : (
92+
<svg
93+
className="w-3 h-3"
94+
fill="none"
95+
stroke="currentColor"
96+
viewBox="0 0 24 24"
97+
>
98+
<path
99+
strokeLinecap="round"
100+
strokeLinejoin="round"
101+
strokeWidth={2}
102+
d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.207A1 1 0 013 6.5V4z"
103+
/>
104+
</svg>
105+
)}
106+
</button>
107+
</div>
108+
</Tooltip>
109+
);
110+
}
111+
);
8112

9113
// Small, focused components following "dereference values late" principle
10114
const ExpandIcon = observer(({ rolloutId }: { rolloutId?: string }) => {
@@ -64,6 +168,22 @@ const RolloutId = observer(
64168
}
65169
);
66170

171+
const InvocationId = observer(({ invocationId }: { invocationId?: string }) => {
172+
if (!invocationId) {
173+
return null;
174+
}
175+
return (
176+
<span className="font-mono text-gray-900 whitespace-nowrap flex items-center gap-1">
177+
{invocationId}
178+
<AddFilterButton
179+
fieldPath="$.execution_metadata.invocation_id"
180+
value={invocationId}
181+
label="Invocation"
182+
/>
183+
</span>
184+
);
185+
});
186+
67187
const RowModel = observer(({ model }: { model: string | undefined }) => (
68188
<span className="text-gray-900 truncate block">{model || "N/A"}</span>
69189
));
@@ -224,6 +344,13 @@ export const EvaluationRow = observer(
224344
/>
225345
</TableCell>
226346

347+
{/* Invocation ID */}
348+
<TableCell className="py-3 text-xs">
349+
<InvocationId
350+
invocationId={row.execution_metadata?.invocation_id}
351+
/>
352+
</TableCell>
353+
227354
{/* Rollout ID */}
228355
<TableCell className="py-3 text-xs">
229356
<RolloutId rolloutId={row.execution_metadata?.rollout_id} />
@@ -248,7 +375,7 @@ export const EvaluationRow = observer(
248375
{/* Expanded Content Row */}
249376
{isExpanded && (
250377
<tr>
251-
<td colSpan={8} className="p-0">
378+
<td colSpan={9} className="p-0">
252379
<ExpandedContent
253380
row={row}
254381
messages={row.messages}

vite-app/src/components/EvaluationTable.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const EvaluationTable = observer(() => {
4848
};
4949

5050
const handleFiltersChange = (filters: any[]) => {
51-
state.updateTableFilterConfig(filters);
51+
state.updateFilterConfig(filters);
5252
};
5353

5454
return (
@@ -59,7 +59,7 @@ export const EvaluationTable = observer(() => {
5959
<div className="flex items-center gap-4">
6060
<h3 className="text-sm font-medium text-gray-700">Table Filters</h3>
6161
<div className="text-xs text-gray-600">
62-
{state.tableFilterConfig.length > 0 ? (
62+
{state.filterConfig.length > 0 ? (
6363
<>
6464
Showing {totalRows} of {state.sortedDataset.length} rows
6565
{totalRows !== state.sortedDataset.length && (
@@ -74,7 +74,7 @@ export const EvaluationTable = observer(() => {
7474
</div>
7575
<div className="bg-white rounded-lg">
7676
<FilterSelector
77-
filters={state.tableFilterConfig}
77+
filters={state.filterConfig}
7878
onFiltersChange={handleFiltersChange}
7979
availableKeys={state.flattenedDatasetKeys}
8080
title=""
@@ -162,6 +162,7 @@ export const EvaluationTable = observer(() => {
162162
<TableHeader className="w-8">&nbsp;</TableHeader>
163163
<TableHeader>Name</TableHeader>
164164
<TableHeader>Status</TableHeader>
165+
<TableHeader>Invocation ID</TableHeader>
165166
<TableHeader>Rollout ID</TableHeader>
166167
<TableHeader>Model</TableHeader>
167168
<TableHeader>Score</TableHeader>

0 commit comments

Comments
 (0)