diff --git a/backend/backend/application/context/no_code_model.py b/backend/backend/application/context/no_code_model.py index 1d49e607..9b8e354b 100644 --- a/backend/backend/application/context/no_code_model.py +++ b/backend/backend/application/context/no_code_model.py @@ -3,13 +3,37 @@ from typing import Any from backend.application.context.application import ApplicationContext +from backend.core.models.config_models import ConfigModels from backend.errors import InvalidModelConfigError +NON_EXECUTION_CONFIG_TYPES = {"presentation"} + + class NoCodeModel(ApplicationContext): def __init__(self, project_id: str, environment_id: str = "") -> None: super().__init__(project_id, environment_id) + def _reset_model_run_status(self, model_name: str) -> None: + """Reset run status when the model spec changes so the explorer + doesn't keep showing a stale failure indicator.""" + try: + model_instance = ConfigModels.objects.get( + project_instance__project_uuid=self.session.project_id, + model_name=model_name, + ) + if model_instance.run_status == ConfigModels.RunStatus.FAILED: + model_instance.run_status = ConfigModels.RunStatus.NOT_STARTED + model_instance.failure_reason = None + model_instance.save(update_fields=["run_status", "failure_reason"]) + except ConfigModels.DoesNotExist: + pass + except Exception: + logging.warning( + f"[_reset_model_run_status] Failed to reset run status for model={model_name}", + exc_info=True, + ) + def _validate_and_update_model( self, model_data: dict[str, Any], @@ -49,7 +73,11 @@ def _validate_and_update_model( config_type=config_type ) # Converting the current model to python. - return self.update_model(model_name=model_name, model_data=model_data) + result = self.update_model(model_name=model_name, model_data=model_data) + # Only reset stale run status for changes that affect model execution + if config_type not in NON_EXECUTION_CONFIG_TYPES: + self._reset_model_run_status(model_name) + return result def set_model_config_and_reference(self, no_code_data: dict[str, Any], model_name: str): """ diff --git a/backend/backend/core/routers/execute/views.py b/backend/backend/core/routers/execute/views.py index eb30a716..73995d72 100644 --- a/backend/backend/core/routers/execute/views.py +++ b/backend/backend/core/routers/execute/views.py @@ -7,9 +7,11 @@ from rest_framework.request import Request from rest_framework.response import Response from visitran.singleton import Singleton +from visitran.errors.base_exceptions import VisitranBaseExceptions import logging from backend.application.context.application import ApplicationContext +from backend.errors.visitran_backend_base_exceptions import VisitranBackendBaseException from backend.core.utils import handle_http_request, sanitize_data from backend.utils.cache_service.decorators.cache_decorator import clear_cache from backend.utils.constants import HTTPMethods @@ -63,10 +65,15 @@ def execute_run_command(request: Request, project_id: str) -> Response: logger.info(f"[execute_run_command] Completed successfully for file_name={file_name}") _data = {"status": "success"} return Response(data=_data) - except Exception: + except (VisitranBaseExceptions, VisitranBackendBaseException) as err: logger.exception(f"[execute_run_command] DAG execution failed for file_name={file_name}") - _data = {"status": "failed", "error_message": "Model execution failed. Check server logs for details."} - return Response(data=_data, status=status.HTTP_400_BAD_REQUEST) + if hasattr(err, 'to_response'): + return err.to_response() + return Response(data=err.error_response(), status=status.HTTP_400_BAD_REQUEST) + except Exception: + logger.exception(f"[execute_run_command] Unexpected error during DAG execution for file_name={file_name}") + _data = {"status": "failed", "error_message": "An unexpected error occurred while executing the model. Please try again or contact support if the issue persists."} + return Response(data=_data, status=status.HTTP_500_INTERNAL_SERVER_ERROR) finally: app.visitran_context.close_db_connection() diff --git a/backend/backend/errors/visitran_backend_base_exceptions.py b/backend/backend/errors/visitran_backend_base_exceptions.py index 0e7ef4cc..a7d64ac0 100644 --- a/backend/backend/errors/visitran_backend_base_exceptions.py +++ b/backend/backend/errors/visitran_backend_base_exceptions.py @@ -57,7 +57,7 @@ def severity(self) -> str: return "Error" def error_response(self) -> dict[str, Any]: - return { + response = { "status": "failed", "class": self.__class__.__name__, "error_message": self.error_message, @@ -65,3 +65,6 @@ def error_response(self) -> dict[str, Any]: "severity": self.severity, "is_markdown": self._is_markdown, } + if "is_rollback" in self._msg_args: + response["is_rollback"] = self._msg_args["is_rollback"] + return response diff --git a/backend/visitran/errors/base_exceptions.py b/backend/visitran/errors/base_exceptions.py index f7bac007..e86e6740 100644 --- a/backend/visitran/errors/base_exceptions.py +++ b/backend/visitran/errors/base_exceptions.py @@ -37,10 +37,13 @@ def __str__(self) -> str: return self._error_msg def error_response(self) -> dict[str, Any]: - return { + response = { "status": "failed", "error_message": self.error_message, "message_args": self._msg_args, "severity": self.severity, "is_markdown": self._is_markdown, } + if "is_rollback" in self._msg_args: + response["is_rollback"] = self._msg_args["is_rollback"] + return response