diff options
| author | Robot Odoo <xmo+roboto@odoo.com> | 2019-06-28 14:51:01 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-06-28 14:51:01 +0200 |
| commit | 20ed8d430023bcf2236cc6ddc30da87a433a5176 (patch) | |
| tree | 35f489d2e66eca339b8c54aabbf5acb908ad1fe7 /addons/web/static/src/js | |
| parent | 2cbbc88dabcbe11baca2f3e30ec7425af39141e4 (diff) | |
| parent | 6e55a3a49666484c38345ba85b14441dc3056ec2 (diff) | |
[MERGE] website, *: handle all website errors with beautiful design
* web, base, website_forum, test_website, account, gamification,
iap, mail, partner_autocomplete, point_of_sale, stock,
website_sale_comparison
All rpc errors are now displayed using the notification system recently
updated for website. For the redirections triggering an error that are
currently displayed as a 500 Error with an ugly white page, the error
page will be similar to 404/403 error but with code 400. All
exception_orm are now handled as a 400 except for the AccessError
which is a 403.
The error card on all the Error pages is now loaded on an open state
displaying clearly the reason of the error without clicking anything.
If there is no error message the error card is not displayed and the
traceback is open. The traceback is only displayed if we are in debug
mode. These changes are also effective for the 500, 404 and 403 error
pages.
KarmaError is now an except_orm exception.
Karma error message now contains the required karma.
All the alerts in website_forum will use the toast notifications
instead of alerts.
A test tour is located in test_website, visit /test_error_view to
trigger some errors manually and look at the result.
task-1894820
closes odoo/odoo#32132
Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
Diffstat (limited to 'addons/web/static/src/js')
| -rw-r--r-- | addons/web/static/src/js/chrome/abstract_web_client.js | 55 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/ajax.js | 6 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/service_mixins.js | 38 | ||||
| -rw-r--r-- | addons/web/static/src/js/services/crash_manager.js | 186 | ||||
| -rw-r--r-- | addons/web/static/src/js/services/notification_service.js | 1 | ||||
| -rw-r--r-- | addons/web/static/src/js/widgets/notification.js | 2 |
6 files changed, 180 insertions, 108 deletions
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 6b7df97b767..2d94501deed 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'], @@ -202,46 +200,6 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi }); core.bus.on('connection_lost', this, this._onConnectionLost); core.bus.on('connection_restored', this, this._onConnectionRestored); - - // crash manager integration - core.bus.on('rpc_error', crash_manager, crash_manager.rpc_error); - window.onerror = function (message, file, line, col, error) { - // Scripts injected in DOM (eg: google API's js files) won't return a clean error on window.onerror. - // The browser will just give you a 'Script error.' as message and nothing else for security issue. - // To enable onerror to work properly with CORS file, you should: - // 1. add crossorigin="anonymous" to your <script> tag loading the file - // 2. enabling 'Access-Control-Allow-Origin' on the server serving the file. - // Since in some case it wont be possible to to this, this handle should have the possibility to be - // handled by the script manipulating the injected file. For this, you will use window.onOriginError - // If it is not handled, we should display something clearer than the common crash_manager error dialog - // since it won't show anything except "Script error." - // This link will probably explain it better: https://blog.sentry.io/2016/05/17/what-is-script-error.html - if (!file && !line && !col) { - // Chrome and Opera set "Script error." on the `message` and hide the `error` - // Firefox handles the "Script error." directly. It sets the error thrown by the CORS file into `error` - if (window.onOriginError) { - window.onOriginError(); - delete window.onOriginError; - } else { - crash_manager.show_error({ - type: _t("Odoo Client Error"), - message: _t("Unknown CORS error"), - data: {debug: _t("An unknown CORS error occured. The error probably originates from a JavaScript file served from a different origin. (Opening your browser console might give you a hint on the error.)")}, - }); - } - } else { - // ignore Chrome video internal error: https://crbug.com/809574 - if (!error && message === 'ResizeObserver loop limit exceeded') { - return; - } - var traceback = error ? error.stack : ''; - crash_manager.show_error({ - type: _t("Odoo Client Error"), - message: message, - data: {debug: file + ':' + line + "\n" + _t('Traceback:') + "\n" + traceback}, - }); - } - }; }, set_action_manager: function () { var self = this; @@ -373,7 +331,7 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi * @private */ _onConnectionLost: function () { - this.connectionNotificationID = this.call('notification', 'notify', { + this.connectionNotificationID = this.displayNotification({ title: _t('Connection lost'), message: _t('Trying to reconnect...'), sticky: true @@ -387,7 +345,8 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi _onConnectionRestored: function () { if (this.connectionNotificationID) { this.call('notification', 'close', this.connectionNotificationID); - this.call('notification', 'notify', { + this.displayNotification({ + type: 'info', title: _t('Connection restored'), message: _t('You are back online'), sticky: false @@ -433,11 +392,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/core/service_mixins.js b/addons/web/static/src/js/core/service_mixins.js index 209f6ccabb3..f34adfe4de8 100644 --- a/addons/web/static/src/js/core/service_mixins.js +++ b/addons/web/static/src/js/core/service_mixins.js @@ -225,34 +225,42 @@ var ServicesMixin = { /** * Displays a notification. * - * @param {string} title - * @param {string} [message] - * @param {string} [type='warning'] 'info', 'success', 'warning', 'danger' or '' - * @param {boolean} [sticky=false] - * @param {string} [className] + * @param {Object} options + * @param {string} options.title + * @param {string} [options.subtitle] + * @param {string} [options.message] + * @param {string} [options.type='warning'] 'info', 'success', 'warning', 'danger' or '' + * @param {boolean} [options.sticky=false] + * @param {string} [options.className] */ - displayNotification: function (title, message, type, sticky, className) { - return this.call('notification', 'notify', { - type: type, - title: title, - message: message, - sticky: sticky, - className: className, - }); + displayNotification: function (options) { + return this.call('notification', 'notify', options); }, /** * @deprecated will be removed as soon as the notification system is reviewed * @see displayNotification */ do_notify: function (title, message, sticky, className) { - return this.displayNotification(title, message, 'warning', sticky, className); + return this.displayNotification({ + type: 'warning', + title: title, + message: message, + sticky: sticky, + className: className, + }); }, /** * @deprecated will be removed as soon as the notification system is reviewed * @see displayNotification */ do_warn: function (title, message, sticky, className) { - return this.displayNotification(title, message, 'danger', sticky, className); + return this.displayNotification({ + type: 'danger', + title: title, + message: message, + sticky: sticky, + className: className, + }); }, }; diff --git a/addons/web/static/src/js/services/crash_manager.js b/addons/web/static/src/js/services/crash_manager.js index ff699af3262..701d97f0a53 100644 --- a/addons/web/static/src/js/services/crash_manager.js +++ b/addons/web/static/src/js/services/crash_manager.js @@ -4,27 +4,128 @@ odoo.define('web.CrashManager', function (require) { var ajax = require('web.ajax'); var core = require('web.core'); var Dialog = require('web.Dialog'); +var mixins = require('web.mixins'); +var ServicesMixin = require('web.ServicesMixin'); +var ServiceProviderMixin = require('web.ServiceProviderMixin'); +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"), + karma_error: _lt("Karma Error"), }; -var CrashManager = core.Class.extend({ +/** + * 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(mixins.EventDispatcherMixin, ServicesMixin, ServiceProviderMixin,{ init: function () { var self = this; this.active = true; this.isConnected = true; + mixins.EventDispatcherMixin.init.call(this); + + // crash manager integration + core.bus.on('rpc_error', this, this.rpc_error); + window.onerror = function (message, file, line, col, error) { + // Scripts injected in DOM (eg: google API's js files) won't return a clean error on window.onerror. + // The browser will just give you a 'Script error.' as message and nothing else for security issue. + // To enable onerror to work properly with CORS file, you should: + // 1. add crossorigin="anonymous" to your <script> tag loading the file + // 2. enabling 'Access-Control-Allow-Origin' on the server serving the file. + // Since in some case it wont be possible to to this, this handle should have the possibility to be + // handled by the script manipulating the injected file. For this, you will use window.onOriginError + // If it is not handled, we should display something clearer than the common crash_manager error dialog + // since it won't show anything except "Script error." + // This link will probably explain it better: https://blog.sentry.io/2016/05/17/what-is-script-error.html + if (!file && !line && !col) { + // Chrome and Opera set "Script error." on the `message` and hide the `error` + // Firefox handles the "Script error." directly. It sets the error thrown by the CORS file into `error` + if (window.onOriginError) { + window.onOriginError(); + delete window.onOriginError; + } else { + self.show_error({ + type: _t("Odoo Client Error"), + message: _t("Unknown CORS error"), + data: {debug: _t("An unknown CORS error occured. The error probably originates from a JavaScript file served from a different origin. (Opening your browser console might give you a hint on the error.)")}, + }); + } + } else { + // ignore Chrome video internal error: https://crbug.com/809574 + if (!error && message === 'ResizeObserver loop limit exceeded') { + return; + } + var traceback = error ? error.stack : ''; + self.show_error({ + type: _t("Odoo Client Error"), + message: message, + data: {debug: file + ':' + line + "\n" + _t('Traceback:') + "\n" + traceback}, + }); + } + }; + // listen to unhandled rejected promises, and throw an error when the // promise has been rejected due to a crash window.addEventListener('unhandledrejection', function (ev) { @@ -43,6 +144,7 @@ var CrashManager = core.Class.extend({ ev.preventDefault(); } }); + ServiceProviderMixin.init.call(this); }, enable: function () { this.active = true; @@ -89,14 +191,14 @@ 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, { message: error.data.arguments[1], - title: error.data.arguments[0] !== 'Warning' ? (" - " + error.data.arguments[0]) : '', + title: error.data.arguments[0] !== 'Warning' ? error.data.arguments[0] : '', }) }); } @@ -117,38 +219,41 @@ var CrashManager = core.Class.extend({ data: _.extend({}, error.data, { message: error.data.arguments[0], - title: map_title[error.data.exception_type] !== 'Warning' ? (" - " + map_title[error.data.exception_type]) : '', + title: map_title[error.data.exception_type] !== 'Warning' ? map_title[error.data.exception_type] : '', }) }); } - this.show_warning(error); - //InternalError - } else { this.show_error(error); } }, - show_warning: function(error) { + show_warning: function (error, options) { if (!this.active) { return; } - return new Dialog(this, { - size: 'medium', - title: _.str.capitalize(error.type || error.message) || _t("Odoo Warning"), - subtitle: error.data.title, - $content: $(QWeb.render('CrashManager.warning', {error: error})) - }).open({shouldFocusButtons:true}); + var message = error.data ? error.data.message : error.message; + var title = _.str.capitalize(error.type) || _t("Oops Something went wrong !"); + var subtitle = error.data ? error.data.title : error.title; + this.displayNotification(_.extend({ + title: title, + message: message, + subtitle: subtitle, + sticky: true, + }, options)); }, - 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; @@ -216,7 +321,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; @@ -224,10 +329,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() { @@ -239,8 +342,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(); } }); @@ -250,9 +354,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); @@ -262,18 +366,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/js/services/notification_service.js b/addons/web/static/src/js/services/notification_service.js index ea16d9ef66f..295b49d760c 100644 --- a/addons/web/static/src/js/services/notification_service.js +++ b/addons/web/static/src/js/services/notification_service.js @@ -60,6 +60,7 @@ var NotificationService = AbstractService.extend({ * @param {function} [params.Notification] javascript class of a notification * to instantiate by default use 'web.Notification' * @param {string} params.title notification title + * @param {string} params.subtitle notification subtitle * @param {string} params.message notification main message * @param {string} params.type 'notification' or 'warning' * @param {boolean} [params.sticky=false] if true, the notification will stay diff --git a/addons/web/static/src/js/widgets/notification.js b/addons/web/static/src/js/widgets/notification.js index 04d42abd7af..a93cc97f91d 100644 --- a/addons/web/static/src/js/widgets/notification.js +++ b/addons/web/static/src/js/widgets/notification.js @@ -25,6 +25,7 @@ var Notification = Widget.extend({ * @param {Widget} parent * @param {Object} params * @param {string} params.title + * @param {string} params.subtitle * @param {string} [params.message] * @param {string} [params.type='warning'] 'info', 'success', 'warning', 'danger' or '' * @param {boolean} [params.sticky=false] if true, the notification will @@ -41,6 +42,7 @@ var Notification = Widget.extend({ init: function (parent, params) { this._super.apply(this, arguments); this.title = params.title; + this.subtitle = params.subtitle; this.message = params.message; this.buttons = params.buttons || []; this.sticky = !!this.buttons.length || !!params.sticky; |
