Skip to content

Commit

Permalink
Merge pull request #1365 from StochSS/develop
Browse files Browse the repository at this point in the history
Release 2.4.12
  • Loading branch information
seanebum authored Sep 26, 2022
2 parents ea29cde + dcd82e4 commit d5e83a9
Show file tree
Hide file tree
Showing 84 changed files with 1,971 additions and 375 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ stochss/dist/stochss-domain-editor.html
stochss/dist/stochss-loading-page.html
stochss/dist/stochss-quick-start.html
stochss/dist/stochss-user-home.html
stochss/dist/stochss-example-library.html
jupyterhub/templates/page.html
jupyterhub/templates/stochss-home.html
jupyterhub/templates/stochss-job-presentation.html
Expand Down
18 changes: 9 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -83,32 +83,32 @@ cert:
@echo "Generating certificate..."
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout $(SSL_KEY) -out $(SSL_CERT)

build_home_page:
npm run build-home
create_dep_dirs:
mkdir -p "./userlist"

build_hub: build_home_page check-files network volumes
build_hub: create_dep_dirs check-files network volumes
export AUTH_CLASS='' && export OAUTH_FILE='.oauth.dummy.env' && \
cd ./jupyterhub && docker-compose build
docker-compose --env-file jupyterhub/.env -f jupyterhub/docker-compose.yml build

build_hub_clean: build_home_page check-files network volumes
build_hub_clean: create_dep_dirs check-files network volumes
export AUTH_CLASS='' && export OAUTH_FILE='.oauth.dummy.env' && \
cd ./jupyterhub && docker-compose build --no-cache
docker-compose --env-file jupyterhub/.env -f jupyterhub/docker-compose.yml build --no-cache


run_hub_dev:
export AUTH_CLASS='jupyterhub.auth.DummyAuthenticator' && \
export OAUTH_FILE='.oauth.dummy.env' && \
cd ./jupyterhub && docker-compose up
cd jupyterhub && docker-compose up &

run_hub_staging: build_hub_clean build_clean check_files_staging kill_hub
export AUTH_CLASS=oauthenticator.GoogleOAuthenticator && \
export OAUTH_FILE='.oauth.staging.env' && \
cd ./jupyterhub && docker-compose up &
cd jupyterhub && docker-compose up &

run_hub_prod: build_hub_clean build_clean check_files_prod kill_hub
export AUTH_CLASS=oauthenticator.GoogleOAuthenticator && \
export OAUTH_FILE='.oauth.prod.env' && \
cd ./jupyterhub && docker-compose up &
cd jupyterhub && docker-compose up &

kill_hub:
export AUTH_CLASS='' && \
Expand Down
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,36 @@ StochSS uses [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/#) as the

- In addition to the single-user requirements, you will need [Docker Compose](https://docs.docker.com/compose/install/).

- [Optional] To set admins for JupyterHub, make a file called `userlist` in the `jupyterhub/` directory. On each line of this file place a username followed by the word 'admin'. For example: `myuser admin`. If using Google OAuth, the uesername will be a Gmail address. Navigate to `/hub/admin` to use the JupyterHub admin interface.
- [Optional] To set admins for JupyterHub, make a file called `userlist` in the `userlist/` directory. On each line of this file place a username followed by the word 'admin'. For example: `myuser admin`. If using Google OAuth, the username will be a Gmail address. Navigate to `/hub/admin` to use the JupyterHub admin interface.

- [Optional] By default multi-user StochSS is set up to allocate 2 logical cpus per user, reserving 2 logical cpus for the hub container and underlying OS. You can define "power users" that are excluded from resource limitations using the same method as above for adding an admin, but instead of following the username with 'admin', use the keyword 'power' instead.

- [Optional] To disseminate messages to all users, make a JSON decodable file called `messages.json` in the `userlist/` directory. The contents of the `messages` file should be formatted as a list of dictionaries defining each message. Accepted keys are `message`, string containing the message to be display with tags for dates and time i.e. `StochSS Live! will be down for scheduled maintenance on __DATE__ from __START__ to __END__` an additional `__DATE__` tag can be added if the start and end dates differ, `start`, string representing the starting date and time i.e. `Sep 26, 2022 14:00 EST`, `end`, string representing the ending date and time i.e. `Sep 26, 2022 18:00 EST`, and `style`, a string containing a background color keyword i.e. `warning` or css i.e. `background-color: rgba(160, 32, 240, 0.5) !important;`.

```python
[
{
"message": "StochSS Live! will be down for scheduled maintenance on __DATE__ from __START__ to __END__",
"start": "Sep 26, 2022 14:00 EST",
"end": "Sep 26, 2022 18:00 EST",
"style": "info"
},
{
"message": "StochSS Live! will be down for scheduled maintenance from"
},
{
"message": "__DATE__ __START__ to __DATE__ __END__",
"start": "Sep 26, 2022 14:00 EST",
"end": "Sep 27, 2022 14:00 EST",
"style": "warning"
},
{
"message": "StochSS Live! is down for scheduled maintenance",
"style": "background-color: rgba(160, 32, 240, 0.5) !important;"
}
]
```

### Run Locally

To run JupyterHub locally run `make hub` and go to `http://127.0.0.1:8000/` .
Expand Down
2 changes: 1 addition & 1 deletion __version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# @website https://github.com/stochss/stochss
# =============================================================================

__version__ = '2.4.10'
__version__ = '2.4.12'
__title__ = 'StochSS'
__description__ = 'StochSS is an integrated development environment (IDE) \
for simulation of biochemical networks.'
Expand Down
78 changes: 69 additions & 9 deletions client/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,41 +167,46 @@ let newWorkflow = (parent, mdlPath, isSpatial, type) => {
if(document.querySelector('#newWorkflowModal')) {
document.querySelector('#newWorkflowModal').remove()
}
let typeCodes = {
"Ensemble Simulation": "_ES",
"Spatial Ensemble Simulation": "_SES",
"Parameter Sweep": "_PS"
}
let self = parent;
let ext = isSpatial ? /.smdl/g : /.mdl/g
let typeCode = type === "Ensemble Simulation" ? "_ES" : "_PS";
let typeCode = typeCodes[type];
let name = mdlPath.split('/').pop().replace(ext, typeCode)
let modal = $(modals.createWorkflowHtml(name, type)).modal();
let okBtn = document.querySelector('#newWorkflowModal .ok-model-btn');
let input = document.querySelector('#newWorkflowModal #workflowNameInput');
okBtn.disabled = false;
input.addEventListener("keyup", function (event) {
input.addEventListener("keyup", (event) => {
if(event.keyCode === 13){
event.preventDefault();
okBtn.click();
}
});
input.addEventListener("input", function (e) {
input.addEventListener("input", (e) => {
let endErrMsg = document.querySelector('#newWorkflowModal #workflowNameInputEndCharError')
let charErrMsg = document.querySelector('#newWorkflowModal #workflowNameInputSpecCharError')
let error = validateName(input.value)
okBtn.disabled = error !== "" || input.value.trim() === ""
charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none"
endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none"
});
okBtn.addEventListener('click', function (e) {
okBtn.addEventListener('click', (e) => {
modal.modal("hide");
let wkflFile = input.value.trim() + ".wkfl";
let wkflFile = `${input.value.trim()}.wkfl`;
if(mdlPath.includes(".proj") && !mdlPath.includes(".wkgp")){
var wkflPath = path.join(path.dirname(mdlPath), "WorkflowGroup1.wkgp", wkflFile);
}else{
var wkflPath = path.join(path.dirname(mdlPath), wkflFile);
}
let queryString = "?path=" + wkflPath + "&model=" + mdlPath + "&type=" + type;
let queryString = `?path=${wkflPath}&model=${mdlPath}&type=${type}`;
let endpoint = path.join(getApiPath(), "workflow/new") + queryString;
getXHR(endpoint, {
success: function (err, response, body) {
window.location.href = path.join(getBasePath(), "stochss/workflow/edit") + "?path=" + body.path;
success: (err, response, body) => {
window.location.href = `${path.join(getBasePath(), "stochss/workflow/edit")}?path=${body.path}`;
}
});
});
Expand Down Expand Up @@ -246,6 +251,60 @@ let switchToEditTab = (view, section) => {
}
}

let maintenance = (view) => {
getXHR("stochss/api/message", {
always: (err, response, body) => {
if(body.messages.length === 0) { console.log(null) }
var html = ``;
body.messages.forEach((data) => {
let styles = {
"primary": "background-color: rgba(0, 123, 255, 0.5) !important;",
"secondary": "background-color: rgba(108, 117, 125, 0.5) !important;",
"light": "background-color: rgba(248, 249, 250, 0.5) !important;",
"dark": "background-color: rgba(52, 58, 64, 0.5) !important;",
"success": "background-color: rgba(40, 167, 69, 0.5) !important;",
"info": "background-color: rgba(23, 162, 184, 0.5) !important;",
"warning": "background-color: rgba(255, 193, 7, 0.5) !important;",
"danger": "background-color: rgba(220, 53, 69, 0.5) !important;"
}
if(data.start) {
let s_date = new Date(data.start);
let day = new Intl.DateTimeFormat('en-US', {weekday: 'short'}).format(s_date);
let mon = new Intl.DateTimeFormat('en-US', {month: 'short'}).format(s_date);
let s_day = `${day} ${mon} ${s_date.getDate()} ${s_date.getFullYear()}`;
data.message = data.message.replace("__DATE__", s_day);

let tz = s_date.toString().split('(').pop().split(')')[0];
var minutes = s_date.getMinutes() < 10 ? `0${s_date.getMinutes()}` : s_date.getMinutes();
let m_start = `${s_date.getHours()}:${minutes} ${tz}`;
data.message = data.message.replace("__START__", m_start);
}
if(data.end) {
let e_date = new Date(data.end);
let day = new Intl.DateTimeFormat('en-US', {weekday: 'short'}).format(e_date);
let mon = new Intl.DateTimeFormat('en-US', {month: 'short'}).format(e_date);
let e_day = `${day} ${mon} ${e_date.getDate()} ${e_date.getFullYear()}`;
data.message = data.message.replace("__DATE__", e_day);

let tz = e_date.toString().split('(').pop().split(')')[0];
var minutes = e_date.getMinutes() < 10 ? `0${e_date.getMinutes()}` : e_date.getMinutes();
let m_end = `${e_date.getHours()}:${minutes} ${tz}`;
data.message = data.message.replace("__END__", m_end);
}
if(!data.style) {
var style = styles.warning;
}else if(Object.keys(styles).includes(data.style)) {
var style = styles[data.style];
}else {
var style = data.style;
}
html += `<h4 class='display-5 mt-2' style='${style}'>${data.message}</h4>`;
});
$(view.queryByHook('message-to-users')).html(html);
}
});
}

module.exports = {
routePrefix: routePrefix,
getApiPath: getApiPath,
Expand All @@ -260,7 +319,8 @@ module.exports = {
documentSetup: documentSetup,
copyToClipboard: copyToClipboard,
switchToEditTab: switchToEditTab,
validateName: validateName
validateName: validateName,
maintenance: maintenance
};


2 changes: 1 addition & 1 deletion client/domain-view/domain-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let path = require('path');
let _ = require('underscore');
//support files
let app = require('../app');
let Plotly = require('../lib/plotly');
let Plotly = require('plotly.js-dist');
//models
let Particle = require('../models/particle');
//views
Expand Down
7 changes: 1 addition & 6 deletions client/file-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,7 @@ let getSpatialModelContext = (view, node) => {
let downloadOptions = {dataType: "json", identifier: "file/json-data"};
return {
edit: view.getEditModelContext(node),
newWorkflow: view.buildContextWithSubmenus({
label: "New Workflow",
submenu: {
jupyterNotebook: view.getNotebookNewWorkflowContext(node)
}
}),
newWorkflow: view.getSpatialNewWorkflowContext(node),
convert: view.getSmdlConvertContext(node, "spatial/to-model"),
download: view.getDownloadContext(node, downloadOptions),
rename: view.getRenameContext(node),
Expand Down
2 changes: 2 additions & 0 deletions client/job-view/job-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ module.exports = View.extend({
this.newFormat = attrs.newFormat;
this.settingsHeader = this.readOnly ? "Settings" : "Review Settings";
this.modelHeader = (this.readOnly ? "Model: " : "Review Model: ") + this.model.model.name;
this.domainPlot = attrs.domainPlot;
},
render: function (attrs, options) {
View.prototype.render.apply(this, arguments);
Expand All @@ -66,6 +67,7 @@ module.exports = View.extend({
renderModelView: function () {
let modelView = new ModelView({
model: this.model.model,
domainPlot: this.domainPlot,
readOnly: true
});
app.registerRenderSubview(this, modelView, "model-viewer-container");
Expand Down
117 changes: 117 additions & 0 deletions client/job-view/templates/spatialResultsView.pug
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
div#workflow-results.card

div.card-header.pb-0

h3.inline Results

button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#collapse-results" data-hook="collapse-results-btn") -

div.collapse.show(id="collapse-results" data-hook="workflow-results")

div.card-body

div.collapse(id="edit-plot-args" data-hook="edit-plot-args")

div.mr-3.inline

h6 <b>Title:</b>

div.inline(style="width: 80%")

div(id="title" data-hook="title")

div.inline.horizontal-space

span.inline(for="target-of-interest") Target of Interest:

div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.spatialTarget) <sup><svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="info" class="svg-inline--fa fa-info fa-w-6" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 512"><path fill="currentColor" d="M20 424.229h20V279.771H20c-11.046 0-20-8.954-20-20V212c0-11.046 8.954-20 20-20h112c11.046 0 20 8.954 20 20v212.229h20c11.046 0 20 8.954 20 20V492c0 11.046-8.954 20-20 20H20c-11.046 0-20-8.954-20-20v-47.771c0-11.046 8.954-20 20-20zM96 0C56.235 0 24 32.235 24 72s32.235 72 72 72 72-32.235 72-72S135.764 0 96 0z"></path></svg></sup>

div.inline(id="target-of-interest" data-hook="target-of-interest-list")

div.inline.horizontal-space.hidden(data-hook="job-results-mode")

span.inline(for="target-of-interest") Mode:

div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.mode) <sup><svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="info" class="svg-inline--fa fa-info fa-w-6" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 512"><path fill="currentColor" d="M20 424.229h20V279.771H20c-11.046 0-20-8.954-20-20V212c0-11.046 8.954-20 20-20h112c11.046 0 20 8.954 20 20v212.229h20c11.046 0 20 8.954 20 20V492c0 11.046-8.954 20-20 20H20c-11.046 0-20-8.954-20-20v-47.771c0-11.046 8.954-20 20-20zM96 0C56.235 0 24 32.235 24 72s32.235 72 72 72 72-32.235 72-72S135.764 0 96 0z"></path></svg></sup>

div.inline(id="target-mode" data-hook="target-mode-list")

div.card

div.card-header.pb-0

h5.inline Plot Trajectories

button.btn.btn-outline-collapse(
data-toggle="collapse"
data-target="#collapse-spatial"
id="collapse-spatial-btn"
data-hook="collapse-spatial-btn"
data-trigger="collapse-plot-container"
data-type="spatial"
) -

div.collapse.show(id="collapse-spatial")

div.card-body

div(data-hook="spatial-plot")

div(data-hook="spatial-plot-loading-msg")
| Spatial results may take a while to load. Load times are dependant on the number of outputs, number of variables, and the number of domain particles.

div.spinner-border.workflow-plot(data-hook="spatial-plot-spinner")

button.btn.btn-primary.box-shadow(data-hook="spatial-edit-plot" data-target="edit-plot" disabled) Edit Plot

button.btn.btn-primary.box-shadow.dropdown-toggle(
id="spatial-download"
data-hook="spatial-download"
data-toggle="dropdown",
aria-haspopup="true",
aria-expanded="false",
type="button"
disabled
) Download <span class="caret"></span>

ul.dropdown-menu(aria-labelledby="#spatial-download")
li.dropdown-item(
data-hook="spatial-download-png-custom"
data-target="download-png-custom"
data-type="spatial"
) Plot as .png
li.dropdown-item(
data-hook="spatial-download-json"
data-target="download-json"
data-type="spatial"
) Plot as .json
li.dropdown-item(
data-hook="spatial-plot-csv"
data-target="download-plot-csv"
data-type="spatial"
) Plot Results as .csv

div

button.btn.btn-primary.box-shadow(id="convert-to-notebook" data-hook="convert-to-notebook") Convert to Notebook

button.btn.btn-primary.box-shadow(id="download-results-csv" data-hook="download-results-csv") Download Full Results as .csv

button.btn.btn-primary.box-shadow(id="job-presentation" data-hook="job-presentation") Publish

div.saving-status(data-hook="job-action-start")

div.spinner-grow

span Publishing ...

div.saved-status(data-hook="job-action-end")

span Published

div.save-error-status(data-hook="job-action-err")

span Error

div.text-info(data-hook="update-format-message" style="display: none;")
| To publish you job the workflows format must be updated.
Loading

0 comments on commit d5e83a9

Please sign in to comment.