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 {