Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

alter skip-traceback to make traceback collapsed, rather than just omitted entirely #957

Merged
merged 3 commits into from
Apr 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 181 additions & 34 deletions src/jupyter_contrib_nbextensions/nbextensions/skip-traceback/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,197 @@ define([
'base/js/namespace',
'jquery',
'require',
'notebook/js/outputarea'
], function(IPython, $, require, outputarea) {
'notebook/js/outputarea',
], function (
Jupyter,
$,
require,
outputarea
) {
"use strict";

var original_append_error = outputarea.OutputArea.prototype.append_error;

/*
* Display only error type and message instead of complete traceback
*/
var new_append_error = function (json) {
var tb = json.ename;
if (tb !== undefined && tb.length > 0) {
var toinsert = this.create_output_area();
var append_text = outputarea.OutputArea.append_map['text/plain'];
if (append_text) {
var s = '' + tb + ': ' + json.evalue;
append_text.apply(this, [s, {}, toinsert]).addClass('output_prompt');
var mod_name = 'skip-traceback';
var log_prefix = '[' + mod_name + ']';

var cfg = {
enable: true,
use_toolbar_button: false,
button_icon: 'fa-warning',
animation_duration: 100,
show_copy_buttons: true,
};

// this will be filled as they're registered
var actions = {};

var apply_patches = function () {

outputarea.OutputArea.prototype.append_error = function (json) {
// firts part is just a copy of the original, but keeping a reference to inserted_text
var inserted_text;
var tb = json.traceback;
if (tb !== undefined && tb.length > 0) {
var s = '';
var len = tb.length;
for (var i = 0; i < len; i++) {
s = s + tb[i] + '\n';
}
s = s + '\n';
var toinsert = this.create_output_area();
var append_text = outputarea.OutputArea.append_map['text/plain'];
if (append_text) {
inserted_text = append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
}
this._safe_append(toinsert);

// now we add our header at the top of the inserted_text
if (!inserted_text) {
return;
}
var copy_btn = $('<i class="fa fa-fw fa-copy" title="Copy full traceback to clipboard"/>')
.on('click', function (evt) {
// prevent event bubbling up to collapse/uncollapse traceback
evt.stopPropagation();
var $copy_btn = $(this)
.tooltip({track: false, trigger: 'manual', placement: 'bottom'});
// create temporary off-screen textarea for copying text
var $copy_txt_area = $('<textarea>')
.css({position: 'absolute', left: '-10000px', top: '-10000px'})
.appendTo('body');
// remember this for later
var was_focussed = document.activeElement;
var msg = 'Failed to copy traceback to clipboard';
try {
$copy_txt_area[0].value = $copy_btn.closest('.skip-traceback-summary').siblings().text();
$copy_txt_area[0].select();
var successful = document.execCommand('copy');
if (successful) {
msg = 'Copied traceback to clipboard!';
console.log(log_prefix, msg);
}
else {
console.warn(log_prefix, msg);
}
}
catch (err) {
console.warn(log_prefix, msg + ':', err);
}
finally {
$copy_txt_area.remove();
was_focussed.focus();
// this tooltip bit relies on jqueryui tooltip, but
// it may have been overwritten by bootstrap tooltip (if loaded).
try {
$copy_btn
.tooltip('option', 'content', msg)
.tooltip('open');
setTimeout(function () {
$copy_btn.tooltip('disable');
}, 1000);
}
catch (err) {
console.warn(log_prefix, err);
}
}
});
var sum = $('<pre/>')
.addClass('skip-traceback-summary')
.css('cursor', 'pointer')
.text(': ' + json.evalue + ' ')
.prepend($('<span class=ansired/>').text(json.ename));

if (cfg.show_copy_buttons) {
sum.prepend(' ').prepend(copy_btn);
}
sum
.append('<i class="fa fa-caret-right" title="Expand traceback"/>')
.append('\n')
.on('click', function (evt) {
var summary = $(this);
var icon = summary.find('.fa-caret-right,.fa-caret-down');
var show = icon.hasClass('fa-caret-right');
icon
.toggleClass('fa-caret-down', show)
.toggleClass('fa-caret-right', !show)
.attr('title', show ? 'Collapse traceback' : 'Expand traceback');
summary.siblings()[show ? 'slideDown' : 'slideUp'](cfg.animation_duration || 100);
})
.prependTo(inserted_text);
if (cfg.enable) {
sum.siblings().css('display', 'none');
}
else {
sum.css('display', 'none');
}
}
this._safe_append(toinsert);
};
};

var toggle_traceback = function (set_on) {
if (set_on === undefined) {
set_on = !cfg.enable;
}
// update config
if (set_on !== cfg.enable) {
cfg.enable = set_on;
var conf_update = {};
conf_update[mod_name] = {enable: set_on};
Jupyter.notebook.config.update(conf_update);
console.log(log_prefix, 'toggled', set_on ? 'on' : 'off');
}
// update button looks
$('#toggle_traceback_btns > .btn').toggleClass('active', set_on).blur();
// update existing OutputAreas
$('.cell .output_area .output_error .skip-traceback-summary').each(function (idx, el) {
var $summary = $(el);
$summary.css('display', set_on ? '' : 'none');
if ($summary.find('.fa').hasClass(set_on ? 'fa-caret-down' : 'fa-caret-right')) {
$summary.click();
}
});
};

var register_new_actions = function () {
actions.toggle = {
help : 'Toggle Hiding Traceback',
help_index: 'zz',
icon : cfg.button_icon || 'fa-warning',
handler : function (env) { toggle_traceback(); },
};
actions.toggle.name = Jupyter.keyboard_manager.actions.register(
actions.toggle, 'toggle', mod_name);
};

var toggle_traceback = function() {
if ($('#toggle_traceback').hasClass('active')) {
$('#toggle_traceback').removeClass('active').blur();
outputarea.OutputArea.prototype.append_error = new_append_error;
} else {
$('#toggle_traceback').addClass('active').blur();
outputarea.OutputArea.prototype.append_error = original_append_error;
var add_toolbar_button = function () {
if (cfg.use_toolbar_button) {
Jupyter.toolbar.add_buttons_group([actions.toggle.name], 'toggle_traceback_btns');
}
};

var load_ipython_extension = function() {
outputarea.OutputArea.prototype.append_error = new_append_error;
IPython.toolbar.add_buttons_group([
{
id: 'toggle_traceback',
label: 'Toggle Hiding Traceback',
icon: 'fa-warning',
callback: function () {
toggle_traceback();
var load_ipython_extension = function () {
apply_patches();

Jupyter.notebook.config.loaded
.then(function () {
$.extend(true, cfg, Jupyter.notebook.config.data[mod_name]);
register_new_actions();
add_toolbar_button();
toggle_traceback(cfg.enable);
// update any OutputArea created before our patch)
$('.cell .output_area .output_error').each(function (idx, el) {
var $el = $(el);
if ($el.children('.skip-traceback-summary').length < 1) {
var oa = $el.closest('.cell').data('cell').output_area;
var json_outputs = oa.toJSON();
// clear; do not wait, ignore queue
oa.clear_output(false, true);
oa.fromJSON(json_outputs);
}
}
]);
});
})
.catch(function (reason) {
console.error(log_prefix, 'Error loading:', reason);
});
};

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,80 @@
Skip traceback
==============
This extension hides Python tracebacks and only displays the error type an name.

This nbextension hides error tracebacks, displaying instead a summary of the
error name and type. Clicking the summary displays the whole traceback.


Example
-------
With extension enabled:

![](skip-traceback.png)
With extension disabled:
With normal traceback:

![](traceback.png)

After loading the extension, only newly executed cells are affected. Previous tracebacks will remain visible until the
corresponding cell is executed again.
With nbextension enabled:

![](skip-traceback.png)



Using the (optional) toolbar button, you can show or hide all tracebacks in the
notebook at once.


Options
-------

The nbextension provides a few options, the values of which are stored in the
notebook section of the nbconfig. The easiest way to configure these is using
the
[jupyter_nbextensions_configurator](https://github.com/Jupyter-contrib/jupyter_nbextensions_configurator)
serverextension, but you can also configure them directly with a few lines of
python.

The available options are:

* `skip-traceback.animation_duration` - duration (in milliseconds) of the
show/hide traceback animations. Defaults to `100`.

* `skip-traceback.button_icon` - a
[fontawesome](http://fontawesome.io/icons/)
class name, used for the action and toolbar button.
Defaults to `fa-warning`.

* `skip-traceback.show_copy_buttons` - add buttons to headings to copy the
full traceback to the clipboard. Defaults to `true`.

* `skip-traceback.use_toolbar_button` - add a button to the toolbar which can
be used to toggle on or off the contracted display of all cells' tracebacks.
Defaults to `false`.

* `skip-traceback.enable` - enable collapsing the tracebacks on loading the
nbextension. Defaults to `true`


For example, to set the animation time to half a second, and enable adding the
toolbar button, we can use the following python snippet:

```python
from notebook.services.config import ConfigManager
cm = ConfigManager()
cm.update("notebook", {"skip-traceback": {
"animation_duration": 500,
"use_toolbar_button": True,
}})
```

If you press the button on the toolbar with the exclamation mark, you can temporarily turn on tracebacks again.

Internals
---------

This extensions works by overriding the `OutputArea.prototype.append_error` function, replacing it with a new function
that only displays the error type and message.
This extensions works by overriding the `OutputArea.prototype.append_error`
function to add a header above the error text, which can be used to show or
hide the traceback.

On loading the extension, only outputs added to cells after the `append_error`
method has been patched are initially affected. In order to apply the collapse
to pre-existing outputs, the nbextension loops through existing uncollapsed
tracebacks, storing them to json, clearing them, then restoring them from the
saved json.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,7 +1,45 @@
Type: IPython Notebook Extension
Type: Jupyter Notebook Extension
Name: Skip-Traceback
Description: Don't display traceback, only error message
Description: |
Hide error tracebacks, displaying instead a summary of the error name and
type. Clicking the summary displays the whole traceback.
Link: readme.md
Icon: icon.png
Main: main.js
Compatibility: 4.x
Parameters:

- name: skip-traceback.animation_duration
description: |
duration (in milliseconds) of the show/hide traceback animations
input_type: number
min: 0
step: 10
default: 100

- name: skip-traceback.button_icon
description: |
a fontawesome (http://fontawesome.io/icons/) class name, used for the
action and toolbar button.
input_type: text
default: fa-warning

- name: skip-traceback.show_copy_buttons
description: |
Add buttons to headings to copy the full traceback to the clipboard
input_type: checkbox
default: true

- name: skip-traceback.enable
description: |
enable collapsing tracebacks on loading the nbextension
input_type: checkbox
default: true

- name: skip-traceback.use_toolbar_button
description: |
add a button to the toolbar which can be used to toggle on or off the
contracted display of all cells' tracebacks at once.
input_type: checkbox
default: false