diff --git a/web/app/components/evaluation/__tests__/store.spec.ts b/web/app/components/evaluation/__tests__/store.spec.ts index 0557f52945..ee7938d661 100644 --- a/web/app/components/evaluation/__tests__/store.spec.ts +++ b/web/app/components/evaluation/__tests__/store.spec.ts @@ -258,6 +258,7 @@ describe('evaluation store', () => { ]) expect(hydratedState.metrics[1].kind).toBe('custom-workflow') expect(hydratedState.metrics[1].customConfig?.workflowId).toBe('workflow-precision-review') + expect(hydratedState.metrics[1].customConfig?.workflowAppId).toBe('workflow-precision-review') expect(hydratedState.metrics[1].customConfig?.mappings[0].inputVariableId).toBe('query') expect(hydratedState.metrics[1].customConfig?.mappings[0].outputVariableId).toBe('answer') expect(hydratedState.metrics[1].customConfig?.outputs).toEqual([{ id: 'reason', valueType: 'string' }]) diff --git a/web/app/components/evaluation/components/custom-metric-editor/__tests__/index.spec.tsx b/web/app/components/evaluation/components/custom-metric-editor/__tests__/index.spec.tsx index 5f2c026908..e72048dc6d 100644 --- a/web/app/components/evaluation/components/custom-metric-editor/__tests__/index.spec.tsx +++ b/web/app/components/evaluation/components/custom-metric-editor/__tests__/index.spec.tsx @@ -199,6 +199,10 @@ describe('CustomMetricEditorCard', () => { mockUseSnippetPublishedWorkflow.mockReturnValue({ data: undefined }) }) + afterEach(() => { + vi.restoreAllMocks() + }) + // Verify the selected evaluation workflow still drives the output summary section. describe('Outputs', () => { it('should render the selected workflow outputs from the end node', () => { @@ -274,6 +278,33 @@ describe('CustomMetricEditorCard', () => { // Verify mapping rows use workflow start variables on the left and current published graph variables on the right. describe('Variable Mapping', () => { + it('should preserve saved mappings and outputs while the selected workflow is loading', () => { + const baseMetric = createMetric() + const metric = { + ...baseMetric, + customConfig: { + ...baseMetric.customConfig!, + outputs: [{ id: 'score', valueType: 'number' }], + }, + } + const syncMappingsSpy = vi.spyOn(useEvaluationStore.getState(), 'syncCustomMetricMappings') + const syncOutputsSpy = vi.spyOn(useEvaluationStore.getState(), 'syncCustomMetricOutputs') + + mockUseAppWorkflow.mockReturnValue({ data: undefined }) + + render( + , + ) + + expect(screen.getByText('Evaluation Workflow')).toBeInTheDocument() + expect(syncMappingsSpy).not.toHaveBeenCalled() + expect(syncOutputsSpy).not.toHaveBeenCalled() + }) + it('should pass the current app published graph and saved selector values to the picker', () => { const selectedWorkflow = createWorkflow([ createStartNode(), diff --git a/web/app/components/evaluation/components/custom-metric-editor/index.tsx b/web/app/components/evaluation/components/custom-metric-editor/index.tsx index 694e48d310..b76d866a50 100644 --- a/web/app/components/evaluation/components/custom-metric-editor/index.tsx +++ b/web/app/components/evaluation/components/custom-metric-editor/index.tsx @@ -44,6 +44,7 @@ const getWorkflowOutputs = (nodes?: Array) => { .map(output => ({ id: output.variable, valueType: typeof output.value_type === 'string' ? output.value_type : null, + nodeId: endNode.id, nodeTitle: typeof endNode.data.title === 'string' && endNode.data.title ? endNode.data.title : 'End', })) }) @@ -110,9 +111,10 @@ const CustomMetricEditorCard = ({ ]) const inputVariableIds = useMemo(() => inputVariables.map(variable => variable.id), [inputVariables]) const isConfigured = isCustomMetricConfigured(metric) + const isSelectedWorkflowLoaded = !!selectedWorkflow useEffect(() => { - if (!metric.customConfig?.workflowId) + if (!metric.customConfig?.workflowId || !isSelectedWorkflowLoaded) return const currentInputVariableIds = metric.customConfig.mappings @@ -125,10 +127,10 @@ const CustomMetricEditorCard = ({ } syncCustomMetricMappings(resourceType, resourceId, metric.id, inputVariableIds) - }, [inputVariableIds, metric.customConfig?.mappings, metric.customConfig?.workflowId, metric.id, resourceId, resourceType, syncCustomMetricMappings]) + }, [inputVariableIds, isSelectedWorkflowLoaded, metric.customConfig?.mappings, metric.customConfig?.workflowId, metric.id, resourceId, resourceType, syncCustomMetricMappings]) useEffect(() => { - if (!metric.customConfig?.workflowId) + if (!metric.customConfig?.workflowId || !isSelectedWorkflowLoaded) return const currentOutputs = metric.customConfig.outputs @@ -142,7 +144,7 @@ const CustomMetricEditorCard = ({ } syncCustomMetricOutputs(resourceType, resourceId, metric.id, workflowOutputs) - }, [metric.customConfig?.outputs, metric.customConfig?.workflowId, metric.id, resourceId, resourceType, syncCustomMetricOutputs, workflowOutputs]) + }, [isSelectedWorkflowLoaded, metric.customConfig?.outputs, metric.customConfig?.workflowId, metric.id, resourceId, resourceType, syncCustomMetricOutputs, workflowOutputs]) if (!metric.customConfig) return null @@ -197,7 +199,7 @@ const CustomMetricEditorCard = ({
{workflowOutputs.map((output, index) => ( -
+
{output.id} {output.valueType && ( {output.valueType} diff --git a/web/app/components/evaluation/store-utils.ts b/web/app/components/evaluation/store-utils.ts index ce2ecf7f18..5e670ece24 100644 --- a/web/app/components/evaluation/store-utils.ts +++ b/web/app/components/evaluation/store-utils.ts @@ -130,7 +130,7 @@ const normalizeCustomMetricMappings = ( const normalizeCustomMetricOutputs = ( value: EvaluationCustomizedMetric['output_fields'], ) => { - if (!value) + if (!Array.isArray(value)) return [] return value @@ -165,6 +165,7 @@ const normalizeCustomMetric = ( ? { ...customMetric.customConfig, workflowId, + workflowAppId: workflowId, mappings: normalizeCustomMetricMappings(value.input_fields), outputs: normalizeCustomMetricOutputs(value.output_fields), }