You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2019/08/20 14:52:12 UTC
[airavata-django-portal] 01/04: Parameterized notebook output viewer
This is an automated email from the ASF dual-hosted git repository.
machristie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git
commit aba53675fd368472c9acffb757e5a1633913f5e9
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Mon Aug 19 15:45:15 2019 -0400
Parameterized notebook output viewer
---
django_airavata/apps/api/output_views.py | 79 +++++++++++++++++++++-
django_airavata/apps/api/urls.py | 2 +
django_airavata/apps/api/views.py | 11 +++
.../js/components/experiment/ExperimentSummary.vue | 1 +
.../output-displays/NotebookOutputDisplay.vue | 53 +++++++++++++++
.../output-displays/OutputDisplayContainer.vue | 12 +++-
requirements.txt | 3 +
setup.py | 4 +-
8 files changed, 162 insertions(+), 3 deletions(-)
diff --git a/django_airavata/apps/api/output_views.py b/django_airavata/apps/api/output_views.py
index ea8fc70..87452d2 100644
--- a/django_airavata/apps/api/output_views.py
+++ b/django_airavata/apps/api/output_views.py
@@ -1,7 +1,11 @@
import json
import logging
+import os
+import nbformat
+import papermill as pm
from django.conf import settings
+from nbconvert import HTMLExporter
from airavata.model.application.io.ttypes import DataType
@@ -9,6 +13,8 @@ from . import data_products_helper
logger = logging.getLogger(__name__)
+BASE_DIR = os.path.dirname(os.path.abspath(__file__))
+
class DefaultViewProvider:
display_type = 'default'
@@ -20,6 +26,39 @@ class DefaultViewProvider:
}
+class ParameterizedNotebookViewProvider:
+ display_type = 'notebook'
+ name = "Example Parameterized Notebook View"
+ # fixture_output_file = os.path.join(BASE_DIR, "data", "Gaussian.log")
+
+ def generate_data(self,
+ experiment_output,
+ experiment,
+ output_file=None,
+ output_dir=None):
+ # use papermill to generate the output notebook
+ output_file_path = os.path.realpath(output_file.name)
+ pm.execute_notebook(
+ os.path.join(BASE_DIR, "path", "to", "notebook.ipynb"),
+ # TODO: use TemporaryFile instead
+ '/tmp/output.ipynb',
+ parameters=dict(
+ experiment_output={},
+ experiment={},
+ output_file=output_file_path,
+ output_dir=output_dir
+ )
+ )
+ # TODO: convert the output notebook into html format
+ output_notebook = nbformat.read('/tmp/output.ipynb', as_version=4)
+ html_exporter = HTMLExporter()
+ (body, resources) = html_exporter.from_notebook_node(output_notebook)
+ # TODO: return the HTML output as the output key
+ return {
+ 'output': body
+ }
+
+
DEFAULT_VIEW_PROVIDERS = {
'default': DefaultViewProvider()
}
@@ -47,17 +86,27 @@ def get_output_views(request, experiment, application_interface=None):
'provider-id': output_view_provider_id,
'display-type': output_view_provider.display_type,
'name': getattr(output_view_provider, 'name',
- output_view_provider_id)
+ output_view_provider_id),
}
if getattr(output_view_provider, 'immediate', False):
# Immediately call generate_data function
data = _generate_data(
request, output_view_provider, output, experiment)
view_config['data'] = data
+ else:
+ view_config['data'] = {}
output_views[output.name].append(view_config)
return output_views
+def _get_output_view_provider(output_view_provider_id):
+
+ if output_view_provider_id in DEFAULT_VIEW_PROVIDERS:
+ return DEFAULT_VIEW_PROVIDERS[output_view_provider_id]
+ elif output_view_provider_id in settings.OUTPUT_VIEW_PROVIDERS:
+ return settings.OUTPUT_VIEW_PROVIDERS[output_view_provider_id]
+
+
def _get_output_view_providers(experiment_output, application_interface):
output_view_providers = []
logger.debug("experiment_output={}".format(experiment_output))
@@ -89,6 +138,7 @@ def _get_application_output_view_providers(application_interface, output_name):
for o in application_interface.applicationOutputs
if o.name == output_name]
if len(app_output) == 1:
+ logger.debug("{}: {}".format(output_name, app_output))
app_output = app_output[0]
else:
return []
@@ -104,6 +154,26 @@ def _get_application_output_view_providers(application_interface, output_name):
return []
+def generate_data(request,
+ output_view_provider_id,
+ experiment_output_name,
+ experiment_id):
+ output_view_provider = _get_output_view_provider(output_view_provider_id)
+ # TODO if output_view_provider is None, return 404
+ experiment = request.airavata_client.getExperiment(
+ request.authz_token, experiment_id)
+ experiment_output = [o
+ for o in experiment.experimentOutputs
+ if o.name == experiment_output_name]
+ # TODO: handle experiment_output not found by name
+ experiment_output = experiment_output[0]
+ # TODO: add experiment_output_dir
+ return _generate_data(request,
+ output_view_provider,
+ experiment_output,
+ experiment)
+
+
def _generate_data(request,
output_view_provider,
experiment_output,
@@ -111,6 +181,9 @@ def _generate_data(request,
# TODO: handle URI_COLLECTION also
logger.debug("getting data product for {}".format(experiment_output.value))
output_file = None
+ fixture_output_file = getattr(output_view_provider,
+ 'fixture_output_file',
+ None)
if (experiment_output.value and
experiment_output.type in (DataType.URI,
DataType.STDOUT,
@@ -120,6 +193,10 @@ def _generate_data(request,
request.authz_token, experiment_output.value)
if data_products_helper.exists(request, data_product):
output_file = data_products_helper.open(request, data_product)
+ elif fixture_output_file is not None:
+ output_file = open(fixture_output_file)
+ # TODO: change interface to provide output_file as a path
+ # TODO: convert experiment and experiment_output to dict/JSON
data = output_view_provider.generate_data(
experiment_output, experiment, output_file=output_file)
return data
diff --git a/django_airavata/apps/api/urls.py b/django_airavata/apps/api/urls.py
index 9f21bda..951ad43 100644
--- a/django_airavata/apps/api/urls.py
+++ b/django_airavata/apps/api/urls.py
@@ -93,6 +93,8 @@ urlpatterns = [
url(r'^api-status-check/',
views.APIServerStatusCheckView.as_view(),
name='api-status-check'),
+ url(r'^notebook-output',
+ views.notebook_output_view, name="notebook-output")
]
if logger.isEnabledFor(logging.DEBUG):
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index a693253..a8e559e 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -1733,3 +1733,14 @@ class APIServerStatusCheckView(APIView):
"apiServerUp": False
}
return Response(data)
+
+
+def notebook_output_view(request):
+ provider_id = request.GET['provider-id']
+ experiment_id = request.GET['experiment-id']
+ experiment_output_name = request.GET['experiment-output-name']
+ data = output_views.generate_data(request,
+ provider_id,
+ experiment_output_name,
+ experiment_id)
+ return HttpResponse(data['output'])
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentSummary.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentSummary.vue
index 68f5106..6c216e5 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentSummary.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentSummary.vue
@@ -66,6 +66,7 @@
:experiment-output="output"
:data-products="outputDataProducts[output.name]"
:output-views="localFullExperiment.outputViews[output.name]"
+ :experiment-id="experiment.experimentId"
/>
</div>
</div>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/NotebookOutputDisplay.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/NotebookOutputDisplay.vue
new file mode 100644
index 0000000..ce23543
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/NotebookOutputDisplay.vue
@@ -0,0 +1,53 @@
+<template>
+ <iframe :src="url"></iframe>
+</template>
+
+<script>
+import { models } from "django-airavata-api";
+export default {
+ name: "notebook-output-display",
+ props: {
+ experimentOutput: {
+ type: models.OutputDataObjectType,
+ required: true
+ },
+ dataProducts: {
+ type: Array,
+ required: true
+ },
+ experimentId: {
+ type: String,
+ required: true
+ },
+ providerId: {
+ type: String,
+ required: true
+ }
+ },
+ data() {
+ return {
+ rawOutput: null
+ };
+ },
+ computed: {
+ url() {
+ return (
+ "/api/notebook-output?" +
+ "experiment-id=" +
+ encodeURIComponent(this.experimentId) +
+ "&experiment-output-name=" +
+ encodeURIComponent(this.experimentOutput.name) +
+ "&provider-id=" + encodeURIComponent(this.providerId)
+ );
+ }
+ },
+};
+</script>
+
+<style scoped>
+iframe {
+ width: 100%;
+ height: 400px;
+ border: none;
+}
+</style>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/OutputDisplayContainer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/OutputDisplayContainer.vue
index 57157ae..4bac1f1 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/OutputDisplayContainer.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/OutputDisplayContainer.vue
@@ -22,6 +22,8 @@
:is="outputDisplayComponentName"
:experiment-output="experimentOutput"
:data-products="dataProducts"
+ :experiment-id="experimentId"
+ :provider-id="currentView['provider-id']"
:data="outputViewData"
/>
</b-card>
@@ -32,6 +34,7 @@ import { models } from "django-airavata-api";
import { components } from "django-airavata-common-ui";
import DefaultOutputDisplay from "./DefaultOutputDisplay";
import LinkDisplay from "./LinkDisplay";
+import NotebookOutputDisplay from "./NotebookOutputDisplay";
export default {
name: "output-viewer-container",
@@ -48,12 +51,17 @@ export default {
type: Array,
required: false,
default: null
+ },
+ experimentId: {
+ type: String,
+ required: true
}
},
components: {
"data-product-viewer": components.DataProductViewer,
DefaultOutputDisplay,
- LinkDisplay
+ LinkDisplay,
+ NotebookOutputDisplay
},
data() {
return {
@@ -69,6 +77,8 @@ export default {
return "default-output-display";
} else if (this.currentView["display-type"] === "link") {
return "link-display";
+ } else if (this.currentView["display-type"] === "notebook") {
+ return "notebook-output-display";
} else {
return null;
}
diff --git a/requirements.txt b/requirements.txt
index b9e4158..e9a853d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -11,4 +11,7 @@ mysqlclient==1.4.2.post1
wagtail==2.0
wagtailfontawesome==1.1.3
+jupyter==1.0.0
+papermill==1.0.1
+
-e "."
diff --git a/setup.py b/setup.py
index 1760221..815d5d5 100644
--- a/setup.py
+++ b/setup.py
@@ -28,7 +28,9 @@ setup(
'thrift',
'thrift_connector',
'wagtail',
- 'wagtailfontawesome'
+ 'wagtailfontawesome',
+ 'jupyter',
+ 'papermill'
],
extras_require={
'dev': [