mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 11:04:27 +08:00
feat(web): template download & upload & run in rag-pipeline
This commit is contained in:
parent
4680535ecd
commit
670ab16ea1
@ -1,15 +1,25 @@
|
|||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
import { act, fireEvent, render, screen } from '@testing-library/react'
|
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||||
import Evaluation from '..'
|
import Evaluation from '..'
|
||||||
import ConditionsSection from '../components/conditions-section'
|
import ConditionsSection from '../components/conditions-section'
|
||||||
import { useEvaluationStore } from '../store'
|
import { useEvaluationStore } from '../store'
|
||||||
|
|
||||||
|
const mockUpload = vi.hoisted(() => vi.fn())
|
||||||
const mockUseAvailableEvaluationMetrics = vi.hoisted(() => vi.fn())
|
const mockUseAvailableEvaluationMetrics = vi.hoisted(() => vi.fn())
|
||||||
const mockUseEvaluationConfig = vi.hoisted(() => vi.fn())
|
const mockUseEvaluationConfig = vi.hoisted(() => vi.fn())
|
||||||
const mockUseEvaluationNodeInfoMutation = vi.hoisted(() => vi.fn())
|
const mockUseEvaluationNodeInfoMutation = vi.hoisted(() => vi.fn())
|
||||||
const mockUseSaveEvaluationConfigMutation = vi.hoisted(() => vi.fn())
|
const mockUseSaveEvaluationConfigMutation = vi.hoisted(() => vi.fn())
|
||||||
const mockUseStartEvaluationRunMutation = vi.hoisted(() => vi.fn())
|
const mockUseStartEvaluationRunMutation = vi.hoisted(() => vi.fn())
|
||||||
|
const mockUsePublishedPipelineInfo = vi.hoisted(() => vi.fn())
|
||||||
|
|
||||||
|
vi.mock('@/context/dataset-detail', () => ({
|
||||||
|
useDatasetDetailContextWithSelector: (selector: (state: { dataset?: { pipeline_id?: string } }) => unknown) => selector({
|
||||||
|
dataset: {
|
||||||
|
pipeline_id: 'pipeline-1',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
|
||||||
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
||||||
useModelList: () => ({
|
useModelList: () => ({
|
||||||
@ -42,6 +52,10 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/model-selec
|
|||||||
),
|
),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/service/base', () => ({
|
||||||
|
upload: (...args: unknown[]) => mockUpload(...args),
|
||||||
|
}))
|
||||||
|
|
||||||
vi.mock('@/service/use-evaluation', () => ({
|
vi.mock('@/service/use-evaluation', () => ({
|
||||||
useEvaluationConfig: (...args: unknown[]) => mockUseEvaluationConfig(...args),
|
useEvaluationConfig: (...args: unknown[]) => mockUseEvaluationConfig(...args),
|
||||||
useAvailableEvaluationMetrics: (...args: unknown[]) => mockUseAvailableEvaluationMetrics(...args),
|
useAvailableEvaluationMetrics: (...args: unknown[]) => mockUseAvailableEvaluationMetrics(...args),
|
||||||
@ -50,6 +64,10 @@ vi.mock('@/service/use-evaluation', () => ({
|
|||||||
useStartEvaluationRunMutation: (...args: unknown[]) => mockUseStartEvaluationRunMutation(...args),
|
useStartEvaluationRunMutation: (...args: unknown[]) => mockUseStartEvaluationRunMutation(...args),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/service/use-pipeline', () => ({
|
||||||
|
usePublishedPipelineInfo: (...args: unknown[]) => mockUsePublishedPipelineInfo(...args),
|
||||||
|
}))
|
||||||
|
|
||||||
vi.mock('@/service/use-workflow', () => ({
|
vi.mock('@/service/use-workflow', () => ({
|
||||||
useAppWorkflow: () => ({
|
useAppWorkflow: () => ({
|
||||||
data: {
|
data: {
|
||||||
@ -147,6 +165,28 @@ describe('Evaluation', () => {
|
|||||||
isPending: false,
|
isPending: false,
|
||||||
mutate: vi.fn(),
|
mutate: vi.fn(),
|
||||||
})
|
})
|
||||||
|
mockUsePublishedPipelineInfo.mockReturnValue({
|
||||||
|
data: {
|
||||||
|
rag_pipeline_variables: [{
|
||||||
|
belong_to_node_id: 'shared',
|
||||||
|
type: 'text-input',
|
||||||
|
label: 'Question',
|
||||||
|
variable: 'question',
|
||||||
|
required: true,
|
||||||
|
}, {
|
||||||
|
belong_to_node_id: 'shared',
|
||||||
|
type: 'number',
|
||||||
|
label: 'Top K',
|
||||||
|
variable: 'top_k',
|
||||||
|
required: false,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
})
|
||||||
|
mockUpload.mockResolvedValue({
|
||||||
|
id: 'uploaded-file-id',
|
||||||
|
name: 'evaluation.csv',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should search, select metric nodes, and save evaluation config', () => {
|
it('should search, select metric nodes, and save evaluation config', () => {
|
||||||
@ -411,4 +451,67 @@ describe('Evaluation', () => {
|
|||||||
expect(screen.getByRole('button', { name: 'evaluation.batch.downloadTemplate' })).toBeEnabled()
|
expect(screen.getByRole('button', { name: 'evaluation.batch.downloadTemplate' })).toBeEnabled()
|
||||||
expect(screen.getByRole('button', { name: 'evaluation.pipeline.uploadAndRun' })).toBeEnabled()
|
expect(screen.getByRole('button', { name: 'evaluation.pipeline.uploadAndRun' })).toBeEnabled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should upload and start a pipeline evaluation run', async () => {
|
||||||
|
const startRun = vi.fn()
|
||||||
|
mockUseStartEvaluationRunMutation.mockReturnValue({
|
||||||
|
isPending: false,
|
||||||
|
mutate: startRun,
|
||||||
|
})
|
||||||
|
mockUpload.mockResolvedValue({
|
||||||
|
id: 'file-1',
|
||||||
|
name: 'pipeline-evaluation.csv',
|
||||||
|
})
|
||||||
|
|
||||||
|
renderWithQueryClient(<Evaluation resourceType="datasets" resourceId="dataset-run" />)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'select-model' }))
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /Context Precision/i }))
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'evaluation.pipeline.uploadAndRun' }))
|
||||||
|
|
||||||
|
expect(screen.getAllByText('question').length).toBeGreaterThan(0)
|
||||||
|
expect(screen.getAllByText('top_k').length).toBeGreaterThan(0)
|
||||||
|
|
||||||
|
const fileInput = document.querySelector<HTMLInputElement>('input[type="file"][accept=".csv,.xlsx"]')
|
||||||
|
expect(fileInput).toBeInTheDocument()
|
||||||
|
|
||||||
|
fireEvent.change(fileInput!, {
|
||||||
|
target: {
|
||||||
|
files: [new File(['case_id,input,expected'], 'pipeline-evaluation.csv', { type: 'text/csv' })],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockUpload).toHaveBeenCalledWith({
|
||||||
|
xhr: expect.any(XMLHttpRequest),
|
||||||
|
data: expect.any(FormData),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'evaluation.batch.run' }))
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(startRun).toHaveBeenCalledWith({
|
||||||
|
params: {
|
||||||
|
targetType: 'datasets',
|
||||||
|
targetId: 'dataset-run',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
evaluation_model: 'gpt-4o-mini',
|
||||||
|
evaluation_model_provider: 'openai',
|
||||||
|
default_metrics: [{
|
||||||
|
metric: 'context-precision',
|
||||||
|
value_type: 'number',
|
||||||
|
node_info_list: [],
|
||||||
|
}],
|
||||||
|
customized_metrics: null,
|
||||||
|
judgment_config: null,
|
||||||
|
file_id: 'file-1',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
onSuccess: expect.any(Function),
|
||||||
|
onError: expect.any(Function),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
|
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
|
||||||
import type { InputVar, Node } from '@/app/components/workflow/types'
|
import type { InputVar, Node } from '@/app/components/workflow/types'
|
||||||
|
import type { RAGPipelineVariables } from '@/models/pipeline'
|
||||||
import { inputVarTypeToVarType } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
import { inputVarTypeToVarType } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||||
|
import { inputVarTypeToVarType as pipelineInputVarTypeToVarType } from '@/app/components/workflow/nodes/data-source/utils'
|
||||||
import { BlockEnum, InputVarType } from '@/app/components/workflow/types'
|
import { BlockEnum, InputVarType } from '@/app/components/workflow/types'
|
||||||
|
|
||||||
export type InputField = {
|
export type InputField = {
|
||||||
@ -27,6 +29,18 @@ export const getStartNodeInputFields = (nodes?: Node[]): InputField[] => {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getRagPipelineInputFields = (variables?: RAGPipelineVariables): InputField[] => {
|
||||||
|
if (!Array.isArray(variables))
|
||||||
|
return []
|
||||||
|
|
||||||
|
return variables
|
||||||
|
.filter(variable => typeof variable.variable === 'string' && !!variable.variable)
|
||||||
|
.map(variable => ({
|
||||||
|
name: variable.variable,
|
||||||
|
type: pipelineInputVarTypeToVarType(variable.type),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
const escapeCsvCell = (value: string) => {
|
const escapeCsvCell = (value: string) => {
|
||||||
if (!/[",\n\r]/.test(value))
|
if (!/[",\n\r]/.test(value))
|
||||||
return value
|
return value
|
||||||
|
|||||||
@ -15,6 +15,7 @@ type UploadRunPopoverProps = {
|
|||||||
open: boolean
|
open: boolean
|
||||||
onOpenChange: (open: boolean) => void
|
onOpenChange: (open: boolean) => void
|
||||||
triggerDisabled: boolean
|
triggerDisabled: boolean
|
||||||
|
triggerLabel?: string
|
||||||
inputFields: InputField[]
|
inputFields: InputField[]
|
||||||
currentFileName: string | null | undefined
|
currentFileName: string | null | undefined
|
||||||
currentFileExtension: string
|
currentFileExtension: string
|
||||||
@ -32,6 +33,7 @@ const UploadRunPopover = ({
|
|||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
triggerDisabled,
|
triggerDisabled,
|
||||||
|
triggerLabel,
|
||||||
inputFields,
|
inputFields,
|
||||||
currentFileName,
|
currentFileName,
|
||||||
currentFileExtension,
|
currentFileExtension,
|
||||||
@ -65,7 +67,7 @@ const UploadRunPopover = ({
|
|||||||
<PopoverTrigger
|
<PopoverTrigger
|
||||||
render={(
|
render={(
|
||||||
<Button className="w-full justify-center" variant="primary" disabled={triggerDisabled}>
|
<Button className="w-full justify-center" variant="primary" disabled={triggerDisabled}>
|
||||||
{t('batch.uploadAndRun')}
|
{triggerLabel ?? t('batch.uploadAndRun')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import type { EvaluationResourceType } from '../../../types'
|
import type { EvaluationResourceType } from '../../../types'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||||
|
import { usePublishedPipelineInfo } from '@/service/use-pipeline'
|
||||||
import { useSnippetPublishedWorkflow } from '@/service/use-snippet-workflows'
|
import { useSnippetPublishedWorkflow } from '@/service/use-snippet-workflows'
|
||||||
import { useAppWorkflow } from '@/service/use-workflow'
|
import { useAppWorkflow } from '@/service/use-workflow'
|
||||||
import { getGraphNodes, getStartNodeInputFields } from './input-fields-utils'
|
import { getGraphNodes, getRagPipelineInputFields, getStartNodeInputFields } from './input-fields-utils'
|
||||||
|
|
||||||
export const usePublishedInputFields = (
|
export const usePublishedInputFields = (
|
||||||
resourceType: EvaluationResourceType,
|
resourceType: EvaluationResourceType,
|
||||||
@ -10,6 +12,8 @@ export const usePublishedInputFields = (
|
|||||||
) => {
|
) => {
|
||||||
const { data: currentAppWorkflow, isLoading: isAppWorkflowLoading } = useAppWorkflow(resourceType === 'apps' ? resourceId : '')
|
const { data: currentAppWorkflow, isLoading: isAppWorkflowLoading } = useAppWorkflow(resourceType === 'apps' ? resourceId : '')
|
||||||
const { data: currentSnippetWorkflow, isLoading: isSnippetWorkflowLoading } = useSnippetPublishedWorkflow(resourceType === 'snippets' ? resourceId : '')
|
const { data: currentSnippetWorkflow, isLoading: isSnippetWorkflowLoading } = useSnippetPublishedWorkflow(resourceType === 'snippets' ? resourceId : '')
|
||||||
|
const pipelineId = useDatasetDetailContextWithSelector(state => state.dataset?.pipeline_id)
|
||||||
|
const { data: currentPipelineWorkflow, isLoading: isPipelineWorkflowLoading } = usePublishedPipelineInfo(resourceType === 'datasets' ? (pipelineId ?? '') : '')
|
||||||
|
|
||||||
const inputFields = useMemo(() => {
|
const inputFields = useMemo(() => {
|
||||||
if (resourceType === 'apps')
|
if (resourceType === 'apps')
|
||||||
@ -18,12 +22,16 @@ export const usePublishedInputFields = (
|
|||||||
if (resourceType === 'snippets')
|
if (resourceType === 'snippets')
|
||||||
return getStartNodeInputFields(getGraphNodes(currentSnippetWorkflow?.graph))
|
return getStartNodeInputFields(getGraphNodes(currentSnippetWorkflow?.graph))
|
||||||
|
|
||||||
|
if (resourceType === 'datasets')
|
||||||
|
return getRagPipelineInputFields(currentPipelineWorkflow?.rag_pipeline_variables)
|
||||||
|
|
||||||
return []
|
return []
|
||||||
}, [currentAppWorkflow?.graph.nodes, currentSnippetWorkflow?.graph, resourceType])
|
}, [currentAppWorkflow?.graph.nodes, currentPipelineWorkflow?.rag_pipeline_variables, currentSnippetWorkflow?.graph, resourceType])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputFields,
|
inputFields,
|
||||||
isInputFieldsLoading: (resourceType === 'apps' && isAppWorkflowLoading)
|
isInputFieldsLoading: (resourceType === 'apps' && isAppWorkflowLoading)
|
||||||
|| (resourceType === 'snippets' && isSnippetWorkflowLoading),
|
|| (resourceType === 'snippets' && isSnippetWorkflowLoading)
|
||||||
|
|| (resourceType === 'datasets' && isPipelineWorkflowLoading),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { EvaluationResourceProps } from '../../types'
|
import type { EvaluationResourceProps } from '../../types'
|
||||||
import { useEffect, useMemo, useRef } from 'react'
|
import { useEffect, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import { toast } from '@/app/components/base/ui/toast'
|
|
||||||
import { useDocLink } from '@/context/i18n'
|
import { useDocLink } from '@/context/i18n'
|
||||||
import { useAvailableEvaluationMetrics } from '@/service/use-evaluation'
|
import { useAvailableEvaluationMetrics } from '@/service/use-evaluation'
|
||||||
import { getEvaluationMockConfig } from '../../mock'
|
import { getEvaluationMockConfig } from '../../mock'
|
||||||
import { isEvaluationRunnable, useEvaluationResource, useEvaluationStore } from '../../store'
|
import { isEvaluationRunnable, useEvaluationResource, useEvaluationStore } from '../../store'
|
||||||
|
import UploadRunPopover from '../batch-test-panel/input-fields/upload-run-popover'
|
||||||
|
import { useInputFieldsActions } from '../batch-test-panel/input-fields/use-input-fields-actions'
|
||||||
|
import { usePublishedInputFields } from '../batch-test-panel/input-fields/use-published-input-fields'
|
||||||
import JudgeModelSelector from '../judge-model-selector'
|
import JudgeModelSelector from '../judge-model-selector'
|
||||||
import PipelineHistoryTable from '../pipeline/pipeline-history-table'
|
import PipelineHistoryTable from '../pipeline/pipeline-history-table'
|
||||||
import PipelineMetricItem from '../pipeline/pipeline-metric-item'
|
import PipelineMetricItem from '../pipeline/pipeline-metric-item'
|
||||||
@ -26,11 +28,8 @@ const PipelineEvaluation = ({
|
|||||||
const addBuiltinMetric = useEvaluationStore(state => state.addBuiltinMetric)
|
const addBuiltinMetric = useEvaluationStore(state => state.addBuiltinMetric)
|
||||||
const removeMetric = useEvaluationStore(state => state.removeMetric)
|
const removeMetric = useEvaluationStore(state => state.removeMetric)
|
||||||
const updateMetricThreshold = useEvaluationStore(state => state.updateMetricThreshold)
|
const updateMetricThreshold = useEvaluationStore(state => state.updateMetricThreshold)
|
||||||
const setUploadedFileName = useEvaluationStore(state => state.setUploadedFileName)
|
|
||||||
const runBatchTest = useEvaluationStore(state => state.runBatchTest)
|
|
||||||
const { data: availableMetricsData } = useAvailableEvaluationMetrics()
|
const { data: availableMetricsData } = useAvailableEvaluationMetrics()
|
||||||
const resource = useEvaluationResource(resourceType, resourceId)
|
const resource = useEvaluationResource(resourceType, resourceId)
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
||||||
const config = getEvaluationMockConfig(resourceType)
|
const config = getEvaluationMockConfig(resourceType)
|
||||||
const builtinMetricMap = useMemo(() => new Map(
|
const builtinMetricMap = useMemo(() => new Map(
|
||||||
resource.metrics
|
resource.metrics
|
||||||
@ -45,6 +44,16 @@ const PipelineEvaluation = ({
|
|||||||
}, [availableMetricIds, builtinMetricMap, config.builtinMetrics])
|
}, [availableMetricIds, builtinMetricMap, config.builtinMetrics])
|
||||||
const isConfigReady = !!resource.judgeModelId && builtinMetricMap.size > 0
|
const isConfigReady = !!resource.judgeModelId && builtinMetricMap.size > 0
|
||||||
const isRunnable = isEvaluationRunnable(resource)
|
const isRunnable = isEvaluationRunnable(resource)
|
||||||
|
const { inputFields, isInputFieldsLoading } = usePublishedInputFields(resourceType, resourceId)
|
||||||
|
const actions = useInputFieldsActions({
|
||||||
|
resourceType,
|
||||||
|
resourceId,
|
||||||
|
inputFields,
|
||||||
|
isInputFieldsLoading,
|
||||||
|
isPanelReady: isConfigReady,
|
||||||
|
isRunnable,
|
||||||
|
templateFileName: config.templateFileName,
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ensureResource(resourceType, resourceId)
|
ensureResource(resourceType, resourceId)
|
||||||
@ -60,23 +69,6 @@ const PipelineEvaluation = ({
|
|||||||
addBuiltinMetric(resourceType, resourceId, metricId)
|
addBuiltinMetric(resourceType, resourceId, metricId)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDownloadTemplate = () => {
|
|
||||||
const content = ['case_id,input,expected', '1,Example input,Example output'].join('\n')
|
|
||||||
const link = document.createElement('a')
|
|
||||||
link.href = `data:text/csv;charset=utf-8,${encodeURIComponent(content)}`
|
|
||||||
link.download = config.templateFileName
|
|
||||||
link.click()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleUploadAndRun = () => {
|
|
||||||
if (!isRunnable) {
|
|
||||||
toast.warning(t('batch.validation'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fileInputRef.current?.click()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full min-h-0 flex-col bg-background-default xl:flex-row">
|
<div className="flex h-full min-h-0 flex-col bg-background-default xl:flex-row">
|
||||||
<div className="flex min-h-0 flex-col border-b border-divider-subtle bg-background-default xl:w-[450px] xl:shrink-0 xl:border-r xl:border-b-0">
|
<div className="flex min-h-0 flex-col border-b border-divider-subtle bg-background-default xl:w-[450px] xl:shrink-0 xl:border-r xl:border-b-0">
|
||||||
@ -138,37 +130,32 @@ const PipelineEvaluation = ({
|
|||||||
<Button
|
<Button
|
||||||
className="flex-1 justify-center"
|
className="flex-1 justify-center"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
disabled={!isConfigReady}
|
disabled={!actions.canDownloadTemplate}
|
||||||
onClick={handleDownloadTemplate}
|
onClick={actions.handleDownloadTemplate}
|
||||||
>
|
>
|
||||||
<span aria-hidden="true" className="mr-1 i-ri-file-excel-2-line h-4 w-4" />
|
<span aria-hidden="true" className="mr-1 i-ri-file-excel-2-line h-4 w-4" />
|
||||||
{t('batch.downloadTemplate')}
|
{t('batch.downloadTemplate')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<div className="flex-1">
|
||||||
className="flex-1 justify-center"
|
<UploadRunPopover
|
||||||
variant="primary"
|
open={actions.isUploadPopoverOpen}
|
||||||
disabled={!isConfigReady}
|
onOpenChange={actions.setIsUploadPopoverOpen}
|
||||||
onClick={handleUploadAndRun}
|
triggerDisabled={actions.uploadButtonDisabled}
|
||||||
>
|
triggerLabel={t('pipeline.uploadAndRun')}
|
||||||
{t('pipeline.uploadAndRun')}
|
inputFields={inputFields}
|
||||||
</Button>
|
currentFileName={actions.currentFileName}
|
||||||
|
currentFileExtension={actions.currentFileExtension}
|
||||||
|
currentFileSize={actions.currentFileSize}
|
||||||
|
isFileUploading={actions.isFileUploading}
|
||||||
|
isRunDisabled={actions.isRunDisabled}
|
||||||
|
isRunning={actions.isRunning}
|
||||||
|
onUploadFile={actions.handleUploadFile}
|
||||||
|
onClearUploadedFile={actions.handleClearUploadedFile}
|
||||||
|
onDownloadTemplate={actions.handleDownloadTemplate}
|
||||||
|
onRun={actions.handleRun}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input
|
|
||||||
ref={fileInputRef}
|
|
||||||
hidden
|
|
||||||
type="file"
|
|
||||||
accept=".csv,.xlsx"
|
|
||||||
onChange={(event) => {
|
|
||||||
const file = event.target.files?.[0]
|
|
||||||
if (!file)
|
|
||||||
return
|
|
||||||
|
|
||||||
setUploadedFileName(resourceType, resourceId, file.name)
|
|
||||||
runBatchTest(resourceType, resourceId)
|
|
||||||
event.target.value = ''
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user