From c95bd3d75d9902cd6f90c2fdb87e64aef471d065 Mon Sep 17 00:00:00 2001 From: Shine1i Date: Fri, 20 Feb 2026 14:05:43 +0100 Subject: [PATCH] feat: refine execution view with enhanced summary and insights - Removed unused model usage properties (`total`, `tps`, `requestsSuccess`, etc.) for cleaner data handling. - Added new metrics: total input/output tokens, null rate, and low uniqueness flags. - Improved UI for execution summary cards with consolidated insights and model usage tables. - Introduced detailed analysis for dataset columns, including dropped columns and LLM column counts. - Optimized rendering logic to reduce clutter and enhance user experience. --- .../components/executions/executions-view.tsx | 276 +++++++++++++----- 1 file changed, 199 insertions(+), 77 deletions(-) diff --git a/studio/frontend/src/features/recipe-studio/components/executions/executions-view.tsx b/studio/frontend/src/features/recipe-studio/components/executions/executions-view.tsx index b2976c95ab..fbdc0b94b0 100644 --- a/studio/frontend/src/features/recipe-studio/components/executions/executions-view.tsx +++ b/studio/frontend/src/features/recipe-studio/components/executions/executions-view.tsx @@ -61,12 +61,6 @@ type ModelUsageRow = { model: string; input: number | null; output: number | null; - total: number | null; - tps: number | null; - requestsSuccess: number | null; - requestsFailed: number | null; - requestsTotal: number | null; - rpm: number | null; }; function formatTimestamp(value: number): string { @@ -210,12 +204,6 @@ function parseModelUsageRows(value: Record | null): ModelUsageR modelObj.tokens && typeof modelObj.tokens === "object" && !Array.isArray(modelObj.tokens) ? (modelObj.tokens as Record) : null; - const requests = - modelObj.requests && - typeof modelObj.requests === "object" && - !Array.isArray(modelObj.requests) - ? (modelObj.requests as Record) - : null; const modelName = typeof modelObj.model === "string" && modelObj.model.length > 0 ? modelObj.model @@ -224,12 +212,6 @@ function parseModelUsageRows(value: Record | null): ModelUsageR model: modelName, input: parseNumber(tokens?.input), output: parseNumber(tokens?.output), - total: parseNumber(tokens?.total), - tps: parseNumber(tokens?.tps), - requestsSuccess: parseNumber(requests?.success), - requestsFailed: parseNumber(requests?.failed), - requestsTotal: parseNumber(requests?.total), - rpm: parseNumber(requests?.rpm), }; }) .filter((item): item is ModelUsageRow => item !== null); @@ -350,6 +332,22 @@ export function ExecutionsView({ () => parseModelUsageRows(selectedExecution?.model_usage ?? null), [selectedExecution?.model_usage], ); + const totalInputTokens = useMemo( + () => + modelUsageRows.reduce( + (acc, item) => acc + (typeof item.input === "number" ? item.input : 0), + 0, + ), + [modelUsageRows], + ); + const totalOutputTokens = useMemo( + () => + modelUsageRows.reduce( + (acc, item) => acc + (typeof item.output === "number" ? item.output : 0), + 0, + ), + [modelUsageRows], + ); const sideEffects = useMemo(() => { const values = selectedExecution?.analysis?.side_effect_column_names; return Array.isArray(values) @@ -390,6 +388,51 @@ export function ExecutionsView({ } return selectedExecution.rows > 0 ? selectedExecution.rows : null; }, [selectedExecution]); + const columnCount = analysisColumns.length; + const llmColumnCount = useMemo( + () => + analysisColumns.reduce( + (acc, column) => (column.column_type.startsWith("llm") ? acc + 1 : acc), + 0, + ), + [analysisColumns], + ); + const totalNulls = useMemo( + () => + analysisColumns.reduce( + (acc, column) => acc + (typeof column.num_null === "number" ? column.num_null : 0), + 0, + ), + [analysisColumns], + ); + const nullRate = useMemo(() => { + if ( + typeof recordsMetric !== "number" || + recordsMetric <= 0 || + columnCount <= 0 + ) { + return null; + } + return (totalNulls / (recordsMetric * columnCount)) * 100; + }, [columnCount, recordsMetric, totalNulls]); + const lowUniquenessColumns = useMemo(() => { + if (typeof recordsMetric !== "number" || recordsMetric <= 0) { + return []; + } + return analysisColumns + .filter( + (column) => + typeof column.num_unique === "number" && + column.num_unique / recordsMetric < 0.5, + ) + .map((column) => column.column_name); + }, [analysisColumns, recordsMetric]); + const runDuration = useMemo(() => { + if (!selectedExecution) { + return "--"; + } + return formatDuration(selectedExecution.createdAt, selectedExecution.finishedAt); + }, [selectedExecution]); const showSummaryCards = selectedExecution?.status === "completed"; const showProgressPanel = selectedExecution?.status === "completed" || @@ -397,6 +440,13 @@ export function ExecutionsView({ const progressComplete = selectedExecution?.status === "completed"; const progressPercent = selectedExecution?.progress?.percent ?? (progressComplete ? 100 : 0); const terminalLines = selectedExecution?.log_lines ?? []; + const rawExecution = useMemo(() => { + if (!selectedExecution) { + return null; + } + const { dataset, log_lines, ...rest } = selectedExecution; + return rest; + }, [selectedExecution]); useEffect(() => { if (!terminalRef.current) { @@ -632,80 +682,152 @@ export function ExecutionsView({ {showSummaryCards && ( -
-
-
+
+
+
-

Records

+

Run summary

-

- {formatMetricValue(recordsMetric)} / {formatMetricValue(totalMetric)} -

-

- generated / requested -

-
-
-
-

Model usage

- +
+

+ Records:{" "} + + {formatMetricValue(recordsMetric)} / {formatMetricValue(totalMetric)} + +

+

+ Duration: {runDuration} +

+

+ Columns analyzed:{" "} + + {formatMetricValue(columnCount)} + +

+

+ Final stage:{" "} + + {selectedExecution.stage ?? "--"} + +

- {modelUsageRows.length === 0 ? ( -

No model usage yet.

- ) : ( -
- {modelUsageRows.map((usage) => ( -
-

|-- model: {usage.model}

-

- |-- tokens: input={formatMetricValue(usage.input)}, output= - {formatMetricValue(usage.output)}, total= - {formatMetricValue(usage.total)}, tps= - {formatMetricValue(usage.tps)} -

-

- |-- requests: success= - {formatMetricValue(usage.requestsSuccess)}, failed= - {formatMetricValue(usage.requestsFailed)}, total= - {formatMetricValue(usage.requestsTotal)}, rpm= - {formatMetricValue(usage.rpm)} -

-
- ))} -
- )}
-
+
-

Dropped columns

+

Insights

- {sideEffects.length === 0 ? ( -

None

- ) : ( -
- {sideEffects.map((name) => ( - - {name} - - ))} -
- )} +
+

+ LLM columns:{" "} + + {formatMetricValue(llmColumnCount)} + +

+

+ Null rate:{" "} + {formatPercent(nullRate)} +

+

+ Dropped columns:{" "} + + {formatMetricValue(sideEffects.length)} + +

+ {sideEffects.length > 0 && ( +
+ {sideEffects.map((name) => ( + + {name} + + ))} +
+ )} +

+ Low uniqueness flags:{" "} + + {formatMetricValue(lowUniquenessColumns.length)} + +

+ {lowUniquenessColumns.length > 0 && ( +
+ {lowUniquenessColumns.slice(0, 3).map((name) => ( + + {name} + + ))} + {lowUniquenessColumns.length > 3 && ( + + +{lowUniquenessColumns.length - 3} more + + )} +
+ )} +
+
+
+

Model usage

+ +
+ {modelUsageRows.length === 0 ? ( +

No model usage yet.

+ ) : ( +
+
+
+

Total input

+

+ {formatMetricValue(totalInputTokens)} +

+
+
+

Total output

+

+ {formatMetricValue(totalOutputTokens)} +

+
+
+
+ + + + Model + Input + Output + + + + {modelUsageRows.map((usage) => ( + + + {usage.model} + + + {formatMetricValue(usage.input)} + + + {formatMetricValue(usage.output)} + + + ))} + +
+
+
+ )} +
)}
@@ -863,7 +985,7 @@ export function ExecutionsView({

Raw execution

-                        {JSON.stringify(selectedExecution, null, 2)}
+                        {JSON.stringify(rawExecution, null, 2)}