Skip to content

Commit ef85129

Browse files
committed
Merge branch 'feature/ui-improvements-test-comparison' into 'develop'
UI Improvements for Test Studio Cost Comparison and Results See merge request genaiic-reusable-assets/engagement-artifacts/genaiic-idp-accelerator!449
2 parents 811e062 + 0ded5a6 commit ef85129

File tree

3 files changed

+271
-53
lines changed

3 files changed

+271
-53
lines changed

src/ui/src/components/test-studio/TestComparison.jsx

Lines changed: 180 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)