feat: track model execution status with failure icon in explorer#54
feat: track model execution status with failure icon in explorer#54abhizipstack wants to merge 1 commit intomainfrom
Conversation
Implement model-level execution status tracking (NOT_STARTED, RUNNING, SUCCESS, FAILED) at DAG node level with visual indicators in the file explorer. Status is tracked per individual DAG node, ensuring accurate status for each model even when downstream dependencies fail. Backend changes: - Add RunStatus enum and 4 new fields to ConfigModels (run_status, failure_reason, last_run_at, run_duration) - Migration 0003_add_model_run_status - Update file_explorer.load_models to return status fields - Add _update_model_status to DAG executor — called before execution (RUNNING), after success (SUCCESS), and on exception (FAILED with reason) - Update execute/views.py to return 400 on DAG execution failures - Fix clear_cache decorator to re-raise exceptions instead of swallowing them silently Frontend changes: - Add getModelRunStatus helper to render colored dot badges next to model names in the explorer tree - Running: blue, Success: green, Failed: red - Show Popover on hover over failed status with full error message and last run timestamp - Trigger explorer refresh via setRefreshModels after runTransformation succeeds or fails Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
| Filename | Overview |
|---|---|
| backend/visitran/visitran.py | Adds _update_model_status and integrates it into execute_graph; has two bugs: non-executable parent nodes are permanently left in RUNNING state, and run_duration is never computed or saved. |
| frontend/src/ide/explorer/explorer-component.jsx | Adds run-status badges and popover; handleModelRun catch branch doesn't refresh the explorer, so the FAILED badge never appears for context-menu-triggered runs that return 400. |
| backend/backend/utils/cache_service/decorators/cache_decorator.py | Fixes clear_cache to re-raise exceptions instead of silently swallowing them; straightforward and correct. |
| backend/backend/core/routers/execute/views.py | Wraps DAG execution in try/except to return 400 on failure instead of letting a NoneType bubble to DRF as a 500; looks correct. |
| backend/backend/core/migrations/0003_add_model_run_status.py | Adds 4 nullable fields to configmodels; migration is safe and correctly chains from 0002_seed_data. |
| backend/backend/core/models/config_models.py | Adds RunStatus TextChoices enum and 4 new nullable fields; model definition aligns with migration. |
| backend/backend/application/file_explorer/file_explorer.py | Builds a model lookup from all_models to attach status fields to each tree node; correctly guards last_run_at.isoformat() with a null check. |
| frontend/src/ide/editor/no-code-model/no-code-model.jsx | Calls setRefreshModels(true) in both success and failure paths of runTransformation, correctly triggering an explorer tree refresh from the editor. |
Sequence Diagram
sequenceDiagram
participant FE as Frontend (Editor / Explorer)
participant API as execute_run_command
participant DAG as Visitran.execute_graph
participant DB as ConfigModels (DB)
participant EXP as file_explorer API
FE->>API: POST /execute-run-command
API->>DAG: execute_visitran_run_command()
loop Each DAG node
DAG->>DB: run_status = RUNNING
DAG->>DAG: node.materialize()
alt is_executable=false (parent node)
DAG-->>DAG: continue - RUNNING never cleared
else execute node
DAG->>DAG: run_model()
alt success
DAG->>DB: run_status = SUCCESS
else failure
DAG->>DB: run_status = FAILED
end
end
end
alt DAG success
API-->>FE: 200 OK
FE->>EXP: getExplorer() + setRefreshModels(true)
else DAG failure
API-->>FE: 400 Bad Request
FE->>FE: notify(error) only - no explorer refresh
end
EXP->>DB: fetch models with run_status fields
EXP-->>FE: model tree with status badges
Comments Outside Diff (2)
-
backend/visitran/visitran.py, line 280-294 (link)Non-executable parent nodes stuck in
RUNNINGstateWhen a model is run individually or in multi-model mode, parent models are imported for DAG dependency resolution but marked
is_executable=False._update_model_status(RUNNING)is called at line 280 beforematerialize(), but whenis_executable=Falsethe loop hitscontinueat line 293 — skipping theSUCCESSupdate entirely. Those parent nodes are left permanently showing the blue "Running" badge in the explorer.Fix: skip the status update for non-executable nodes, or set
SUCCESSbeforecontinue:# Set status to RUNNING before execution if is_executable: self._update_model_status(str(node_name), ConfigModels.RunStatus.RUNNING if ConfigModels else "RUNNING") self._apply_model_config_override(node) node.materialize( parent_class=node.__class__.__base__, db_connection=self.db_adapter.db_connection, ) if not is_executable: continue
Prompt To Fix With AI
This is a comment left during a code review. Path: backend/visitran/visitran.py Line: 280-294 Comment: **Non-executable parent nodes stuck in `RUNNING` state** When a model is run individually or in multi-model mode, parent models are imported for DAG dependency resolution but marked `is_executable=False`. `_update_model_status(RUNNING)` is called at line 280 before `materialize()`, but when `is_executable=False` the loop hits `continue` at line 293 — skipping the `SUCCESS` update entirely. Those parent nodes are left permanently showing the blue "Running" badge in the explorer. Fix: skip the status update for non-executable nodes, or set `SUCCESS` before `continue`: ```python # Set status to RUNNING before execution if is_executable: self._update_model_status(str(node_name), ConfigModels.RunStatus.RUNNING if ConfigModels else "RUNNING") self._apply_model_config_override(node) node.materialize( parent_class=node.__class__.__base__, db_connection=self.db_adapter.db_connection, ) if not is_executable: continue ``` How can I resolve this? If you propose a fix, please make it concise.
-
frontend/src/ide/explorer/explorer-component.jsx, line 573-577 (link)Explorer not refreshed after context-menu run failure
When
expService.runModelreturns a 400 (the new behavior introduced in this PR), axios throws and the.catch()path runs — but it only callsnotify({ error }).getExploreris never called andsetRefreshModelsis never set, so the FAILED status written to the DB (by_update_model_status) is never reflected in the explorer tree. Users won't see the red failure badge for any model run triggered from the context menu..catch((error) => { messageApi.destroy(`model-run-${modelName}`); notify({ error }); getExplorer(projectId); // add this to reflect FAILED status })
Prompt To Fix With AI
This is a comment left during a code review. Path: frontend/src/ide/explorer/explorer-component.jsx Line: 573-577 Comment: **Explorer not refreshed after context-menu run failure** When `expService.runModel` returns a 400 (the new behavior introduced in this PR), axios throws and the `.catch()` path runs — but it only calls `notify({ error })`. `getExplorer` is never called and `setRefreshModels` is never set, so the FAILED status written to the DB (by `_update_model_status`) is never reflected in the explorer tree. Users won't see the red failure badge for any model run triggered from the context menu. ```javascript .catch((error) => { messageApi.destroy(`model-run-${modelName}`); notify({ error }); getExplorer(projectId); // add this to reflect FAILED status }) ``` How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: backend/visitran/visitran.py
Line: 280-294
Comment:
**Non-executable parent nodes stuck in `RUNNING` state**
When a model is run individually or in multi-model mode, parent models are imported for DAG dependency resolution but marked `is_executable=False`. `_update_model_status(RUNNING)` is called at line 280 before `materialize()`, but when `is_executable=False` the loop hits `continue` at line 293 — skipping the `SUCCESS` update entirely. Those parent nodes are left permanently showing the blue "Running" badge in the explorer.
Fix: skip the status update for non-executable nodes, or set `SUCCESS` before `continue`:
```python
# Set status to RUNNING before execution
if is_executable:
self._update_model_status(str(node_name), ConfigModels.RunStatus.RUNNING if ConfigModels else "RUNNING")
self._apply_model_config_override(node)
node.materialize(
parent_class=node.__class__.__base__,
db_connection=self.db_adapter.db_connection,
)
if not is_executable:
continue
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: frontend/src/ide/explorer/explorer-component.jsx
Line: 573-577
Comment:
**Explorer not refreshed after context-menu run failure**
When `expService.runModel` returns a 400 (the new behavior introduced in this PR), axios throws and the `.catch()` path runs — but it only calls `notify({ error })`. `getExplorer` is never called and `setRefreshModels` is never set, so the FAILED status written to the DB (by `_update_model_status`) is never reflected in the explorer tree. Users won't see the red failure badge for any model run triggered from the context menu.
```javascript
.catch((error) => {
messageApi.destroy(`model-run-${modelName}`);
notify({ error });
getExplorer(projectId); // add this to reflect FAILED status
})
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: backend/visitran/visitran.py
Line: 266-268
Comment:
**`run_duration` field added but never populated**
The migration and model both add `run_duration` (seconds), and the explorer already returns it to the frontend, but `_update_model_status` never calculates or writes it. `update_fields` at line 266 omits it, so it stays `null` for every model.
Consider recording start time before execution and computing duration on completion/failure:
```python
# before execute_graph loop body
start_time = time.monotonic()
# in SUCCESS path
run_duration = time.monotonic() - start_time
model_instance.run_duration = run_duration
model_instance.save(update_fields=["run_status", "last_run_at", "failure_reason", "run_duration"])
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "feat: track model execution status and s..." | Re-trigger Greptile
| model_instance.save(update_fields=["run_status", "last_run_at", "failure_reason"]) | ||
| except Exception as e: | ||
| logging.warning(f"Failed to update model status for {model_name}: {e}") |
There was a problem hiding this comment.
run_duration field added but never populated
The migration and model both add run_duration (seconds), and the explorer already returns it to the frontend, but _update_model_status never calculates or writes it. update_fields at line 266 omits it, so it stays null for every model.
Consider recording start time before execution and computing duration on completion/failure:
# before execute_graph loop body
start_time = time.monotonic()
# in SUCCESS path
run_duration = time.monotonic() - start_time
model_instance.run_duration = run_duration
model_instance.save(update_fields=["run_status", "last_run_at", "failure_reason", "run_duration"])Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/visitran/visitran.py
Line: 266-268
Comment:
**`run_duration` field added but never populated**
The migration and model both add `run_duration` (seconds), and the explorer already returns it to the frontend, but `_update_model_status` never calculates or writes it. `update_fields` at line 266 omits it, so it stays `null` for every model.
Consider recording start time before execution and computing duration on completion/failure:
```python
# before execute_graph loop body
start_time = time.monotonic()
# in SUCCESS path
run_duration = time.monotonic() - start_time
model_instance.run_duration = run_duration
model_instance.save(update_fields=["run_status", "last_run_at", "failure_reason", "run_duration"])
```
How can I resolve this? If you propose a fix, please make it concise.| try: | ||
| from backend.core.models.config_models import ConfigModels | ||
| except ImportError: | ||
| ConfigModels = None |
There was a problem hiding this comment.
@abhizipstack Why are we conditionally importing ConfigModels? It is required for the OSS version and is always present in the OSS codebase.
| if not session: | ||
| return | ||
|
|
||
| project_id = session.project_id | ||
| if not project_id: | ||
| return |
There was a problem hiding this comment.
@abhizipstack Should we raise helpful exceptions when session or project_id is not present? Right now, we’re simply returning, which allows the process to continue without any errors. Please let me know if this behavior is expected.
| self._update_model_status( | ||
| str(node_name), | ||
| ConfigModels.RunStatus.FAILED if ConfigModels else "FAILED", | ||
| failure_reason=str(visitran_err), | ||
| ) |
There was a problem hiding this comment.
@abhizipstack Do we need this fallback here? We’ve already defined the run status constants in config_models.py, and this code is already using ConfigModels.RunStatus.FAILED. Because of that, falling back to "FAILED" feels a bit over-defensive. Please let me know if I’m missing something.
| } | ||
| }; | ||
|
|
||
| const getModelRunStatus = (runStatus, failureReason, lastRunAt) => { |
There was a problem hiding this comment.
@abhizipstack getModelRunStatus is currently defined inside the component body, so it gets recreated on every render. Since it does not depend on the component’s state or props, consider moving it outside the component. This is especially relevant because transformTree, which calls this function, runs frequently during modelsRunning state changes, tree rebuilds, and refreshes.
| if (runStatus === "RUNNING") { | ||
| return ( | ||
| <Tooltip title="Running"> | ||
| <span style={{ ...dotStyle, backgroundColor: "#1890ff" }} /> |
There was a problem hiding this comment.
@abhizipstack Should we use Ant Design theme tokens here instead of hardcoded hex colors for the status indicators? Since the component already uses theme.useToken(), using semantic tokens like token.colorInfo, token.colorError, and token.colorSuccess would help with theme consistency, dark/light mode support, and maintenance.
What
RunStatusenum and 4 new fields toConfigModels:run_status,failure_reason,last_run_at,run_duration0003_add_model_run_status.pyfile_explorer.load_models()to return status fields for each model_update_model_status()to DAG executor invisitran.py— called before execution (RUNNING), after success (SUCCESS), and on exception (FAILED)execute/views.pyto return 400 on DAG execution failuresclear_cachedecorator to re-raise exceptions instead of silently swallowing themsetRefreshModelsafterrunTransformationsucceeds or failsWhy
After a model build/run, users had no visual indication of which models succeeded or failed. They had to manually open each model to check. This change shows failure icons directly in the explorer so users can quickly identify broken models and fix them via prompts or manual edits.
How
Status is tracked at the DAG node level (not API level). When the DAG executor processes each node, it updates the corresponding
ConfigModelsrow in the database with the current status. This ensures accurate per-model status even when Model A succeeds but downstream Model B fails — Model A is marked SUCCESS while Model B is marked FAILED.The frontend
file_explorerAPI already returns the full model tree — we just add the new status fields to each model entry. The explorer renders a small colored dot inline before the model name based onrun_status. Failed models show a Popover on hover with the fullfailure_reasonandlast_run_at.Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)
Low risk:
NOT_STARTED), so existing models continue to workclear_cachedecorator fix actually addresses a pre-existing silent bug where execution errors were being swallowed and returningNoneTypeto DRF (causing 500 errors instead of proper error responses)ConfigModelsimport is wrapped in try/except sovisitranCLI still works without DjangoDatabase Migrations
backend/backend/core/migrations/0003_add_model_run_status.py— adds 4 nullable fields toconfigmodelstable (run_status, failure_reason, last_run_at, run_duration)Env Config
None
Relevant Docs
None
Related Issues or PRs
None
Dependencies Versions
No changes
Notes on Testing
Tested locally with Python 3.11:
clear_cachefix verified —MultipleColumnDependencyexception now returns proper 400 error response instead of 500 NoneTypeScreenshots
TBD — will add after testing locally
Checklist
I have read and understood the Contribution Guidelines.
🤖 Generated with Claude Code