From 38ec5d109aae03edc419dde50369f82ae827c59a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Gaulin?= Date: Mon, 23 May 2016 22:03:31 +0200 Subject: [PATCH] Javascript plugin to beautify exceptions on the backend event log page (#2035) --- .../system/assets/css/eventlogs/preview.css | 83 +++++++ modules/system/assets/js/eventlogs/preview.js | 208 ++++++++++++++++++ .../assets/js/eventlogs/preview.links.js | 109 +++++++++ modules/system/controllers/EventLogs.php | 14 ++ modules/system/lang/en/client.php | 10 + modules/system/lang/fr/client.php | 10 + modules/system/models/eventlog/fields.yaml | 2 + 7 files changed, 436 insertions(+) create mode 100644 modules/system/assets/css/eventlogs/preview.css create mode 100644 modules/system/assets/js/eventlogs/preview.js create mode 100644 modules/system/assets/js/eventlogs/preview.links.js diff --git a/modules/system/assets/css/eventlogs/preview.css b/modules/system/assets/css/eventlogs/preview.css new file mode 100644 index 000000000..041e8b89e --- /dev/null +++ b/modules/system/assets/css/eventlogs/preview.css @@ -0,0 +1,83 @@ +[data-plugin="exception-beautifier"] { + width: auto !important; +} + +.markup_exception_container { + overflow-x: scroll; +} + +.markup_exception_name { + display: block; + font-weight: bold; + font-size: 1.2em; +} + +.markup_exception_message { + display: block; + padding: 15px 0; + font-size: 1.1em; +} + +.markup_exception_stacktrace_title { + display: block; + margin-top: 15px; + font-weight: bold; +} + +.markup_exception_stacktrace_line { + display: block; + white-space: nowrap; + padding: 8px 0; +} + +.markup_exception_stacktrace_line:nth-child(even) { + background-color: #ffffff; +} + +.markup_exception_stacktrace_line:nth-child(odd) { + background-color: #f2f2f2; +} + +.markup_exception_stacktrace_line_number { + display: inline-block; + width: 40px; + margin-right: 15px; + text-align: right; + color: black; +} + +.markup_exception_stacktrace_line_internal { + color: #2C3E50; +} + +.markup_exception_stacktrace_line_function { + display: block; + margin-left: 55px; +} + +.markup_exception_file { + color: #0c7eff; +} + +.markup_exception_line_number { + color: #af0016; +} + +.markup_exception_class { + font-weight: bold; + font-family: monospace; +} + +.markup_exception_function { + font-weight: bold; + color: #4d9a98; + font-family: monospace; +} + +.markup_exception_function.--system { + color: #3d7a78; +} + +.markup_exception_sign { + color: #4d9a98; +} \ No newline at end of file diff --git a/modules/system/assets/js/eventlogs/preview.js b/modules/system/assets/js/eventlogs/preview.js new file mode 100644 index 000000000..6bb4594de --- /dev/null +++ b/modules/system/assets/js/eventlogs/preview.js @@ -0,0 +1,208 @@ +/* + * Previews class + */ + ++function ($) { + "use strict"; + + var ExceptionBeautifier = function (el, options) { + var self = this + + self.$el = $(el) + self.options = options || {} + + // Init + self.init() + } + + ExceptionBeautifier.DEFAULTS = { + editor: 'phpstorm' + } + + ExceptionBeautifier.REGEX = { + phpLine: /^(#[0-9]+)\s+(.+\.php)(?:\(([0-9]+)\))?\s*:(.*)/, + internalLine: /^(#[0-9]+)\s+(\[internal function\]\s*:)(.*)/, + defaultLine: /^(#[0-9]+)\s*(.*)/, + className: /([a-z0-9]+\\[a-z0-9\\]+)/gi, + filePath: /((?:[A-Z]:[/\\]|\/)?[\w/\\]+\.php)(\(([0-9]+)\)|:([0-9]+))?/gi, + functionCall: /->([^(]+)\((.*)\)/i + } + + ExceptionBeautifier.extensions = [] + + ExceptionBeautifier.prototype.init = function () { + var self = this + + ExceptionBeautifier.extensions.forEach(function (extension) { + extension.onInit(self); + }) + + self.$el.html(self.parseSource()) + } + + ExceptionBeautifier.prototype.parseSource = function () { + var self = this, + source = self.$el.text(), + markup = {lines: []}, + start = 0, + end, + matches + + // We only parse stack trace message. Just transform newline to
and format spaces for standard messages. + if (source.indexOf('Stack trace:') < 0) { + return '
' + self.formatMessage(source) + '
'; + } + + end = source.indexOf(':', start) + 1 + markup.name = source.substring(start, end) + + start = end + end = source.indexOf('Stack trace:', start) + markup.message = source.substring(start, end) + + start = source.indexOf('#', end) + while ((end = source.indexOf('#', start + 1)) > 0) { + markup.lines.push(self.parseLine(source.substring(start, end))) + start = end + } + + markup.lines.push(self.parseLine(source.substring(start))) + + source = '
' + + '' + markup.name + '' + + '' + self.formatMessage(markup.message) + '' + + '
' + + 'Stack trace:' + + markup.lines.forEach(function (line) { + source += '' + self.formatLine(line) + '' + }) + + ExceptionBeautifier.extensions.forEach(function (extension) { + extension.onParse(self); + }) + + return '
' + source + '
' + } + + ExceptionBeautifier.prototype.parseLine = function (str) { + var self = this, + line = {}, + matches + + if (matches = str.match(ExceptionBeautifier.REGEX.phpLine)) { + line.type = 'phpLine' + line.number = $.trim(matches[1]) + line.file = $.trim(matches[2]) + line.lineNumber = $.trim(matches[3]) + line.function = $.trim(matches[4]) + } + else if (matches = str.match(ExceptionBeautifier.REGEX.internalLine)) { + line.type = 'internal' + line.number = $.trim(matches[1]) + line.internal = $.trim(matches[2]) + line.function = $.trim(matches[3]) + } else if (matches = str.match(ExceptionBeautifier.REGEX.defaultLine)){ + line.type = 'default' + line.number = $.trim(matches[1]) + line.function = $.trim(matches[2]) + } + + return line + } + + ExceptionBeautifier.prototype.formatLine = function (line) { + var self = this + + if (line.function) { + line.function = self.formatClasses(line.function) + } + + switch (line.type) { + case 'phpLine': + return '' + line.number + '' + + self.formatFilePath(line.file, line.lineNumber) + + '(' + line.lineNumber + '): ' + + '' + line.function + '' + + case 'internal': + return '' + line.number + '' + + '' + line.internal + '' + + '' + line.function + '' + + case 'default': + return '' + line.number + '' + + '' + line.function + '' + } + + return '' + } + + ExceptionBeautifier.prototype.formatClasses = function (str) { + var self = this + + str = str.replace(ExceptionBeautifier.REGEX.className, '$1') + + str = str.replace(ExceptionBeautifier.REGEX.filePath, self.formatFilePath('$1', '$3$4') + + '$2') + + if (str.match(/^\s*(call_user_func|spl_autoload_call)/)) { + str = str.replace(/^\s*(?:call_user_func|spl_autoload_call)([^(]*)\((.*)\)/, + 'call_user_func$1' + + '(' + + '$2' + + ')') + } else { + str = str.replace(ExceptionBeautifier.REGEX.functionCall, + '->' + + '$1' + + '(' + + '$2' + + ')') + } + + return str + } + + ExceptionBeautifier.prototype.formatFilePath = function (path, line) { + var self = this + + return '' + path + '' + } + + ExceptionBeautifier.prototype.formatMessage = function (str) { + var self = this + + return self.formatClasses(str) + .replace(/^\s+/, '') + .replace(/\r|\n|\r\r/g, '
') + .replace(/\r| {4}/g, '    ') + } + + + // EXCEPTION BEAUTIFIER PLUGIN DEFINITION + // ============================ + + $.fn.exceptionBeautifier = function (option) { + var args = arguments, + result + + this.each(function () { + var $this = $(this) + var data = $this.data('oc.exceptionBeautifier') + var options = $.extend({}, ExceptionBeautifier.DEFAULTS, $this.data(), typeof option == 'object' && option) + if (!data) $this.data('oc.exceptionBeautifier', (data = new ExceptionBeautifier(this, options))) + if (typeof option == 'string') result = data[option].call($this) + if (typeof result != 'undefined') return false + }) + + return result ? result : this + } + + $.fn.exceptionBeautifier.Constructor = ExceptionBeautifier + + $(document).render(function () { + $('[data-plugin="exception-beautifier"]').exceptionBeautifier() + }) + +}(window.jQuery) \ No newline at end of file diff --git a/modules/system/assets/js/eventlogs/preview.links.js b/modules/system/assets/js/eventlogs/preview.links.js new file mode 100644 index 000000000..6ab81a4a3 --- /dev/null +++ b/modules/system/assets/js/eventlogs/preview.links.js @@ -0,0 +1,109 @@ +/* + * Previews class + */ + ++function ($) { + "use strict"; + + var ExceptionBeautifier = $.fn.exceptionBeautifier.Constructor; + + ExceptionBeautifier.EDITORS = { + subl: {scheme: 'subl://open?url=file://%file&line=%line', name: 'Sublime (subl://)'}, + txmt: {scheme: 'txmt://open/?url=file://%file&line=%line', name: 'TextMate (txmt://)'}, + mvim: {scheme: 'mvim://open/?url=file://%file&line=%line', name: 'MacVim (mvim://)'}, + phpstorm: {scheme: 'phpstorm://open?file=%file&line=%line', name: 'PhpStorm (phpstorm://)'}, + editor: {scheme: 'editor://open/?file=%file&line=%line', name: 'Custom (editor://)'} + } + + ExceptionBeautifier.REGEX.editor = /idelink:\/\/([^#]+)&([0-9]+)?/ + + ExceptionBeautifier.extensions.push({ + onInit: function (exceptionBeautfier) { + exceptionBeautfier.initEditorPopup(); + }, + onParse: function (exceptionBeautfier) { + exceptionBeautfier.$el.on('click', 'a', function () { + exceptionBeautfier.openWithEditor($(this).data('href')); + }) + } + }) + + ExceptionBeautifier.prototype.initEditorPopup = function () { + var self = this, + title = $.oc.lang.get('eventlog.editor.title'), + description = $.oc.lang.get('eventlog.editor.description'), + openWith = $.oc.lang.get('eventlog.editor.openWith'), + saveRememberClose = $.oc.lang.get('eventlog.editor.openAndRemember'), + openClose = $.oc.lang.get('eventlog.editor.open') + + self.$el.parent().append(' \ + ') + } + + ExceptionBeautifier.prototype.openWithEditor = function (link) { + var self = this, + matches, + open = function (value) { + window.open(link.replace( + ExceptionBeautifier.REGEX.editor, + ExceptionBeautifier.EDITORS[value].scheme + .replace(/%file/, matches[1]) + .replace(/%line/, matches[2]) + ), '_self') + } + + if (matches = link.match(ExceptionBeautifier.REGEX.editor)) { + if (window.sessionStorage && window.sessionStorage['oc-exception-editor']) { + open(window.sessionStorage['oc-exception-editor']); + } else { + $('#exception-link-editor') + .popup({content: $('#exception-link-editor').html()}) + .on('shown.oc.popup', function (event, source, popup) { + var $select = $('select', popup).empty() + + if (!window.sessionStorage) { + $('[data-trigger="editor-open-save"]', popup).hide(); + } + + $('[data-trigger="editor-open-close"]', popup).on('click', function () { + open($select.val()); + }) + + $('[data-trigger="editor-open-save"]', popup).on('click', function () { + window.sessionStorage['oc-exception-editor'] = $select.val(); + + open($select.val()); + }) + + for (var key in ExceptionBeautifier.EDITORS) { + $select.append('') + } + }) + .on('hide.oc.popup', function (event, source, popup) { + $('[data-trigger]', popup).off('click') + }) + } + } + } + + ExceptionBeautifier.prototype.formatFilePath = function (path, line) { + return '' + path + '' + } + +}(window.jQuery) \ No newline at end of file diff --git a/modules/system/controllers/EventLogs.php b/modules/system/controllers/EventLogs.php index 99d1d5ae3..57a7fba03 100644 --- a/modules/system/controllers/EventLogs.php +++ b/modules/system/controllers/EventLogs.php @@ -1,5 +1,6 @@ listRefresh(); } + + + public function preview($id) + { + $this->addCss('/modules/system/assets/css/eventlogs/preview.css', 'core'); + $this->addJs('/modules/system/assets/js/eventlogs/preview.js', 'core'); + + if (in_array(App::environment(), ['dev', 'local'])) { + $this->addJs('/modules/system/assets/js/eventlogs/preview.links.js', 'core'); + } + + return $this->asExtension('FormController')->preview($id); + } } diff --git a/modules/system/lang/en/client.php b/modules/system/lang/en/client.php index f9f92346d..376b61630 100644 --- a/modules/system/lang/en/client.php +++ b/modules/system/lang/en/client.php @@ -72,6 +72,16 @@ return [ 'before_placeholder' => 'Before' ] ], + + 'eventlog' => [ + 'editor' => [ + 'title' => 'Select the source code editor to use', + 'description' => 'Your environnement must be configured to listen to one of these URL schemes.', + 'openWith' => 'Open with', + 'openAndRemember' => 'Open & Remember for this session', + 'open' => 'Open' + ] + ] ]; diff --git a/modules/system/lang/fr/client.php b/modules/system/lang/fr/client.php index ef37d6a43..2c7324c79 100644 --- a/modules/system/lang/fr/client.php +++ b/modules/system/lang/fr/client.php @@ -75,6 +75,16 @@ return [ 'before_placeholder' => 'Avant le', ] ], + + 'eventlog' => [ + 'editor' => [ + 'title' => 'Sélectionnez l’éditeur de code source à utiliser', + 'description' => 'Votre environnement doit être configuré pour ouvrir l’un des schéma d’URL ci-dessous.', + 'openWith' => 'Ouvrir avec', + 'openAndRemember' => 'Ouvrir et enregistrer pour la session', + 'open' => 'Ouvrir' + ] + ] ]; diff --git a/modules/system/models/eventlog/fields.yaml b/modules/system/models/eventlog/fields.yaml index bc9c06114..44ac077bc 100644 --- a/modules/system/models/eventlog/fields.yaml +++ b/modules/system/models/eventlog/fields.yaml @@ -6,3 +6,5 @@ fields: message: type: textarea + containerAttributes: + data-plugin: exception-beautifier