@@ -104,6 +104,30 @@ type Matcher = {
104104 diff ?: DiffMatcherItem [ ] ;
105105} & Record < string , ToolMatcher | IdMatcher | DiffMatcherItem [ ] | undefined > ;
106106
107+ type IntermediateStatusColumn = ToolColumn & {
108+ type : "status" ;
109+ categories : Record < string , true > ;
110+ statuses : Record < string , true > ;
111+ idx : number ;
112+ } ;
113+
114+ type IntermediateTextColumn = ToolColumn & {
115+ type : "text" ;
116+ distincts : Record < string , true > ;
117+ idx : number ;
118+ } ;
119+
120+ type IntermediateNumericColumn = ToolColumn & {
121+ min : number ;
122+ max : number ;
123+ idx : number ;
124+ } ;
125+
126+ type IntermediateColumn =
127+ | IntermediateStatusColumn
128+ | IntermediateTextColumn
129+ | IntermediateNumericColumn ;
130+
107131const asRecord = ( value : unknown ) : value is Record < string , unknown > =>
108132 typeof value === "object" && value !== null ;
109133
@@ -126,80 +150,80 @@ const getFilterableData = ({ tools, rows }: Dataset) => {
126150 const { tool : toolName , date, niceName } = tool ;
127151 const name = `${ toolName } ${ date } ${ niceName } ` ;
128152
129- const columns = tool . columns . map ( ( col , colIdx ) => {
130- if ( ! col ) {
131- return undefined ;
132- }
133- if ( col . type === "status" ) {
134- statusIdx = colIdx ;
153+ const columns = tool . columns . map (
154+ ( col , colIdx ) : IntermediateColumn | undefined => {
155+ if ( ! col ) {
156+ return undefined ;
157+ }
158+ if ( col . type === "status" ) {
159+ statusIdx = colIdx ;
160+ return {
161+ ...col ,
162+ type : "status" ,
163+ categories : { } ,
164+ statuses : { } ,
165+ idx : colIdx ,
166+ } ;
167+ }
168+ if ( col . type === "text" ) {
169+ return {
170+ ...col ,
171+ type : "text" ,
172+ distincts : { } ,
173+ idx : colIdx ,
174+ } ;
175+ }
135176 return {
136177 ...col ,
137- categories : { } as Record < string , true > ,
138- statuses : { } as Record < string , true > ,
178+ min : Infinity ,
179+ max : - Infinity ,
139180 idx : colIdx ,
140181 } ;
141- }
142- if ( col . type === "text" ) {
143- return { ...col , distincts : { } as Record < string , true > , idx : colIdx } ;
144- }
145- return { ...col , min : Infinity , max : - Infinity , idx : colIdx } ;
146- } ) ;
182+ } ,
183+ ) ;
147184
148185 if ( ! isNil ( statusIdx ) ) {
149186 const current = columns [ statusIdx ] ;
150- if ( current ) {
187+ if ( current && current . type === "status" ) {
151188 // NOTE (JS->TS): Added defensive check because columns entries may be undefined.
152189 columns [ statusIdx ] = {
153190 ...current ,
154- categories : { } as Record < string , true > ,
155- statuses : { } as Record < string , true > ,
191+ categories : { } ,
192+ statuses : { } ,
156193 } ;
157194 }
158195 }
159196
160- for ( const row of rows ) {
197+ rows . forEach ( ( row ) => {
161198 const result = row . results [ idx ] ;
162199 // convention as of writing this commit is to postfix categories with a space character
163200 if ( ! isNil ( statusIdx ) ) {
164- const statusCol = columns [ statusIdx ] ;
165- if ( statusCol && "categories" in statusCol ) {
166- ( statusCol . categories as Record < string , true > ) [
167- ` ${ result . category } `
168- ] = true ;
201+ const statusCol = columns [ statusIdx ] as
202+ | IntermediateStatusColumn
203+ | undefined ;
204+ if ( statusCol ) {
205+ statusCol . categories [ ` ${ result . category } ` ] = true ;
169206 }
170207 }
171208
172- for ( const colIdxStr in result . values ) {
173- // NOTE (JS->TS): Convert for..in string keys to numbers for safe array indexing.
174- const colIdx = Number ( colIdxStr ) ;
175-
176- const col = result . values [ colIdx ] ;
209+ result . values . forEach ( ( col , colIdx ) => {
177210 const { raw } = col ;
178211 const filterCol = columns [ colIdx ] ;
179212 if ( ! filterCol || isNil ( raw ) ) {
180- continue ;
213+ return ;
181214 }
182215
183216 if ( filterCol . type === "status" ) {
184- ( filterCol as { statuses : Record < string , true > } ) . statuses [
185- String ( raw )
186- ] = true ;
217+ ( filterCol as IntermediateStatusColumn ) . statuses [ String ( raw ) ] = true ;
187218 } else if ( filterCol . type === "text" ) {
188- ( filterCol as { distincts : Record < string , true > } ) . distincts [
189- String ( raw )
190- ] = true ;
191- } else {
192- ( filterCol as { min : number ; max : number } ) . min = Math . min (
193- ( filterCol as { min : number } ) . min ,
194- Number ( raw ) ,
195- ) ;
196- ( filterCol as { min : number ; max : number } ) . max = Math . max (
197- ( filterCol as { max : number } ) . max ,
198- Number ( raw ) ,
199- ) ;
219+ ( filterCol as IntermediateTextColumn ) . distincts [ String ( raw ) ] = true ;
220+ } else if ( "min" in filterCol && "max" in filterCol ) {
221+ const numCol = filterCol as IntermediateNumericColumn ;
222+ numCol . min = Math . min ( numCol . min , Number ( raw ) ) ;
223+ numCol . max = Math . max ( numCol . max , Number ( raw ) ) ;
200224 }
201- }
202- }
225+ } ) ;
226+ } ) ;
203227
204228 return {
205229 name,
@@ -208,29 +232,25 @@ const getFilterableData = ({ tools, rows }: Dataset) => {
208232 return undefined ;
209233 }
210234
211- const colRec = c as Record < string , unknown > ;
212- const distincts = colRec . distincts as Record < string , true > | undefined ;
213- const categories = colRec . categories as
214- | Record < string , true >
215- | undefined ;
216- const statuses = colRec . statuses as Record < string , true > | undefined ;
217-
218- const rest = { ...colRec } ;
235+ const { idx : _idx , ...rest } = c ;
219236
220- // NOTE (JS->TS): Explicitly remove internal aggregation fields instead of
221- // using unused destructuring bindings to satisfy ESLint no-unused-vars rule.
222- delete ( rest as Record < string , unknown > ) . distincts ;
223- delete ( rest as Record < string , unknown > ) . categories ;
224- delete ( rest as Record < string , unknown > ) . statuses ;
225-
226- if ( distincts ) {
227- return { ...rest , distincts : Object . keys ( distincts ) } ;
228- }
229- if ( categories ) {
237+ if ( c . type === "status" ) {
238+ const statusCol = c as IntermediateStatusColumn ;
239+ const { categories, statuses, ...restOfCol } = statusCol ;
240+ void restOfCol ;
230241 return {
231242 ...rest ,
232243 categories : Object . keys ( categories ) ,
233- statuses : Object . keys ( statuses ?? { } ) ,
244+ statuses : Object . keys ( statuses ) ,
245+ } ;
246+ }
247+ if ( c . type === "text" ) {
248+ const textCol = c as IntermediateTextColumn ;
249+ const { distincts, ...restOfCol } = textCol ;
250+ void restOfCol ;
251+ return {
252+ ...rest ,
253+ distincts : Object . keys ( distincts ) ,
234254 } ;
235255 }
236256 return rest ;
@@ -243,10 +263,7 @@ const getFilterableData = ({ tools, rows }: Dataset) => {
243263const applyNumericFilter = (
244264 filter : { id : string ; value : string } ,
245265 row : Record < string , unknown > ,
246- cell : unknown ,
247266) => {
248- void cell ;
249-
250267 // NOTE (JS->TS): Pass explicit default value for compatibility with typed signature.
251268 const raw = getRawOrDefault ( row [ filter . id ] as unknown , undefined ) ;
252269 if ( raw === undefined ) {
@@ -275,10 +292,7 @@ const applyNumericFilter = (
275292const applyTextFilter = (
276293 filter : { id : string ; value : string } ,
277294 row : Record < string , unknown > ,
278- cell : unknown ,
279295) => {
280- void cell ;
281-
282296 // NOTE (JS->TS): Pass explicit default value for compatibility with typed signature.
283297 const raw = getRawOrDefault ( row [ filter . id ] as unknown , undefined ) ;
284298 if ( raw === undefined ) {
@@ -464,40 +478,29 @@ const applyMatcher = (matcher: Matcher) => (data: TableRow[]) => {
464478 const columnIdx = Number ( columnKey ) ;
465479
466480 for ( const filter of columnFilters ) {
467- if ( ! asRecord ( filter ) ) {
468- continue ;
469- }
470-
471- const value = filter . value ;
472- const min = filter . min ;
473- const max = filter . max ;
474- const category = filter . category ;
475- const status = filter . status ;
476-
477- if ( ! isNil ( min ) && ! isNil ( max ) ) {
481+ if ( "min" in filter && "max" in filter ) {
478482 const rawValue = row . results [ toolIdx ] ?. values [ columnIdx ] ?. raw ;
479483 if ( isNil ( rawValue ) ) {
480484 columnPass = false ;
481485 continue ;
482486 }
483487 const num = Number ( rawValue ) ;
484- columnPass =
485- columnPass || ( num >= Number ( min ) && num <= Number ( max ) ) ;
486- } else if ( ! isNil ( category ) ) {
488+ columnPass = columnPass || ( num >= filter . min && num <= filter . max ) ;
489+ } else if ( "category" in filter ) {
487490 categoryPass =
488- row . results [ toolIdx ] ?. category === String ( category ) ||
491+ row . results [ toolIdx ] ?. category === String ( filter . category ) ||
489492 categoryPass ;
490493 columnPass = categoryPass && statusPass ;
491- } else if ( ! isNil ( status ) ) {
494+ } else if ( " status" in filter ) {
492495 const emptyRowPass =
493496 row . results [ toolIdx ] ?. category === "empty" &&
494- String ( status ) === statusForEmptyRows ;
497+ String ( filter . status ) === statusForEmptyRows ;
495498 statusPass =
496- row . results [ toolIdx ] ?. values [ columnIdx ] ?. raw === status ||
499+ row . results [ toolIdx ] ?. values [ columnIdx ] ?. raw === filter . status ||
497500 statusPass ||
498501 emptyRowPass ;
499502 columnPass = categoryPass && statusPass ;
500- } else {
503+ } else if ( "value" in filter ) {
501504 const rawValue = row . results [ toolIdx ] ?. values [ columnIdx ] ?. raw ;
502505 if ( isNil ( rawValue ) ) {
503506 columnPass = false ;
@@ -506,8 +509,8 @@ const applyMatcher = (matcher: Matcher) => (data: TableRow[]) => {
506509 const rawStr = String ( rawValue ) ;
507510 columnPass =
508511 columnPass ||
509- String ( value ) === rawStr ||
510- rawStr . includes ( String ( value ) ) ;
512+ String ( filter . value ) === rawStr ||
513+ rawStr . includes ( String ( filter . value ) ) ;
511514 }
512515
513516 if ( columnPass ) {
0 commit comments