@@ -125,6 +125,120 @@ const AggregatorSelector = ({
125125 </ div >
126126) ;
127127
128+ const FilterSelector = ( {
129+ filters,
130+ onFiltersChange,
131+ availableKeys,
132+ } : {
133+ filters : Array < { field : string ; operator : string ; value : string } > ;
134+ onFiltersChange : (
135+ filters : Array < { field : string ; operator : string ; value : string } >
136+ ) => void ;
137+ availableKeys : string [ ] ;
138+ } ) => {
139+ const addFilter = ( ) => {
140+ onFiltersChange ( [ ...filters , { field : "" , operator : "==" , value : "" } ] ) ;
141+ } ;
142+
143+ const removeFilter = ( index : number ) => {
144+ onFiltersChange ( filters . filter ( ( _ , i ) => i !== index ) ) ;
145+ } ;
146+
147+ const updateFilter = (
148+ index : number ,
149+ field : string ,
150+ operator : string ,
151+ value : string
152+ ) => {
153+ const newFilters = [ ...filters ] ;
154+ newFilters [ index ] = { field, operator, value } ;
155+ onFiltersChange ( newFilters ) ;
156+ } ;
157+
158+ const operators = [
159+ { value : "==" , label : "equals" } ,
160+ { value : "!=" , label : "not equals" } ,
161+ { value : ">" , label : "greater than" } ,
162+ { value : "<" , label : "less than" } ,
163+ { value : ">=" , label : "greater than or equal" } ,
164+ { value : "<=" , label : "less than or equal" } ,
165+ { value : "contains" , label : "contains" } ,
166+ { value : "!contains" , label : "not contains" } ,
167+ ] ;
168+
169+ return (
170+ < div className = "mb-4" >
171+ < div className = "text-xs font-medium text-gray-700 mb-2" > Filters:</ div >
172+ < div className = "space-y-2" >
173+ { filters . map ( ( filter , index ) => (
174+ < div key = { index } className = "flex items-center space-x-2" >
175+ < Select
176+ value = { filter . field }
177+ onChange = { ( e ) =>
178+ updateFilter (
179+ index ,
180+ e . target . value ,
181+ filter . operator ,
182+ filter . value
183+ )
184+ }
185+ size = "sm"
186+ className = "min-w-48"
187+ >
188+ < option value = "" > Select a field...</ option >
189+ { availableKeys ?. map ( ( key ) => (
190+ < option key = { key } value = { key } >
191+ { key }
192+ </ option >
193+ ) ) }
194+ </ Select >
195+ < Select
196+ value = { filter . operator }
197+ onChange = { ( e ) =>
198+ updateFilter ( index , filter . field , e . target . value , filter . value )
199+ }
200+ size = "sm"
201+ className = "min-w-32"
202+ >
203+ { operators . map ( ( op ) => (
204+ < option key = { op . value } value = { op . value } >
205+ { op . label }
206+ </ option >
207+ ) ) }
208+ </ Select >
209+ < input
210+ type = "text"
211+ value = { filter . value }
212+ onChange = { ( e ) =>
213+ updateFilter (
214+ index ,
215+ filter . field ,
216+ filter . operator ,
217+ e . target . value
218+ )
219+ }
220+ placeholder = "Value"
221+ className = "px-2 py-1 text-xs border border-gray-300 rounded focus:outline-none focus:border-gray-500 min-w-32"
222+ />
223+ < button
224+ onClick = { ( ) => removeFilter ( index ) }
225+ className = "text-xs text-red-600 hover:text-red-800 px-2 py-1"
226+ >
227+ Remove
228+ </ button >
229+ </ div >
230+ ) ) }
231+ < button
232+ onClick = { addFilter }
233+ className = "text-xs text-blue-600 hover:text-blue-800 px-2 py-1"
234+ >
235+ + Add Filter
236+ </ button >
237+ </ div >
238+ </ div >
239+ ) ;
240+ } ;
241+
128242const PivotTab = observer ( ( ) => {
129243 const [ selectedRowFields , setSelectedRowFields ] = useState < string [ ] > ( [
130244 "$.eval_metadata.name" ,
@@ -136,6 +250,9 @@ const PivotTab = observer(() => {
136250 "$.evaluation_result.score"
137251 ) ;
138252 const [ selectedAggregator , setSelectedAggregator ] = useState < string > ( "avg" ) ;
253+ const [ filters , setFilters ] = useState <
254+ Array < { field : string ; operator : string ; value : string } >
255+ > ( [ ] ) ;
139256
140257 const createFieldHandler = (
141258 setter : React . Dispatch < React . SetStateAction < string [ ] > >
@@ -165,7 +282,48 @@ const PivotTab = observer(() => {
165282 } ;
166283 } ;
167284
168- const availableKeys = state . flattenedDatasetKeys [ 0 ] || [ ] ;
285+ const availableKeys = state . flattenedDatasetKeys ;
286+
287+ // Create filter function from filter configuration
288+ const createFilterFunction = (
289+ filters : Array < { field : string ; operator : string ; value : string } >
290+ ) => {
291+ if ( filters . length === 0 ) return undefined ;
292+
293+ return ( record : any ) => {
294+ return filters . every ( ( filter ) => {
295+ if ( ! filter . field || ! filter . value ) return true ; // Skip incomplete filters
296+
297+ const fieldValue = record [ filter . field ] ;
298+ const filterValue = filter . value ;
299+
300+ switch ( filter . operator ) {
301+ case "==" :
302+ return String ( fieldValue ) === filterValue ;
303+ case "!=" :
304+ return String ( fieldValue ) !== filterValue ;
305+ case ">" :
306+ return Number ( fieldValue ) > Number ( filterValue ) ;
307+ case "<" :
308+ return Number ( fieldValue ) < Number ( filterValue ) ;
309+ case ">=" :
310+ return Number ( fieldValue ) >= Number ( filterValue ) ;
311+ case "<=" :
312+ return Number ( fieldValue ) <= Number ( filterValue ) ;
313+ case "contains" :
314+ return String ( fieldValue )
315+ . toLowerCase ( )
316+ . includes ( filterValue . toLowerCase ( ) ) ;
317+ case "!contains" :
318+ return ! String ( fieldValue )
319+ . toLowerCase ( )
320+ . includes ( filterValue . toLowerCase ( ) ) ;
321+ default :
322+ return true ;
323+ }
324+ } ) ;
325+ } ;
326+ } ;
169327
170328 return (
171329 < div >
@@ -208,6 +366,12 @@ const PivotTab = observer(() => {
208366 onAggregatorChange = { setSelectedAggregator }
209367 />
210368
369+ < FilterSelector
370+ filters = { filters }
371+ onFiltersChange = { setFilters }
372+ availableKeys = { availableKeys }
373+ />
374+
211375 < PivotTable
212376 data = { state . flattenedDataset }
213377 rowFields = {
@@ -226,6 +390,7 @@ const PivotTab = observer(() => {
226390 aggregator = { selectedAggregator as any }
227391 showRowTotals
228392 showColumnTotals
393+ filter = { createFilterFunction ( filters ) }
229394 />
230395 </ div >
231396 ) ;
0 commit comments