diff --git a/web/packages/studio/src/components/dataViews/ExperimentGroupDataView/index.tsx b/web/packages/studio/src/components/dataViews/ExperimentGroupDataView/index.tsx index 31a6464fca..bd74d88da9 100644 --- a/web/packages/studio/src/components/dataViews/ExperimentGroupDataView/index.tsx +++ b/web/packages/studio/src/components/dataViews/ExperimentGroupDataView/index.tsx @@ -113,6 +113,18 @@ export const ExperimentGroupDataView: FC = ({ [tableData] ); + // One column per metadata key: keys are lowercased so case variants (e.g. "status" + // and "Status") collapse into one column rather than producing duplicate headers. + const metadataKeys = useMemo( + () => + [ + ...new Set( + tableData.flatMap((e) => Object.keys(e.metadata ?? {}).map((k) => k.toLowerCase())) + ), + ].sort(), + [tableData] + ); + const makeColumns = useCallback< ComponentProps>['makeColumns'] >( @@ -181,6 +193,36 @@ export const ExperimentGroupDataView: FC = ({ enableSorting: false, cell: ({ getValue }) => {getValue() || '-'}, }), + ...metadataKeys.map((key) => + accessor( + (original) => { + const meta = original.metadata ?? {}; + // Match the first key that lowercases to this column's key. + const match = Object.keys(meta).find((k) => k.toLowerCase() === key); + return match ? meta[match] : undefined; + }, + { + id: `metadata-${key}`, + header: snakeCaseToTitleCase(key), + enableSorting: false, + cell: ({ getValue }) => { + const raw = getValue(); + if (raw == null) return -; + const str = typeof raw === 'object' ? JSON.stringify(raw) : String(raw); + if (str.length <= 50) return {str}; + return ( + {str}} + className={tooltipClassName} + side="bottom" + > + {str.slice(0, 50)}… + + ); + }, + } + ) + ), ...evaluatorNames.map((name, index) => accessor((original) => original.aggregate_scores?.[name]?.mean, { id: `score-${index}`, @@ -239,7 +281,7 @@ export const ExperimentGroupDataView: FC = ({ }, }), ], - [evaluatorNames] + [evaluatorNames, metadataKeys] ); if (groupError) {