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:25 UTC

[airavata-django-portal] 04/16: AIRAVATA-3557 Display text preview inline of intermediate and final text files

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 e46aff105a91f70a9fc8921f1861eb26fa15cac2
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Feb 4 14:28:45 2022 -0500

    AIRAVATA-3557 Display text preview inline of intermediate and final text files
---
 .../output-displays/DefaultOutputDisplay.vue       | 150 +++++++++++++++++++--
 .../output-displays/OutputDisplayContainer.vue     |  55 ++++++--
 2 files changed, 186 insertions(+), 19 deletions(-)

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 383676c..cf3ed8a 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,18 +1,43 @@
 <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"
+    <!-- Show the final data products if available, otherwise, display intermediate outputs -->
+    <template v-if="dataProducts.length > 0">
+      <pre v-if="finalOutputText">
+        {{ finalOutputText }}
+      </pre>
+      <div v-else v-for="dp in dataProducts" :key="dp.productUri">
+        <img
+          v-if="dp.isImage && dp.downloadURL"
+          class="image-preview rounded"
+          :src="dp.downloadURL"
+        />
+        <data-product-viewer :data-product="dp" :mime-type="fileMimeType" />
+      </div>
+    </template>
+
+    <template v-else-if="intermediateOutputDataProduct">
+      <pre v-if="intermediateOutputText">
+        {{ intermediateOutputText }}
+      </pre>
+      <data-product-viewer
+        v-else
+        :data-product="intermediateOutputDataProduct"
+        :mime-type="fileMimeType"
       />
-      <data-product-viewer :data-product="dp" :mime-type="fileMimeType" />
-    </div>
+    </template>
+    <template v-else-if="intermediateOutputMultipleDataProducts">
+      <div
+        v-for="dp in intermediateOutputMultipleDataProducts"
+        :key="dp.productUri"
+      >
+        <data-product-viewer :data-product="dp" :mime-type="fileMimeType" />
+      </div>
+    </template>
   </div>
 </template>
 
 <script>
-import { models } from "django-airavata-api";
+import { models, utils } from "django-airavata-api";
 import DataProductViewer from "django-airavata-common-ui/js/components/DataProductViewer.vue";
 
 export default {
@@ -30,6 +55,17 @@ export default {
   components: {
     DataProductViewer,
   },
+  data() {
+    return {
+      intermediateOutputText: null,
+      finalOutputText: null,
+    };
+  },
+  async created() {
+    // Check and load intermediate or final output as text if available and applicable
+    this.loadIntermediateOutputText();
+    this.loadFinalOutputText();
+  },
   computed: {
     fileMimeType() {
       if (this.experimentOutput.fileMetadataMimeType) {
@@ -43,6 +79,92 @@ export default {
         return null;
       }
     },
+    intermediateOutputProcessStatusState() {
+      if (
+        this.experimentOutput &&
+        this.experimentOutput.intermediateOutput &&
+        this.experimentOutput.intermediateOutput.processStatus
+      ) {
+        return this.experimentOutput.intermediateOutput.processStatus.state;
+      } else {
+        return null;
+      }
+    },
+    intermediateOutputDataProduct() {
+      if (
+        this.experimentOutput &&
+        this.experimentOutput.intermediateOutput &&
+        this.experimentOutput.intermediateOutput.dataProducts &&
+        this.experimentOutput.intermediateOutput.dataProducts.length === 1
+      ) {
+        return this.experimentOutput.intermediateOutput.dataProducts[0];
+      } else {
+        return null;
+      }
+    },
+    intermediateOutputMultipleDataProducts() {
+      if (
+        this.experimentOutput &&
+        this.experimentOutput.intermediateOutput &&
+        this.experimentOutput.intermediateOutput.dataProducts &&
+        this.experimentOutput.intermediateOutput.dataProducts.length > 1
+      ) {
+        return this.experimentOutput.intermediateOutput.dataProducts;
+      } else {
+        return null;
+      }
+    },
+    intermediateOutputFileSize() {
+      if (this.intermediateOutputDataProduct) {
+        return this.intermediateOutputDataProduct.filesize;
+      } else {
+        return -1;
+      }
+    },
+  },
+  methods: {
+    async loadIntermediateOutputText() {
+      if (
+        this.intermediateOutputDataProduct &&
+        (this.intermediateOutputDataProduct.isText ||
+          this.fileMimeType === "text/plain") &&
+        this.intermediateOutputDataProduct.downloadURL
+      ) {
+        this.intermediateOutputText = await utils.FetchUtils.get(
+          this.intermediateOutputDataProduct.downloadURL,
+          "",
+          {
+            responseType: "text",
+          }
+        );
+      }
+    },
+    async loadFinalOutputText() {
+      if (
+        this.dataProducts &&
+        this.dataProducts.length === 1 &&
+        (this.dataProducts[0].isText || this.fileMimeType === "text/plain") &&
+        this.dataProducts[0].downloadURL
+      ) {
+        this.finalOutputText = await utils.FetchUtils.get(
+          this.dataProducts[0].downloadURL,
+          "",
+          {
+            responseType: "text",
+          }
+        );
+      }
+    },
+  },
+  watch: {
+    intermediateOutputFileSize() {
+      this.loadIntermediateOutputText();
+    },
+    dataProducts(value, oldValue) {
+      if ((!oldValue || oldValue.length === 0) && value && value.length > 0) {
+        this.loadFinalOutputText();
+      }
+    },
   },
 };
 </script>
@@ -52,4 +174,16 @@ export default {
   max-width: 100%;
   max-height: 120px;
 }
+pre {
+  max-height: 340px;
+  overflow: auto;
+  max-width: 100%;
+  margin-bottom: 0;
+  /* background-color: #efefef; */
+  background-color: var(--light);
+  border-style: solid;
+  border-width: 1px;
+  border-color: var(--gray);
+  border-radius: 3px;
+}
 </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 4f3231c..2390048 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
@@ -24,21 +24,19 @@
       :parameters="viewData.interactive"
       @input="parametersUpdated"
     />
-    <div slot="footer" v-if="dataProducts.length > 0 || isExecuting">
+    <div slot="footer" v-if="dataProducts.length > 0 || isExecuting" class="d-flex justify-content-end align-items-baseline">
       <template v-if="isExecuting">
-        <span v-if="experimentOutput.intermediateOutput.processStatus"
-          >Status of intermediate output fetch:
-          {{
-            experimentOutput.intermediateOutput.processStatus.state.name
-          }}</span
+        <span class="small text-muted mr-2">
+          {{ fetchIntermediateOutputStatusMessage }}</span
         >
-        <b-btn size="sm" @click="fetchLatest" :disabled="fetchLatestDisabled"
-          >Fetch Latest</b-btn
+        <b-btn size="sm" @click="fetchLatest" :disabled="fetchLatestDisabled">
+          <b-spinner small v-if="fetchLatestDisabled"></b-spinner>
+          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>
+        <b-btn size="sm" :href="dataProducts[0].downloadURL + '&download'">Download</b-btn>
       </template>
     </div>
   </b-card>
@@ -55,6 +53,7 @@ import NotebookOutputDisplay from "./NotebookOutputDisplay";
 import InteractiveParametersPanel from "./interactive-parameters/InteractiveParametersPanel";
 import OutputViewDataLoader from "./OutputViewDataLoader";
 import { mapActions, mapGetters, mapState } from "vuex";
+import ProcessState from "django-airavata-api/static/django_airavata_api/js/models/ProcessState";
 
 export default {
   name: "output-viewer-container",
@@ -168,13 +167,47 @@ export default {
     canFetchIntermediateOutput() {
       return (
         this.isExecuting &&
-        (!this.experimentOutput.intermediateOutput.processStatus ||
-          this.experimentOutput.intermediateOutput.processStatus.isFinished)
+        (!this.experimentOutput.intermediateOutput ||
+          !this.experimentOutput.intermediateOutput.processStatus ||
+          (this.experimentOutput.intermediateOutput.processStatus &&
+            this.experimentOutput.intermediateOutput.processStatus.isFinished))
       );
     },
     fetchLatestDisabled() {
       return !this.canFetchIntermediateOutput;
     },
+    fetchIntermediateOutputStatusMessage() {
+      let msg = "";
+      if (
+        this.experimentOutput.intermediateOutput &&
+        this.experimentOutput.intermediateOutput.dataProducts &&
+        this.experimentOutput.intermediateOutput.dataProducts.length > 0
+      ) {
+        const timestamp = this.experimentOutput.intermediateOutput
+          .dataProducts[0].lastModifiedTime;
+        msg +=
+          "Latest output fetched on " +
+          timestamp.toLocaleString([], {
+            dateStyle: "medium",
+            timeStyle: "short",
+          }) +
+          ". ";
+      }
+      if (
+        this.experimentOutput.intermediateOutput &&
+        this.experimentOutput.intermediateOutput.processStatus
+      ) {
+        if (
+          this.experimentOutput.intermediateOutput.processStatus.state ===
+          ProcessState.FAILED
+        ) {
+          msg += "Last fetch failed, please try again.";
+        }
+      }
+      return msg;
+      // TODO: show timestamp for data products
+      // TODO: show failure message if failed
+    },
   },
   methods: {
     ...mapActions("viewExperiment", ["submitFetchIntermediateOutputs"]),