Skip to content

Commit bd06a62

Browse files
abhizipstackclaude
andauthored
fix: run error handling, rollback visibility, stale status indicator, and spurious notifications (#69)
* fix: run error handling, rollback visibility, stale status indicator, and spurious notifications Bug 1: Generic "Check server logs" error on model run failure - execute_run_command caught all exceptions with a blanket `except Exception` and returned "Model execution failed. Check server logs for details." - Now catches VisitranBaseExceptions/VisitranBackendBaseException separately and returns the detailed error via err.error_response() (e.g. model name, actual error cause, remediation hint) - Generic Exception fallback now returns 500 with a clearer message Bug 2: Rollback button never appearing on run failure - execute_visitran_run_command sets is_rollback on the exception's error_args, but error_response() only exposed it nested inside message_args - Frontend checks error.response.data.is_rollback (top level) - Fixed both VisitranBaseExceptions and VisitranBackendBaseException error_response() to surface is_rollback at the top level when present Bug 3: Stale failure indicator in explorer after fixing model - run_status and failure_reason on ConfigModels were only updated during execute_graph and never cleared when the user modified the model spec - After deleting a bad transformation, the explorer still showed the old red dot with the previous error - Added _reset_model_run_status in NoCodeModel._validate_and_update_model to clear FAILED status back to NOT_STARTED when the spec changes Bug 4: "Something went wrong" popup on frontend console errors - .catch(error => notify({ error })) catches both API errors and JS runtime errors (e.g. TypeError from unexpected response shape) - JS runtime errors have no error.response, so notification-service fell through to the generic "Something went wrong" description - Now skips the popup for non-API errors and logs them to console instead Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address greptile review — network errors and status code handling - Add !error.request check so Axios network errors (connection refused, server down) still show notifications — only suppress true JS runtime errors that have neither .response nor .request - Use err.to_response() for VisitranBackendBaseException to preserve the exception's own HTTP status code (403, 404, etc.) instead of hardcoding 400 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: prettier formatting for multi-condition guard Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: skip run status reset for presentation-only changes Presentation changes (column sort/hide/order) don't affect model execution, so they should not clear a FAILED status indicator. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: extract NON_EXECUTION_CONFIG_TYPES constant Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * revert: remove FE notification guard — to be handled separately Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: catch all exceptions in _reset_model_run_status to prevent cascade A DB error during status reset should not fail the outer model update. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9104be8 commit bd06a62

4 files changed

Lines changed: 47 additions & 6 deletions

File tree

backend/backend/application/context/no_code_model.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,37 @@
33
from typing import Any
44

55
from backend.application.context.application import ApplicationContext
6+
from backend.core.models.config_models import ConfigModels
67
from backend.errors import InvalidModelConfigError
78

89

10+
NON_EXECUTION_CONFIG_TYPES = {"presentation"}
11+
12+
913
class NoCodeModel(ApplicationContext):
1014
def __init__(self, project_id: str, environment_id: str = "") -> None:
1115
super().__init__(project_id, environment_id)
1216

17+
def _reset_model_run_status(self, model_name: str) -> None:
18+
"""Reset run status when the model spec changes so the explorer
19+
doesn't keep showing a stale failure indicator."""
20+
try:
21+
model_instance = ConfigModels.objects.get(
22+
project_instance__project_uuid=self.session.project_id,
23+
model_name=model_name,
24+
)
25+
if model_instance.run_status == ConfigModels.RunStatus.FAILED:
26+
model_instance.run_status = ConfigModels.RunStatus.NOT_STARTED
27+
model_instance.failure_reason = None
28+
model_instance.save(update_fields=["run_status", "failure_reason"])
29+
except ConfigModels.DoesNotExist:
30+
pass
31+
except Exception:
32+
logging.warning(
33+
f"[_reset_model_run_status] Failed to reset run status for model={model_name}",
34+
exc_info=True,
35+
)
36+
1337
def _validate_and_update_model(
1438
self,
1539
model_data: dict[str, Any],
@@ -49,7 +73,11 @@ def _validate_and_update_model(
4973
config_type=config_type
5074
)
5175
# Converting the current model to python.
52-
return self.update_model(model_name=model_name, model_data=model_data)
76+
result = self.update_model(model_name=model_name, model_data=model_data)
77+
# Only reset stale run status for changes that affect model execution
78+
if config_type not in NON_EXECUTION_CONFIG_TYPES:
79+
self._reset_model_run_status(model_name)
80+
return result
5381

5482
def set_model_config_and_reference(self, no_code_data: dict[str, Any], model_name: str):
5583
"""

backend/backend/core/routers/execute/views.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
from rest_framework.request import Request
88
from rest_framework.response import Response
99
from visitran.singleton import Singleton
10+
from visitran.errors.base_exceptions import VisitranBaseExceptions
1011
import logging
1112

1213
from backend.application.context.application import ApplicationContext
14+
from backend.errors.visitran_backend_base_exceptions import VisitranBackendBaseException
1315
from backend.core.utils import handle_http_request, sanitize_data
1416
from backend.utils.cache_service.decorators.cache_decorator import clear_cache
1517
from backend.utils.constants import HTTPMethods
@@ -63,10 +65,15 @@ def execute_run_command(request: Request, project_id: str) -> Response:
6365
logger.info(f"[execute_run_command] Completed successfully for file_name={file_name}")
6466
_data = {"status": "success"}
6567
return Response(data=_data)
66-
except Exception:
68+
except (VisitranBaseExceptions, VisitranBackendBaseException) as err:
6769
logger.exception(f"[execute_run_command] DAG execution failed for file_name={file_name}")
68-
_data = {"status": "failed", "error_message": "Model execution failed. Check server logs for details."}
69-
return Response(data=_data, status=status.HTTP_400_BAD_REQUEST)
70+
if hasattr(err, 'to_response'):
71+
return err.to_response()
72+
return Response(data=err.error_response(), status=status.HTTP_400_BAD_REQUEST)
73+
except Exception:
74+
logger.exception(f"[execute_run_command] Unexpected error during DAG execution for file_name={file_name}")
75+
_data = {"status": "failed", "error_message": "An unexpected error occurred while executing the model. Please try again or contact support if the issue persists."}
76+
return Response(data=_data, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
7077
finally:
7178
app.visitran_context.close_db_connection()
7279

backend/backend/errors/visitran_backend_base_exceptions.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,14 @@ def severity(self) -> str:
5757
return "Error"
5858

5959
def error_response(self) -> dict[str, Any]:
60-
return {
60+
response = {
6161
"status": "failed",
6262
"class": self.__class__.__name__,
6363
"error_message": self.error_message,
6464
"message_args": self._msg_args,
6565
"severity": self.severity,
6666
"is_markdown": self._is_markdown,
6767
}
68+
if "is_rollback" in self._msg_args:
69+
response["is_rollback"] = self._msg_args["is_rollback"]
70+
return response

backend/visitran/errors/base_exceptions.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,13 @@ def __str__(self) -> str:
3737
return self._error_msg
3838

3939
def error_response(self) -> dict[str, Any]:
40-
return {
40+
response = {
4141
"status": "failed",
4242
"error_message": self.error_message,
4343
"message_args": self._msg_args,
4444
"severity": self.severity,
4545
"is_markdown": self._is_markdown,
4646
}
47+
if "is_rollback" in self._msg_args:
48+
response["is_rollback"] = self._msg_args["is_rollback"]
49+
return response

0 commit comments

Comments
 (0)