Skip to content

Commit

Permalink
[MDS-5607] - Exception handling + Email Notification (#2824)
Browse files Browse the repository at this point in the history
* code for 5607 and 5617, TODO: username in the email

* exception updated

* resolving exception import

* update exception

* email body change

* adding Exception to mine resources and modules

* updating the error email

* updating email body

* resolving core-web minespace diff

* resolving merge conflicts

* updating env

* setting env variable to read the email address for notificaiton

* fixing email config env var

* fixing cypress tests with new env. var

* fixing cypress tests

* changing email resource to report error

* removed unnecessary env var

* adding feature flag + formatting email

* correcting typo
  • Loading branch information
isuru-aot authored Dec 4, 2023
1 parent 4c0de23 commit 3ad7dbd
Show file tree
Hide file tree
Showing 16 changed files with 493 additions and 123 deletions.
3 changes: 3 additions & 0 deletions services/common/src/constants/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ export const CREATE_EXPLOSIVES_PERMIT_AMENDMENT = (mineGuid) =>
export const EXPLOSIVES_PERMIT_AMENDMENT = (mineGuid, explosivesPermitGuid) =>
`/mines/${mineGuid}/explosives-permits-amendment/${explosivesPermitGuid}`;

// Common
export const REPORT_ERROR = `/report-error`;

// EPIC Mine Information
export const EPIC_INFO = (mineGuid) => `/mines/${mineGuid}/epic`;

Expand Down
3 changes: 1 addition & 2 deletions services/common/src/constants/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const DEFAULT_ENVIRONMENT = {
keycloak_idpHint: "test",
keycloak_url: "https://test.loginproxy.gov.bc.ca/auth",
flagsmithKey: "4Eu9eEMDmWVEHKDaKoeWY7",
flagsmithUrl: "https://mds-flags-dev.apps.silver.devops.gov.bc.ca/api/v1/",
flagsmithUrl: "https://mds-flags-dev.apps.silver.devops.gov.bc.ca/api/v1/"
};

export const ENVIRONMENT = {
Expand Down Expand Up @@ -82,7 +82,6 @@ export function setupEnvironment(
if (!flagsmithUrl) {
throw new Error("flagsmithUrl Is Mandatory");
}

ENVIRONMENT.apiUrl = apiUrl;
ENVIRONMENT.docManUrl = docManUrl;
ENVIRONMENT.filesystemProviderUrl = filesystemProviderUrl;
Expand Down
61 changes: 56 additions & 5 deletions services/common/src/redux/customAxios.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import axios from "axios";
import { notification } from "antd";
import { notification, Button } from "antd";
import * as String from "@mds/common/constants/strings";
import React from 'react';
import * as API from "@mds/common/constants/API";
import { ENVIRONMENT } from "@mds/common";
import { createRequestHeader } from "./utils/RequestHeaders";
import { Feature, isFeatureEnabled } from "@mds/common";

// https://stackoverflow.com/questions/39696007/axios-with-promise-prototype-finally-doesnt-work
// eslint-disable-next-line @typescript-eslint/no-var-requires
Expand All @@ -15,6 +20,29 @@ const formatErrorMessage = (errorMessage) => {
return errorMessage.replace("(psycopg2.", "(DatabaseError.");
};

const notifymAdmin = (error) => {

const business_message = error?.response?.data?.message;
const detailed_error = error?.response?.data?.detailed_error;

const payload = {
"business_error" : business_message,
"detailed_error": detailed_error
};

CustomAxios().post(ENVIRONMENT.apiUrl + API.REPORT_ERROR, payload, createRequestHeader())
.then((response) => {
notification.success({
message: "Error details sent to Admin. Thank you.",
duration: 5,
});
return response;
})
.catch((err) => {
throw new Error(err);
})
};

// @ts-ignore
const CustomAxios = ({ errorToastMessage, suppressErrorNotification = false } = {}) => {
const instance = axios.create();
Expand All @@ -34,10 +62,33 @@ const CustomAxios = ({ errorToastMessage, suppressErrorNotification = false } =
(errorToastMessage === "default" || errorToastMessage === undefined) &&
!suppressErrorNotification
) {
notification.error({
message: formatErrorMessage(error?.response?.data?.message ?? String.ERROR),
duration: 10,
});
console.error('Detailed Error: ', error?.response?.data?.detailed_error)
const notificationKey = 'errorNotification';

if (isFeatureEnabled(Feature.REPORT_ERROR)) {
notification.error({
key: notificationKey,
message: formatErrorMessage(error?.response?.data?.message ?? String.ERROR),
description: <p style={{ color: 'grey' }}>If you think this is a system error please help us to improve by informing the system Admin</p>,
duration: 10,
btn: (
<Button type="primary" size="small"
onClick={() => {
notifymAdmin(error);
notification.close(notificationKey);
}}>
Tell Admin
</Button>
),
});
} else {
notification.error({
key: notificationKey,
message: formatErrorMessage(error?.response?.data?.message ?? String.ERROR),
duration: 10,
});
}

} else if (errorToastMessage && !suppressErrorNotification) {
notification.error({
message: errorToastMessage,
Expand Down
1 change: 1 addition & 0 deletions services/common/src/utils/featureFlag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export enum Feature {
TSF_V2 = "tsf_v2",
VERIFIABLE_CREDENTIALS = "verifiable_credentials",
MINESPACE_ESUPS = "minespace_esups",
REPORT_ERROR = "report_error"
}

export const initializeFlagsmith = async (flagsmithUrl, flagsmithKey) => {
Expand Down
19 changes: 15 additions & 4 deletions services/core-api/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from app.api.activity.namespace import api as activity_api
from app.api.dams.namespace import api as dams_api
from app.api.verifiable_credentials.namespace import api as verifiable_credential_api
from app.api.report_error.namespace import api as report_error_api

from app.commands import register_commands
from app.config import Config
Expand All @@ -47,6 +48,7 @@
from sqlalchemy.sql import text
from app.tasks.celery import celery
from app.tasks.celery_health_check import HealthCheckProbe
from app.api.exception.mds_core_api_exceptions import MDSCoreAPIException

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
Expand Down Expand Up @@ -189,6 +191,7 @@ def register_routes(app):
root_api_namespace.add_namespace(activity_api)
root_api_namespace.add_namespace(dams_api)
root_api_namespace.add_namespace(verifiable_credential_api)
root_api_namespace.add_namespace(report_error_api)

@root_api_namespace.route('/version/')
class VersionCheck(Resource):
Expand Down Expand Up @@ -336,7 +339,15 @@ def _add_sqlalchemy_error_handlers(classname):
@root_api_namespace.errorhandler(Exception)
def default_error_handler(error):
app.logger.error(str(error))
return {
'status': getattr(error, 'code', 500),
'message': str(error),
}, getattr(error, 'code', 500)
if isinstance(error, MDSCoreAPIException):
return {
"status": getattr(error, "code", 500),
"message": str(getattr(error, "message", "")),
"detailed_error": str(getattr(error, "detailed_error", "")),
}, getattr(error, 'code', 500)
else:
return {
"status": getattr(error, "code", 500),
"message": str(error),
"detailed_error": str(getattr(error, "detailed_error", "Not provided")),
}, getattr(error, 'code', 500)
12 changes: 12 additions & 0 deletions services/core-api/app/api/exception/mds_core_api_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class MDSCoreAPIException(Exception):
"""A base Exception class for MDS Core API"""

description = (
"Exception occurred in MDS Core API"
)

def __init__(self, message="Oops! Something went wrong", **kwargs):
super().__init__(message)
self.code = int(kwargs.get("status_code", 500))
self.message = message
self.detailed_error = kwargs.get("detailed_error", "")
42 changes: 42 additions & 0 deletions services/core-api/app/api/mines/exceptions/mine_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import json
from app.api.exception.mds_core_api_exceptions import MDSCoreAPIException

class MineException(MDSCoreAPIException):
"""A Custom Exception for Errors in MINE Namespace"""

description = (
"Generic exeption for errors occurred in the Mine module"
)
def __init__(self, message = "Oops! Something went wrong", **kwargs):
super().__init__(message, **kwargs)
self.code = int(kwargs.get("status_code", 500))

class ExplosivesPermitDocumentException(MineException):
"""Exception for Explosives Permit Document Related Exception"""

description = (
"Exception For Explosives Permit Document Related Errors"
)
def __init__(self, message = "An error occurred while processing Explosives Permit Document", **kwargs):
super().__init__(message, **kwargs)
self.code = int(kwargs.get("status_code", 500))

class ExplosivesPermitExeption(MineException):
"""Exception for Explosive Permit related errors"""

description = (
"Exception for errors in Explosive Permit"
)
def __init__(self, message = "Error in Explosive Permit", **kwargs):
super().__init__(message, **kwargs)
self.code = int(kwargs.get("status_code", 500))

class ExplosivePermitNumberAlreadyExistExeption(MineException):
"""Exception for already existing permit number"""

description = (
"Exception for already existing permit number"
)
def __init__(self, message = "A record already exists with the provided 'Explosives Permit Number'", **kwargs):
super().__init__(message, **kwargs)
self.code = int(kwargs.get("status_code", 422))
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from app.api.utils.helpers import create_image_with_aspect_ratio
from app.api.utils.models_mixins import AuditMixin, Base
from app.extensions import db
from app.api.mines.exceptions.mine_exceptions import ExplosivesPermitExeption

PERMIT_SIGNATURE_IMAGE_HEIGHT_INCHES = 0.8
LETTER_SIGNATURE_IMAGE_HEIGHT_INCHES = 0.8
Expand Down Expand Up @@ -42,7 +43,8 @@ def get_with_context(cls, document_type_code, context_guid):
def transform_template_data(self, template_data, explosives_permit):
def validate_issuing_inspector(explosives_permit):
if not explosives_permit.issuing_inspector:
raise Exception('No Issuing Inspector has been assigned')
raise ExplosivesPermitExeption("No Issuing Inspector has been assigned",
status_code = 422)
if not explosives_permit.issuing_inspector.signature:
raise Exception('No signature for the Issuing Inspector has been provided')

Expand All @@ -57,33 +59,39 @@ def validate_issuing_inspector(explosives_permit):
issuing_inspector_party_guid = template_data['issuing_inspector_party_guid']
issuing_inspector = Party.find_by_party_guid(issuing_inspector_party_guid)
if issuing_inspector is None:
raise Exception('Party for Issuing Inspector not found')
raise ExplosivesPermitExeption("Can't find the provided issuing inspector",
status_code = 404)
else:
validate_issuing_inspector(explosives_permit)
issuing_inspector = explosives_permit.issuing_inspector
template_data['issuing_inspector_name'] = issuing_inspector.name

mine_manager = explosives_permit.mine_manager
if mine_manager is None:
raise Exception('Appointment for Mine Manager not found')
raise ExplosivesPermitExeption("Provided Mine Manager not found",
status_code = 404)

if mine_manager.party.first_address is None:
raise Exception('Address for Mine Manager not found')
raise ExplosivesPermitExeption("Mine Manager does not have an address",
status_code = 422)
template_data['mine_manager_address'] = mine_manager.party.first_address.full
template_data['mine_manager_name'] = mine_manager.party.name

permittee = explosives_permit.permittee
if permittee is None:
raise Exception('Appointment for Permittee not found')
raise ExplosivesPermitExeption("Provided permittee not found",
status_code = 404)

if permittee.party.first_address is None:
raise Exception('Address for Permittee not found')
raise ExplosivesPermitExeption("Permittee does not have an address",
status_code = 422)
template_data['permittee_address'] = permittee.party.first_address.full
template_data['permittee_name'] = permittee.party.name

permit_number = 'BC-XXXXX' if is_draft else explosives_permit.permit_number
if permit_number is None:
raise Exception('Explosives Permit has not been issued')
raise ExplosivesPermitExeption("Explosives Permit has not been issued",
status_code = 400)
template_data['permit_number'] = permit_number

# Transform template data for "Explosives Storage and Use Permit" (PER)
Expand Down
Loading

0 comments on commit 3ad7dbd

Please sign in to comment.