@@ -4,9 +4,10 @@ use axum::extract::State;
44use axum:: http:: StatusCode ;
55use axum:: Json ;
66
7- use flowctl_core:: id:: is_task_id;
7+ use flowctl_core:: id:: { epic_id_from_task , is_task_id} ;
88use flowctl_core:: state_machine:: { Status , Transition } ;
99use flowctl_core:: types:: FLOW_DIR ;
10+ use flowctl_scheduler:: FlowEvent ;
1011use flowctl_service:: lifecycle:: { BlockTaskRequest , DoneTaskRequest , RestartTaskRequest , StartTaskRequest } ;
1112use flowctl_service:: ServiceError ;
1213
@@ -77,9 +78,18 @@ pub async fn start_task_handler(
7778 . map_err ( |e| AppError :: Internal ( format ! ( "spawn_blocking failed: {e}" ) ) ) ?;
7879
7980 match result {
80- Ok ( resp) => Ok ( Json (
81- serde_json:: json!( { "success" : true , "id" : resp. task_id} ) ,
82- ) ) ,
81+ Ok ( resp) => {
82+ let epic_id = epic_id_from_task ( & resp. task_id ) . unwrap_or_default ( ) ;
83+ state. event_bus . emit ( FlowEvent :: TaskStatusChanged {
84+ task_id : resp. task_id . clone ( ) ,
85+ epic_id,
86+ from_status : "todo" . to_string ( ) ,
87+ to_status : format ! ( "{:?}" , resp. status) . to_lowercase ( ) ,
88+ } ) ;
89+ Ok ( Json (
90+ serde_json:: json!( { "success" : true , "id" : resp. task_id} ) ,
91+ ) )
92+ }
8393 Err ( e) => Err ( service_error_to_app_error ( e) ) ,
8494 }
8595}
@@ -109,10 +119,19 @@ pub async fn start_task_rest_handler(
109119 . map_err ( |e| AppError :: Internal ( format ! ( "spawn_blocking failed: {e}" ) ) ) ?;
110120
111121 match result {
112- Ok ( resp) => Ok ( Json ( serde_json:: json!( {
113- "id" : resp. task_id,
114- "status" : format!( "{:?}" , resp. status) . to_lowercase( )
115- } ) ) ) ,
122+ Ok ( resp) => {
123+ let epic_id = epic_id_from_task ( & resp. task_id ) . unwrap_or_default ( ) ;
124+ state. event_bus . emit ( FlowEvent :: TaskStatusChanged {
125+ task_id : resp. task_id . clone ( ) ,
126+ epic_id,
127+ from_status : "todo" . to_string ( ) ,
128+ to_status : format ! ( "{:?}" , resp. status) . to_lowercase ( ) ,
129+ } ) ;
130+ Ok ( Json ( serde_json:: json!( {
131+ "id" : resp. task_id,
132+ "status" : format!( "{:?}" , resp. status) . to_lowercase( )
133+ } ) ) )
134+ }
116135 Err ( e) => Err ( service_error_to_app_error ( e) ) ,
117136 }
118137}
@@ -148,11 +167,20 @@ pub async fn done_task_rest_handler(
148167 . map_err ( |e| AppError :: Internal ( format ! ( "spawn_blocking failed: {e}" ) ) ) ?;
149168
150169 match result {
151- Ok ( resp) => Ok ( Json ( serde_json:: json!( {
152- "id" : resp. task_id,
153- "status" : format!( "{:?}" , resp. status) . to_lowercase( ) ,
154- "duration_seconds" : resp. duration_seconds
155- } ) ) ) ,
170+ Ok ( resp) => {
171+ let epic_id = epic_id_from_task ( & resp. task_id ) . unwrap_or_default ( ) ;
172+ state. event_bus . emit ( FlowEvent :: TaskStatusChanged {
173+ task_id : resp. task_id . clone ( ) ,
174+ epic_id,
175+ from_status : "in_progress" . to_string ( ) ,
176+ to_status : format ! ( "{:?}" , resp. status) . to_lowercase ( ) ,
177+ } ) ;
178+ Ok ( Json ( serde_json:: json!( {
179+ "id" : resp. task_id,
180+ "status" : format!( "{:?}" , resp. status) . to_lowercase( ) ,
181+ "duration_seconds" : resp. duration_seconds
182+ } ) ) )
183+ }
156184 Err ( e) => Err ( service_error_to_app_error ( e) ) ,
157185 }
158186}
@@ -180,10 +208,19 @@ pub async fn block_task_rest_handler(
180208 . map_err ( |e| AppError :: Internal ( format ! ( "spawn_blocking failed: {e}" ) ) ) ?;
181209
182210 match result {
183- Ok ( resp) => Ok ( Json ( serde_json:: json!( {
184- "id" : resp. task_id,
185- "status" : format!( "{:?}" , resp. status) . to_lowercase( )
186- } ) ) ) ,
211+ Ok ( resp) => {
212+ let epic_id = epic_id_from_task ( & resp. task_id ) . unwrap_or_default ( ) ;
213+ state. event_bus . emit ( FlowEvent :: TaskStatusChanged {
214+ task_id : resp. task_id . clone ( ) ,
215+ epic_id,
216+ from_status : "in_progress" . to_string ( ) ,
217+ to_status : format ! ( "{:?}" , resp. status) . to_lowercase ( ) ,
218+ } ) ;
219+ Ok ( Json ( serde_json:: json!( {
220+ "id" : resp. task_id,
221+ "status" : format!( "{:?}" , resp. status) . to_lowercase( )
222+ } ) ) )
223+ }
187224 Err ( e) => Err ( service_error_to_app_error ( e) ) ,
188225 }
189226}
@@ -216,11 +253,20 @@ pub async fn restart_task_rest_handler(
216253 . map_err ( |e| AppError :: Internal ( format ! ( "spawn_blocking failed: {e}" ) ) ) ?;
217254
218255 match result {
219- Ok ( resp) => Ok ( Json ( serde_json:: json!( {
220- "id" : resp. cascade_from,
221- "status" : "todo" ,
222- "cascaded" : resp. reset_ids
223- } ) ) ) ,
256+ Ok ( resp) => {
257+ let epic_id = epic_id_from_task ( & resp. cascade_from ) . unwrap_or_default ( ) ;
258+ state. event_bus . emit ( FlowEvent :: TaskStatusChanged {
259+ task_id : resp. cascade_from . clone ( ) ,
260+ epic_id,
261+ from_status : "done" . to_string ( ) ,
262+ to_status : "todo" . to_string ( ) ,
263+ } ) ;
264+ Ok ( Json ( serde_json:: json!( {
265+ "id" : resp. cascade_from,
266+ "status" : "todo" ,
267+ "cascaded" : resp. reset_ids
268+ } ) ) )
269+ }
224270 Err ( e) => Err ( service_error_to_app_error ( e) ) ,
225271 }
226272}
@@ -237,12 +283,21 @@ pub async fn skip_task_rest_handler(
237283 . get ( & task_id)
238284 . map_err ( |_| AppError :: InvalidInput ( format ! ( "task not found: {task_id}" ) ) ) ?;
239285
286+ let from_status = format ! ( "{:?}" , task. status) . to_lowercase ( ) ;
240287 Transition :: new ( task. status , Status :: Skipped ) . map_err ( |e| {
241288 AppError :: InvalidTransition ( format ! ( "cannot skip task '{task_id}': {e}" ) )
242289 } ) ?;
243290
244291 repo. update_status ( & task_id, Status :: Skipped ) ?;
245292
293+ let epic_id = epic_id_from_task ( & task_id) . unwrap_or_default ( ) ;
294+ state. event_bus . emit ( FlowEvent :: TaskStatusChanged {
295+ task_id : task_id. clone ( ) ,
296+ epic_id,
297+ from_status,
298+ to_status : "skipped" . to_string ( ) ,
299+ } ) ;
300+
246301 Ok ( Json ( serde_json:: json!( {
247302 "id" : task_id,
248303 "status" : "skipped"
@@ -280,9 +335,18 @@ pub async fn done_task_handler(
280335 . map_err ( |e| AppError :: Internal ( format ! ( "spawn_blocking failed: {e}" ) ) ) ?;
281336
282337 match result {
283- Ok ( resp) => Ok ( Json (
284- serde_json:: json!( { "success" : true , "id" : resp. task_id} ) ,
285- ) ) ,
338+ Ok ( resp) => {
339+ let epic_id = epic_id_from_task ( & resp. task_id ) . unwrap_or_default ( ) ;
340+ state. event_bus . emit ( FlowEvent :: TaskStatusChanged {
341+ task_id : resp. task_id . clone ( ) ,
342+ epic_id,
343+ from_status : "in_progress" . to_string ( ) ,
344+ to_status : format ! ( "{:?}" , resp. status) . to_lowercase ( ) ,
345+ } ) ;
346+ Ok ( Json (
347+ serde_json:: json!( { "success" : true , "id" : resp. task_id} ) ,
348+ ) )
349+ }
286350 Err ( e) => Err ( service_error_to_app_error ( e) ) ,
287351 }
288352}
@@ -298,12 +362,21 @@ pub async fn skip_task_handler(
298362 . get ( & body. task_id )
299363 . map_err ( |_| AppError :: InvalidInput ( format ! ( "task not found: {}" , body. task_id) ) ) ?;
300364
365+ let from_status = format ! ( "{:?}" , task. status) . to_lowercase ( ) ;
301366 Transition :: new ( task. status , Status :: Skipped ) . map_err ( |e| {
302367 AppError :: InvalidTransition ( format ! ( "cannot skip task '{}': {}" , body. task_id, e) )
303368 } ) ?;
304369
305370 repo. update_status ( & body. task_id , Status :: Skipped ) ?;
306371
372+ let epic_id = epic_id_from_task ( & body. task_id ) . unwrap_or_default ( ) ;
373+ state. event_bus . emit ( FlowEvent :: TaskStatusChanged {
374+ task_id : body. task_id . clone ( ) ,
375+ epic_id,
376+ from_status,
377+ to_status : "skipped" . to_string ( ) ,
378+ } ) ;
379+
307380 Ok ( Json ( serde_json:: json!( {
308381 "success" : true ,
309382 "id" : body. task_id,
@@ -334,11 +407,20 @@ pub async fn block_task_handler(
334407 . map_err ( |e| AppError :: Internal ( format ! ( "spawn_blocking failed: {e}" ) ) ) ?;
335408
336409 match result {
337- Ok ( resp) => Ok ( Json ( serde_json:: json!( {
338- "success" : true ,
339- "id" : resp. task_id,
340- "status" : "blocked"
341- } ) ) ) ,
410+ Ok ( resp) => {
411+ let epic_id = epic_id_from_task ( & resp. task_id ) . unwrap_or_default ( ) ;
412+ state. event_bus . emit ( FlowEvent :: TaskStatusChanged {
413+ task_id : resp. task_id . clone ( ) ,
414+ epic_id,
415+ from_status : "in_progress" . to_string ( ) ,
416+ to_status : format ! ( "{:?}" , resp. status) . to_lowercase ( ) ,
417+ } ) ;
418+ Ok ( Json ( serde_json:: json!( {
419+ "success" : true ,
420+ "id" : resp. task_id,
421+ "status" : "blocked"
422+ } ) ) )
423+ }
342424 Err ( e) => Err ( service_error_to_app_error ( e) ) ,
343425 }
344426}
@@ -369,10 +451,19 @@ pub async fn restart_task_handler(
369451 . map_err ( |e| AppError :: Internal ( format ! ( "spawn_blocking failed: {e}" ) ) ) ?;
370452
371453 match result {
372- Ok ( resp) => Ok ( Json ( serde_json:: json!( {
373- "success" : true ,
374- "reset" : resp. reset_ids
375- } ) ) ) ,
454+ Ok ( resp) => {
455+ let epic_id = epic_id_from_task ( & resp. cascade_from ) . unwrap_or_default ( ) ;
456+ state. event_bus . emit ( FlowEvent :: TaskStatusChanged {
457+ task_id : resp. cascade_from . clone ( ) ,
458+ epic_id,
459+ from_status : "done" . to_string ( ) ,
460+ to_status : "todo" . to_string ( ) ,
461+ } ) ;
462+ Ok ( Json ( serde_json:: json!( {
463+ "success" : true ,
464+ "reset" : resp. reset_ids
465+ } ) ) )
466+ }
376467 Err ( e) => Err ( service_error_to_app_error ( e) ) ,
377468 }
378469}
0 commit comments