diff --git a/src/agents/orchestrator.py b/src/agents/orchestrator.py index ed85947..225a666 100644 --- a/src/agents/orchestrator.py +++ b/src/agents/orchestrator.py @@ -627,16 +627,32 @@ class AgentOrchestrator: self._queue.task_done() async def _mark_refresh_cancelled(self, incident_id: int): - """Markiert den laufenden Refresh-Log-Eintrag als cancelled.""" + """Markiert den laufenden Refresh-Log-Eintrag als cancelled und schliesst + alle noch aktiven Pipeline-Schritte. Ohne den zweiten Schritt blieb der + zuletzt aktive Step-Eintrag verwaist und das Frontend zeigte dauerhaft + 'Schritt X laeuft', weil /api/incidents//pipeline aus + refresh_pipeline_steps liest.""" from database import get_db + from services.pipeline_tracker import cancel_active_steps db = await get_db() try: + now_str = datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S') + cur = await db.execute( + "SELECT id FROM refresh_log WHERE incident_id = ? AND status = 'running'", + (incident_id,), + ) + row = await cur.fetchone() + refresh_log_id = row["id"] if row else None + await db.execute( """UPDATE refresh_log SET status = 'cancelled', error_message = 'Vom Nutzer abgebrochen', completed_at = ? WHERE incident_id = ? AND status = 'running'""", - (datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S'), incident_id), + (now_str, incident_id), ) await db.commit() + + if refresh_log_id is not None: + await cancel_active_steps(db, refresh_log_id=refresh_log_id) except Exception as e: logger.warning(f"Konnte Refresh-Log nicht als abgebrochen markieren: {e}") finally: diff --git a/src/services/pipeline_tracker.py b/src/services/pipeline_tracker.py index 5ec0e82..d192964 100644 --- a/src/services/pipeline_tracker.py +++ b/src/services/pipeline_tracker.py @@ -228,3 +228,25 @@ async def error_step(db, ws_manager, *, step_id: Optional[int], refresh_log_id: "status": "error", "pass_number": pass_number, }, visibility, created_by, tenant_id) + + +async def cancel_active_steps(db, *, refresh_log_id: int) -> int: + """Schliesst alle noch aktiven Pipeline-Schritte eines Refreshs als 'cancelled' ab. + + Wird vom Orchestrator nach einem User-Cancel aufgerufen. Ohne diesen Schritt + bleibt der zuletzt aktive Step-Eintrag verwaist und der Pipeline-Endpoint + liefert dauerhaft 'Schritt X laeuft' an die UI. + """ + try: + cur = await db.execute( + """UPDATE refresh_pipeline_steps + SET status = 'cancelled', completed_at = ? + WHERE refresh_log_id = ? AND status = 'active'""", + (_now_db(), refresh_log_id), + ) + await db.commit() + return cur.rowcount or 0 + except Exception as e: + logger.warning(f"Pipeline cancel_active_steps DB-Fehler: {e}") + return 0 +