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 e72048dc6d..f70c8f7a91 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
@@ -12,6 +12,7 @@ import CustomMetricEditorCard from '..'
import { useEvaluationStore } from '../../../store'
const mockUseAppWorkflow = vi.hoisted(() => vi.fn())
+const mockUseAppDetail = vi.hoisted(() => vi.fn())
const mockUseSnippetPublishedWorkflow = vi.hoisted(() => vi.fn())
const mockUseAvailableEvaluationWorkflows = vi.hoisted(() => vi.fn())
const mockUseInfiniteScroll = vi.hoisted(() => vi.fn())
@@ -21,6 +22,10 @@ vi.mock('@/service/use-workflow', () => ({
useAppWorkflow: (...args: unknown[]) => mockUseAppWorkflow(...args),
}))
+vi.mock('@/service/use-apps', () => ({
+ useAppDetail: (...args: unknown[]) => mockUseAppDetail(...args),
+}))
+
vi.mock('@/service/use-snippet-workflows', () => ({
useSnippetPublishedWorkflow: (...args: unknown[]) => mockUseSnippetPublishedWorkflow(...args),
}))
@@ -179,6 +184,7 @@ describe('CustomMetricEditorCard', () => {
vi.clearAllMocks()
useEvaluationStore.setState({ resources: {} })
mockPublishedGraphVariablePicker.mockReset()
+ mockUseAppDetail.mockReturnValue({ data: undefined })
mockUseInfiniteScroll.mockImplementation(() => undefined)
mockUseAvailableEvaluationWorkflows.mockReturnValue({
@@ -305,6 +311,46 @@ describe('CustomMetricEditorCard', () => {
expect(syncOutputsSpy).not.toHaveBeenCalled()
})
+ it('should show the selected workflow app name from app detail when the config only has workflow id', () => {
+ const selectedWorkflow = {
+ ...createWorkflow([createStartNode()]),
+ marked_name: '',
+ }
+ const baseMetric = createMetric()
+ const metric = {
+ ...baseMetric,
+ customConfig: {
+ ...baseMetric.customConfig!,
+ workflowName: null,
+ },
+ }
+
+ mockUseAppDetail.mockReturnValue({
+ data: {
+ id: 'workflow-app-1',
+ name: 'Review Workflow App',
+ },
+ })
+ mockUseAppWorkflow.mockImplementation((appId: string) => {
+ if (appId === 'workflow-app-1')
+ return { data: selectedWorkflow }
+
+ return { data: undefined }
+ })
+
+ render(
+ ,
+ )
+
+ expect(mockUseAppDetail).toHaveBeenCalledWith('workflow-app-1')
+ expect(screen.getByText('Review Workflow App')).toBeInTheDocument()
+ expect(screen.queryByText('workflow-1')).not.toBeInTheDocument()
+ })
+
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/__tests__/workflow-selector.spec.tsx b/web/app/components/evaluation/components/custom-metric-editor/__tests__/workflow-selector.spec.tsx
index 3cf8f30b99..e9386c6031 100644
--- a/web/app/components/evaluation/components/custom-metric-editor/__tests__/workflow-selector.spec.tsx
+++ b/web/app/components/evaluation/components/custom-metric-editor/__tests__/workflow-selector.spec.tsx
@@ -98,12 +98,22 @@ describe('WorkflowSelector', () => {
setupWorkflowQueryMock({ workflows: [] })
renderWorkflowSelector({
- value: 'workflow-1',
+ value: 'app-1',
selectedWorkflowName: 'Saved Review Workflow',
})
expect(screen.getByText('Saved Review Workflow')).toBeInTheDocument()
})
+
+ it('should resolve the selected workflow from app id', () => {
+ setupWorkflowQueryMock()
+
+ renderWorkflowSelector({
+ value: 'app-1',
+ })
+
+ expect(screen.getByText('Review Workflow')).toBeInTheDocument()
+ })
})
// Cover opening the popover and choosing one workflow option.
@@ -120,6 +130,14 @@ describe('WorkflowSelector', () => {
expect(onSelect).toHaveBeenCalledWith(createWorkflow())
})
+
+ it('should mark the option selected when its app id matches the value', async () => {
+ renderWorkflowSelector({ value: 'app-1' })
+
+ fireEvent.click(screen.getByRole('button', { name: 'evaluation.metrics.custom.workflowLabel' }))
+
+ expect(await screen.findByRole('option', { name: 'Review Workflow', selected: true })).toBeInTheDocument()
+ })
})
// Cover the infinite-scroll callback used by the ScrollArea viewport.
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 b76d866a50..fcfcbf1417 100644
--- a/web/app/components/evaluation/components/custom-metric-editor/index.tsx
+++ b/web/app/components/evaluation/components/custom-metric-editor/index.tsx
@@ -8,6 +8,7 @@ import { useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { inputVarTypeToVarType } from '@/app/components/workflow/nodes/_base/components/variable/utils'
import { BlockEnum, InputVarType } from '@/app/components/workflow/types'
+import { useAppDetail } from '@/service/use-apps'
import { useSnippetPublishedWorkflow } from '@/service/use-snippet-workflows'
import { useAppWorkflow } from '@/service/use-workflow'
import { isCustomMetricConfigured, useEvaluationStore } from '../../store'
@@ -76,7 +77,9 @@ const CustomMetricEditorCard = ({
const syncCustomMetricMappings = useEvaluationStore(state => state.syncCustomMetricMappings)
const syncCustomMetricOutputs = useEvaluationStore(state => state.syncCustomMetricOutputs)
const updateCustomMetricMapping = useEvaluationStore(state => state.updateCustomMetricMapping)
- const { data: selectedWorkflow } = useAppWorkflow(metric.customConfig?.workflowAppId ?? '')
+ const selectedWorkflowAppId = metric.customConfig?.workflowAppId ?? metric.customConfig?.workflowId ?? ''
+ const { data: selectedWorkflowApp } = useAppDetail(selectedWorkflowAppId)
+ const { data: selectedWorkflow } = useAppWorkflow(selectedWorkflowAppId)
const { data: currentAppWorkflow } = useAppWorkflow(resourceType === 'apps' ? resourceId : '')
const { data: currentSnippetWorkflow } = useSnippetPublishedWorkflow(resourceType === 'snippets' ? resourceId : '')
const inputVariables = useMemo(() => {
@@ -153,9 +156,9 @@ const CustomMetricEditorCard = ({
setCustomMetricWorkflow(resourceType, resourceId, metric.id, {
- workflowId: workflow.id,
+ workflowId: workflow.app_id,
workflowAppId: workflow.app_id,
workflowName: getWorkflowName(workflow),
})}
@@ -163,7 +166,7 @@ const CustomMetricEditorCard = ({
-
{t('metrics.custom.mappingTitle')}
+
{t('metrics.custom.mappingTitle')}
{inputVariables.map((inputVariable) => {
diff --git a/web/app/components/evaluation/components/custom-metric-editor/workflow-selector.tsx b/web/app/components/evaluation/components/custom-metric-editor/workflow-selector.tsx
index e2b93e7f22..eb88b7ff44 100644
--- a/web/app/components/evaluation/components/custom-metric-editor/workflow-selector.tsx
+++ b/web/app/components/evaluation/components/custom-metric-editor/workflow-selector.tsx
@@ -34,6 +34,11 @@ const getWorkflowName = (workflow: AvailableEvaluationWorkflow) => {
return workflow.marked_name || workflow.app_name || workflow.id
}
+const isSelectedWorkflow = (
+ workflow: AvailableEvaluationWorkflow,
+ value: string | null,
+) => workflow.app_id === value
+
const WorkflowSelector = ({
value,
selectedWorkflowName,
@@ -71,7 +76,7 @@ const WorkflowSelector = ({
if (!value)
return null
- const selectedWorkflow = workflows.find(workflow => workflow.id === value)
+ const selectedWorkflow = workflows.find(workflow => isSelectedWorkflow(workflow, value))
if (selectedWorkflow)
return getWorkflowName(selectedWorkflow)
@@ -171,7 +176,7 @@ const WorkflowSelector = ({
key={workflow.id}
type="button"
role="option"
- aria-selected={workflow.id === value}
+ aria-selected={isSelectedWorkflow(workflow, value)}
className="flex w-full items-center gap-2 rounded-lg px-2 py-1 text-left hover:bg-state-base-hover"
onClick={() => {
onSelect(workflow)
@@ -187,7 +192,7 @@ const WorkflowSelector = ({
{getWorkflowName(workflow)}
- {workflow.id === value && (
+ {isSelectedWorkflow(workflow, value) && (
)}