Skip to content

Commit

Permalink
Merge pull request #1595 from kbase/develop
Browse files Browse the repository at this point in the history
Merge for deployment of version 4.1.0
  • Loading branch information
briehl committed Feb 20, 2020
2 parents 660b595 + b6f1cdf commit b6d7e46
Show file tree
Hide file tree
Showing 12 changed files with 280 additions and 130 deletions.
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ The Narrative Interface allows users to craft KBase Narratives using a combinati

This is built on the Jupyter Notebook v6.0.2 (more notes will follow).

### Version 4.1.0
- Introduce Static Narratives, available under the Share menu. First release!

### Version 4.0.0
- Update various software packages
- Python to 3.6.9
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kbase-narrative",
"version": "4.0.0",
"version": "4.1.0",
"homepage": "https://kbase.us",
"dependencies": {
"bluebird": "3.4.7",
Expand Down
1 change: 1 addition & 0 deletions kbase-extension/ipython/profile_default/ipython_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Configuration file for ipython.

c = get_config()
c.Completer.use_jedi = False

#------------------------------------------------------------------------------
# InteractiveShellApp configuration
Expand Down
7 changes: 7 additions & 0 deletions kbase-extension/static/kbase/custom/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,13 @@ define([
nbUtils.encode_uri_components(path)));
};

// Patch the save widget to change the "Jupyter Notebook" part of the title to
// "KBase Narrative".
saveWidget.SaveWidget.prototype.update_document_title = function () {
var nbname = this.notebook.get_notebook_name();
document.title = nbname + ' - KBase Narrative';
}

// Patch the save widget to take in options at save time
saveWidget.SaveWidget.prototype.rename_notebook = function (options) {
Jupyter.narrative.getUserPermissions()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,33 @@
* A more global manager for all Narratives a user owns might be here later.
*/
define([
'jquery', // needed to set up Bootstrap popover
'bluebird',
'narrativeConfig',
'common/runtime',
'common/events',
'common/ui',
'util/display',
'util/timeFormat',
'kbase-generic-client-api',
'kb_common/html',
'kb_service/client/workspace',
'base/js/namespace',
'handlebars',
'text!kbase/templates/static_narrative.html'
],
function(
$,
Promise,
Config,
Runtime,
Events,
UI,
DisplayUtil,
TimeFormat,
GenericClient,
HTML,
Workspace,
Jupyter,
Handlebars,
StaticNarrativeTmpl
) {
'use strict';

let t=HTML.tag,
div=t('div'),
a=t('a'),
i=t('i'),
b=t('b'),
button=t('button'),
span=t('span');

class StaticNarrativesPanel {
/**
* Builds the widget. This doesn't actually do any rendering - it expects
Expand All @@ -55,10 +45,15 @@ function(
const runtime = Runtime.make();
const token = runtime.authToken();
this.workspaceId = runtime.workspaceId();
this.userId = runtime.userId();
this.serviceClient = new GenericClient(
Config.url('service_wizard'),
{token: token}
);
this.wsClient = new Workspace(
Config.url('workspace'),
{token: token}
);
}

/**
Expand All @@ -82,94 +77,54 @@ function(
* rendering. If any errors happen, an error panel gets rendered.
*/
render() {
let events = Events.make();
this.detach();
this.ui = UI.make({node: this.container});
const loadingDiv = DisplayUtil.loadingDiv();
this.container.appendChild(loadingDiv.div[0]);

return this.getStaticNarratives()
.then(info => {
this.container.innerHTML = this.renderNarrativeInfo(info) +
this.renderNewStatic(events);
events.attachEvents(this.container);
})
.catch(error => {
const docInfo = Jupyter.narrative.documentVersionInfo;
if (!docInfo) {
return Promise.try(() => {
this.container.innerHTML = '';
this.container.appendChild(this.renderError(error)[0]);
this.container.appendChild(this.renderError({
code: -1,
error: 'Narrative document info not found',
name: 'Narrative error',
message: 'Unable to find current Narrative version!'
}));
});
}

/**
* Renders the block containing static Narrative information, if available.
* This judges that by seeing if info.url is defined or not.
* This returns a DIV DOM element that can be put in place by the main
* renderer function.
* @param {Object} info - a data object, expected to contain the following:
* - version - the version of the narrative saved
* - url - the path to the static Narrative, based on the configured URL.
* - narr_saved - the time (ms since epoch) that the Narrative used to create a
* static narrative was saved.
* - static_saved - the time (ms since epoch) that the Static Narrative was saved.
*/
renderNarrativeInfo(info) {
let tmpl = Handlebars.compile(StaticNarrativeTmpl);
if (info.url) {
info.narr_saved = TimeFormat.prettyTimestamp(info.narr_saved);
info.static_saved = TimeFormat.prettyTimestamp(info.static_saved);
info.url = Config.url('static_narrative_root') + info.url;
}
console.log(info);
return tmpl(info);
}

/**
* Renders the panel that invites the user to create a new static narrative.
* This uses the documentVersionInfo available in the main Narrative object
* (maybe that should be in Runtime?).
* This returns a DIV DOM element that can be put in place by the main
* renderer function.
* @param {Object} events - the Events object used to add click events to the
* create static narrative button.
*/
renderNewStatic(events) {
let self = this;
const docInfo = Jupyter.narrative.documentVersionInfo;
if (!docInfo) {
throw {
code: -1,
error: docInfo,
name: 'Narrative error',
message: 'Unable to find current Narrative version!'
};
}
return div([
div(b('This is version ' + docInfo[4] + ' of this Narrative, and was saved ' + TimeFormat.prettyTimestamp(docInfo[3]))),
div(b('Make a new static narrative?')),
div([
button({
class: 'btn btn-sm btn-primary',
id: events.addEvent({
type: 'click',
handler: () => self.saveStaticNarrative()
})
}, [
span('Create new Static Narrative')
]),
span({
dataElement: 'saving-spinner',
class: 'hidden fa fa-spinner fa-pulse fa-ex fa-fw',
style: {
'margin-left': '0.5em'
}
})
])
]);
return Promise.all([
this.getStaticNarratives(),
this.getPermissionInfo()
]).spread((narrativeInfo, permissions) => {
const info = Object.assign(narrativeInfo, permissions);
if (info.url) {
info.narr_saved = TimeFormat.prettyTimestamp(info.narr_saved);
info.static_saved = TimeFormat.prettyTimestamp(info.static_saved);
info.url = Config.url('static_narrative_root') + info.url;
}
info.canMakeStatic = info.isAdmin && info.isPublic;
info.currentVersion = docInfo[4];
info.currentVersionSaved = TimeFormat.prettyTimestamp(docInfo[3]);
info.isCurrentVersion = info.currentVersion === info.version;
let tmpl = Handlebars.compile(StaticNarrativeTmpl);
this.container.innerHTML = tmpl(info);

let createBtn = this.hostNode.querySelector('button.btn-primary');
if (createBtn) {
createBtn.addEventListener('click', this.saveStaticNarrative.bind(this));
}
$(this.hostNode.querySelector('#kb-sn-help')).popover();
})
.catch(error => {
console.error(JSON.stringify(error));
this.container.innerHTML = '';
this.container.appendChild(this.renderError(error));
});
}

/**
* Renders an error panel if something bad occurs.
* Also dumps the error to console.error.
* @param {Object} error - the error to render. Often a JSON-RPC error from some
* KBase service, so it'll have a wacky format, like:
* {
Expand All @@ -192,7 +147,7 @@ function(
name: error.error.error.name
};
}
return DisplayUtil.createError('Static Narrative Error', error);
return DisplayUtil.createError('Static Narrative Error', error)[0];
}

/**
Expand All @@ -210,11 +165,31 @@ function(
.then(info => info[0])
.catch(error => {
this.detach();
this.container.appendChild(this.renderError(error)[0]);
this.container.appendChild(this.renderError(error));
throw error;
});
}

/**
* Returns permissions for the current user on this narrative (workspace).
* @returns {Promise} - resolves into an object with two boolean keys:
* isAdmin - true if user has admin (sharing) rights
* isPublic - true if the narrative has public access
*
* Note that errors will propagate and not be caught here.
*/
getPermissionInfo() {
return this.wsClient.get_permissions_mass({'workspaces': [{'id': this.workspaceId}]})
.then(perms => {
const perm = perms.perms[0];
return {
isAdmin: perm[this.userId] && perm[this.userId] === 'a',
isPublic: perm['*'] && perm['*'] === 'r'
};
})
.catch((error) => { return {isAdmin: false, isPublic: false} });
}

/**
* Calls out to the StaticNarrative service to create a new Static Narrative
* from the currently loaded Narrative document.
Expand All @@ -223,15 +198,15 @@ function(
saveStaticNarrative() {
const docInfo = Jupyter.narrative.documentVersionInfo;
const narrativeRef = docInfo[6] + '/' + docInfo[0] + '/' + docInfo[4];
this.ui.showElement('saving-spinner');
this.hostNode.querySelector('span[data-element="saving-spinner"]').classList.toggle('hidden');
return Promise.resolve(this.serviceClient.sync_call(
'StaticNarrative.create_static_narrative',
[{'narrative_ref': narrativeRef}])
)
.then(() => this.refresh() )
.catch(error => {
this.detach();
this.container.appendChild(this.renderError(error)[0]);
this.container.appendChild(this.renderError(error));
});
}
}
Expand Down
61 changes: 51 additions & 10 deletions kbase-extension/static/kbase/templates/static_narrative.html
Original file line number Diff line number Diff line change
@@ -1,20 +1,61 @@
<div style="display: flex; border-bottom: 1px solid #eee; margin-bottom: 0.5rem; padding-bottom: 0.5rem;">
{{#if url }}
<div>
<div><b>A static version of this Narrative exists.</b></div>
<div>Based on version {{ version }}</div>
<div>Originally saved {{{ narr_saved }}}</div>
<div>And made static {{{ static_saved }}}</div>
</div>
<div style="margin-left: auto">
<a target="_blank" title="Go to existing static Narrative" href="{{ url }}">
<i class="fa fa-external-link fa-2x"></i>
</a>
<div>
<a href="{{ url }}" target="_blank">
<b>Existing static Narrative</b>
<i class="fa fa-external-link"></i>
</a>
</div>
<div>
<ul>
<li>Made from version {{ version }}</li>
<li>Narrative was saved {{{ narr_saved }}}</li>
</ul>
</div>
</div>
{{else}}
<div>
<div><b>No static version exists for this Narrative yet!</b></div>
<div>You can create one by clicking below.</div>
</div>
{{/if}}
<div style="margin-left: auto">
<a tabindex="0"
role="button" id="kb-sn-help" data-placement="bottom" data-trigger="focus"
data-toggle="popover" title="Static Narrative"
data-content="A static Narrative is a publicly viewable, non-interactive version of your Narrative. It has all the same content, and you can view the results of Apps, but cannot run any more.">
<i class="fa fa-question-circle fa-2x"></i>
</a>
</div>
</div>
{{#if canMakeStatic}}
<div>
<div style="margin-bottom: 2rem">
<b>This is version {{currentVersion}} of this Narrative and was saved {{{currentVersionSaved}}}</b>
</div>
{{#if isCurrentVersion}}
<div>No recent changes have been made to the Narrative, but you can still make a new Static Narrative.</div>
{{/if}}
<div>
<button class="btn btn-sm btn-primary">Create static narrative</button>
<span data-element="saving-spinner" class="hidden fa fa-spinner fa-pulse fa-ex fa-fw" style="margin-left: 0.5rem"></span>
</div>
</div>
{{else}}
<div class="alert alert-danger">
{{#unless isAdmin}}
<div>
<strong>Not an admin</strong>
<br>
You must have sharing rights to create a static Narrative.
</div>
{{/unless}}
{{#unless isPublic}}
<div {{#unless isAdmin}}style="padding-top: 0.5rem"{{/unless}}>
<strong>Not public</strong>
<br>
Narratives must be publicly viewable to be made static.
</div>
{{/unless}}
</div>
{{/if}}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "kbase-narrative-core",
"description": "Core components for the KBase Narrative Interface",
"version": "4.0.0",
"version": "4.1.0",
"private": true,
"repository": "github.com/kbase/narrative",
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/biokbase/narrative/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
__all__ = ['magics', 'common', 'handlers', 'contents', 'services', 'widgetmanager']

from semantic_version import Version
__version__ = Version("4.0.0")
__version__ = Version("4.1.0")
version = lambda: __version__

# if run directly:
Expand Down
Loading

0 comments on commit b6d7e46

Please sign in to comment.