Skip to content

Commit

Permalink
Use uuid as key in object and add notification when published
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathan Curran committed May 18, 2018
1 parent d0c54ca commit c643736
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 75 deletions.
5 changes: 4 additions & 1 deletion rsconnect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ def post(self, action):
except RSConnectException as exc:
raise web.HTTPError(400, exc.args[0])

self.finish(json.dumps({'app_id': published_app['id']}))
self.finish(json.dumps({
'app_id': published_app['id'],
'url': published_app['url']
}))
return


Expand Down
30 changes: 16 additions & 14 deletions rsconnect/rsconnect.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,14 @@ def response(self):
def json_response(self):
response = self.conn.getresponse()
raw = response.read()
if response.status >= 400:
try:
data = json.loads(raw)
raise RSConnectException(data['error'])
except:
raise RSConnectException('Unexpected response code: %d' % (response.status))
data = json.loads(raw)
return data
if response.status >= 500:
raise RSConnectException('Unexpected response code: %d' % (response.status))
elif response.status >= 400:
data = json.loads(raw)
raise RSConnectException(data['error'])
else:
data = json.loads(raw)
return data

def whoami(self):
self.conn.request('GET', '/__api__/me')
Expand Down Expand Up @@ -156,16 +156,18 @@ def mk_manifest(file_name):


def deploy(scheme, host, port, api_key, app_id, app_name, tarball):
# TODO deal with app_id?
with RSConnect(scheme, host, api_key, port) as api:
app = api.app_find(app_name)

if app is None:
app = api.app_create(app_name)
if app_id is None:
app = api.app_find(app_name)
if app is None:
app = api.app_create(app_name)
else:
app = {'id': app_id}

app_bundle = api.app_upload(app['id'], tarball)
task = api.app_deploy(app['id'], app_bundle['id'])

# 10 minute timeout
timeout = 600
def task_is_finished(task_id):
return api.task_get(task_id)['finished']
Expand All @@ -178,4 +180,4 @@ def task_is_finished(task_id):
return api.app_publish(app['id'], 'acl')
else:
# app failed to deploy
print('Unsuccessful deployment :(')
raise RSConnectException('Failed to deploy successfully')
119 changes: 59 additions & 60 deletions rsconnect/static/connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,15 @@ define([
function RSConnect(c) {
/* sample value of `Jupyter.notebook.metadata`:
{ previousServerId: "abc-def-ghi-jkl"
servers: [
{id: "xyz-uvw", server: "http://172.0.0.3:3939/", serverName: "dev"}
{id: "rst-opq", server: "http://somewhere/connect/", serverName: "prod", notebookTitle:"Meow", appId: 42}
]
servers: {
"xyz-uvw": { server: "http://172.0.0.3:3939/", serverName: "dev" }
"rst-opq": { server: "http://somewhere/connect/", serverName: "prod", notebookTitle:"Meow", appId: 42 }
}
}
*/

this.previousServerId = null;
this.servers = [];
this.servers = {};

// TODO more rigorous checking?
var metadata = JSON.parse(JSON.stringify(Jupyter.notebook.metadata));
Expand All @@ -78,21 +78,22 @@ define([
save: function() {
var result = $.Deferred();
var self = this;
// overwrite metadata (user may have changed it)
Jupyter.notebook.metadata.rsconnect = {
previousServerId: self.previousServerId,
servers: self.servers
};

// save_notebook returns a native Promise while the rest of
// the code including parts of Jupyter return jQuery.Deferred
Jupyter.notebook
.save_notebook()
.then(function() {
// notebook is writable
// overwrite metadata (user may have changed it)
Jupyter.notebook.metadata.rsconnect = {
previousServerId: self.previousServerId,
servers: self.servers
};

result.resolve();
})
["catch"](function(e) {
debug.error(e);
// notebook is read-only (server details will likely not be persisted)
result.resolve();
});
Expand All @@ -117,11 +118,10 @@ define([
// verify the server exists, then save
return this.verifyServer(server)
.then(function() {
self.servers.push({
id: id,
self.servers[id] = {
server: server,
serverName: serverName
});
};
return self.save();
})
.then(function() {
Expand All @@ -130,21 +130,13 @@ define([
},

updateServer: function(id, appId, notebookTitle) {
var idx = this.servers.findIndex(function(s) {
return s.id === id;
});
if (idx < 0) {
return $.Deferred().reject("Server (" + id + ") not found");
}
this.servers[idx].appId = appId;
this.servers[idx].notebookTitle = notebookTitle;
this.servers[id].appId = appId;
this.servers[id].notebookTitle = notebookTitle;
return this.save();
},

removeServer: function(id) {
this.servers = this.servers.filter(function(s) {
return s.id !== id;
});
delete this.servers[id];
return this.save();
},

Expand All @@ -154,12 +146,7 @@ define([
Jupyter.notebook.notebook_path
);

var server = this.servers.find(function(s) {
return s.id === id;
});
if (!server) {
return new $.Deferred().reject("Server (" + id + ") not found");
}
var entry = this.servers[id];

var xhr = Utils.ajax({
url: "/rsconnect_jupyter/deploy",
Expand All @@ -168,27 +155,42 @@ define([
data: JSON.stringify({
notebook_path: notebookPath,
notebook_title: notebookTitle,
app_id: server.appId,
server_address: server.server,
app_id: entry.appId,
server_address: entry.server,
api_key: apiKey
})
});

// update server with title and appId and set recently selected
// server
xhr.then(function(result) {
self.previousServerId = server.id;
notify.set_message(
" Successfully published content",
// timeout in milliseconds after which the notification
// should disappear
15 * 1000,
// click handler
function() {
window.open(result.url, "rsconnect");
},
// options
{
class: "info",
icon: "fa fa-link",
// tooltip
title: "Click to open published content on RStudio Connect"
}
);
self.previousServerId = id;
return self.updateServer(id, result.app_id, notebookTitle);
});

return xhr;
},

getNotebookTitle: function(entry) {
if (entry) {
var e = this.servers.find(function(s) {
return s.id === entry.id;
});
getNotebookTitle: function(id) {
if (id) {
var e = this.servers[id];
// if title was saved then return it
if (e.notebookTitle) {
return e.notebookTitle;
Expand Down Expand Up @@ -394,13 +396,13 @@ define([
return dialogResult;
}

function showSelectServerDialog(id) {
function showSelectServerDialog(serverId) {
var dialogResult = $.Deferred();
var selectedEntry = null;
var selectedEntryId = null;
// will be set during modal initialization
var btnPublish = null;

function mkServerItem(server, active) {
function mkServerItem(id, active) {
var btnRemove = $("<button></button>")
.addClass("pull-right btn btn-danger btn-xs")
.attr("type", "button")
Expand All @@ -410,14 +412,14 @@ define([

const $a = $(this).closest("a");
config
.removeServer(server.id)
.removeServer(id)
.then(function() {
$a.remove();
// if active server is removed, disable publish button
if ($a.hasClass("active")) {
btnPublish.addClass("disabled");
selectedEntryId = null;
}
selectedEntry = null;
})
.fail(function(err) {
// highly unlikely this will ever be triggered
Expand All @@ -426,12 +428,12 @@ define([
});
var title = $("<small></small>")
.addClass("rsc-text-light")
.text("— " + server.server);
.text("— " + config.servers[id].server);
var a = $("<a></a>")
.addClass("list-group-item")
.toggleClass("active", active)
.attr("href", "#")
.text(server.serverName)
.text(config.servers[id].serverName)
.append(title)
.append(btnRemove)
.on("click", function() {
Expand All @@ -444,10 +446,10 @@ define([
// toggle publish button disable state based on whether
// there is a selected server
if ($this.hasClass("active")) {
selectedEntry = server;
selectedEntryId = id;
btnPublish.removeClass("disabled");
} else {
selectedEntry = null;
selectedEntryId = null;
btnPublish.addClass("disabled");
}
});
Expand Down Expand Up @@ -510,22 +512,19 @@ define([
// add server button
publishModal.find("#rsc-add-server").on("click", function() {
publishModal.modal("hide");
showAddServerDialog(true, (selectedEntry || {}).id);
showAddServerDialog(true, selectedEntryId);
});

// generate server list
var serverItems = config.servers.map(function(s) {
var matchingServer = s.id === id;
if (matchingServer) {
selectedEntry = s;
}
return mkServerItem(s, matchingServer);
var serverItems = Object.keys(config.servers).map(function(id) {
var matchingServer = serverId === id;
return mkServerItem(id, matchingServer);
});
publishModal.find(".list-group").append(serverItems);

// add default title
txtTitle = publishModal.find("[name=title]");
txtTitle.val(config.getNotebookTitle(selectedEntry));
txtTitle.val(config.getNotebookTitle(selectedEntryId));

txtApiKey = publishModal.find("[name=api-key]");

Expand All @@ -548,14 +547,14 @@ define([
"Title must be between 3 and 64 alphanumeric characters, dashes, and underscores."
);

if (selectedEntry !== null && validApiKey && validTitle) {
if (selectedEntryId !== null && validApiKey && validTitle) {
btnPublish
.addClass("disabled")
.find("i.fa")
.removeClass("hidden");

config
.publishContent(selectedEntry.id, txtApiKey.val(), txtTitle.val())
.publishContent(selectedEntryId, txtApiKey.val(), txtTitle.val())
.then(function() {
publishModal.modal("hide");
})
Expand All @@ -581,7 +580,7 @@ define([
btnPublish = $(
'<a class="btn btn-primary" aria-hidden="true"><i class="fa fa-spinner fa-spin hidden"></i> Publish</a>'
);
btnPublish.toggleClass("disabled", id === null);
btnPublish.toggleClass("disabled", serverId === null);
btnPublish.on("click", function() {
form.trigger("submit");
});
Expand All @@ -603,7 +602,7 @@ define([
window.RSConnect = config;
}

if (config.servers.length === 0) {
if (Object.keys(config.servers).length === 0) {
showAddServerDialog(false).then(showSelectServerDialog);
} else {
showSelectServerDialog(config.previousServerId);
Expand Down

0 comments on commit c643736

Please sign in to comment.