@@ -29,12 +29,14 @@ import useWorkflowNodeDetailsPanelStore from '@/pages/platform/workflow-editor/s
2929import DeleteWorkflowAlertDialog from '@/shared/components/DeleteWorkflowAlertDialog' ;
3030import WorkflowDialog from '@/shared/components/workflow/WorkflowDialog' ;
3131import { useAnalytics } from '@/shared/hooks/useAnalytics' ;
32+ import { useWorkflowTestStream } from '@/shared/hooks/useWorkflowTestStream' ;
3233import { WorkflowTestApi } from '@/shared/middleware/platform/workflow/test' ;
3334import { useEnvironmentStore } from '@/shared/stores/useEnvironmentStore' ;
3435import { UpdateWorkflowMutationType } from '@/shared/types' ;
36+ import { getTestWorkflowAttachRequest , getTestWorkflowStreamPostRequest } from '@/shared/util/testWorkflow-utils' ;
3537import { useQueryClient } from '@tanstack/react-query' ;
3638import { PlusIcon } from 'lucide-react' ;
37- import { RefObject , useState } from 'react' ;
39+ import { RefObject , useCallback , useEffect , useState } from 'react' ;
3840import { ImperativePanelHandle } from 'react-resizable-panels' ;
3941import { useLoaderData , useNavigate , useSearchParams } from 'react-router-dom' ;
4042import { useShallow } from 'zustand/react/shallow' ;
@@ -54,6 +56,7 @@ const IntegrationHeader = ({
5456 runDisabled : boolean ;
5557 updateWorkflowMutation : UpdateWorkflowMutationType ;
5658} ) => {
59+ const [ jobId , setJobId ] = useState < string | null > ( null ) ;
5760 const [ showDeleteIntegrationAlertDialog , setShowDeleteIntegrationAlertDialog ] = useState ( false ) ;
5861 const [ showDeleteWorkflowAlertDialog , setShowDeleteWorkflowAlertDialog ] = useState ( false ) ;
5962 const [ showEditIntegrationDialog , setShowEditIntegrationDialog ] = useState ( false ) ;
@@ -82,6 +85,8 @@ const IntegrationHeader = ({
8285
8386 const { captureIntegrationWorkflowCreated, captureIntegrationWorkflowTested} = useAnalytics ( ) ;
8487
88+ const queryClient = useQueryClient ( ) ;
89+
8590 const navigate = useNavigate ( ) ;
8691
8792 const [ searchParams ] = useSearchParams ( ) ;
@@ -92,7 +97,18 @@ const IntegrationHeader = ({
9297 ! showDeleteIntegrationAlertDialog
9398 ) ;
9499
95- const queryClient = useQueryClient ( ) ;
100+ const { close, error, getPersistedJobId, persistJobId, setStreamRequest} = useWorkflowTestStream (
101+ workflow . id ! ,
102+ ( ) => {
103+ if ( bottomResizablePanelRef . current && bottomResizablePanelRef . current . getSize ( ) === 0 ) {
104+ bottomResizablePanelRef . current . resize ( 35 ) ;
105+ }
106+
107+ setJobId ( null ) ;
108+ } ,
109+ ( ) => setJobId ( null ) ,
110+ ( jobId ) => setJobId ( jobId )
111+ ) ;
96112
97113 const createIntegrationWorkflowMutation = useCreateIntegrationWorkflowMutation ( {
98114 onSuccess : ( integrationWorkflowId ) => {
@@ -173,13 +189,12 @@ const IntegrationHeader = ({
173189 setCurrentNode ( undefined ) ;
174190
175191 navigate (
176- `/embedded/integrations/${ integrationId } /integration-workflows/${ integrationWorkflowId } ?${ searchParams } `
192+ `/embedded/integrations/${ integrationId } /integration-workflows/${ integrationWorkflowId } ?${ searchParams . toString ( ) } `
177193 ) ;
178194 } ;
179195
180- const handleRunClick = ( ) => {
196+ const handleRunClick = useCallback ( ( ) => {
181197 setShowBottomPanelOpen ( true ) ;
182- setWorkflowIsRunning ( true ) ;
183198 setWorkflowTestExecution ( undefined ) ;
184199
185200 if ( bottomResizablePanelRef . current ) {
@@ -189,25 +204,65 @@ const IntegrationHeader = ({
189204 if ( workflow . id ) {
190205 captureIntegrationWorkflowTested ( ) ;
191206
192- workflowTestApi
193- . testWorkflow ( {
194- environmentId : currentEnvironmentId ,
195- id : workflow . id ,
196- } )
197- . then ( ( workflowTestExecution ) => {
198- setWorkflowTestExecution ( workflowTestExecution ) ;
199- setWorkflowIsRunning ( false ) ;
200-
201- if ( bottomResizablePanelRef . current && bottomResizablePanelRef . current . getSize ( ) === 0 ) {
202- bottomResizablePanelRef . current . resize ( 35 ) ;
203- }
204- } )
205- . catch ( ( ) => {
206- setWorkflowIsRunning ( false ) ;
207- setWorkflowTestExecution ( undefined ) ;
208- } ) ;
207+ setWorkflowIsRunning ( true ) ;
208+ setJobId ( null ) ;
209+ persistJobId ( null ) ;
210+
211+ const req = getTestWorkflowStreamPostRequest ( {
212+ environmentId : currentEnvironmentId ,
213+ id : workflow . id ,
214+ } ) ;
215+ setStreamRequest ( req ) ;
209216 }
210- } ;
217+ } , [
218+ captureIntegrationWorkflowTested ,
219+ currentEnvironmentId ,
220+ bottomResizablePanelRef ,
221+ persistJobId ,
222+ setShowBottomPanelOpen ,
223+ setStreamRequest ,
224+ setWorkflowIsRunning ,
225+ setWorkflowTestExecution ,
226+ workflow . id ,
227+ ] ) ;
228+
229+ const handleStopClick = useCallback ( ( ) => {
230+ setWorkflowIsRunning ( false ) ;
231+ close ( ) ;
232+ setStreamRequest ( null ) ;
233+
234+ if ( jobId ) {
235+ workflowTestApi . stopWorkflowTest ( { jobId} ) . finally ( ( ) => {
236+ persistJobId ( null ) ;
237+ setJobId ( null ) ;
238+ } ) ;
239+ }
240+ } , [ close , jobId , persistJobId , setStreamRequest , setWorkflowIsRunning ] ) ;
241+
242+ useEffect ( ( ) => {
243+ if ( ! workflow . id || currentEnvironmentId === undefined ) return ;
244+
245+ const jobId = getPersistedJobId ( ) ;
246+
247+ if ( ! jobId ) {
248+ return ;
249+ }
250+
251+ setWorkflowIsRunning ( true ) ;
252+ setJobId ( jobId ) ;
253+
254+ setStreamRequest ( getTestWorkflowAttachRequest ( { jobId} ) ) ;
255+ } , [ workflow . id , currentEnvironmentId , getPersistedJobId , setWorkflowIsRunning , setJobId , setStreamRequest ] ) ;
256+
257+ // On transport error (e.g., 4xx/5xx), make sure to reset running state and clear the request to prevent retries
258+ useEffect ( ( ) => {
259+ if ( error ) {
260+ setWorkflowIsRunning ( false ) ;
261+ setStreamRequest ( null ) ;
262+ persistJobId ( null ) ;
263+ setJobId ( null ) ;
264+ }
265+ } , [ error , persistJobId , setWorkflowIsRunning , setStreamRequest ] ) ;
211266
212267 return (
213268 < header className = "flex items-center px-3 py-2.5" >
@@ -246,7 +301,7 @@ const IntegrationHeader = ({
246301 ) }
247302
248303 { workflowIsRunning ? (
249- < IntegrationHeaderStopButton />
304+ < IntegrationHeaderStopButton onClick = { handleStopClick } />
250305 ) : (
251306 < IntegrationHeaderRunButton onRunClick = { handleRunClick } runDisabled = { runDisabled } />
252307 ) }
0 commit comments