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 2019/06/17 14:23:23 UTC

[airavata-django-portal] 10/11: AIRAVATA-2990 Experiment details tab view

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

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

commit d447ed96710d80838f19cafb2113861d268baa17
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu Jun 13 09:56:47 2019 -0400

    AIRAVATA-2990 Experiment details tab view
---
 .../statistics/ExperimentDetailsView.vue           | 258 +++++++++++++++++++++
 .../statistics/ExperimentStatisticsContainer.vue   |  35 ++-
 .../js/components/experiment/ExperimentSummary.vue |   3 +-
 .../experiment/input-editors/FileInputEditor.vue   |   3 +-
 .../output-displays/DownloadOutputDisplay.vue      |   3 +-
 .../output-displays/OutputDisplayContainer.vue     |   3 +-
 .../common/js/components}/DataProductViewer.vue    |   0
 django_airavata/static/common/js/index.js          |   2 +
 8 files changed, 296 insertions(+), 11 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentDetailsView.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentDetailsView.vue
new file mode 100644
index 0000000..6cf4ad6
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentDetailsView.vue
@@ -0,0 +1,258 @@
+<template>
+  <div>
+    <table
+      class="table"
+      v-if="fullExperiment"
+    >
+      <tbody>
+        <tr>
+          <th scope="row">Name</th>
+          <td>
+            <div :title="experiment.experimentId">{{ experiment.experimentName }}</div>
+            <small class="text-muted">
+              ID: {{ experiment.experimentId }}
+              (<clipboard-copy-link
+                :text="experiment.experimentId"
+                :link-classes="['text-reset']"
+              >
+                copy
+                <span slot="icon"></span>
+                <span slot="tooltip">Copied ID!</span>
+              </clipboard-copy-link>)
+            </small>
+          </td>
+        </tr>
+        <tr>
+          <th scope="row">Description</th>
+          <td>{{ experiment.description }}</td>
+        </tr>
+        <tr>
+          <th scope="row">Project</th>
+          <td v-if="fullExperiment.project">{{ fullExperiment.projectName }}</td>
+          <td v-else>
+            <em>You don't have access to this project.</em>
+          </td>
+        </tr>
+        <tr>
+          <th scope="row">Owner</th>
+          <td>{{ experiment.userName }}</td>
+        </tr>
+        <tr>
+          <th scope="row">Application</th>
+          <td v-if="fullExperiment.applicationName">{{ fullExperiment.applicationName }}</td>
+          <td
+            v-else
+            class="font-italic text-muted"
+          >Unable to load interface {{ fullExperiment.experiment.executionId }}</td>
+        </tr>
+        <tr>
+          <th scope="row">Compute Resource</th>
+          <td v-if="fullExperiment.computeHostName">{{ fullExperiment.computeHostName }}</td>
+          <td
+            v-else
+            class="font-italic text-muted"
+          >Unable to load compute resource {{ fullExperiment.resourceHostId }}</td>
+        </tr>
+        <tr>
+          <th scope="row">Experiment Status</th>
+          <td>
+            <template v-if="fullExperiment.experiment.isProgressing">
+              <i class="fa fa-sync-alt fa-spin"></i>
+              <span class="sr-only">Progressing...</span>
+            </template>
+            {{ fullExperiment.experimentStatusName }}
+          </td>
+        </tr>
+        <tr v-if="fullExperiment.jobDetails && fullExperiment.jobDetails.length > 0">
+          <th scope="row">Job</th>
+          <td>
+            <table class="table">
+              <thead>
+          <th>Name</th>
+          <th>ID</th>
+          <th>Status</th>
+          <th>Creation Time</th>
+          </thead>
+        <tr
+          v-for="(jobDetail, index) in fullExperiment.jobDetails"
+          :key="jobDetail.jobId"
+        >
+          <td>{{ jobDetail.jobName }}</td>
+          <td>{{ jobDetail.jobId }}</td>
+          <td>{{ jobDetail.jobStatusStateName }}</td>
+          <td>
+            <span :title="jobDetail.creationTime.toString()">{{ jobCreationTimes[index] }}</span>
+          </td>
+        </tr>
+    </table>
+    </td>
+    </tr>
+    <tr>
+      <th scope="row">Notification List</th>
+      <td>{{ experiment.emailAddresses
+        ? experiment.emailAddresses.join(", ")
+        : '' }}</td>
+    </tr>
+    <tr v-if="fullExperiment.jobDetails && fullExperiment.jobDetails.length > 0">
+      <th scope="row">Working Dir</th>
+      <td>
+        <div
+          v-for="jobDetail in fullExperiment.jobDetails"
+          :key="jobDetail.jobId"
+        >
+          {{ jobDetail.jobName }}: {{ jobDetail.workingDir }}
+        </div>
+      </td>
+    </tr>
+    <tr>
+      <th scope="row">Creation Time</th>
+      <td>
+        <span :title="experiment.creationTime.toString()">{{ creationTime }}</span>
+      </td>
+    </tr>
+    <tr>
+      <th scope="row">Last Modified Time</th>
+      <td>
+        <span :title="fullExperiment.experimentStatus.timeOfStateChange.toString()">{{ lastModifiedTime }}</span>
+      </td>
+    </tr>
+    <tr>
+      <th scope="row">Wall Time Limit</th>
+      <td>{{ experiment.userConfigurationData.computationalResourceScheduling.wallTimeLimit }} minutes</td>
+    </tr>
+    <tr>
+      <th scope="row">CPU Count</th>
+      <td>{{ experiment.userConfigurationData.computationalResourceScheduling.totalCPUCount }}</td>
+    </tr>
+    <tr>
+      <th scope="row">Node Count</th>
+      <td>{{ experiment.userConfigurationData.computationalResourceScheduling.nodeCount }}</td>
+    </tr>
+    <tr>
+      <th scope="row">Queue</th>
+      <td>{{ experiment.userConfigurationData.computationalResourceScheduling.queueName }}</td>
+    </tr>
+    <tr>
+      <th scope="row">Inputs</th>
+      <td>
+        <ul>
+          <li
+            v-for="input in experiment.experimentInputs"
+            :key="input.name"
+          >
+            {{ input.name }}:
+            <template v-if="input.type.isSimpleValueType">
+              <span class="text-break">{{ input.value }}</span>
+            </template>
+            <data-product-viewer
+              v-for="dp in inputDataProducts[input.name]"
+              v-else-if="input.type.isFileValueType"
+              :data-product="dp"
+              :input-file="true"
+              class="data-product"
+              :key="dp.productUri"
+            />
+          </li>
+        </ul>
+      </td>
+    </tr>
+    <tr>
+      <!-- TODO -->
+      <th scope="row">Errors</th>
+      <td></td>
+    </tr>
+    </tbody>
+    </table>
+  </div>
+</template>
+
+<script>
+import { models, services } from "django-airavata-api";
+import { components } from "django-airavata-common-ui";
+
+import moment from "moment";
+
+export default {
+  name: "experiment-details-view",
+  props: {
+    experiment: {
+      type: models.Experiment,
+      required: true
+    }
+  },
+  components: {
+    "clipboard-copy-link": components.ClipboardCopyLink,
+    "data-product-viewer": components.DataProductViewer
+  },
+  data() {
+    return {
+      fullExperiment: null
+    };
+  },
+  computed: {
+    inputDataProducts() {
+      const result = {};
+      if (this.fullExperiment && this.fullExperiment.inputDataProducts) {
+        this.fullExperiment.experiment.experimentInputs.forEach(input => {
+          result[input.name] = this.getDataProducts(
+            input,
+            this.fullExperiment.inputDataProducts
+          );
+        });
+      }
+      return result;
+    },
+    outputDataProducts() {
+      const result = {};
+      if (this.fullExperiment && this.fullExperiment.outputDataProducts) {
+        this.fullExperiment.experiment.experimentOutputs.forEach(output => {
+          result[output.name] = this.getDataProducts(
+            output,
+            this.fullExperiment.outputDataProducts
+          );
+        });
+      }
+      return result;
+    },
+    creationTime: function() {
+      return moment(this.fullExperiment.experiment.creationTime).fromNow();
+    },
+    lastModifiedTime: function() {
+      return moment(
+        this.fullExperiment.experimentStatus.timeOfStateChange
+      ).fromNow();
+    },
+    jobCreationTimes: function() {
+      return this.fullExperiment.jobDetails.map(jobDetail =>
+        moment(jobDetail.creationTime).fromNow()
+      );
+    }
+  },
+  created() {
+    services.FullExperimentService.retrieve({
+      lookup: this.experiment.experimentId
+    }).then(fullExperiment => (this.fullExperiment = fullExperiment));
+  },
+  methods: {
+    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)) : [];
+    }
+  }
+};
+</script>
+
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue
index cf784ae..1030d49 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/statistics/ExperimentStatisticsContainer.vue
@@ -228,8 +228,23 @@
                 >
                   <experiment-status-badge :status-name="data.value.name" />
                 </template>
+                <template
+                  slot="actions"
+                  slot-scope="data"
+                >
+                  <b-link @click="showExperimentDetails(data.item.experimentId)">
+                    View Details
+                    <i
+                      class="far fa-chart-bar"
+                      aria-hidden="true"
+                    ></i>
+                  </b-link>
+                </template>
               </b-table>
             </b-tab>
+            <b-tab v-for="experimentDetail in experimentDetails" :key="experimentDetail.experimentId" :title="experimentDetail.experimentName">
+              <experiment-details-view :experiment="experimentDetail"/>
+            </b-tab>
           </b-tabs>
         </b-card>
       </div>
@@ -240,6 +255,7 @@
 import { models, services, utils } from "django-airavata-api";
 import { components } from "django-airavata-common-ui";
 import ExperimentStatisticsCard from "./ExperimentStatisticsCard";
+import ExperimentDetailsView from "./ExperimentDetailsView";
 
 import moment from "moment";
 
@@ -267,7 +283,8 @@ export default {
       hostnameFilterEnabled: false,
       hostnameFilter: null,
       appInterfaces: null,
-      computeResourceNames: null
+      computeResourceNames: null,
+      experimentDetails: []
     };
   },
   created() {
@@ -276,6 +293,7 @@ export default {
     this.loadComputeResources();
   },
   components: {
+    ExperimentDetailsView,
     ExperimentStatisticsCard,
     "application-name": components.ApplicationName,
     "compute-resource-name": components.ComputeResourceName,
@@ -395,9 +413,13 @@ export default {
         return "Created Experiments";
       } else if (this.selectedExperimentSummariesKey === "runningExperiments") {
         return "Running Experiments";
-      } else if (this.selectedExperimentSummariesKey === "completedExperiments") {
+      } else if (
+        this.selectedExperimentSummariesKey === "completedExperiments"
+      ) {
         return "Completed Experiments";
-      } else if (this.selectedExperimentSummariesKey === "cancelledExperiments") {
+      } else if (
+        this.selectedExperimentSummariesKey === "cancelledExperiments"
+      ) {
         return "Cancelled Experiments";
       } else if (this.selectedExperimentSummariesKey === "failedExperiments") {
         return "Failed Experiments";
@@ -476,6 +498,13 @@ export default {
       this.hostnameFilter = null;
       this.hostnameFilterEnabled = false;
       this.loadStatistics();
+    },
+    showExperimentDetails(experimentId) {
+      // TODO: if experiment details already loaded, select its tab
+      // TODO: maybe don't need to load the experiment first since ExperimentDetailsView will load FullExperiment?
+      services.ExperimentService.retrieve({
+        lookup: experimentId
+      }).then(exp => this.experimentDetails.push(exp));
     }
   }
 };
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 0339172..97b1336 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
@@ -222,7 +222,6 @@
 <script>
 import { models, services } from "django-airavata-api";
 import { components } from "django-airavata-common-ui";
-import DataProductViewer from "./DataProductViewer.vue";
 import OutputDisplayContainer from "./output-displays/OutputDisplayContainer";
 import urls from "../../utils/urls";
 
@@ -246,7 +245,7 @@ export default {
     };
   },
   components: {
-    DataProductViewer,
+    "data-product-viewer": components.DataProductViewer,
     "clipboard-copy-link": components.ClipboardCopyLink,
     "share-button": components.ShareButton,
     OutputDisplayContainer
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/FileInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/FileInputEditor.vue
index 2c65b9f..c1daef4 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/FileInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/FileInputEditor.vue
@@ -56,7 +56,6 @@
 <script>
 import { models, services, utils } from "django-airavata-api";
 import { InputEditorMixin } from "django-airavata-workspace-plugin-api";
-import DataProductViewer from "../DataProductViewer.vue";
 import { components } from "django-airavata-common-ui";
 import UserStorageFileSelectionContainer from "../../storage/UserStorageFileSelectionContainer";
 
@@ -64,7 +63,7 @@ export default {
   name: "file-input-editor",
   mixins: [InputEditorMixin],
   components: {
-    DataProductViewer,
+    "data-product-viewer": components.DataProductViewer,
     "delete-link": components.DeleteLink,
     UserStorageFileSelectionContainer
   },
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/DownloadOutputDisplay.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/DownloadOutputDisplay.vue
index 51657c5..603b35a 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/DownloadOutputDisplay.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/DownloadOutputDisplay.vue
@@ -7,7 +7,6 @@
 
 <script>
 import { models } from "django-airavata-api"
-import DataProductViewer from "../DataProductViewer.vue";
 
 export default {
   name: "download-output-viewer",
@@ -25,7 +24,7 @@ export default {
     }
   },
   components: {
-    DataProductViewer
+    "data-product-viewer": components.DataProductViewer
   }
 }
 </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 0b2d400..ed8bbe0 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,7 +24,6 @@
 import { models } from "django-airavata-api";
 import DownloadOutputDisplay from "./DownloadOutputDisplay";
 import LinkDisplay from "./LinkDisplay";
-import DataProductViewer from "../DataProductViewer";
 
 export default {
   name: "output-viewer-container",
@@ -44,7 +43,7 @@ export default {
     }
   },
   components: {
-    DataProductViewer,
+    "data-product-viewer": components.DataProductViewer,
     DownloadOutputDisplay,
     LinkDisplay
   },
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/DataProductViewer.vue b/django_airavata/static/common/js/components/DataProductViewer.vue
similarity index 100%
rename from django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/DataProductViewer.vue
rename to django_airavata/static/common/js/components/DataProductViewer.vue
diff --git a/django_airavata/static/common/js/index.js b/django_airavata/static/common/js/index.js
index e7eda5d..48a040c 100644
--- a/django_airavata/static/common/js/index.js
+++ b/django_airavata/static/common/js/index.js
@@ -5,6 +5,7 @@ import ClipboardCopyButton from "./components/ClipboardCopyButton.vue";
 import ClipboardCopyLink from "./components/ClipboardCopyLink.vue";
 import ComputeResourceName from "./components/ComputeResourceName";
 import ConfirmationDialog from "./components/ConfirmationDialog.vue";
+import DataProductViewer from "./components/DataProductViewer";
 import DeleteButton from "./components/DeleteButton.vue";
 import DeleteLink from "./components/DeleteLink.vue";
 import ExperimentStatusBadge from "./components/ExperimentStatusBadge";
@@ -40,6 +41,7 @@ const components = {
   ClipboardCopyLink,
   ComputeResourceName,
   ConfirmationDialog,
+  DataProductViewer,
   DeleteButton,
   DeleteLink,
   ExperimentStatusBadge,