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 2020/06/12 19:51:14 UTC
[airavata-django-portal] 01/05: AIRAVATA-3285
InteractiveParametersPanel for all display types
This is an automated email from the ASF dual-hosted git repository.
machristie pushed a commit to branch AIRAVATA-3285--Interactive-output-view-providers
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git
commit 202b23bf430877b9132e33616aa48868e6a154d4
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Apr 28 10:09:55 2020 -0400
AIRAVATA-3285 InteractiveParametersPanel for all display types
---
django_airavata/apps/api/output_views.py | 33 +++++-
django_airavata/apps/api/urls.py | 2 +
django_airavata/apps/api/views.py | 37 ++++---
.../output-displays/DefaultOutputDisplay.vue | 9 +-
.../output-displays/HtmlOutputDisplay.vue | 60 ++++-------
.../output-displays/ImageOutputDisplay.vue | 40 ++-----
.../experiment/output-displays/LinkDisplay.vue | 26 -----
.../output-displays/LinkOutputDisplay.vue | 15 +++
.../output-displays/OutputDisplayContainer.vue | 115 +++++++++++++++------
.../output-displays/OutputViewDataLoader.js | 36 +++++++
.../InteractiveParameterCheckboxWidget.vue | 15 +++
.../InteractiveParametersPanel.vue | 42 ++++++++
12 files changed, 278 insertions(+), 152 deletions(-)
diff --git a/django_airavata/apps/api/output_views.py b/django_airavata/apps/api/output_views.py
index 0614e56..d10fd25 100644
--- a/django_airavata/apps/api/output_views.py
+++ b/django_airavata/apps/api/output_views.py
@@ -1,3 +1,4 @@
+import inspect
import json
import logging
import os
@@ -158,7 +159,8 @@ def _get_application_output_view_providers(application_interface, output_name):
def generate_data(request,
output_view_provider_id,
experiment_output_name,
- experiment_id):
+ experiment_id,
+ **kwargs):
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(
@@ -169,16 +171,20 @@ def generate_data(request,
# TODO: handle experiment_output not found by name
experiment_output = experiment_output[0]
# TODO: add experiment_output_dir
+ # convert the extra/interactive arguments to appropriate types
+ kwargs = _convert_params_to_type(output_view_provider, kwargs)
return _generate_data(request,
output_view_provider,
experiment_output,
- experiment)
+ experiment,
+ **kwargs)
def _generate_data(request,
output_view_provider,
experiment_output,
- experiment):
+ experiment,
+ **kwargs):
# TODO: handle URI_COLLECTION also
logger.debug("getting data product for {}".format(experiment_output.value))
output_file = None
@@ -198,6 +204,23 @@ def _generate_data(request,
output_file = open(test_output_file, 'rb')
# 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(
- request, experiment_output, experiment, output_file=output_file)
+ data = output_view_provider.generate_data(request,
+ experiment_output,
+ experiment,
+ output_file=output_file,
+ **kwargs)
return data
+
+
+def _convert_params_to_type(output_view_provider, params):
+ method_sig = inspect.signature(output_view_provider.generate_data)
+ method_params = method_sig.parameters
+ for k, v in params.items():
+ if (k in method_params and
+ method_params[k].default is not inspect.Parameter.empty and
+ method_params[k].default is not None):
+ # TODO: handle lists?
+ # Handle boolean and numeric values, converting from string
+ if type(method_params[k]) is not str:
+ params[k] = json.loads(v)
+ return params
diff --git a/django_airavata/apps/api/urls.py b/django_airavata/apps/api/urls.py
index 70db566..af8a0a7 100644
--- a/django_airavata/apps/api/urls.py
+++ b/django_airavata/apps/api/urls.py
@@ -101,6 +101,8 @@ urlpatterns = [
views.html_output_view, name="html-output"),
url(r'^image-output',
views.image_output_view, name="image-output"),
+ url(r'^link-output',
+ views.link_output_view, name="link-output"),
]
if logger.isEnabledFor(logging.DEBUG):
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index 174d3b1..6b8ee27 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -1,3 +1,4 @@
+import base64
import json
import logging
import os
@@ -1847,24 +1848,30 @@ def notebook_output_view(request):
def html_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)
+ data = _generate_output_view_data(request)
return JsonResponse(data)
def image_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)
+ data = _generate_output_view_data(request)
# data should contain 'image' as a file-like object or raw bytes with the
# file data and 'mime-type' with the images mimetype
- return HttpResponse(data['image'], content_type=data['mime-type'])
+ data['image'] = base64.b64encode(data['image']).decode('utf-8')
+ return JsonResponse(data)
+
+
+def link_output_view(request):
+ data = _generate_output_view_data(request)
+ return JsonResponse(data)
+
+
+def _generate_output_view_data(request):
+ params = request.GET.copy()
+ provider_id = params.pop('provider-id')[0]
+ experiment_id = params.pop('experiment-id')[0]
+ experiment_output_name = params.pop('experiment-output-name')[0]
+ return output_views.generate_data(request,
+ provider_id,
+ experiment_output_name,
+ experiment_id,
+ **params.dict())
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/DefaultOutputDisplay.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/DefaultOutputDisplay.vue
index 6ef5520..f048582 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/DefaultOutputDisplay.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/DefaultOutputDisplay.vue
@@ -1,7 +1,11 @@
<template>
<div>
<div v-for="dp in dataProducts" :key="dp.productUri">
- <img v-if="dp.isImage && dp.downloadURL" class="image-preview rounded" :src="dp.downloadURL" />
+ <img
+ v-if="dp.isImage && dp.downloadURL"
+ class="image-preview rounded"
+ :src="dp.downloadURL"
+ />
<data-product-viewer :data-product="dp" :mime-type="fileMimeType" />
</div>
</div>
@@ -21,9 +25,6 @@ export default {
dataProducts: {
type: Array,
required: true
- },
- data: {
- type: Object
}
},
components: {
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/HtmlOutputDisplay.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/HtmlOutputDisplay.vue
index 54c6b2e..28cbd1b 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/HtmlOutputDisplay.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/HtmlOutputDisplay.vue
@@ -1,42 +1,31 @@
<template>
- <div v-html="rawOutput"/>
+ <div v-html="rawOutput" />
</template>
<script>
-import { models, utils } from "django-airavata-api";
export default {
name: "html-output-display",
props: {
- experimentOutput: {
- type: models.OutputDataObjectType,
- required: true
- },
- dataProducts: {
- type: Array,
- required: true
- },
- experimentId: {
- type: String,
- required: true
- },
- providerId: {
- type: String,
+ viewData: {
+ type: Object,
required: true
}
},
- data() {
- return {
- rawOutput: null,
- isLoading: true,
- rawJSFile : null,
- };
+ computed: {
+ rawOutput() {
+ return this.viewData && this.viewData.output
+ ? this.viewData.output
+ : null;
+ },
+ rawJSFile() {
+ return this.viewData && this.viewData.js ? this.viewData.js : null;
+ }
},
- methods : {
+ methods: {
//Attaches the script to the head, the name of the script can be passed from
//output view provider
loadScripts() {
return new Promise(resolve => {
-
let scriptEl = document.createElement("script");
scriptEl.src = this.rawJSFile;
scriptEl.type = "text/javascript";
@@ -44,32 +33,19 @@ export default {
// Attach script to head
document.getElementsByTagName("head")[0].appendChild(scriptEl);
// Wait for tag to load before promise is resolved
- scriptEl.addEventListener('load',() => {
+ scriptEl.addEventListener("load", () => {
resolve();
});
});
- },
- },
- created() {
- utils.FetchUtils.get("/api/html-output", {
- "experiment-id": this.experimentId,
- "experiment-output-name": this.experimentOutput.name,
- "provider-id": this.providerId
- }).then(data => {
- this.rawOutput = data.output
- this.rawJSFile = data.js
- this.isLoading = false
- });
+ }
},
watch: {
- isLoading() {
- if(!this.isLoading) {
+ rawJSFile() {
+ // TODO: check if script is already loaded
+ if (this.rawJSFile) {
this.loadScripts();
}
}
}
};
</script>
-
-<style scoped>
-</style>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/ImageOutputDisplay.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/ImageOutputDisplay.vue
index bb7fc8c..52bf8cf 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/ImageOutputDisplay.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/ImageOutputDisplay.vue
@@ -1,45 +1,23 @@
<template>
- <img :src="url" />
+ <img :src="dataUrl" />
</template>
<script>
-import { models } from "django-airavata-api";
export default {
name: "image-output-display",
props: {
- experimentOutput: {
- type: models.OutputDataObjectType,
- required: true
- },
- dataProducts: {
- type: Array,
- required: true
- },
- experimentId: {
- type: String,
- required: true
- },
- providerId: {
- type: String,
+ viewData: {
+ type: Object,
required: true
}
},
- data() {
- return {
- rawOutput: null
- };
- },
computed: {
- url() {
- return (
- "/api/image-output?" +
- "experiment-id=" +
- encodeURIComponent(this.experimentId) +
- "&experiment-output-name=" +
- encodeURIComponent(this.experimentOutput.name) +
- "&provider-id=" +
- encodeURIComponent(this.providerId)
- );
+ dataUrl() {
+ if (this.viewData) {
+ return `data:${this.viewData["mime-type"]};base64,${this.viewData["image"]}`;
+ } else {
+ return null;
+ }
}
}
};
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/LinkDisplay.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/LinkDisplay.vue
deleted file mode 100644
index 7afd4ca..0000000
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/LinkDisplay.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-<template>
- <a :href="data.url">{{ data.label }}</a>
-</template>
-
-<script>
-import { models } from "django-airavata-api"
-
-export default {
- name: "link-viewer",
- props: {
- experimentOutput: {
- type: models.OutputDataObjectType,
- required: true
- },
- dataProducts: {
- type: Array,
- required: true
- },
- data: {
- type: Object
- }
- },
-}
-</script>
-
-
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/LinkOutputDisplay.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/LinkOutputDisplay.vue
new file mode 100644
index 0000000..a2cf4c5
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/LinkOutputDisplay.vue
@@ -0,0 +1,15 @@
+<template>
+ <a :href="viewData.url">{{ viewData.label }}</a>
+</template>
+
+<script>
+export default {
+ name: "link-output-display",
+ props: {
+ viewData: {
+ type: Object,
+ required: true
+ }
+ }
+};
+</script>
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 2ae3498..4cd9384 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
@@ -1,30 +1,27 @@
<template>
<b-card>
- <div
- slot="header"
- class="d-flex align-items-baseline"
- >
+ <div slot="header" class="d-flex align-items-baseline">
<h6>{{ experimentOutput.name }}</h6>
- <b-dropdown
- v-if="showMenu"
- :text="currentView['name']"
- class="ml-auto"
- >
+ <b-dropdown v-if="showMenu" :text="currentView['name']" class="ml-auto">
<b-dropdown-item
v-for="view in outputViews"
:key="view['provider-id']"
:active="view['provider-id'] === currentView['provider-id']"
@click="selectView(view)"
- >{{ view['name']}}</b-dropdown-item>
+ >{{ view["name"] }}</b-dropdown-item
+ >
</b-dropdown>
</div>
<component
:is="outputDisplayComponentName"
- :experiment-output="experimentOutput"
+ :view-data="viewData"
:data-products="dataProducts"
- :experiment-id="experimentId"
- :provider-id="currentView['provider-id']"
- :data="outputViewData"
+ :experiment-output="experimentOutput"
+ />
+ <interactive-parameters-panel
+ v-if="viewData && viewData.interactive"
+ :parameters="viewData.interactive"
+ @input="parametersUpdated"
/>
</b-card>
</template>
@@ -35,8 +32,10 @@ import { components } from "django-airavata-common-ui";
import DefaultOutputDisplay from "./DefaultOutputDisplay";
import HtmlOutputDisplay from "./HtmlOutputDisplay";
import ImageOutputDisplay from "./ImageOutputDisplay";
-import LinkDisplay from "./LinkDisplay";
+import LinkOutputDisplay from "./LinkOutputDisplay";
import NotebookOutputDisplay from "./NotebookOutputDisplay";
+import InteractiveParametersPanel from "./interactive-parameters/InteractiveParametersPanel";
+import OutputViewDataLoader from "./OutputViewDataLoader";
export default {
name: "output-viewer-container",
@@ -64,42 +63,100 @@ export default {
DefaultOutputDisplay,
HtmlOutputDisplay,
ImageOutputDisplay,
- LinkDisplay,
- NotebookOutputDisplay
+ LinkOutputDisplay,
+ NotebookOutputDisplay,
+ InteractiveParametersPanel
+ },
+ created() {
+ if (this.providerId !== "default") {
+ this.loader = this.createLoader();
+ this.loader.load();
+ }
},
data() {
return {
- currentView: this.outputViews[0]
+ currentView: this.outputViews[0],
+ loader: null
};
},
computed: {
+ viewData() {
+ return this.loader && this.loader.data
+ ? this.loader.data
+ : this.outputViewData;
+ },
outputViewData() {
return this.currentView.data ? this.currentView.data : {};
},
+ displayTypeData() {
+ return {
+ default: {
+ component: "default-output-display",
+ url: null
+ },
+ link: {
+ component: "link-output-display",
+ url: "/api/link-output/"
+ },
+ notebook: {
+ component: "notebook-output-display",
+ url: "/api/notebook-output/"
+ },
+ html: {
+ component: "html-output-display",
+ url: "/api/html-output/"
+ },
+ image: {
+ component: "image-output-display",
+ url: "/api/image-output/"
+ }
+ };
+ },
+ displayType() {
+ return this.currentView["display-type"];
+ },
outputDisplayComponentName() {
- if (this.currentView["display-type"] === "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 if (this.currentView["display-type"] === "html") {
- return "html-output-display";
- } else if (this.currentView["display-type"] === "image") {
- return "image-output-display";
+ if (this.displayType in this.displayTypeData) {
+ return this.displayTypeData[this.displayType].component;
+ } else {
+ return null;
+ }
+ },
+ outputDataURL() {
+ if (this.displayType in this.displayTypeData) {
+ return this.displayTypeData[this.displayType].url;
} else {
return null;
}
},
showMenu() {
return this.outputViews.length > 1;
+ },
+ providerId() {
+ return this.currentView["provider-id"];
}
},
methods: {
selectView(outputView) {
this.currentView = outputView;
+ if (this.outputDataURL === null) {
+ this.loader = null;
+ } else {
+ this.loader = this.createLoader();
+ this.loader.load();
+ }
+ },
+ parametersUpdated(newParams) {
+ this.loader.load(newParams);
+ },
+ createLoader() {
+ return new OutputViewDataLoader({
+ url: this.outputDataURL,
+ experimentId: this.experimentId,
+ experimentOutputName: this.experimentOutput.name,
+ providerId: this.providerId
+ });
}
}
};
</script>
-
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/OutputViewDataLoader.js b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/OutputViewDataLoader.js
new file mode 100644
index 0000000..62a4be7
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/OutputViewDataLoader.js
@@ -0,0 +1,36 @@
+import { utils } from "django-airavata-api";
+
+export default class OutputViewDataLoader {
+ constructor({ url, experimentId, experimentOutputName, providerId }) {
+ this.url = url;
+ this.experimentId = experimentId;
+ this.experimentOutputName = experimentOutputName;
+ this.providerId = providerId;
+ this.data = null;
+ }
+
+ load(newParams = null) {
+ if (newParams && this.data) {
+ this.data.interactive = newParams;
+ }
+ return utils.FetchUtils.get(this.url, {
+ "experiment-id": this.experimentId,
+ "experiment-output-name": this.experimentOutputName,
+ "provider-id": this.providerId,
+ ...this.createInteractiveParams()
+ }).then(resp => {
+ this.data = resp;
+ return resp;
+ });
+ }
+
+ createInteractiveParams() {
+ const params = {};
+ if (this.data && this.data.interactive) {
+ this.data.interactive.forEach(p => {
+ params[p.name] = p.value;
+ });
+ }
+ return params;
+ }
+}
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParameterCheckboxWidget.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParameterCheckboxWidget.vue
new file mode 100644
index 0000000..ab9edf2
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParameterCheckboxWidget.vue
@@ -0,0 +1,15 @@
+<template>
+ <b-form-checkbox :checked="value" @input="$emit('input', $event)"/>
+</template>
+
+<script>
+export default {
+ name: "interactive-parameter-checkbox-widget",
+ props: {
+ value: {
+ type: Boolean,
+ required: true
+ }
+ }
+};
+</script>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParametersPanel.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParametersPanel.vue
new file mode 100644
index 0000000..0b3527d
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParametersPanel.vue
@@ -0,0 +1,42 @@
+<template>
+ <b-card title="Parameters">
+ <b-form-group
+ v-for="param in parameters"
+ :key="param.name"
+ :label="param.name"
+ >
+ <!-- TODO: use dynamic components to pick the right widget for the type of parameter -->
+ <interactive-parameter-checkbox-widget
+ :value="param.value"
+ @input="updated(param, $event)"
+ />
+ </b-form-group>
+ </b-card>
+</template>
+
+<script>
+import InteractiveParameterCheckboxWidget from "./InteractiveParameterCheckboxWidget";
+export default {
+ name: "interactive-parameters-panel",
+ components: {
+ InteractiveParameterCheckboxWidget
+ },
+ props: {
+ parameters: {
+ type: Array,
+ required: true
+ }
+ },
+ methods: {
+ updated(param, value) {
+ const params = this.parametersCopy();
+ const i = params.findIndex(x => x.name === param.name);
+ params[i].value = value;
+ this.$emit("input", params);
+ },
+ parametersCopy() {
+ return JSON.parse(JSON.stringify(this.parameters));
+ }
+ }
+};
+</script>