88 * @license Pimcore Open Core License (POCL)
99 */
1010
11- import React , { useEffect , useState } from 'react'
11+ import React from 'react'
1212import {
13- Button ,
1413 DatePicker ,
1514 Form ,
16- Progress ,
17- Select ,
18- Text ,
19- useMessage
15+ Select
2016} from '@pimcore/studio-ui-bundle/components'
2117import { useTranslation } from '@pimcore/studio-ui-bundle/app'
22- import { ApiError , trackError } from '@pimcore/studio-ui-bundle/modules/app'
23- import {
24- useBundleDataImporterConfigStartImportMutation ,
25- useBundleDataImporterConfigCancelExecutionMutation ,
26- useBundleDataImporterConfigCheckImportProgressQuery
27- } from '../../data-importer-api-slice.gen'
2818import type { DataImporterFormValues } from '../../types'
2919import { DataImporterPanel } from './steps/data-importer-panel/data-importer-panel'
30- import { useStyles } from './execution-tab.styles'
3120import { CronDefinitionSection } from './execution-tab/cron-definition-section/cron-definition-section'
32- import { ManualExecutionButton } from './execution-tab/manual-execution-button/manual-execution-button'
33-
34- const POLL_INTERVAL_MS = 5000
21+ import { ExecutionStatus } from './execution-tab/execution-status/execution-status'
3522
3623export interface ExecutionTabProps {
3724 configName : string
@@ -46,88 +33,6 @@ const isOneTimeJob = (values: DataImporterFormValues): boolean =>
4633
4734export const ExecutionTab = ( { configName, isDirty } : ExecutionTabProps ) : React . JSX . Element => {
4835 const { t } = useTranslation ( )
49- const messageApi = useMessage ( )
50- const { styles } = useStyles ( )
51-
52- const [ startImport , { isLoading : isStarting } ] = useBundleDataImporterConfigStartImportMutation ( )
53- const [ cancelExecution , { isLoading : isCancelling } ] = useBundleDataImporterConfigCancelExecutionMutation ( )
54-
55- const { data : progressData , refetch : refetchProgress } = useBundleDataImporterConfigCheckImportProgressQuery (
56- { name : configName } ,
57- { pollingInterval : POLL_INTERVAL_MS }
58- )
59-
60- const [ optimisticRunning , setOptimisticRunning ] = useState ( false )
61- const [ optimisticProgress , setOptimisticProgress ] = useState < { processedItems : number , totalItems : number , progress : number } | null > ( null )
62- const [ optimisticCancelled , setOptimisticCancelled ] = useState ( false )
63- const [ hasCompleted , setHasCompleted ] = useState ( false )
64-
65- // Once a real "running" response arrives, clear start-optimistic flags
66- useEffect ( ( ) => {
67- if ( progressData ?. isRunning === true ) {
68- setOptimisticRunning ( false )
69- setOptimisticProgress ( null )
70- setHasCompleted ( false )
71- }
72- } , [ progressData ?. isRunning ] )
73-
74- // Once polling confirms not running, clear cancel-optimistic flag; mark completed if items were processed
75- useEffect ( ( ) => {
76- if ( progressData ?. isRunning === false ) {
77- setOptimisticCancelled ( false )
78- if ( ( progressData . processedItems ?? 0 ) > 0 ) {
79- setHasCompleted ( true )
80- }
81- }
82- } , [ progressData ?. isRunning ] )
83-
84- const isRunning = ! optimisticCancelled && ( optimisticRunning || ( progressData ?. isRunning ?? false ) )
85- const progress = optimisticProgress ?. progress ?? progressData ?. progress ?? 0
86- const processedItems = optimisticProgress ?. processedItems ?? progressData ?. processedItems ?? 0
87- const totalItems = optimisticProgress ?. totalItems ?? progressData ?. totalItems ?? 0
88-
89- const handleStartImport = async ( ) : Promise < void > => {
90- const result = await startImport ( { name : configName } )
91-
92- if ( 'error' in result ) {
93- if ( result . error !== undefined ) {
94- trackError ( new ApiError ( result . error ) )
95- }
96- void messageApi . error ( t ( 'data-importer.execution.start-import.error' ) )
97- return
98- }
99-
100- if ( result . data . success ) {
101- void messageApi . success ( t ( 'data-importer.execution.start-import.success' ) )
102- // Immediately show progress bar at 0, resetting any previous run's values
103- setOptimisticProgress ( { processedItems : 0 , totalItems : progressData ?. totalItems ?? 0 , progress : 0 } )
104- setOptimisticRunning ( true )
105- setHasCompleted ( false )
106- } else {
107- void messageApi . error ( t ( 'data-importer.execution.start-import.error' ) )
108- }
109- void refetchProgress ( )
110- }
111-
112- const handleCancelExecution = async ( ) : Promise < void > => {
113- const result = await cancelExecution ( { name : configName } )
114-
115- if ( 'error' in result ) {
116- if ( result . error !== undefined ) {
117- trackError ( new ApiError ( result . error ) )
118- }
119- void messageApi . error ( t ( 'data-importer.execution.cancel.error' ) )
120- return
121- }
122-
123- void messageApi . success ( t ( 'data-importer.execution.cancel.success' ) )
124- // Immediately hide the progress bar before polling confirms
125- setOptimisticCancelled ( true )
126- setOptimisticRunning ( false )
127- setOptimisticProgress ( null )
128- setHasCompleted ( false )
129- void refetchProgress ( )
130- }
13136
13237 const scheduleTypeOptions = [
13338 { value : 'recurring' , label : t ( 'data-importer.execution.schedule-type.recurring' ) } ,
@@ -136,15 +41,11 @@ export const ExecutionTab = ({ configName, isDirty }: ExecutionTabProps): React.
13641
13742 return (
13843 < >
139- { /* ── Manual Execution ── */ }
140- < DataImporterPanel title = { t ( 'data-importer.execution.manual-execution' ) } >
141- < ManualExecutionButton
142- isDirty = { isDirty }
143- isStarting = { isStarting }
144- label = { t ( 'data-importer.execution.start-import' ) }
145- onStart = { ( ) => { void handleStartImport ( ) } }
146- />
147- </ DataImporterPanel >
44+ { /* ── Manual Execution + Execution Status (isolated to avoid poll-driven re-renders) ── */ }
45+ < ExecutionStatus
46+ configName = { configName }
47+ isDirty = { isDirty }
48+ />
14849
14950 { /* ── Scheduled Execution ── */ }
15051 < DataImporterPanel title = { t ( 'data-importer.execution.settings.title' ) } >
@@ -179,52 +80,13 @@ export const ExecutionTab = ({ configName, isDirty }: ExecutionTabProps): React.
17980 name = { [ 'executionConfig' , 'scheduledAt' ] }
18081 >
18182 < DatePicker
182- outputFormat = "DD -MM-YYYY HH:mm"
83+ outputFormat = "YYYY -MM-DD HH:mm"
18384 outputType = "dateString"
18485 showTime = { { format : 'HH:mm' } }
18586 />
18687 </ Form . Item >
18788 </ Form . Conditional >
18889 </ DataImporterPanel >
189-
190- { /* ── Execution Status ── */ }
191- < DataImporterPanel
192- noWidthLimit
193- title = { t ( 'data-importer.execution.status.title' ) }
194- >
195- { isRunning
196- ? (
197- < >
198- < p className = { styles . progressLabel } >
199- { t ( 'data-importer.execution.status.current-progress' ) }
200- </ p >
201- < div className = { styles . progressWrapper } >
202- < Progress
203- format = { ( ) => t ( 'data-importer.execution.status.processing' , { processedItems, totalItems } ) }
204- percent = { Math . round ( progress * 100 ) }
205- percentPosition = { { align : 'start' , type : 'inner' } }
206- size = { [ - 1 , 32 ] }
207- status = "active"
208- strokeColor = { styles . colorFill }
209- trailColor = { 'rgba(0, 0, 0, 0.06)' }
210- />
211- </ div >
212- < Button
213- loading = { isCancelling }
214- onClick = { ( ) => { void handleCancelExecution ( ) } }
215- >
216- { t ( 'data-importer.execution.status.cancel' ) }
217- </ Button >
218- </ >
219- )
220- : (
221- < Text >
222- { t ( hasCompleted
223- ? 'data-importer.execution.status.finished'
224- : 'data-importer.execution.status.not-running' ) }
225- </ Text >
226- ) }
227- </ DataImporterPanel >
22890 </ >
22991 )
23092}
0 commit comments