@@ -103,15 +103,39 @@ const TestComparison = ({ preSelectedTestRunIds = [] }) => {
103103 } , [ preSelectedTestRunIds ] ) ;
104104
105105 // Helper function to create clickable test run ID headers
106- const createTestRunHeader = ( testRunId ) => {
106+ const createTestRunHeader = ( testRunId , truncate = false ) => {
107+ const displayId = truncate ? `T${ Object . keys ( completeTestRuns ) . indexOf ( testRunId ) + 1 } ` : testRunId ;
108+
109+ if ( truncate ) {
110+ return (
111+ < span
112+ title = { testRunId }
113+ style = { { cursor : 'pointer' , color : '#0073bb' } }
114+ role = "button"
115+ tabIndex = { 0 }
116+ onClick = { ( ) => {
117+ window . location . hash = `#/test-studio?tab=results&testRunId=${ testRunId } ` ;
118+ } }
119+ onKeyDown = { ( e ) => {
120+ if ( e . key === 'Enter' || e . key === ' ' ) {
121+ window . location . hash = `#/test-studio?tab=results&testRunId=${ testRunId } ` ;
122+ }
123+ } }
124+ >
125+ { displayId }
126+ </ span >
127+ ) ;
128+ }
129+
107130 return (
108131 < Button
109132 variant = "link"
110133 onClick = { ( ) => {
111134 window . location . hash = `#/test-studio?tab=results&testRunId=${ testRunId } ` ;
112135 } }
136+ title = { testRunId }
113137 >
114- { testRunId }
138+ { displayId }
115139 </ Button >
116140 ) ;
117141 } ;
@@ -199,29 +223,75 @@ const TestComparison = ({ preSelectedTestRunIds = [] }) => {
199223 // Add cost breakdown header
200224 costRows . push ( [ 'Context' , 'Service/Api' , 'Unit' , ...Object . keys ( completeTestRuns ) ] ) ;
201225
202- Array . from ( allCostItems )
203- . sort ( )
204- . forEach ( ( itemKey ) => {
205- const [ context , serviceApi , unit ] = itemKey . split ( '|' ) ;
206- const row = [ context , serviceApi , unit ] ;
226+ const sortedCostItems = Array . from ( allCostItems ) . sort ( ) ;
227+ const contexts = [ ...new Set ( sortedCostItems . map ( ( item ) => item . split ( '|' ) [ 0 ] ) ) ] ;
228+
229+ contexts . forEach ( ( context ) => {
230+ const contextItems = sortedCostItems . filter ( ( item ) => item . startsWith ( `${ context } |` ) ) ;
231+
232+ // Add context items
233+ contextItems . forEach ( ( itemKey ) => {
234+ const [ ctx , serviceApi , unit ] = itemKey . split ( '|' ) ;
235+ const row = [ ctx , serviceApi , unit ] ;
207236
208237 Object . entries ( completeTestRuns ) . forEach ( ( [ testRunId , testRun ] ) => {
209- const services = testRun . costBreakdown ?. [ context ] || { } ;
238+ const services = testRun . costBreakdown ?. [ ctx ] || { } ;
210239 const serviceKey = Object . keys ( services ) . find ( ( key ) => {
211240 const lastUnderscoreIndex = key . lastIndexOf ( '_' ) ;
212241 const keyServiceApi = key . substring ( 0 , lastUnderscoreIndex ) ;
213- const [ keyService , keyApi ] = keyServiceApi . split ( '/' ) ;
214- return ` ${ keyService } / ${ keyApi } ` === serviceApi ;
242+ const keyUnit = key . substring ( lastUnderscoreIndex + 1 ) ;
243+ return keyServiceApi === serviceApi && keyUnit === unit ;
215244 } ) ;
216245
217246 const details = services [ serviceKey ] || { } ;
218247 const estimatedCost = details . estimated_cost || 0 ;
219- row . push ( estimatedCost > 0 ? `$${ estimatedCost . toFixed ( 4 ) } ` : '$0.0000 ' ) ;
248+ row . push ( estimatedCost > 0 ? `$${ estimatedCost . toFixed ( 4 ) } ` : 'N/A ' ) ;
220249 } ) ;
221250
222251 costRows . push ( row ) ;
223252 } ) ;
224253
254+ // Add subtotal row
255+ const subtotalRow = [ '' , `${ context } Subtotal` , '' ] ;
256+ Object . keys ( completeTestRuns ) . forEach ( ( testRunId ) => {
257+ const contextTotal = contextItems . reduce ( ( sum , itemKey ) => {
258+ const [ ctx , serviceApi , unit ] = itemKey . split ( '|' ) ;
259+ const services = completeTestRuns [ testRunId ] . costBreakdown ?. [ ctx ] || { } ;
260+ const serviceKey = Object . keys ( services ) . find ( ( key ) => {
261+ const lastUnderscoreIndex = key . lastIndexOf ( '_' ) ;
262+ const keyServiceApi = key . substring ( 0 , lastUnderscoreIndex ) ;
263+ const keyUnit = key . substring ( lastUnderscoreIndex + 1 ) ;
264+ return keyServiceApi === serviceApi && keyUnit === unit ;
265+ } ) ;
266+ const details = services [ serviceKey ] || { } ;
267+ const estimatedCost = details . estimated_cost || 0 ;
268+ return sum + estimatedCost ;
269+ } , 0 ) ;
270+ subtotalRow . push ( `$${ contextTotal . toFixed ( 4 ) } ` ) ;
271+ } ) ;
272+ costRows . push ( subtotalRow ) ;
273+ } ) ;
274+
275+ // Add total row
276+ const totalRow = [ '' , 'Total' , '' ] ;
277+ Object . keys ( completeTestRuns ) . forEach ( ( testRunId ) => {
278+ const grandTotal = sortedCostItems . reduce ( ( sum , itemKey ) => {
279+ const [ context , serviceApi , unit ] = itemKey . split ( '|' ) ;
280+ const services = completeTestRuns [ testRunId ] . costBreakdown ?. [ context ] || { } ;
281+ const serviceKey = Object . keys ( services ) . find ( ( key ) => {
282+ const lastUnderscoreIndex = key . lastIndexOf ( '_' ) ;
283+ const keyServiceApi = key . substring ( 0 , lastUnderscoreIndex ) ;
284+ const keyUnit = key . substring ( lastUnderscoreIndex + 1 ) ;
285+ return keyServiceApi === serviceApi && keyUnit === unit ;
286+ } ) ;
287+ const details = services [ serviceKey ] || { } ;
288+ const estimatedCost = details . estimated_cost || 0 ;
289+ return sum + estimatedCost ;
290+ } , 0 ) ;
291+ totalRow . push ( `$${ grandTotal . toFixed ( 4 ) } ` ) ;
292+ } ) ;
293+ costRows . push ( totalRow ) ;
294+
225295 // Add usage breakdown rows
226296 const usageRows = [ ] ;
227297 usageRows . push ( [ 'Context' , 'Service/Api' , 'Unit' , ...Object . keys ( completeTestRuns ) ] ) ;
@@ -237,13 +307,13 @@ const TestComparison = ({ preSelectedTestRunIds = [] }) => {
237307 const serviceKey = Object . keys ( services ) . find ( ( key ) => {
238308 const lastUnderscoreIndex = key . lastIndexOf ( '_' ) ;
239309 const keyServiceApi = key . substring ( 0 , lastUnderscoreIndex ) ;
240- const [ keyService , keyApi ] = keyServiceApi . split ( '/' ) ;
241- return ` ${ keyService } / ${ keyApi } ` === serviceApi ;
310+ const keyUnit = key . substring ( lastUnderscoreIndex + 1 ) ;
311+ return keyServiceApi === serviceApi && keyUnit === unit ;
242312 } ) ;
243313
244314 const details = services [ serviceKey ] || { } ;
245315 const value = details . value || 0 ;
246- row . push ( value > 0 ? value . toLocaleString ( ) : '0 ' ) ;
316+ row . push ( value > 0 ? value . toLocaleString ( ) : 'N/A ' ) ;
247317 } ) ;
248318
249319 usageRows . push ( row ) ;
@@ -584,7 +654,7 @@ const TestComparison = ({ preSelectedTestRunIds = [] }) => {
584654 { id : 'metric' , header : 'Metric' , cell : ( item ) => item . metric } ,
585655 ...Object . keys ( completeTestRuns ) . map ( ( testRunId ) => ( {
586656 id : testRunId ,
587- header : createTestRunHeader ( testRunId ) ,
657+ header : createTestRunHeader ( testRunId , true ) ,
588658 cell : ( item ) => {
589659 return item [ testRunId ] ;
590660 } ,
@@ -619,7 +689,7 @@ const TestComparison = ({ preSelectedTestRunIds = [] }) => {
619689 { id : 'setting' , header : 'Config' , cell : ( item ) => item . setting } ,
620690 ...Object . keys ( completeTestRuns ) . map ( ( testRunId ) => ( {
621691 id : testRunId ,
622- header : createTestRunHeader ( testRunId ) ,
692+ header : createTestRunHeader ( testRunId , true ) ,
623693 cell : ( item ) => item [ testRunId ] || 'N/A' ,
624694 } ) ) ,
625695 ] }
@@ -679,7 +749,7 @@ const TestComparison = ({ preSelectedTestRunIds = [] }) => {
679749 { id : 'metric' , header : 'Accuracy Metric' , cell : ( item ) => item . metric } ,
680750 ...Object . keys ( completeTestRuns ) . map ( ( testRunId ) => ( {
681751 id : testRunId ,
682- header : createTestRunHeader ( testRunId ) ,
752+ header : createTestRunHeader ( testRunId , true ) ,
683753 cell : ( item ) => item [ testRunId ] ,
684754 } ) ) ,
685755 ] }
@@ -723,13 +793,13 @@ const TestComparison = ({ preSelectedTestRunIds = [] }) => {
723793 const serviceKey = Object . keys ( services ) . find ( ( key ) => {
724794 const lastUnderscoreIndex = key . lastIndexOf ( '_' ) ;
725795 const keyServiceApi = key . substring ( 0 , lastUnderscoreIndex ) ;
726- const [ keyService , keyApi ] = keyServiceApi . split ( '/' ) ;
727- return ` ${ keyService } / ${ keyApi } ` === serviceApi ;
796+ const keyUnit = key . substring ( lastUnderscoreIndex + 1 ) ;
797+ return keyServiceApi === serviceApi && keyUnit === unit ;
728798 } ) ;
729799
730800 const details = services [ serviceKey ] || { } ;
731801 const estimatedCost = details . estimated_cost || 0 ;
732- row [ testRunId ] = estimatedCost > 0 ? `$${ estimatedCost . toFixed ( 4 ) } ` : '$0.0000 ' ;
802+ row [ testRunId ] = estimatedCost > 0 ? `$${ estimatedCost . toFixed ( 4 ) } ` : 'N/A ' ;
733803 } ) ;
734804
735805 return row ;
@@ -741,33 +811,104 @@ const TestComparison = ({ preSelectedTestRunIds = [] }) => {
741811 return a . serviceApi . localeCompare ( b . serviceApi ) ;
742812 } ) ;
743813
744- return tableItems . length > 0 ? (
814+ // Add context subtotals
815+ const finalItems = [ ] ;
816+ tableItems . forEach ( ( item , index ) => {
817+ finalItems . push ( item ) ;
818+
819+ // Check if this is the last item for this context
820+ const nextItem = tableItems [ index + 1 ] ;
821+ const isLastInContext = ! nextItem || nextItem . context !== item . context ;
822+
823+ if ( isLastInContext ) {
824+ // Calculate subtotal for this context
825+ const contextItems = tableItems . filter ( ( i ) => i . context === item . context ) ;
826+ const subtotalRow = {
827+ context : '' ,
828+ serviceApi : `${ item . context } Subtotal` ,
829+ unit : '' ,
830+ isSubtotal : true ,
831+ } ;
832+
833+ Object . keys ( completeTestRuns ) . forEach ( ( testRunId ) => {
834+ const contextTotal = contextItems . reduce ( ( sum , contextItem ) => {
835+ const value = contextItem [ testRunId ] ;
836+ if ( value === 'N/A' || ! value ) return sum ;
837+ const numValue = parseFloat ( value . replace ( '$' , '' ) ) ;
838+ return sum + ( isNaN ( numValue ) ? 0 : numValue ) ;
839+ } , 0 ) ;
840+ subtotalRow [ testRunId ] = `$${ contextTotal . toFixed ( 4 ) } ` ;
841+ } ) ;
842+
843+ finalItems . push ( subtotalRow ) ;
844+ }
845+ } ) ;
846+
847+ // Add total row
848+ const totalRow = {
849+ context : '' ,
850+ serviceApi : 'Total' ,
851+ unit : '' ,
852+ isTotal : true ,
853+ } ;
854+
855+ Object . keys ( completeTestRuns ) . forEach ( ( testRunId ) => {
856+ const grandTotal = tableItems . reduce ( ( sum , item ) => {
857+ const value = item [ testRunId ] ;
858+ if ( value === 'N/A' || ! value ) return sum ;
859+ const numValue = parseFloat ( value . replace ( '$' , '' ) ) ;
860+ return sum + ( isNaN ( numValue ) ? 0 : numValue ) ;
861+ } , 0 ) ;
862+ totalRow [ testRunId ] = `$${ grandTotal . toFixed ( 4 ) } ` ;
863+ } ) ;
864+
865+ finalItems . push ( totalRow ) ;
866+
867+ return finalItems . length > 0 ? (
745868 < Table
746- items = { tableItems }
869+ items = { finalItems }
747870 columnDefinitions = { [
748871 {
749872 id : 'context' ,
750873 header : 'Context' ,
751- cell : ( item ) => item . context ,
752- width : 150 ,
874+ cell : ( item ) => ( item . isSubtotal || item . isTotal ? '' : item . context ) ,
875+ width : 120 ,
753876 } ,
754877 {
755878 id : 'serviceApi' ,
756879 header : 'Service/Api' ,
757- cell : ( item ) => item . serviceApi ,
758- width : 250 ,
880+ cell : ( item ) => (
881+ < span
882+ style = { {
883+ fontWeight : item . isSubtotal || item . isTotal ? 'bold' : 'normal' ,
884+ color : item . isTotal ? '#0073bb' : 'inherit' ,
885+ } }
886+ >
887+ { item . serviceApi }
888+ </ span >
889+ ) ,
890+ width : 200 ,
759891 } ,
760892 {
761893 id : 'unit' ,
762894 header : 'Unit' ,
763- cell : ( item ) => item . unit ,
764- width : 120 ,
895+ cell : ( item ) => ( item . isSubtotal || item . isTotal ? '' : item . unit ) ,
896+ width : 100 ,
765897 } ,
766898 ...Object . keys ( completeTestRuns ) . map ( ( testRunId ) => ( {
767899 id : testRunId ,
768- header : createTestRunHeader ( testRunId ) ,
769- cell : ( item ) => item [ testRunId ] || '$0.0000' ,
770- width : 120 ,
900+ header : createTestRunHeader ( testRunId , true ) ,
901+ cell : ( item ) => (
902+ < span
903+ style = { {
904+ fontWeight : item . isSubtotal || item . isTotal ? 'bold' : 'normal' ,
905+ color : item . isTotal ? '#0073bb' : 'inherit' ,
906+ } }
907+ >
908+ { item [ testRunId ] || '$0.0000' }
909+ </ span >
910+ ) ,
911+ width : 80 ,
771912 } ) ) ,
772913 ] }
773914 variant = "embedded"
@@ -812,13 +953,13 @@ const TestComparison = ({ preSelectedTestRunIds = [] }) => {
812953 const serviceKey = Object . keys ( services ) . find ( ( key ) => {
813954 const lastUnderscoreIndex = key . lastIndexOf ( '_' ) ;
814955 const keyServiceApi = key . substring ( 0 , lastUnderscoreIndex ) ;
815- const [ keyService , keyApi ] = keyServiceApi . split ( '/' ) ;
816- return ` ${ keyService } / ${ keyApi } ` === serviceApi ;
956+ const keyUnit = key . substring ( lastUnderscoreIndex + 1 ) ;
957+ return keyServiceApi === serviceApi && keyUnit === unit ;
817958 } ) ;
818959
819960 const details = services [ serviceKey ] || { } ;
820961 const value = details . value || 0 ;
821- row [ testRunId ] = value > 0 ? value . toLocaleString ( ) : '0 ' ;
962+ row [ testRunId ] = value > 0 ? value . toLocaleString ( ) : 'N/A ' ;
822963 } ) ;
823964
824965 return row ;
@@ -838,25 +979,25 @@ const TestComparison = ({ preSelectedTestRunIds = [] }) => {
838979 id : 'context' ,
839980 header : 'Context' ,
840981 cell : ( item ) => item . context ,
841- width : 150 ,
982+ width : 120 ,
842983 } ,
843984 {
844985 id : 'serviceApi' ,
845986 header : 'Service/Api' ,
846987 cell : ( item ) => item . serviceApi ,
847- width : 250 ,
988+ width : 200 ,
848989 } ,
849990 {
850991 id : 'unit' ,
851992 header : 'Unit' ,
852993 cell : ( item ) => item . unit ,
853- width : 120 ,
994+ width : 100 ,
854995 } ,
855996 ...Object . keys ( completeTestRuns ) . map ( ( testRunId ) => ( {
856997 id : testRunId ,
857- header : createTestRunHeader ( testRunId ) ,
998+ header : createTestRunHeader ( testRunId , true ) ,
858999 cell : ( item ) => item [ testRunId ] || '0' ,
859- width : 120 ,
1000+ width : 60 ,
8601001 } ) ) ,
8611002 ] }
8621003 variant = "embedded"
0 commit comments