From f8fe314e786d2100833ecc8b0cb661252f648ddc Mon Sep 17 00:00:00 2001 From: fja-odoo Date: Tue, 11 Jun 2019 12:26:45 +0000 Subject: [PATCH] [IMP] web, *: display warning/error in frontend * = account, iap, point_of_sale Removing the CrashManager templates from base.xml and moving it to a new file (crash_manager.xml) to allow lazy loading the required xml only when a crash occurs (necessary for the frontend as no xml is pre-loaded). All rpc errors/warnings are now displayed using the same modal system as the backend (crash_manager.js). CrashManager will now return the WarningDialog and ErrorDialog in addition to the CrashManager itself. In some cases an instance of CrashManager was created just to display a warning/error dialog. In the backend all rpc errors but sessionExpired will be logged. If the exception is a warning, NotFound (403) or except_orm, it will be a warning log. The rest will be exception log. In the frontend all rpc errors will be logged using console.debug instead of console.warn and console.error. This way we can trigger rpc errors in tests without failing them. If a real error happend during a test it will fall back on the backend logger to fail the test. Part of https://github.com/odoo/odoo/pull/32132 task-1894820 --- .../js/reconciliation/reconciliation_model.js | 4 +- addons/iap/static/src/js/crash_manager.js | 2 +- addons/point_of_sale/static/src/js/chrome.js | 2 +- .../src/js/chrome/abstract_web_client.js | 10 +- addons/web/static/src/js/core/ajax.js | 6 +- .../static/src/js/services/crash_manager.js | 126 +++++++++++++----- addons/web/static/src/xml/base.xml | 24 ---- addons/web/static/src/xml/crash_manager.xml | 25 ++++ addons/web/views/webclient_templates.xml | 2 +- odoo/http.py | 19 ++- 10 files changed, 140 insertions(+), 80 deletions(-) create mode 100644 addons/web/static/src/xml/crash_manager.xml diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index 2846e2d2e2f..bfea511f72e 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -5,7 +5,7 @@ var BasicModel = require('web.BasicModel'); var field_utils = require('web.field_utils'); var utils = require('web.utils'); var session = require('web.session'); -var CrashManager = require('web.CrashManager'); +var WarningDialog = require('web.CrashManager').WarningDialog; var core = require('web.core'); var _t = core._t; @@ -636,7 +636,7 @@ var StatementModel = BasicModel.extend({ }); break; } - } + } else if (fieldName === 'tax_ids') { switch(value.operation) { case "ADD_M2M": diff --git a/addons/iap/static/src/js/crash_manager.js b/addons/iap/static/src/js/crash_manager.js index 59b07af1294..c1d01e2cd2a 100644 --- a/addons/iap/static/src/js/crash_manager.js +++ b/addons/iap/static/src/js/crash_manager.js @@ -3,7 +3,7 @@ odoo.define('iap.CrashManager', function (require) { var ajax = require('web.ajax'); var core = require('web.core'); -var CrashManager = require('web.CrashManager'); +var CrashManager = require('web.CrashManager').CrashManager; var Dialog = require('web.Dialog'); var _t = core._t; diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js index 5e1ad64eb32..a63264a6e4d 100644 --- a/addons/point_of_sale/static/src/js/chrome.js +++ b/addons/point_of_sale/static/src/js/chrome.js @@ -8,7 +8,7 @@ var models = require('point_of_sale.models'); var AbstractAction = require('web.AbstractAction'); var core = require('web.core'); var ajax = require('web.ajax'); -var CrashManager = require('web.CrashManager'); +var CrashManager = require('web.CrashManager').CrashManager; var BarcodeEvents = require('barcodes.BarcodeEvents').BarcodeEvents; diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js index 5adaaa36783..270f2871afe 100644 --- a/addons/web/static/src/js/chrome/abstract_web_client.js +++ b/addons/web/static/src/js/chrome/abstract_web_client.js @@ -15,9 +15,8 @@ var ActionManager = require('web.ActionManager'); var concurrency = require('web.concurrency'); var core = require('web.core'); var config = require('web.config'); -var crash_manager = require('web.crash_manager'); +var WarningDialog = require('web.CrashManager').WarningDialog; var data_manager = require('web.data_manager'); -var Dialog = require('web.Dialog'); var dom = require('web.dom'); var KeyboardNavigationMixin = require('web.KeyboardNavigationMixin'); var Loading = require('web.Loading'); @@ -29,7 +28,6 @@ var utils = require('web.utils'); var Widget = require('web.Widget'); var _t = core._t; -var qweb = core.qweb; var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMixin, { dependencies: ['notification'], @@ -393,11 +391,9 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi _onDisplayWarning: function (e) { var data = e.data; if (data.type === 'dialog') { - new Dialog(this, { - size: 'medium', + new WarningDialog(this, { title: data.title, - $content: qweb.render("CrashManager.warning", data), - }).open({shouldFocusButtons: true}); + }, data).open(); } else { this.call('notification', 'notify', e.data); } diff --git a/addons/web/static/src/js/core/ajax.js b/addons/web/static/src/js/core/ajax.js index af7628cc5ea..b17a3f501e6 100644 --- a/addons/web/static/src/js/core/ajax.js +++ b/addons/web/static/src/js/core/ajax.js @@ -27,11 +27,7 @@ function _genericJsonRpc (fct_name, params, settings, fct) { core.bus.trigger('rpc:result', data, result); if (result.error !== undefined) { if (result.error.data.arguments[0] !== "bus.Bus not available in test mode") { - var func = console.error; - if (result.error.data.exception_type === "user_error") { - func = console.log; - } - func( + console.debug( "Server application error\n", "Error code:", result.error.code, "\n", "Error message:", result.error.message, "\n", diff --git a/addons/web/static/src/js/services/crash_manager.js b/addons/web/static/src/js/services/crash_manager.js index 7b3469e5e47..8681a869aba 100644 --- a/addons/web/static/src/js/services/crash_manager.js +++ b/addons/web/static/src/js/services/crash_manager.js @@ -4,21 +4,76 @@ odoo.define('web.CrashManager', function (require) { var ajax = require('web.ajax'); var core = require('web.core'); var Dialog = require('web.Dialog'); +var Widget = require('web.Widget'); -var QWeb = core.qweb; var _t = core._t; var _lt = core._lt; var map_title ={ - user_error: _lt('Warning'), - warning: _lt('Warning'), - access_error: _lt('Access Error'), - missing_error: _lt('Missing Record'), - validation_error: _lt('Validation Error'), - except_orm: _lt('Global Business Error'), - access_denied: _lt('Access Denied'), + user_error: _lt("User Error"), + warning: _lt("Warning"), + access_error: _lt("Access Error"), + missing_error: _lt("Missing Record"), + validation_error: _lt("Validation Error"), + except_orm: _lt("Global Business Error"), + access_denied: _lt("Access Denied"), }; +/** + * An extension of Dialog Widget to render the warnings and errors on the website. + * Extend it with your template of choice like ErrorDialog/WarningDialog + */ +var CrashManagerDialog = Dialog.extend({ + xmlDependencies: (Dialog.prototype.xmlDependencies || []).concat( + ['/web/static/src/xml/crash_manager.xml'] + ), + + /** + * @param {Object} error + * @param {string} error.message the message in Warning/Error Dialog + * @param {string} error.traceback the traceback in ErrorDialog + * + * @constructor + */ + init: function (parent, options, error) { + this._super.apply(this, [parent, options]); + this.message = error.message; + this.traceback = error.traceback; + }, +}); + +var ErrorDialog = CrashManagerDialog.extend({ + template: 'CrashManager.error', +}); + +var WarningDialog = CrashManagerDialog.extend({ + template: 'CrashManager.warning', + + /** + * Sets size to medium by default. + * + * @override + */ + init: function (parent, options, error) { + this._super(parent, _.extend({ + size: 'medium', + }, options), error); + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Focuses the ok button. + * + * @override + */ + open: function () { + this._super({shouldFocusButtons: true}); + }, +}); + var CrashManager = core.Class.extend({ init: function () { var self = this; @@ -129,8 +184,8 @@ var CrashManager = core.Class.extend({ return; } if (_.has(map_title, error.data.exception_type)) { - if(error.data.exception_type === 'except_orm'){ - if(error.data.arguments[1]) { + if (error.data.exception_type === 'except_orm') { + if (error.data.arguments[1]) { error = _.extend({}, error, { data: _.extend({}, error.data, @@ -169,26 +224,30 @@ var CrashManager = core.Class.extend({ this.show_error(error); } }, - show_warning: function(error) { + show_warning: function (error) { if (!this.active) { return; } - return new Dialog(this, { - size: 'medium', - title: _.str.capitalize(error.type || error.message) || _t("Odoo Warning"), + var message = error.data ? error.data.message : error.message; + return new WarningDialog(this, { + title: _.str.capitalize(error.type) || _t("Odoo Warning"), subtitle: error.data.title, - $content: $(QWeb.render('CrashManager.warning', {error: error})) - }).open({shouldFocusButtons:true}); + }, { + message: message, + }).open(); }, - show_error: function(error) { + show_error: function (error) { if (!this.active) { return; } - var dialog = new Dialog(this, { - title: _.str.capitalize(error.type || error.message) || _t("Odoo Error"), - $content: $(QWeb.render('CrashManager.error', {error: error})) + var dialog = new ErrorDialog(this, { + title: _.str.capitalize(error.type) || _t("Odoo Error"), + }, { + message: error.message, + traceback: error.data.debug, }); + // When the dialog opens, initialize the copy feature and destroy it when the dialog is closed var $clipboardBtn; var clipboard; @@ -256,7 +315,7 @@ var ExceptionHandler = { * Handle redirection warnings, which behave more or less like a regular * warning, with an additional redirection button. */ -var RedirectWarningHandler = Dialog.extend(ExceptionHandler, { +var RedirectWarningHandler = Widget.extend(ExceptionHandler, { init: function(parent, error) { this._super(parent); this.error = error; @@ -264,10 +323,8 @@ var RedirectWarningHandler = Dialog.extend(ExceptionHandler, { display: function() { var self = this; var error = this.error; - error.data.message = error.data.arguments[0]; - new Dialog(this, { - size: 'medium', + new WarningDialog(this, { title: _.str.capitalize(error.type) || _t("Odoo Warning"), buttons: [ {text: error.data.arguments[2], classes : "btn-primary", click: function() { @@ -279,8 +336,9 @@ var RedirectWarningHandler = Dialog.extend(ExceptionHandler, { location.reload(); }}, {text: _t("Cancel"), click: function() { self.destroy(); }, close: true} - ], - $content: QWeb.render('CrashManager.warning', {error: error}), + ] + }, { + message: error.data.arguments[0], }).open(); } }); @@ -290,9 +348,9 @@ core.crash_registry.add('odoo.exceptions.RedirectWarning', RedirectWarningHandle function session_expired(cm) { return { display: function () { - cm.show_warning({type: _t("Odoo Session Expired"), data: {message: _t("Your Odoo session expired. Please refresh the current web page.")}}); + cm.show_warning({type: _t("Odoo Session Expired"), message: _t("Your Odoo session expired. Please refresh the current web page.")}); } - } + }; } core.crash_registry.add('odoo.http.SessionExpiredException', session_expired); core.crash_registry.add('werkzeug.exceptions.Forbidden', session_expired); @@ -302,18 +360,22 @@ core.crash_registry.add('504', function (cm) { display: function () { cm.show_warning({ type: _t("Request timeout"), - data: {message: _t("The operation was interrupted. This usually means that the current operation is taking too much time.")}}); + message: _t("The operation was interrupted. This usually means that the current operation is taking too much time.")}); } - } + }; }); -return CrashManager; +return { + CrashManager: CrashManager, + ErrorDialog: ErrorDialog, + WarningDialog: WarningDialog, +}; }); odoo.define('web.crash_manager', function (require) { "use strict"; -var CrashManager = require('web.CrashManager'); +var CrashManager = require('web.CrashManager').CrashManager; return new CrashManager(); }); diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 950f24c792c..d214e2e692e 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -120,30 +120,6 @@ - -
- - - - - -
-
diff --git a/addons/web/static/src/xml/crash_manager.xml b/addons/web/static/src/xml/crash_manager.xml new file mode 100644 index 00000000000..a6b506c0190 --- /dev/null +++ b/addons/web/static/src/xml/crash_manager.xml @@ -0,0 +1,25 @@ + + + + + +
+ + + + + +
+ +
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index ed504c9050e..82464dc9dba 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -153,6 +153,7 @@ + @@ -247,7 +248,6 @@ - diff --git a/odoo/http.py b/odoo/http.py index 8702fd9a950..ab4512a6321 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -630,13 +630,18 @@ class JsonRequest(WebRequest): try: return super(JsonRequest, self)._handle_exception(exception) except Exception: - if not isinstance(exception, (odoo.exceptions.Warning, SessionExpiredException, - odoo.exceptions.except_orm, werkzeug.exceptions.NotFound)): - _logger.exception("Exception during JSON request handling.") + if not isinstance(exception, SessionExpiredException): + if exception.args and exception.args[0] == "bus.Bus not available in test mode": + _logger.info(exception) + elif isinstance(exception, (odoo.exceptions.Warning, odoo.exceptions.except_orm, + werkzeug.exceptions.NotFound)): + _logger.warning(exception) + else: + _logger.exception("Exception during JSON request handling.") error = { - 'code': 200, - 'message': "Odoo Server Error", - 'data': serialize_exception(exception) + 'code': 200, + 'message': "Odoo Server Error", + 'data': serialize_exception(exception), } if isinstance(exception, werkzeug.exceptions.NotFound): error['http_status'] = 404 @@ -1414,7 +1419,7 @@ class Root(object): httprequest = werkzeug.wrappers.Request(environ) httprequest.app = self httprequest.parameter_storage_class = werkzeug.datastructures.ImmutableOrderedMultiDict - + current_thread = threading.current_thread() current_thread.url = httprequest.url current_thread.query_count = 0