diff --git a/web/app/components/evaluation/__tests__/store.spec.ts b/web/app/components/evaluation/__tests__/store.spec.ts index 843a0107e8..18bd31a24c 100644 --- a/web/app/components/evaluation/__tests__/store.spec.ts +++ b/web/app/components/evaluation/__tests__/store.spec.ts @@ -41,6 +41,24 @@ describe('evaluation store', () => { expect(configuredMetric!.customConfig!.workflowName).toBe(config.workflowOptions[0].label) }) + it('should only add one custom metric', () => { + const resourceType = 'apps' + const resourceId = 'app-custom-limit' + const store = useEvaluationStore.getState() + + store.ensureResource(resourceType, resourceId) + store.addCustomMetric(resourceType, resourceId) + store.addCustomMetric(resourceType, resourceId) + + expect( + useEvaluationStore + .getState() + .resources['apps:app-custom-limit'] + .metrics + .filter(metric => metric.kind === 'custom-workflow'), + ).toHaveLength(1) + }) + it('should add and remove builtin metrics', () => { const resourceType = 'apps' const resourceId = 'app-2' diff --git a/web/app/components/evaluation/components/metric-section/__tests__/index.spec.tsx b/web/app/components/evaluation/components/metric-section/__tests__/index.spec.tsx index 03343d2a1c..0b76e4387b 100644 --- a/web/app/components/evaluation/components/metric-section/__tests__/index.spec.tsx +++ b/web/app/components/evaluation/components/metric-section/__tests__/index.spec.tsx @@ -210,5 +210,20 @@ describe('MetricSection', () => { expect(screen.getByText('evaluation.metrics.custom.workflowPlaceholder')).toBeInTheDocument() expect(screen.getByRole('button', { name: 'evaluation.metrics.custom.addMapping' })).toBeInTheDocument() }) + + it('should disable adding another custom metric when one already exists', () => { + // Arrange + act(() => { + useEvaluationStore.getState().addCustomMetric(resourceType, resourceId) + }) + + // Act + renderMetricSection() + fireEvent.click(screen.getByRole('button', { name: 'evaluation.metrics.add' })) + + // Assert + expect(screen.getByRole('button', { name: /evaluation.metrics.custom.footerTitle/i })).toBeDisabled() + expect(screen.getByText('evaluation.metrics.custom.limitDescription')).toBeInTheDocument() + }) }) }) diff --git a/web/app/components/evaluation/components/metric-selector/index.tsx b/web/app/components/evaluation/components/metric-selector/index.tsx index a01256bb8d..4870952b9b 100644 --- a/web/app/components/evaluation/components/metric-selector/index.tsx +++ b/web/app/components/evaluation/components/metric-selector/index.tsx @@ -12,7 +12,7 @@ import { PopoverTrigger, } from '@/app/components/base/ui/popover' import { cn } from '@/utils/classnames' -import { useEvaluationStore } from '../../store' +import { useEvaluationResource, useEvaluationStore } from '../../store' import SelectorEmptyState from './selector-empty-state' import SelectorFooter from './selector-footer' import SelectorMetricSection from './selector-metric-section' @@ -26,12 +26,14 @@ const MetricSelector = ({ triggerStyle = 'button', }: MetricSelectorProps) => { const { t } = useTranslation('evaluation') + const resource = useEvaluationResource(resourceType, resourceId) const addCustomMetric = useEvaluationStore(state => state.addCustomMetric) const [open, setOpen] = useState(false) const [query, setQuery] = useState('') const [nodeInfoMap, setNodeInfoMap] = useState>>({}) const [collapsedMetricMap, setCollapsedMetricMap] = useState>({}) const [expandedMetricNodesMap, setExpandedMetricNodesMap] = useState>({}) + const hasCustomMetric = resource.metrics.some(metric => metric.kind === 'custom-workflow') const { builtinMetricMap, @@ -134,7 +136,8 @@ const MetricSelector = ({ { addCustomMetric(resourceType, resourceId) setOpen(false) diff --git a/web/app/components/evaluation/components/metric-selector/selector-footer.tsx b/web/app/components/evaluation/components/metric-selector/selector-footer.tsx index 3b8d04474a..74163c275d 100644 --- a/web/app/components/evaluation/components/metric-selector/selector-footer.tsx +++ b/web/app/components/evaluation/components/metric-selector/selector-footer.tsx @@ -1,18 +1,21 @@ type SelectorFooterProps = { title: string description: string + disabled?: boolean onClick: () => void } const SelectorFooter = ({ title, description, + disabled = false, onClick, }: SelectorFooterProps) => { return (