fix(web): custom metric display

This commit is contained in:
JzoNg 2026-04-30 12:20:56 +08:00
parent 48c38ace54
commit da482ec455
4 changed files with 41 additions and 6 deletions

View File

@ -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' }])

View File

@ -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(
<CustomMetricEditorCard
resourceType="apps"
resourceId="app-under-test"
metric={metric}
/>,
)
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(),

View File

@ -44,6 +44,7 @@ const getWorkflowOutputs = (nodes?: Array<Node>) => {
.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 = ({
</div>
<div className="flex flex-wrap items-center gap-y-1 px-2 py-2 system-xs-regular text-text-tertiary">
{workflowOutputs.map((output, index) => (
<div key={`${output.nodeTitle}-${output.id}-${index}`} className="flex items-center">
<div key={`${output.nodeId}-${output.id}`} className="flex items-center">
<span className="px-1 system-xs-medium text-text-secondary">{output.id}</span>
{output.valueType && (
<span>{output.valueType}</span>

View File

@ -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),
}