@@ -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,11 +189,11 @@ 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 ) ;
182198 setWorkflowIsRunning ( true ) ;
183199 setWorkflowTestExecution ( undefined ) ;
@@ -189,25 +205,66 @@ const IntegrationHeader = ({
189205 if ( workflow . id ) {
190206 captureIntegrationWorkflowTested ( ) ;
191207
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- } ) ;
208+ setWorkflowIsRunning ( true ) ;
209+ setJobId ( null ) ;
210+ persistJobId ( null ) ;
211+
212+ const req = getTestWorkflowStreamPostRequest ( {
213+ environmentId : currentEnvironmentId ,
214+ id : workflow . id ,
215+ } ) ;
216+ setStreamRequest ( req ) ;
209217 }
210- } ;
218+ } , [
219+ captureIntegrationWorkflowTested ,
220+ currentEnvironmentId ,
221+ bottomResizablePanelRef ,
222+ persistJobId ,
223+ setShowBottomPanelOpen ,
224+ setStreamRequest ,
225+ setWorkflowIsRunning ,
226+ setWorkflowTestExecution ,
227+ workflow . id ,
228+ ] ) ;
229+
230+ const handleStopClick = useCallback ( ( ) => {
231+ setWorkflowIsRunning ( false ) ;
232+ close ( ) ;
233+ setStreamRequest ( null ) ;
234+
235+ if ( jobId ) {
236+ void workflowTestApi . stopWorkflowTest ( { jobId} ) . finally ( ( ) => {
237+ persistJobId ( null ) ;
238+ setJobId ( null ) ;
239+ } ) ;
240+ }
241+ } , [ close , jobId , persistJobId , setStreamRequest , setWorkflowIsRunning ] ) ;
242+
243+ useEffect ( ( ) => {
244+ if ( ! workflow . id || currentEnvironmentId === undefined ) return ;
245+
246+ const jobId = getPersistedJobId ( ) ;
247+
248+ if ( ! jobId ) {
249+ return ;
250+ }
251+
252+ setWorkflowIsRunning ( true ) ;
253+ setJobId ( jobId ) ;
254+
255+ setStreamRequest ( getTestWorkflowAttachRequest ( { jobId} ) ) ;
256+ // eslint-disable-next-line react-hooks/exhaustive-deps
257+ } , [ workflow . id , currentEnvironmentId , getPersistedJobId ] ) ;
258+
259+ // On transport error (e.g., 4xx/5xx), make sure to reset running state and clear the request to prevent retries
260+ useEffect ( ( ) => {
261+ if ( error ) {
262+ setWorkflowIsRunning ( false ) ;
263+ setStreamRequest ( null ) ;
264+ persistJobId ( null ) ;
265+ setJobId ( null ) ;
266+ }
267+ } , [ error , persistJobId , setWorkflowIsRunning , setStreamRequest ] ) ;
211268
212269 return (
213270 < header className = "flex items-center px-3 py-2.5" >
@@ -246,7 +303,7 @@ const IntegrationHeader = ({
246303 ) }
247304
248305 { workflowIsRunning ? (
249- < IntegrationHeaderStopButton />
306+ < IntegrationHeaderStopButton onClick = { handleStopClick } />
250307 ) : (
251308 < IntegrationHeaderRunButton onRunClick = { handleRunClick } runDisabled = { runDisabled } />
252309 ) }
0 commit comments