fix(web): selected workflow name fix

This commit is contained in:
JzoNg 2026-04-30 14:18:32 +08:00
parent 552f202ca8
commit 3763efbc7c
4 changed files with 80 additions and 8 deletions

View File

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

View File

@ -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.

View File

@ -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 = ({
<div className="px-3 pt-1 pb-3">
<WorkflowSelector
value={metric.customConfig.workflowId}
selectedWorkflowName={metric.customConfig.workflowName ?? (selectedWorkflow ? getWorkflowName(selectedWorkflow) : null)}
selectedWorkflowName={metric.customConfig.workflowName ?? selectedWorkflowApp?.name ?? null}
onSelect={workflow => 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 = ({
<div className="mt-4">
<div className="mb-2 flex items-center justify-between gap-3">
<div className="system-xs-medium-uppercase text-text-secondary">{t('metrics.custom.mappingTitle')}</div>
<div className="system-xs-medium-uppercase text-text-tertiary">{t('metrics.custom.mappingTitle')}</div>
</div>
<div className="space-y-2">
{inputVariables.map((inputVariable) => {

View File

@ -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 = ({
<div className="min-w-0 flex-1 truncate px-1 py-1 system-sm-medium text-text-secondary">
{getWorkflowName(workflow)}
</div>
{workflow.id === value && (
{isSelectedWorkflow(workflow, value) && (
<span aria-hidden="true" className="i-ri-check-line h-4 w-4 shrink-0 text-text-accent" />
)}
</button>