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 2022/02/17 00:43:23 UTC
[airavata-django-portal] 02/16: AIRAVATA-3557 Integrate calling fetchIntermediateOutputs into REST API and UI
This is an automated email from the ASF dual-hosted git repository.
machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git
commit debca1307777bab4eddc6276dbd6e1243a781e3e
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Jan 14 14:23:24 2022 -0500
AIRAVATA-3557 Integrate calling fetchIntermediateOutputs into REST API and UI
---
.../django_airavata_api/js/service_config.js | 7 +++
django_airavata/apps/api/views.py | 12 ++++
.../js/components/experiment/ExperimentSummary.vue | 13 ++---
.../output-displays/OutputDisplayContainer.vue | 68 +++++++++++++++-------
.../js/store/modules/view-experiment.js | 53 ++++++++++++++++-
5 files changed, 120 insertions(+), 33 deletions(-)
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
index 152a396..696b2c0 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
@@ -189,6 +189,13 @@ export default {
requestType: "post",
modelClass: Experiment,
},
+ fetchIntermediateOutputs: {
+ url: "/api/experiments/<lookup>/fetch_intermediate_outputs/",
+ requestType: "post",
+ bodyParams: {
+ name: "data",
+ },
+ },
},
modelClass: Experiment,
},
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index b9b68fd..abd2a89 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -264,6 +264,18 @@ class ExperimentViewSet(mixins.CreateModelMixin,
log.error("Cancel action has thrown the following error: ", e)
raise e
+ @action(methods=['post'], detail=True)
+ def fetch_intermediate_outputs(self, request, experiment_id=None):
+ if "outputNames" not in request.data:
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+ try:
+ request.airavata_client.fetchIntermediateOutputs(
+ request.authz_token, experiment_id, request.data["outputNames"])
+ return Response({'success': True})
+ except Exception as e:
+ log.error("fetchIntermediateOutputs failed with the following error: ", e)
+ raise e
+
def _update_workspace_preferences(self, project_id,
group_resource_profile_id,
compute_resource_id):
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 c622480..a2140a4 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
@@ -27,13 +27,7 @@
</div>
</div>
<template v-for="output in experiment.experimentOutputs">
- <div
- class="row"
- v-if="
- experiment.isFinished && outputDataProducts[output.name].length > 0
- "
- :key="output.name"
- >
+ <div class="row" v-if="finishedOrExecuting" :key="output.name">
<div class="col">
<output-display-container
:experiment-output="output"
@@ -44,7 +38,7 @@
</div>
</div>
</template>
- <div class="row" v-if="experiment.isFinished">
+ <div class="row" v-if="finishedOrExecuting">
<div class="col">
<experiment-storage-view-container
:experimentId="experiment.experimentId"
@@ -301,7 +295,7 @@ import urls from "../../utils/urls";
import moment from "moment";
import ExperimentStorageViewContainer from "../storage/ExperimentStorageViewContainer.vue";
import DataProductViewer from "django-airavata-common-ui/js/components/DataProductViewer.vue";
-import { mapActions, mapState } from "vuex";
+import { mapActions, mapGetters, mapState } from "vuex";
export default {
name: "experiment-summary",
@@ -318,6 +312,7 @@ export default {
"launching",
"clonedExperiment",
]),
+ ...mapGetters("viewExperiment", ["finishedOrExecuting"]),
localFullExperiment() {
return this.fullExperiment;
},
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 43b8403..ed37b55 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
@@ -4,10 +4,10 @@
<h6>{{ experimentOutput.name }}</h6>
<b-dropdown v-if="showMenu" :text="currentView['name']" class="ml-auto">
<b-dropdown-item
- v-for="view in outputViews"
+ v-for="(view, index) in outputViews"
:key="view['provider-id']"
:active="view['provider-id'] === currentView['provider-id']"
- @click="selectView(view)"
+ @click="selectView(index)"
>{{ view["name"] }}</b-dropdown-item
>
</b-dropdown>
@@ -24,6 +24,15 @@
:parameters="viewData.interactive"
@input="parametersUpdated"
/>
+ <div slot="footer" v-if="dataProducts.length > 0 || isExecuting">
+ <template v-if="isExecuting">
+ <b-btn size="sm" @click="fetchLatest">Fetch Latest</b-btn>
+ </template>
+ <template v-else>
+ <!-- TODO: support downloading URI_COLLECTIONs as well -->
+ <b-btn size="sm" :href="dataProducts[0].downloadURL">Download</b-btn>
+ </template>
+ </div>
</b-card>
</template>
@@ -37,6 +46,7 @@ import LinkOutputDisplay from "./LinkOutputDisplay";
import NotebookOutputDisplay from "./NotebookOutputDisplay";
import InteractiveParametersPanel from "./interactive-parameters/InteractiveParametersPanel";
import OutputViewDataLoader from "./OutputViewDataLoader";
+import { mapActions, mapGetters, mapState } from "vuex";
export default {
name: "output-viewer-container",
@@ -45,19 +55,6 @@ export default {
type: models.OutputDataObjectType,
required: true,
},
- outputViews: {
- type: Array,
- required: true,
- },
- dataProducts: {
- type: Array,
- required: false,
- default: null,
- },
- experimentId: {
- type: String,
- required: true,
- },
},
components: {
"data-product-viewer": components.DataProductViewer,
@@ -69,25 +66,46 @@ export default {
InteractiveParametersPanel,
},
created() {
- if (this.providerId !== "default") {
+ if (this.providerId && this.providerId !== "default") {
this.loader = this.createLoader();
this.loader.load();
}
},
data() {
return {
- currentView: this.outputViews[0],
+ currentViewIndex: 0,
loader: null,
};
},
computed: {
+ ...mapState("viewExperiment", ["fullExperiment"]),
+ ...mapGetters("viewExperiment", [
+ "outputDataProducts",
+ "experimentId",
+ "isExecuting",
+ ]),
+ outputViews() {
+ return this.fullExperiment
+ ? this.fullExperiment.outputViews[this.experimentOutput.name]
+ : [];
+ },
+ dataProducts() {
+ return this.outputDataProducts[this.experimentOutput.name];
+ },
+ currentView() {
+ return this.outputViews.length > this.currentViewIndex
+ ? this.outputViews[this.currentViewIndex]
+ : null;
+ },
viewData() {
return this.loader && this.loader.data
? this.loader.data
: this.outputViewData;
},
outputViewData() {
- return this.currentView.data ? this.currentView.data : {};
+ return this.currentView && this.currentView.data
+ ? this.currentView.data
+ : {};
},
displayTypeData() {
return {
@@ -114,7 +132,7 @@ export default {
};
},
displayType() {
- return this.currentView["display-type"];
+ return this.currentView ? this.currentView["display-type"] : null;
},
outputDisplayComponentName() {
if (this.displayType in this.displayTypeData) {
@@ -134,15 +152,16 @@ export default {
return this.outputViews.length > 1;
},
providerId() {
- return this.currentView["provider-id"];
+ return this.currentView ? this.currentView["provider-id"] : null;
},
hasInteractiveParameters() {
return this.viewData && this.viewData.interactive;
},
},
methods: {
- selectView(outputView) {
- this.currentView = outputView;
+ ...mapActions("viewExperiment", ["submitFetchIntermediateOutputs"]),
+ selectView(outputViewIndex) {
+ this.currentViewIndex = outputViewIndex;
if (this.outputDataURL === null) {
this.loader = null;
} else {
@@ -168,6 +187,11 @@ export default {
providerId: this.providerId,
});
},
+ fetchLatest() {
+ this.submitFetchIntermediateOutputs({
+ outputNames: [this.experimentOutput.name],
+ });
+ },
},
};
</script>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/store/modules/view-experiment.js b/django_airavata/apps/workspace/static/django_airavata_workspace/js/store/modules/view-experiment.js
index 72e09c8..9f2fc26 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/store/modules/view-experiment.js
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/store/modules/view-experiment.js
@@ -1,4 +1,5 @@
-import { services } from "django-airavata-api";
+import { models, services } from "django-airavata-api";
+import ExperimentState from "django-airavata-api/static/django_airavata_api/js/models/ExperimentState";
// const PROMISES = {
// workspacePreferences: null,
@@ -103,11 +104,59 @@ export const actions = {
});
dispatch("loadExperiment", { experimentId: getters.experimentId });
},
+ async submitFetchIntermediateOutputs({ dispatch, getters }, { outputNames }) {
+ await services.ExperimentService.fetchIntermediateOutputs({
+ lookup: getters.experimentId,
+ data: {
+ outputNames,
+ },
+ });
+ dispatch("loadExperiment", { experimentId: getters.experimentId });
+ },
};
+function getDataProducts(io, collection) {
+ if (!io.value || !collection) {
+ return [];
+ }
+ let dataProducts = null;
+ if (io.type === models.DataType.URI_COLLECTION) {
+ const dataProductURIs = io.value.split(",");
+ dataProducts = dataProductURIs.map((uri) =>
+ collection.find((dp) => dp.productUri === uri)
+ );
+ } else {
+ const dataProductURI = io.value;
+ dataProducts = collection.filter((dp) => dp.productUri === dataProductURI);
+ }
+ return dataProducts ? dataProducts.filter((dp) => (dp ? true : false)) : [];
+}
+
export const getters = {
isPolling: (state) => state.polling,
- experimentId: (state) => state.fullExperiment.experimentId,
+ experimentId: (state) =>
+ state.fullExperiment ? state.fullExperiment.experimentId : null,
+ experiment: (state) =>
+ state.fullExperiment ? state.fullExperiment.experiment : null,
+ isExecuting: (state, getters) =>
+ getters.experiment &&
+ getters.experiment.latestStatus &&
+ getters.experiment.latestStatus.state === ExperimentState.EXECUTING,
+ finishedOrExecuting: (state, getters) =>
+ getters.experiment &&
+ (getters.experiment.isFinished || getters.isExecuting),
+ outputDataProducts(state) {
+ const result = {};
+ if (state.fullExperiment && state.fullExperiment.outputDataProducts) {
+ state.fullExperiment.experiment.experimentOutputs.forEach((output) => {
+ result[output.name] = getDataProducts(
+ output,
+ state.fullExperiment.outputDataProducts
+ );
+ });
+ }
+ return result;
+ },
};
export default {