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': [