Modifies Contents API to return Error objects

Modfies the Contents class to return JavaScript Error objects instead of passing on the return values from $.ajax().  This has two advantages.  First, it allows the content manager to parse errors and give more informative messages than the ajax response.  Second, it makes the Contents interface more general, since other kinds of backends might generate client-side errors.
Kester Tong 12 years ago committed by Thomas Kluyver
parent e1cbaf3d3c
commit 89df330129

@ -563,6 +563,22 @@ define([
);
};
/** Error type for wrapped XHR errors. */
var XHR_ERROR = 'XhrError';
/**
* Wraps an AJAX error as an Error object.
*/
var wrap_ajax_error = function (jqXHR, status, error) {
var wrapped_error = new Error(ajax_error_msg(jqXHR));
wrapped_error.name = XHR_ERROR;
// provide xhr response
wrapped_error.xhr = jqXHR;
wrapped_error.xhr_status = status;
wrapped_error.xhr_error = error;
return wrapped_error;
}
var utils = {
regex_split : regex_split,
uuid : uuid,
@ -588,6 +604,8 @@ define([
ajax_error_msg : ajax_error_msg,
log_ajax_error : log_ajax_error,
requireCodeMirrorMode : requireCodeMirrorMode,
XHR_ERROR : XHR_ERROR,
wrap_ajax_error : wrap_ajax_error
};
// Backwards compatability.

@ -91,23 +91,17 @@ define([
// notebook's path.
that.contents.new_notebook(that.notebook.notebook_path,
{
success: function (data, status, xhr) {
success: function (data) {
window.open(
utils.url_join_encode(
that.base_url, 'notebooks',
data.path, data.name
), '_blank');
},
error: function(xhr, status, error) {
var msg;
if (xhr.responseJSON && xhr.responseJSON.message) {
msg = xhr.responseJSON.message;
} else {
msg = xhr.statusText;
}
error: function(error) {
dialog.modal({
title : 'Creating Notebook Failed',
body : "The error was: " + msg,
body : "The error was: " + error.message,
buttons : {'OK' : {'class' : 'btn-primary'}}
});
}

@ -1923,7 +1923,7 @@ define([
this.contents.save_file(this.notebook_path, this.notebook_name, model, {
extra_settings: extra_settings,
success: $.proxy(this.save_notebook_success, this, start),
error: function (xhr, status, error) {
error: function (error) {
that.events.trigger('notebook_save_failed.Notebook');
}
});
@ -1935,10 +1935,8 @@ define([
* @method save_notebook_success
* @param {Integer} start Time when the save request start
* @param {Object} data JSON representation of a notebook
* @param {String} status Description of response status
* @param {jqXHR} xhr jQuery Ajax object
*/
Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
Notebook.prototype.save_notebook_success = function (start, data) {
this.set_dirty(false);
if (data.message) {
// save succeeded, but validation failed.
@ -2078,7 +2076,7 @@ define([
var that = this;
this.contents.rename_file(this.notebook_path, this.notebook_name,
this.notebook_path, new_name, {
success: function (json, status, xhr) {
success: function (json) {
var name = that.notebook_name = json.name;
that.session.rename_notebook(name, json.path);
that.events.trigger('notebook_renamed.Notebook', json);
@ -2091,12 +2089,12 @@ define([
this.contents.delete_file(this.notebook_name, this.notebook_path);
};
Notebook.prototype.rename_error = function (xhr, status, error) {
Notebook.prototype.rename_error = function (error) {
var that = this;
var dialog_body = $('<div/>').append(
$("<p/>").text('This notebook name already exists.')
);
this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
this.events.trigger('notebook_rename_failed.Notebook', error);
dialog.modal({
notebook: this,
keyboard_manager: this.keyboard_manager,
@ -2146,10 +2144,8 @@ define([
*
* @method load_notebook_success
* @param {Object} data JSON representation of a notebook
* @param {String} status Description of response status
* @param {jqXHR} xhr jQuery Ajax object
*/
Notebook.prototype.load_notebook_success = function (data, status, xhr) {
Notebook.prototype.load_notebook_success = function (data) {
var failed;
try {
this.fromJSON(data);
@ -2281,20 +2277,18 @@ define([
* Failure callback for loading a notebook from the server.
*
* @method load_notebook_error
* @param {jqXHR} xhr jQuery Ajax object
* @param {String} status Description of response status
* @param {String} error HTTP error message
*/
Notebook.prototype.load_notebook_error = function (xhr, status, error) {
this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
utils.log_ajax_error(xhr, status, error);
var msg = $("<div>");
if (xhr.status === 400) {
msg.text(utils.ajax_error_msg(xhr));
} else if (xhr.status === 500) {
msg.text("An unknown error occurred while loading this notebook. " +
* @param {Error} error
*/
Notebook.prototype.load_notebook_error = function (error) {
this.events.trigger('notebook_load_failed.Notebook', error);
var msg;
if (error.name = utils.XHR_ERROR && error.xhr.status === 500) {
utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
msg = "An unknown error occurred while loading this notebook. " +
"This version can load notebook formats " +
"v" + this.nbformat + " or earlier. See the server log for details.");
"v" + this.nbformat + " or earlier. See the server log for details.";
} else {
msg = error.message;
}
dialog.modal({
notebook: this,
@ -2350,7 +2344,7 @@ define([
var that = this;
this.contents.list_checkpoints(this.notebook_path, this.notebook_name, {
success: $.proxy(this.list_checkpoints_success, this),
error: function(xhr, status, error_msg) {
error: function(error) {
that.events.trigger('list_checkpoints_failed.Notebook');
}
});
@ -2361,10 +2355,8 @@ define([
*
* @method list_checkpoint_success
* @param {Object} data JSON representation of a checkpoint
* @param {String} status Description of response status
* @param {jqXHR} xhr jQuery Ajax object
*/
Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
Notebook.prototype.list_checkpoints_success = function (data) {
data = $.parseJSON(data);
this.checkpoints = data;
if (data.length) {
@ -2384,7 +2376,7 @@ define([
var that = this;
this.contents.create_checkpoint(this.notebook_path, this.notebook_name, {
success: $.proxy(this.create_checkpoint_success, this),
error: function (xhr, status, error_msg) {
error: function (error) {
that.events.trigger('checkpoint_failed.Notebook');
}
});
@ -2395,10 +2387,8 @@ define([
*
* @method create_checkpoint_success
* @param {Object} data JSON representation of a checkpoint
* @param {String} status Description of response status
* @param {jqXHR} xhr jQuery Ajax object
*/
Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
Notebook.prototype.create_checkpoint_success = function (data) {
data = $.parseJSON(data);
this.add_checkpoint(data);
this.events.trigger('checkpoint_created.Notebook', data);
@ -2457,7 +2447,7 @@ define([
this.contents.restore_checkpoint(this.notebook_path, this.notebook_name,
checkpoint, {
success: $.proxy(this.create_checkpoint_success, this),
error: function (xhr, status, error_msg) {
error: function (error) {
that.events.trigger('checkpoint_restore_failed.Notebook');
}
});
@ -2467,11 +2457,8 @@ define([
* Success callback for restoring a notebook to a checkpoint.
*
* @method restore_checkpoint_success
* @param {Object} data (ignored, should be empty)
* @param {String} status Description of response status
* @param {jqXHR} xhr jQuery Ajax object
*/
Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
Notebook.prototype.restore_checkpoint_success = function () {
this.events.trigger('checkpoint_restored.Notebook');
this.load_notebook(this.notebook_name, this.notebook_path);
};
@ -2488,8 +2475,8 @@ define([
this.contents.delete_checkpoint(this.notebook_path, this.notebook_name,
checkpoint, {
success: $.proxy(this.create_checkpoint_success, this),
error: function (xhr, status, error_msg) {
that.events.trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]);
error: function (error) {
that.events.trigger('checkpoint_delete_failed.Notebook', error);
}
});
};
@ -2498,12 +2485,9 @@ define([
* Success callback for deleting a notebook checkpoint
*
* @method delete_checkpoint_success
* @param {Object} data (ignored, should be empty)
* @param {String} status Description of response status
* @param {jqXHR} xhr jQuery Ajax object
*/
Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
this.events.trigger('checkpoint_deleted.Notebook', data);
Notebook.prototype.delete_checkpoint_success = function () {
this.events.trigger('checkpoint_deleted.Notebook');
this.load_notebook(this.notebook_name, this.notebook_path);
};

@ -21,12 +21,47 @@ define([
this.base_url = options.base_url;
};
/** Error type */
Contents.DIRECTORY_NOT_EMPTY_ERROR = 'DirectoryNotEmptyError';
Contents.DirectoryNotEmptyError = function() {
// Constructor
//
// An error representing the result of attempting to delete a non-empty
// directory.
this.message = 'A directory must be empty before being deleted.';
}
Contents.DirectoryNotEmptyError.prototype = new Error;
Contents.DirectoryNotEmptyError.prototype.name =
Contents.DIRECTORY_NOTE_EMPTY_ERROR;
Contents.prototype.api_url = function() {
var url_parts = [this.base_url, 'api/contents'].concat(
Array.prototype.slice.apply(arguments));
return utils.url_join_encode.apply(null, url_parts);
};
/**
* Creates a basic error handler that wraps a jqXHR error as an Error.
*
* Takes a callback that accepts an Error, and returns a callback that can
* be passed directly to $.ajax, which will wrap the error from jQuery
* as an Error, and pass that to the original callback.
*
* @method create_basic_error_handler
* @param{Function} callback
* @return{Function}
*/
Contents.prototype.create_basic_error_handler = function(callback) {
if (!callback) {
return function(xhr, status, error) { };
}
return function(xhr, status, error) {
callback(utils.wrap_ajax_error(xhr, status, error));
};
}
/**
* File Functions (including notebook operations)
*/
@ -50,7 +85,7 @@ define([
type : "GET",
dataType : "json",
success : options.success,
error : options.error || function() {}
error : this.create_basic_error_handler(options.error)
};
var url = this.api_url(path, name);
$.ajax(url, settings);
@ -71,7 +106,7 @@ define([
type : "POST",
dataType : "json",
success : options.success || function() {},
error : options.error || function() {}
error : this.create_basic_error_handler(options.error)
};
$.ajax(this.api_url(path), settings);
};
@ -86,8 +121,12 @@ define([
dataType : "json",
success : options.success || function() {},
error : function(xhr, status, error) {
utils.log_ajax_error(xhr, status, error);
error(xhr, status, error);
// TODO: update IPEP27 to specify errors more precisely, so
// that error types can be detected here with certainty.
if (xhr.status === 400) {
error(new Contents.DirectoryNotEmptyError());
}
error(utils.wrap_ajax_error(xhr, status, error));
}
};
var url = this.api_url(path, name);
@ -103,8 +142,8 @@ define([
data : JSON.stringify(data),
dataType: "json",
contentType: 'application/json',
success : options.success || function() {},
error : options.error || function() {}
success : options.success || function() {},
error : this.create_basic_error_handler(options.error)
};
var url = this.api_url(path, name);
$.ajax(url, settings);
@ -119,7 +158,7 @@ define([
data : JSON.stringify(model),
contentType: 'application/json',
success : options.success || function() {},
error : options.error || function() {}
error : this.create_basic_error_handler(options.error)
};
if (options.extra_settings) {
$.extend(settings, options.extra_settings);
@ -137,7 +176,7 @@ define([
var settings = {
type : "POST",
success: options.success || function() {},
error: options.error || function() {}
error : this.create_basic_error_handler(options.error)
};
$.ajax(url, settings);
};
@ -147,7 +186,7 @@ define([
var settings = {
type : "GET",
success: options.success,
error: options.error || function() {}
error : this.create_basic_error_handler(options.error)
};
$.ajax(url, settings);
};
@ -157,17 +196,17 @@ define([
var settings = {
type : "POST",
success: options.success || function() {},
error: options.error || function() {}
error : this.create_basic_error_handler(options.error)
};
$.ajax(url, settings);
};
Contents.prototype.delete_checkpoint = function(path, name, checkpoint_id, options) {
Contents.prototype.delete_checkpoint = function(path, name, checkpoint_id, options) {
var url = this.api_url(path, name, 'checkpoints', checkpoint_id);
var settings = {
type : "DELETE",
success: options.success || function() {},
error: options.error || function() {}
error : this.create_basic_error_handler(options.error)
};
$.ajax(url, settings);
};
@ -199,7 +238,7 @@ define([
type : "GET",
dataType : "json",
success : options.success,
error : options.error || function() {}
error : this.create_basic_error_handler(options.error)
};
$.ajax(this.api_url(path), settings);
@ -209,4 +248,4 @@ define([
IPython.Contents = Contents;
return {'Contents': Contents};
});
});

@ -62,23 +62,17 @@ require([
$('#new_notebook').click(function (e) {
contents.new_notebook(common_options.notebook_path,
{
success: function (data, status, xhr) {
success: function (data) {
window.open(
utils.url_join_encode(
common_options.base_url, 'notebooks',
data.path, data.name
), '_blank');
},
error: function(xhr, status, error) {
var msg;
if (xhr.responseJSON && xhr.responseJSON.message) {
msg = xhr.responseJSON.message;
} else {
msg = xhr.statusText;
}
error: function(error) {
dialog.modal({
title : 'Creating Notebook Failed',
body : "The error was: " + msg,
body : "The error was: " + error.message,
buttons : {'OK' : {'class' : 'btn-primary'}}
});
}

@ -144,9 +144,8 @@ define([
var that = this
this.contents.list_contents(that.notebook_path, {
success: $.proxy(this.draw_notebook_list, this),
error: function(xhr, status, error) {
utils.log_ajax_error(xhr, status, error);
that.draw_notebook_list([], "Error connecting to server.");
error: function(error) {
that.draw_notebook_list([], "Server error: " + error.message);
}
});
};

Loading…
Cancel
Save