Skip to content

Commit

Permalink
Merge pull request #377 from bcgov/fix/CommonModelTable
Browse files Browse the repository at this point in the history
DF-1758 : DF Error Code and moved the model file to common folder
  • Loading branch information
adimar-aot authored Oct 1, 2024
2 parents 8c958e2 + aaa6581 commit ab4a45a
Show file tree
Hide file tree
Showing 44 changed files with 953 additions and 1,623 deletions.
123 changes: 123 additions & 0 deletions python/common/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
from enum import Enum
from dataclasses import dataclass

@dataclass
class EnumDetails:
code: str
description: str

class BaseEnum(Enum):
def __str__(self):
return self.value.code

@property
def code(self):
return self.value.code

@property
def description(self):
return self.value.description

class EventType(BaseEnum):
TWELVE_HOUR = EnumDetails("TwelveHour", "12 Hour Prohibition")
TWENTY_FOUR_HOUR = EnumDetails("TwentyFourHour", "24 Hour Prohibition")
IRP = EnumDetails("IRP", "Immediate Roadside Prohibition")
VI = EnumDetails("VI", "Vehicle Impoundment")
OTHER = EnumDetails("OTHER", "Other")

class ErrorCategory(BaseEnum):
VALIDATION = EnumDetails("VALIDATION", "Validation Error")
SYSTEM = EnumDetails("SYSTEM", "System Error")
CONNECTION = EnumDetails("CONNECTION", "Connection Error")
DATA = EnumDetails("DATA", "Data Error")
OTHER = EnumDetails("OTHER", "Other Error")

class ErrorSeverity(BaseEnum):
LOW = EnumDetails("LOW", "Low Severity")
MEDIUM = EnumDetails("MEDIUM", "Medium Severity")
HIGH = EnumDetails("HIGH", "High Severity")
CRITICAL = EnumDetails("CRITICAL", "Critical Severity")

class ErrorStatus(BaseEnum):
NEW = EnumDetails("NEW", "New Error")
VIEWED = EnumDetails("VIEWED", "Viewed Error")
IN_PROGRESS = EnumDetails("IN_PROGRESS", "In Progress")
ASSIGNED = EnumDetails("ASSIGNED", "Assigned")
RESOLVED = EnumDetails("RESOLVED", "Resolved")
CANCELLED = EnumDetails("CANCELLED", "Cancelled")
CLOSED = EnumDetails("CLOSED", "Closed")

@dataclass
class ErrorCodeDetails:
code: str
description: str # max 200 characters
category: ErrorCategory
severity: ErrorSeverity
resolution: str
is_business_error: bool

class ErrorCode(BaseEnum):
# General error
G00 = ErrorCodeDetails("G00", "General error", ErrorCategory.OTHER, ErrorSeverity.LOW,
"Contact DF application support for further investigation", False)

# Events related error

E01 = ErrorCodeDetails("E01", "Event saving error", ErrorCategory.DATA, ErrorSeverity.HIGH,
"Contact DF application support for further investigation", False)
E02 = ErrorCodeDetails("E02", "Event PDF saving error", ErrorCategory.DATA, ErrorSeverity.HIGH,
"Contact DF application support for further investigation", False)

E03 = ErrorCodeDetails("E03", "Error putting event to queue", ErrorCategory.SYSTEM, ErrorSeverity.HIGH,
"Contact DF application support for further investigation", False)
E04 = ErrorCodeDetails("E04", "Form handler: Unknown Event Type", ErrorCategory.DATA, ErrorSeverity.CRITICAL,
"Contact DF application support for further investigation", False)
E05 = ErrorCodeDetails("E05", "Form handler: Retry count exceed maximum retires", ErrorCategory.DATA, ErrorSeverity.CRITICAL,
"Contact DF application support for further investigation", False)
E06 = ErrorCodeDetails("E06", "Form handler: Event On Hold", ErrorCategory.DATA, ErrorSeverity.CRITICAL,
"Contact DF application support for further investigation", False)
E07 = ErrorCodeDetails("E07", "Form handlerr: Event process error", ErrorCategory.DATA, ErrorSeverity.CRITICAL,
"Contact DF application support for further investigation", False)
E08 = ErrorCodeDetails("E08", "General Event process error", ErrorCategory.SYSTEM, ErrorSeverity.CRITICAL,
"Contact DF application support for further investigation", False)

# Forms related error

F01 = ErrorCodeDetails("F01", "Form id lease error", ErrorCategory.DATA, ErrorSeverity.HIGH,
"Contact DF application support for further investigation", False)
F02 = ErrorCodeDetails("F02", "Renew form id lease error", ErrorCategory.DATA, ErrorSeverity.HIGH,
"Contact DF application support for further investigation", False)
F03 = ErrorCodeDetails("F02", "Admin form create error", ErrorCategory.DATA, ErrorSeverity.HIGH,
"Contact DF application support for further investigation", False)

# Ride actions related error

R01 = ErrorCodeDetails("R01", "Error in sending event to RIDE", ErrorCategory.CONNECTION, ErrorSeverity.HIGH,
"Contact DF application support for further investigation", False)

# ICBC actions related error

I01 = ErrorCodeDetails("I01", "Error in sending event to ICBC", ErrorCategory.CONNECTION, ErrorSeverity.HIGH,
"Contact DF application support for further investigation", False)
I02 = ErrorCodeDetails("I02", "Error in preparing payload to ICBC", ErrorCategory.DATA, ErrorSeverity.HIGH,
"Contact DF application support for further investigation", False)



# Add more error codes as needed...

@property
def category(self):
return self.value.category

@property
def severity(self):
return self.value.severity

@property
def resolution(self):
return self.value.resolution

@property
def is_business_error(self):
return self.value.is_business_error
120 changes: 120 additions & 0 deletions python/common/error_middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Error middleware functions
import json
import traceback
import logging
import functools
import inspect
from flask import request, current_app
from sqlalchemy.exc import SQLAlchemyError
from datetime import datetime

from python.common.enums import EventType, ErrorCode, ErrorSeverity, ErrorStatus, ErrorCategory
from python.common.models import db, DFErrors, Event

def get_safe_payload():
"""
Safely extract and serialize the request payload.
"""
try:
# Try to get JSON payload
payload = request.get_json(silent=True)
if payload is not None:
return json.dumps(payload)

# If not JSON, try to get form data
payload = request.form.to_dict()
if payload:
return json.dumps(payload)

# If no form data, get query parameters
payload = request.args.to_dict()
if payload:
return json.dumps(payload)

# If all else fails, return a message indicating no payload
return json.dumps({"message": "No payload found in request"})
except Exception as e:
return json.dumps({"message": "No payload found in request"})

def get_function_info(func):
"""
Extract detailed information about the function or method.
"""
module = inspect.getmodule(func)
if module:
module_name = module.__name__
else:
module_name = "unknown_module"

if inspect.ismethod(func):
class_name = func.__self__.__class__.__name__
return f"{module_name}.{class_name}.{func.__name__}"
elif inspect.isfunction(func):
return f"{module_name}.{func.__name__}"
else:
return f"{module_name}.unknown_function"

def record_error(error_code: ErrorCode, error_details, event_id: int = None, event_type: EventType = None, ticket_no=None, func=None, payload=None):
"""
Record an error in the database.
"""
try:

function_path = get_function_info(func) if func else "unknown"

if not payload:
payload = get_safe_payload()

# Handle stack trace extraction if error_details is an exception
if isinstance(error_details, Exception):
stack_trace = ''.join(traceback.format_exception(type(error_details), error_details, error_details.__traceback__))
else:
stack_trace = str(error_details)

error_log = DFErrors(
error_cd=str(error_code.code),
error_cd_desc=str(error_code.description),
error_category_cd=str(error_code.category),
error_resolution=str(error_code.resolution),
error_severity_level_cd=str(error_code.severity),
error_status_cd=str(ErrorStatus.NEW),
event_id=event_id,
event_type=str(event_type) if event_type else None,
ticket_no=ticket_no,
req_payload=payload,
error_details=stack_trace,
error_path=function_path,
created_by='SYSTEM',
received_dt=datetime.now(),
)
db.session.add(error_log)
db.session.commit()
logging.error(f"Error recorded: {error_code} - {error_code.description} - Event ID: {event_id} - Event Type: {event_type} - Function: {function_path} - {error_details}")
except SQLAlchemyError as e:
db.session.rollback()
logging.error(f"Failed to record error: {error_code} - {error_code.description} - Event ID: {event_id} - Event Type: {event_type} - Function: {function_path} - {error_details}")

def error_handler(func):
"""
Decorator to handle errors in functions.
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
error_code = ErrorCode.G00 # Default to general error
error_details = str(e)

# Attempt to get event_id from kwargs or request
event_id = kwargs.get('event_id')
if not event_id and hasattr(request, 'view_args'):
event_id = request.view_args.get('event_id')

if not event_id:
current_app.logger.warning("No event_id found for error logging")
event_id = None # or some default value

record_error(error_code, error_details, event_id, func=func)
raise # Re-raise the exception after recording
return wrapper
27 changes: 27 additions & 0 deletions python/prohibition_web_svc/models.py → python/common/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
import logging
from python.common.enums import ErrorStatus

db = SQLAlchemy()
migrate = Migrate()
Expand Down Expand Up @@ -790,3 +791,29 @@ class IloIdCrossRef(db.Model):
vips_impound_lot_operator_id = db.Column(db.Integer)


@dataclass
class DFErrors(db.Model):
__tablename__ = 'df_errors'

error_id: int = db.Column(db.Integer, primary_key=True)
error_cd: str = db.Column(db.String(5), nullable=False)
error_cd_desc: str = db.Column(db.String(200), nullable=False)
error_resolution: str = db.Column(db.Text)
error_category_cd: str = db.Column(db.String(10), nullable=False)
error_severity_level_cd: str = db.Column(db.String(10), nullable=False)
error_details: str = db.Column(db.Text)
error_path: str = db.Column(db.String(200))
event_id: int = db.Column(db.Integer, db.ForeignKey('event.event_id'), nullable=True)
event_type: str = db.Column(db.String(30), nullable=True)
ticket_no: str = db.Column(db.String(50), nullable=True)
received_dt: datetime = db.Column(db.DateTime, default=datetime.now())
error_status_cd: str = db.Column(db.String(200), default=ErrorStatus.NEW)
req_payload: str = db.Column(db.Text)

created_by: str = db.Column(db.String(150))
created_dt: datetime = db.Column(db.DateTime, default=datetime.now())
updated_by: str = db.Column(db.String(150))
updated_dt: datetime = db.Column(db.DateTime, onupdate=datetime.now())

def __repr__(self):
return f'<DF_Error {self.error_id}: {self.error_category_cd.name} - {self.error_severity_level_cd.name}>'
51 changes: 50 additions & 1 deletion python/common/ride_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import requests
from python.common.helper import date_time_to_local_tz_string, format_date_only, yes_no_string_to_bool
from python.common.config import Config
from python.common.enums import ErrorCode
import pytz

from python.form_handler.models import Agency, City, ImpoundLotOperator, JurisdictionCrossRef, Province, Vehicle, VehicleColour, VehicleStyle, VehicleType
from python.common.models import Agency, City, ImpoundLotOperator, JurisdictionCrossRef, Province, Vehicle, VehicleColour, VehicleStyle, VehicleType

ride_url=Config.RIDE_API_URL
ride_key=Config.RIDE_API_KEY
Expand Down Expand Up @@ -39,6 +40,14 @@ def twelve_hours_event(**args):
logging.debug(f'headers: {headers}')
response = requests.post(endpoint, json=eventPayload, verify=False, headers=headers)
if response.status_code != 200:
args['error'] = {
'error_code': ErrorCode.R01,
'error_details': f'Error in sending 12hr_submitted event to RIDE, Response code: {response.status_code} response text: {response.json()}',
'event_type': '12hr',
'func': twelve_hours_event,
'ticket_no': args['form_data']['twelve_hour_number'],
'event_id': args['message']['event_id']
}
logging.error('error in sending 12hr_submitted event to RIDE')
logging.error(f'error code: {response.status_code} error message: {response.json()}')
return False, args
Expand All @@ -47,6 +56,14 @@ def twelve_hours_event(**args):
except Exception as e:
logging.error('error in sending 12hr_submitted event to RIDE')
logging.error(e)
args['error'] = {
'error_code': ErrorCode.R01,
'error_details': e,
'event_type': '12hr',
'func': twelve_hours_event,
'ticket_no': args['form_data']['twelve_hour_number'],
'event_id': args['message']['event_id']
}
return False, args

return True, args
Expand Down Expand Up @@ -88,6 +105,14 @@ def twenty_four_hours_event(**args):
logging.debug(f'headers: {headers}')
response = requests.post(endpoint, json=eventPayload, verify=False,headers=headers)
if response.status_code != 200:
args['error'] = {
'error_code': ErrorCode.R01,
'error_details': f'Error in sending 24hr_submitted event to RIDE, Response code: {response.status_code} response text: {response.json()}',
'event_type': '24hr',
'func': twenty_four_hours_event,
'ticket_no': args['form_data']['twenty_four_hour_number'],
'event_id': args['message']['event_id']
}
logging.error('error in sending 24hr_submitted event to RIDE')
logging.error(f'error code: {response.status_code} error message: {response.json()}')
return False, args
Expand All @@ -96,6 +121,14 @@ def twenty_four_hours_event(**args):
except Exception as e:
logging.error('error in sending 24hr_submitted event to RIDE')
logging.error(e)
args['error'] = {
'error_code': ErrorCode.R01,
'error_details': e,
'event_type': '24hr',
'func': twenty_four_hours_event,
'ticket_no': args['form_data']['twenty_four_hour_number'],
'event_id': args['message']['event_id']
}
return False, args

return True, args
Expand Down Expand Up @@ -147,6 +180,14 @@ def vi_event(**args):
logging.debug(f'headers: {headers}')
response = requests.post(endpoint, json=eventPayload, verify=False,headers=headers)
if response.status_code != 200:
args['error'] = {
'error_code': ErrorCode.R01,
'error_details': f'Error in sending vi_submitted event to RIDE, Response code: {response.status_code} response text: {response.json()}',
'event_type': 'VI',
'func': vi_event,
'ticket_no': args['form_data']['VI_number'],
'event_id': args['message']['event_id']
}
logging.error('error in sending vi_submitted event to RIDE')
logging.error(f'error code: {response.status_code} error message: {response.json()}')
return False, args
Expand All @@ -155,6 +196,14 @@ def vi_event(**args):
except Exception as e:
logging.error('error in sending vi_submitted event to RIDE')
logging.error(e)
args['error'] = {
'error_code': ErrorCode.R01,
'error_details': e,
'event_type': 'VI',
'func': vi_event,
'ticket_no': args['form_data']['VI_number'],
'event_id': args['message']['event_id']
}
return False, args

return True, args
Expand Down
Empty file.
Loading

0 comments on commit ab4a45a

Please sign in to comment.