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 2021/06/18 19:38:18 UTC

[airavata-django-portal] 01/20: AIRAVATA-3453 POC: initial supcrtbl2 custom interface with WCs

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch airavata-3453
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 702d90505103c044aa8672b1ff3cd9e1d2ebf6b9
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Apr 27 09:41:43 2021 -0400

    AIRAVATA-3453 POC: initial supcrtbl2 custom interface with WCs
---
 django_airavata/apps/workspace/package.json        |   4 +-
 .../static/django_airavata_workspace/.gitignore    |   1 +
 .../js/web-components/ExperimentEditor.vue         | 114 +++++++++++++++++++++
 .../js/web-components/Foo.vue                      |   3 +
 .../js/web-components/store.js                     |  44 ++++++++
 .../django_airavata_workspace/supcrtbl2.html       |  80 +++++++++++++++
 django_airavata/apps/workspace/views.py            |   6 +-
 django_airavata/apps/workspace/vue.config.js       |  15 +--
 8 files changed, 258 insertions(+), 9 deletions(-)

diff --git a/django_airavata/apps/workspace/package.json b/django_airavata/apps/workspace/package.json
index 8d53566..6d227b1 100644
--- a/django_airavata/apps/workspace/package.json
+++ b/django_airavata/apps/workspace/package.json
@@ -12,7 +12,9 @@
     "test": "npm run test:unit",
     "test:unit": "vue-cli-service test:unit",
     "test:unit:watch": "vue-cli-service test:unit --watch",
-    "format": "prettier --write ."
+    "format": "prettier --write .",
+    "wc": "WC_MODE='true' vue-cli-service build --target wc --inline-vue --name adpf './static/django_airavata_workspace/js/web-components/*.vue' --dest ./static/django_airavata_workspace/wc",
+    "wc:watch": "WC_MODE='true' vue-cli-service build --target wc --inline-vue --name adpf './static/django_airavata_workspace/js/web-components/*.vue' --dest ./static/django_airavata_workspace/wc --watch"
   },
   "dependencies": {
     "bootstrap": "^4.3.1",
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/.gitignore b/django_airavata/apps/workspace/static/django_airavata_workspace/.gitignore
new file mode 100644
index 0000000..ab46eb7
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/.gitignore
@@ -0,0 +1 @@
+wc
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentEditor.vue
new file mode 100644
index 0000000..6b956f5
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentEditor.vue
@@ -0,0 +1,114 @@
+<template>
+  <form v-if="experiment" @input="onInput" @submit.prevent="onSubmit">
+    <slot name="experiment-name">
+      <input
+        type="text"
+        name="experiment-name"
+        :value="experiment.experimentName"
+      />
+    </slot>
+    <template v-for="input in experiment.experimentInputs">
+      <div :key="input.name">
+        <slot :name="input.name">
+          {{input.name}} <input  v-if="input.type.name == 'STRING'" :name="`input:${input.name}`" :value="input.value"/>
+        </slot>
+        <!-- TODO: add support for other input types -->
+      </div>
+    </template>
+    <slot name="save-button">
+      <button type="submit" name="save-experiment-button">Save</button>
+    </slot>
+  </form>
+</template>
+
+<script>
+import {
+  getApplicationModule,
+  getApplicationInterfaceForModule,
+  saveExperiment,
+  getDefaultProjectId,
+  getExperiment,
+} from "./store";
+
+export default {
+  props: {
+    applicationId: {
+      type: String,
+      required: true,
+    },
+    experimentId: {
+      type: String,
+      required: false,
+    },
+  },
+  async created() {
+    this.applicationModule = await getApplicationModule(this.applicationId);
+    this.appInterface = await getApplicationInterfaceForModule(
+      this.applicationId
+    );
+    this.experiment = await this.loadExperiment();
+  },
+  data() {
+    return {
+      applicationModule: null,
+      appInterface: null,
+      experiment: null,
+    };
+  },
+  methods: {
+    onInput(event) {
+      console.log(event.target.name, event.target.value);
+      if (event.target.name === "experiment-name") {
+        this.experiment.experimentName = event.target.value;
+      }
+      if (event.target.name.startsWith("input:")) {
+        for (const input of this.experiment.experimentInputs) {
+          if (event.target.name === `input:${input.name}`){
+            input.value = event.target.value;
+          }
+        }
+      }
+    },
+    onSubmit(event) {
+      // console.log(event);
+      // 'save' event is cancelable. Listener can call .preventDefault() on the event to cancel.
+      // composed: true allows the shadow DOM event to bubble up through the shadow shadow root.
+      const saveEvent = new CustomEvent('save', {detail: [this.experiment], cancelable: true, composed: true});
+      this.$el.dispatchEvent(saveEvent);
+      if (saveEvent.defaultPrevented) {
+        return;
+      }
+      if (event.submitter.name === "save-experiment-button") {
+        this.saveExperiment();
+      } else {
+        // Default submit button handling is save and launch
+      }
+    },
+    async saveExperiment() {
+      return await saveExperiment(this.experiment);
+    },
+    async loadExperiment() {
+
+      if (this.experimentId) {
+        const experiment = await getExperiment(this.experimentId);
+        this.$emit('loaded', experiment);
+        return experiment;
+      } else {
+        const experiment = this.appInterface.createExperiment();
+        experiment.experimentName =
+          this.applicationModule.appModuleName +
+          " on " +
+          new Date().toLocaleString();
+        const defaultProjectId = await getDefaultProjectId();
+        experiment.projectId = defaultProjectId;
+        experiment.userConfigurationData.computationalResourceScheduling.resourceHostId =
+          "bigred3.uits.iu.edu_2141bf96-c458-4ecd-8759-aa3a08f31956";
+        this.$emit('loaded', experiment);
+        return experiment;
+      }
+    }
+  },
+};
+</script>
+
+<style></style>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/Foo.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/Foo.vue
new file mode 100644
index 0000000..7b8b46c
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/Foo.vue
@@ -0,0 +1,3 @@
+<template>
+  <div></div>
+</template>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js
new file mode 100644
index 0000000..2f66bfb
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js
@@ -0,0 +1,44 @@
+import { services } from "django-airavata-api";
+const APPLICATION_MODULES = {};
+const APPLICATION_INTERFACES = {};
+export async function getApplicationModule(applicationId) {
+  if (applicationId in APPLICATION_MODULES) {
+    return APPLICATION_MODULES[applicationId];
+  }
+  const result = await services.ApplicationModuleService.retrieve({
+    lookup: applicationId,
+  });
+  APPLICATION_MODULES[applicationId] = result;
+  return result;
+}
+
+export async function getApplicationInterfaceForModule(applicationId) {
+  if (applicationId in APPLICATION_INTERFACES) {
+    return APPLICATION_INTERFACES[applicationId];
+  }
+  const result = await services.ApplicationModuleService.getApplicationInterface(
+    { lookup: applicationId }
+  );
+  APPLICATION_INTERFACES[applicationId] = result;
+  return result;
+}
+
+export async function saveExperiment(experiment) {
+  if (experiment.experimentId) {
+    return await services.ExperimentService.update({
+      data: experiment,
+      lookup: experiment.experimentId,
+    });
+  } else {
+    return await services.ExperimentService.create({ data: experiment });
+  }
+}
+
+export async function getDefaultProjectId() {
+  const preferences = await services.WorkspacePreferencesService.get();
+  return preferences.most_recent_project_id;
+}
+
+export async function getExperiment(experimentId) {
+  return await services.ExperimentService.retrieve({ lookup: experimentId });
+}
diff --git a/django_airavata/apps/workspace/templates/django_airavata_workspace/supcrtbl2.html b/django_airavata/apps/workspace/templates/django_airavata_workspace/supcrtbl2.html
new file mode 100644
index 0000000..588fc3b
--- /dev/null
+++ b/django_airavata/apps/workspace/templates/django_airavata_workspace/supcrtbl2.html
@@ -0,0 +1,80 @@
+{% extends 'base.html' %}
+
+{% load static %}
+{% load render_bundle from webpack_loader %}
+
+{% block css %}
+{% comment %} TODO: are chunk-vendors and chunk-common needed? {% endcomment %}
+{% comment %} {% render_bundle 'chunk-vendors' 'css' 'WORKSPACE' %} {% endcomment %}
+{% comment %} {% render_bundle 'chunk-common' 'css' 'WORKSPACE' %} {% endcomment %}
+{% comment %} {% render_bundle 'adpf' 'css' 'WORKSPACE' %} {% endcomment %}
+{% endblock %}
+
+{% block content %}
+{% comment %} TODO: maybe use wc- as a prefix? {% endcomment %}
+<div class="main-content-wrapper">
+    <main class="main-content">
+        <div class="container-fluid">
+          <h1>SUPCRTBL Online Version 1.0.1</h2>
+          <adpf-experiment-editor
+            id="experiment-editor"
+            application-id="supcrtbl_eb4216b3-fcbf-4422-a70d-27af2550cfb6"
+            {% if experiment_id %}
+            experiment-id="{{ experiment_id }}"
+            {% endif %} >
+            <div id="solvent" slot="Specify Solvent Phase Region">
+              <label for="input:Specify Solvent Phase Region">Specify solvent phase region:</label>
+              <input type="radio" name="input:Specify Solvent Phase Region" value="0">One-phase region</input> <br/>
+              <input type="radio" name="input:Specify Solvent Phase Region" value="1">liquid vapor saturation curve</input>
+            </div>
+
+            <div slot="Input-to-Echo">
+            My custom input editor: <input id="myinput" style="background-color: lightgrey;" name="input:Input-to-Echo" value=""/>
+            </div>
+          </adpf-experiment-editor>
+        </div>
+    </main>
+</div>
+{% endblock content %}
+
+{% block scripts %}
+{% comment %} {% render_bundle 'chunk-vendors' 'js' 'WORKSPACE' %} {% endcomment %}
+<script src="{% static 'django_airavata_workspace/wc/adpf.chunk-vendors.js' %}"></script>
+{% comment %} {% render_bundle 'chunk-common' 'js' 'WORKSPACE' %} {% endcomment %}
+{% comment %} {% render_bundle 'adpf' 'js' 'WORKSPACE' %} {% endcomment %}
+<script src="{% static 'django_airavata_workspace/wc/adpf.min.js' %}"></script>
+<script>
+document.getElementById("experiment-editor").addEventListener('loaded', e => {
+  const [experiment] = e.detail;
+  for (const input of experiment.experimentInputs) {
+    // This runs before the custom element is registered which is probably why we need setAttribute here
+    // TODO: just iterate over the slotted inputs
+    const inputEl = document.querySelector(`[name="input:${input.name}"]`)
+    if (!inputEl) {
+      console.warn("Could not find input editor for ", input);
+    } else {
+      if (inputEl.type === 'radio') {
+        const radios = document.querySelectorAll(`[name="input:${input.name}"]`);
+        for (const radio of radios) {
+          if (radio.value === input.value) {
+            radio.checked = true;
+            break;
+          }
+        }
+      }
+      inputEl.setAttribute("value", input.value);
+    }
+    // document.getElementById("myinput").setAttribute("value", input.value);
+  }
+});
+function validateExperiment(event) {
+  const [experiment] = event.detail;
+  // This works, can check the current values of all inputs
+  if (experiment.experimentInputs[0].value === "Valcustom3") {
+    console.log('save: preventing default');
+    event.preventDefault();
+  }
+}
+document.getElementById('experiment-editor').addEventListener('save', validateExperiment);
+</script>
+{% endblock %}
diff --git a/django_airavata/apps/workspace/views.py b/django_airavata/apps/workspace/views.py
index 3f6d336..84cc7e6 100644
--- a/django_airavata/apps/workspace/views.py
+++ b/django_airavata/apps/workspace/views.py
@@ -114,7 +114,8 @@ def create_experiment(request, app_module_id):
         context['experiment_data_dir'] = request.GET['experiment-data-dir']
 
     return render(request,
-                  'django_airavata_workspace/create_experiment.html',
+                  #   'django_airavata_workspace/create_experiment.html',
+                  'django_airavata_workspace/supcrtbl2.html',
                   context)
 
 
@@ -123,7 +124,8 @@ def edit_experiment(request, experiment_id):
     request.active_nav_item = 'experiments'
 
     return render(request,
-                  'django_airavata_workspace/edit_experiment.html',
+                #   'django_airavata_workspace/edit_experiment.html',
+                  'django_airavata_workspace/supcrtbl2.html',
                   {'bundle_name': 'edit-experiment',
                    'experiment_id': experiment_id})
 
diff --git a/django_airavata/apps/workspace/vue.config.js b/django_airavata/apps/workspace/vue.config.js
index 01afb3d..c08ea40 100644
--- a/django_airavata/apps/workspace/vue.config.js
+++ b/django_airavata/apps/workspace/vue.config.js
@@ -31,12 +31,15 @@ module.exports = {
     },
   },
   configureWebpack: {
-    plugins: [
-      new BundleTracker({
-        filename: "webpack-stats.json",
-        path: "./static/django_airavata_workspace/dist/",
-      }),
-    ],
+    plugins:
+      process.env.WC_MODE !== "true"
+        ? [
+            new BundleTracker({
+              filename: "webpack-stats.json",
+              path: "./static/django_airavata_workspace/dist/",
+            }),
+          ]
+        : [],
     optimization: {
       /*
        * Force creating a vendor bundle so we can load the 'app' and 'vendor'