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

Show logs while deploying #146

Merged
merged 5 commits into from
Jan 9, 2019
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
- Renamed plugin from `rsconnect` to `rsconnect_jupyter`
- Publish notebooks with source allowing them to be rendered on RStudio Connect.
`pip` and `virtualenv` are used to determine dependent packages.
- Deploy logs are shown while notebook is deployed.

# v1.0.1
- Ensure json is decoded as utf-8
Expand Down
10 changes: 10 additions & 0 deletions docs/guide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Connect](https://www.rstudio.com/products/connect/).
- Jupyter Notebook 5.x
- [pip](https://pypi.org/project/pip/)
- [wheel](https://pypi.org/project/wheel/)
- [RStudio Connect](https://www.rstudio.com/products/connect/download-commercial/) v1.7.0
or higher, configured with Python support.

If using `conda`, `pip` and `wheel` should already be installed.

Expand All @@ -34,6 +36,14 @@ virtualenv /my/path
source /my/path/bin/activate
```

Install Jupyter inside the `virtualenv`:
```bash
pip install jupyter
```

> Note: be sure to run Jupyter from the virtual environment, not from a global
> installation.

Install the `rsconnect-jupyter` package with the following command:

```bash
Expand Down
30 changes: 27 additions & 3 deletions rsconnect_jupyter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from notebook.utils import url_path_join
from tornado import web

from .api import app_get, app_search, deploy, verify_server, RSConnectException
from .api import app_config, app_get, app_search, deploy, task_get, verify_server, RSConnectException
from .bundle import make_html_bundle, make_source_bundle

__version__ = '1.0.0'
Expand Down Expand Up @@ -122,11 +122,11 @@ def post(self, action):
raise web.HTTPError(400, 'Invalid app_mode: %s, must be "static" or "jupyter-static"' % app_mode)

try:
published_app = deploy(uri, api_key, app_id, nb_name, nb_title, bundle)
retval = deploy(uri, api_key, app_id, nb_name, nb_title, bundle)
except RSConnectException as exc:
raise web.HTTPError(400, exc.message)

self.finish(published_app)
self.finish(retval)
return

if action == 'app_get':
Expand Down Expand Up @@ -159,6 +159,30 @@ def post(self, action):
self.set_secure_cookie('key_' + address_hash, api_key, expires_days=3650)
return

if action == 'get_log':
uri = urlparse(data['server_address'])
api_key = data['api_key']
task_id = data['task_id']
last_status = data['last_status']
try:
retval = task_get(uri, api_key, task_id, last_status)
except RSConnectException as exc:
raise web.HTTPError(400, exc.message)
self.finish(json.dumps(retval))
return

if action == 'app_config':
uri = urlparse(data['server_address'])
api_key = data['api_key']
app_id = data['app_id']
try:
retval = app_config(uri, api_key, app_id)
except RSConnectException as exc:
raise web.HTTPError(400, exc.message)
self.finish(json.dumps(retval))
return



def load_jupyter_server_extension(nb_app):
nb_app.log.info("rsconnect_jupyter enabled!")
Expand Down
27 changes: 10 additions & 17 deletions rsconnect_jupyter/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,27 +244,20 @@ def deploy(uri, api_key, app_id, app_name, app_title, tarball):

app_bundle = api.app_upload(app['id'], tarball)
task_id = api.app_deploy(app['id'], app_bundle['id'])['id']
return {
'task_id': task_id,
'app_id': app['id'],
}

# 10 minute timeout
timeout = 600
task = wait_for_task(api, task_id, timeout)

if task is None:
raise RSConnectException('Deployment timed out')
def task_get(uri, api_key, task_id, last_status):
with RSConnect(uri, api_key) as api:
return api.task_get(task_id, first_status=last_status)

if task['code'] != 0:
# app failed to deploy
err_msg = 'Failed to deploy successfully'
if 'error' in task:
err_msg += ': ' + task['error']
raise RSConnectException(err_msg)

# app deployed successfully
config = api.app_config(app['id'])
return {
'app_id': app['id'],
'config': config,
}
def app_config(uri, api_key, app_id):
with RSConnect(uri, api_key) as api:
return api.app_config(app_id)


APP_MODE_STATIC = 4
Expand Down
95 changes: 83 additions & 12 deletions rsconnect_jupyter/static/connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,14 @@ define([
var path = Jupyter.notebook.notebook_name;

try {
var cmd = ["!", Jupyter.notebook.kernel_selector.kernelspecs[Jupyter.notebook.kernel.name].spec.argv[0], " -m rsconnect_jupyter.environment ${PWD}/", path].join("");
var cmd = [
"!",
Jupyter.notebook.kernel_selector.kernelspecs[
Jupyter.notebook.kernel.name
].spec.argv[0],
" -m rsconnect_jupyter.environment ${PWD}/",
path
].join("");
console.log("executing: " + cmd);
} catch (e) {
return $.Deferred().reject(e);
Expand Down Expand Up @@ -296,6 +303,67 @@ define([

var entry = this.servers[serverId];

var $log = $("#rsc-log").attr("hidden", null);
$log.text("Deploying...\n");

function getLogs(deployResult) {
function inner(lastStatus) {
lastStatus = lastStatus || null;
return Utils.ajax({
url: Jupyter.notebook.base_url + "rsconnect_jupyter/get_log",
method: "POST",
headers: { "Content-Type": "application/json" },
data: JSON.stringify({
server_address: entry.server,
api_key: apiKey,
task_id: deployResult["task_id"],
last_status: lastStatus
})
}).then(function(result) {
if (result["last_status"] != lastStatus) {
lastStatus = result["lastStatus"];
var output = result["status"].join("\n");
$log.text(output);
// scroll to bottom
$log.scrollTop($log.get(0).scrollHeight);
}
if (result["finished"]) {
if (result["code"] != 0) {
return $.Deferred().reject(
"Failed to deploy successfully: " + result["error"]
);
}
debug.info("logs:", result["status"].join("\n"));
return $.Deferred().resolve(deployResult["app_id"]);
}
var next = $.Deferred();
setTimeout(function() {
return inner(lastStatus).then(next.resolve);
}, 1000);
return next;
});
}
return inner();
}

function appConfig(receivedAppId) {
return Utils.ajax({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Someday (not in this PR), utils should grow a postJson that factors out the base_url, method, content-type, and json.stringify.

url: Jupyter.notebook.base_url + "rsconnect_jupyter/app_config",
method: "POST",
headers: { "Content-Type": "application/json" },
data: JSON.stringify({
server_address: entry.server,
api_key: apiKey,
app_id: receivedAppId
})
}).then(function(config) {
return {
appId: receivedAppId,
config: config
};
});
}

function deploy(environment) {
var data = {
notebook_path: notebookPath,
Expand All @@ -313,18 +381,20 @@ define([
method: "POST",
headers: { "Content-Type": "application/json" },
data: JSON.stringify(data)
});
})
.then(getLogs)
.then(appConfig);

// update server with title and appId and set recently selected
// server
xhr.then(function(result) {
xhr.then(function(configResult) {
self.previousServerId = serverId;
return self.updateServer(
serverId,
result.app_id,
configResult.appId,
notebookTitle,
appMode,
result.config.config_url
configResult.config.config_url
);
});

Expand Down Expand Up @@ -763,18 +833,23 @@ define([
" <label>Publish Source Code</label>",
' <div class="list-group">',
' <a href="#" id="rsc-publish-with-source" class="list-group-item rsc-appmode" data-appmode="jupyter-static">',
' <img src="' + Jupyter.notebook.base_url + 'nbextensions/rsconnect_jupyter/images/publishDocWithSource.png" class="rsc-image">',
' <img src="' +
Jupyter.notebook.base_url +
'nbextensions/rsconnect_jupyter/images/publishDocWithSource.png" class="rsc-image">',
' <span class="rsc-label">Publish document with source code</span><br/>',
' <span class="rsc-text-light">Choose this option if you want to create a scheduled report or rebuild your document on the server</span>',
" </a>",
' <a href="#" id="rsc-publish-without-source" class="list-group-item rsc-appmode" data-appmode="static">',
' <img src="' + Jupyter.notebook.base_url + 'nbextensions/rsconnect_jupyter/images/publishDocWithoutSource.png" class="rsc-image">',
' <img src="' +
Jupyter.notebook.base_url +
'nbextensions/rsconnect_jupyter/images/publishDocWithoutSource.png" class="rsc-image">',
' <span class="rsc-label">Publish finished document only</span><br/>',
' <span class="rsc-text-light">Choose this option to publish a snapshot of the notebook as it appears in Jupyter</span>',
" </a>",
' <span class="help-block"></span>',
" </div>",
" </div>",
' <pre id="rsc-log" hidden></pre>',
' <div class="text-center" data-id="configUrl"></div>',
' <input type="submit" hidden>',
"</form>"
Expand Down Expand Up @@ -910,11 +985,7 @@ define([
}

function handleFailure(xhr) {
addValidationMarkup(
false,
txtTitle,
xhr.responseJSON.message
);
addValidationMarkup(false, txtTitle, xhr.responseJSON.message);
togglePublishButton(true);
}

Expand Down
7 changes: 7 additions & 0 deletions rsconnect_jupyter/static/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,10 @@
color: black;
padding-left: 10px;
}

#rsc-log {
height: 10rem;
margin-top: 1rem;
overflow-y: scroll;
overflow: break-word;
}