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

[airavata-django-portal] 11/11: AIRAVATA-2990 Process level details added to ExperimentDetailsView

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 d6441a853a04c2975a6fe8e5f55a37e956cd9aed
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Jun 14 12:47:31 2019 -0400

    AIRAVATA-2990 Process level details added to ExperimentDetailsView
---
 .../statistics/ExperimentDetailsView.vue           | 193 ++++++++++++++++++++-
 .../api/static/django_airavata_api/js/index.js     |   2 +
 .../django_airavata_api/js/models/ProcessModel.js  |  94 +++++++++-
 .../django_airavata_api/js/models/ProcessState.js  |  19 ++
 .../django_airavata_api/js/models/ProcessStatus.js |  21 +++
 .../js/models/ProcessWorkflow.js                   |  17 ++
 .../static/django_airavata_api/js/models/Task.js   |  55 ++++++
 .../django_airavata_api/js/models/TaskState.js     |   4 +
 .../django_airavata_api/js/models/TaskStatus.js    |  22 +++
 .../django_airavata_api/js/models/TaskTypes.js     |  11 ++
 .../js/components/experiment/ExperimentSummary.vue |  18 +-
 .../output-displays/DownloadOutputDisplay.vue      |   2 +-
 12 files changed, 437 insertions(+), 21 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
index 6cf4ad6..471555a 100644
--- 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
@@ -73,6 +73,7 @@
           <th>Status</th>
           <th>Creation Time</th>
           </thead>
+      <tbody>
         <tr
           v-for="(jobDetail, index) in fullExperiment.jobDetails"
           :key="jobDetail.jobId"
@@ -84,6 +85,7 @@
             <span :title="jobDetail.creationTime.toString()">{{ jobCreationTimes[index] }}</span>
           </td>
         </tr>
+      </tbody>
     </table>
     </td>
     </tr>
@@ -104,6 +106,18 @@
         </div>
       </td>
     </tr>
+    <tr v-if="fullExperiment.jobDetails && fullExperiment.jobDetails.length > 0">
+      <th scope="row">Job Description</th>
+      <td>
+        <b-card
+          v-for="jobDetail in fullExperiment.jobDetails"
+          :key="jobDetail.jobId"
+          :header="jobDetail.jobName"
+        >
+          <pre>{{ jobDetail.jobDescription }}</pre>
+        </b-card>
+      </td>
+    </tr>
     <tr>
       <th scope="row">Creation Time</th>
       <td>
@@ -149,7 +163,6 @@
               v-else-if="input.type.isFileValueType"
               :data-product="dp"
               :input-file="true"
-              class="data-product"
               :key="dp.productUri"
             />
           </li>
@@ -157,12 +170,150 @@
       </td>
     </tr>
     <tr>
-      <!-- TODO -->
+      <th scope="row">Outputs</th>
+      <td>
+        <ul>
+          <li
+            v-for="output in experiment.experimentOutputs"
+            :key="output.name"
+          >
+            {{ output.name }}:
+            <template v-if="output.type.isSimpleValueType">
+              <span class="text-break">{{ output.value }}</span>
+            </template>
+            <data-product-viewer
+              v-for="dp in outputDataProducts[output.name]"
+              v-else-if="output.type.isFileValueType"
+              :data-product="dp"
+              :key="dp.productUri"
+            />
+          </li>
+        </ul>
+      </td>
+    </tr>
+    <tr v-if="storageDirLink">
+      <th scope="row">Storage Directory</th>
+      <td>
+        <b-link :href="storageDirLink">Open</b-link>
+      </td>
+    </tr>
+    <tr>
       <th scope="row">Errors</th>
-      <td></td>
+      <td>
+        <b-card
+          v-for="error in experiment.errors"
+          :key="error.errorId"
+          header="Error"
+        >
+          <p>{{error.userFriendlyMessage}}</p>
+          <pre class="pre-scrollable">{{error.actualErrorMessage}}</pre>
+        </b-card>
+      </td>
     </tr>
+    <template v-if="failedJobs.length > 0">
+
+      <tr
+        v-for="job in failedJobs"
+        :key="job.jobId"
+      >
+        <th scope="row">Job Submission Response</th>
+        <td>
+          <b-card
+            v-if="job.stdOut"
+            :header="job.jobName + ' STDOUT'"
+          >
+            <pre class="pre-scrollable">{{job.stdOut}}</pre>
+          </b-card>
+          <b-card
+            v-if="job.stdErr"
+            :header="job.jobName + ' STDERR'"
+          >
+            <pre class="pre-scrollable">{{job.stdErr}}</pre>
+          </b-card>
+        </td>
+      </tr>
+    </template>
     </tbody>
     </table>
+    <h2 class="h5 mb-3">Process Details</h2>
+    <b-card
+      v-for="process in experiment.processes"
+      :key="process.processId"
+      :header="process.processId"
+    >
+      <b-card
+        v-for="task in process.sortedTasks"
+        :key="task.taskId"
+        :header="task.taskId"
+      >
+        <table class="table table-sm">
+          <tbody>
+            <tr>
+              <th scope="row">Task Id</th>
+              <td>{{task.taskId}}</td>
+            </tr>
+            <tr>
+              <th scope="row">Task Type</th>
+              <td>{{task.taskType.name}}</td>
+            </tr>
+            <tr>
+              <th scope="row">Task Status</th>
+              <td>{{task.latestStatus.state.name}}</td>
+            </tr>
+            <tr>
+              <th scope="row">Task Status Time</th>
+              <td>
+                <human-date :date="task.latestStatus.timeOfStateChange" />
+              </td>
+            </tr>
+            <tr>
+              <th scope="row">Task Status Reason</th>
+              <td>{{ task.latestStatus.reason }}</td>
+            </tr>
+            <template v-if="task.taskErrors && task.taskErrors.length > 0">
+
+              <tr>
+                <th scope="row">Task Errors</th>
+                <td>
+                  <b-card
+                    v-for="error in task.taskErrors"
+                    :key="error.errorId"
+                    :header="error.errorId"
+                  >
+                    <p>{{error.userFriendlyMessage}}</p>
+                    <pre class="pre-scrollable">{{error.actualErrorMessage}}</pre>
+                  </b-card>
+                </td>
+              </tr>
+            </template>
+            <template v-if="task.jobs && task.jobs.length > 0">
+              <tr>
+                <th scope="row">Jobs</th>
+                <td>
+                  <b-card
+                    v-for="job in task.jobs"
+                    :key="job.jobId"
+                    :header="job.jobName"
+                  >
+                    <pre>{{ job.jobDescription }}</pre>
+                  </b-card>
+                </td>
+              </tr>
+            </template>
+          </tbody>
+        </table>
+      </b-card>
+
+      <b-card
+        v-for="error in process.processErrors"
+        :key="error.errorId"
+        :header="'Process Error ' + error.errorId"
+      >
+        <p>{{error.userFriendlyMessage}}</p>
+        <pre class="pre-scrollable">{{error.actualErrorMessage}}</pre>
+      </b-card>
+    </b-card>
+
   </div>
 </template>
 
@@ -182,7 +333,8 @@ export default {
   },
   components: {
     "clipboard-copy-link": components.ClipboardCopyLink,
-    "data-product-viewer": components.DataProductViewer
+    "data-product-viewer": components.DataProductViewer,
+    "human-date": components.HumanDate
   },
   data() {
     return {
@@ -226,6 +378,25 @@ export default {
       return this.fullExperiment.jobDetails.map(jobDetail =>
         moment(jobDetail.creationTime).fromNow()
       );
+    },
+    storageDirLink() {
+      if (this.experiment.relativeExperimentDataDir) {
+        return this.storageDirectory(this.experiment.relativeExperimentDataDir);
+      } else {
+        return null;
+      }
+    },
+    failedJobs() {
+      if (this.fullExperiment && this.fullExperiment.jobDetails) {
+        return this.fullExperiment.jobDetails.filter(
+          job =>
+            this.experiment.latestStatus.state ===
+              models.ExperimentState.FAILED ||
+            job.latestJobStatus.jobState === models.JobState.FAILED
+        );
+      } else {
+        return [];
+      }
     }
   },
   created() {
@@ -251,8 +422,22 @@ export default {
         );
       }
       return dataProducts ? dataProducts.filter(dp => (dp ? true : false)) : [];
+    },
+    storageDirectory(relativePath) {
+      if (relativePath.startsWith("/")) {
+        relativePath = relativePath.substring(1);
+      }
+      return "/workspace/storage/~/" + relativePath;
     }
   }
 };
 </script>
 
+<style scoped>
+.table {
+  table-layout: fixed;
+}
+.table th[scope="row"] {
+  width: 20%;
+}
+</style>
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/index.js b/django_airavata/apps/api/static/django_airavata_api/js/index.js
index 1729443..a43fecf 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/index.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/index.js
@@ -22,6 +22,7 @@ import GroupComputeResourcePreference from "./models/GroupComputeResourcePrefere
 import GroupPermission from "./models/GroupPermission";
 import GroupResourceProfile from "./models/GroupResourceProfile";
 import InputDataObjectType from "./models/InputDataObjectType";
+import JobState from "./models/JobState";
 import ManagedUserProfile from "./models/ManagedUserProfile";
 import OutputDataObjectType from "./models/OutputDataObjectType";
 import ParallelismType from "./models/ParallelismType";
@@ -77,6 +78,7 @@ const models = {
   GroupPermission,
   GroupResourceProfile,
   InputDataObjectType,
+  JobState,
   ManagedUserProfile,
   OutputDataObjectType,
   ParallelismType,
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ProcessModel.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ProcessModel.js
index ee37636..fd962a3 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/ProcessModel.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ProcessModel.js
@@ -1,13 +1,93 @@
-import BaseModel from './BaseModel';
+import BaseModel from "./BaseModel";
+import ProcessStatus from "./ProcessStatus";
+import InputDataObjectType from "./InputDataObjectType";
+import OutputDataObjectType from "./OutputDataObjectType";
+import ComputationalResourceSchedulingModel from "./ComputationalResourceSchedulingModel";
+import Task from "./Task";
+import ErrorModel from "./ErrorModel";
+import ProcessWorkflow from "./ProcessWorkflow";
 
 const FIELDS = [
-    'processId',
-    'experimentId',
-    // TODO: finish mapping fields
+  "processId",
+  "experimentId",
+  {
+    name: "creationTime",
+    type: Date
+  },
+  {
+    name: "lastUpdateTime",
+    type: Date
+  },
+  {
+    name: "processStatuses",
+    type: ProcessStatus,
+    list: true
+  },
+  "processDetail",
+  "applicationInterfaceId",
+  "applicationDeploymentId",
+  "computeResourceId",
+  {
+    name: "processInputs",
+    type: InputDataObjectType,
+    list: true
+  },
+  {
+    name: "processOutputs",
+    type: OutputDataObjectType,
+    list: true
+  },
+  {
+    name: "processResourceSchedule",
+    type: ComputationalResourceSchedulingModel
+  },
+  {
+    name: "tasks",
+    type: Task,
+    list: true
+  },
+  "taskDag",
+  {
+    name: "processErrors",
+    type: ErrorModel,
+    list: true
+  },
+  "gatewayExecutionId",
+  "enableEmailNotification",
+  "emailAddresses",
+  "storageResourceId",
+  "userDn",
+  "generateCert",
+  "experimentDataDir",
+  "userName",
+  "useUserCRPref",
+  "groupResourceProfileId",
+  {
+    name: "processWorkflows",
+    type: ProcessWorkflow,
+    list: true
+  }
 ];
 
 export default class ProcessModel extends BaseModel {
-    constructor(data = {}) {
-        super(FIELDS, data);
-    }
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+
+  /**
+   * Return tasks sorted by task DAG order.
+   */
+  get sortedTasks() {
+    const tasksArrCopy = this.tasks.slice();
+    tasksArrCopy.sort((a, b) => {
+      const aIndex = this.taskDagArray.findIndex(t => t === a.taskId);
+      const bIndex = this.taskDagArray.findIndex(t => t === b.taskId);
+      return aIndex - bIndex;
+    });
+    return tasksArrCopy;
+  }
+
+  get taskDagArray() {
+    return this.taskDag ? this.taskDag.split(",") : [];
+  }
 }
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ProcessState.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ProcessState.js
new file mode 100644
index 0000000..7f59e72
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ProcessState.js
@@ -0,0 +1,19 @@
+import BaseEnum from "./BaseEnum";
+
+export default class ProcessState extends BaseEnum {}
+ProcessState.init([
+  "CREATED",
+  "VALIDATED",
+  "STARTED",
+  "PRE_PROCESSING",
+  "CONFIGURING_WORKSPACE",
+  "INPUT_DATA_STAGING",
+  "EXECUTING",
+  "MONITORING",
+  "OUTPUT_DATA_STAGING",
+  "POST_PROCESSING",
+  "COMPLETED",
+  "FAILED",
+  "CANCELLING",
+  "CANCELED"
+]);
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ProcessStatus.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ProcessStatus.js
new file mode 100644
index 0000000..767b170
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ProcessStatus.js
@@ -0,0 +1,21 @@
+import BaseModel from "./BaseModel";
+import ProcessState from "./ProcessState";
+
+const FIELDS = [
+  {
+    name: "state",
+    type: ProcessState
+  },
+  {
+    name: "timeOfStateChange",
+    type: Date
+  },
+  "reason",
+  "statusId"
+];
+
+export default class ProcessStatus extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ProcessWorkflow.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ProcessWorkflow.js
new file mode 100644
index 0000000..4ccd59e
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ProcessWorkflow.js
@@ -0,0 +1,17 @@
+import BaseModel from "./BaseModel";
+
+const FIELDS = [
+  "processId",
+  "workflowId",
+  {
+    name: "creationTime",
+    type: Date
+  },
+  "type"
+];
+
+export default class ProcessWorkflow extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/Task.js b/django_airavata/apps/api/static/django_airavata_api/js/models/Task.js
new file mode 100644
index 0000000..7c7acd4
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/Task.js
@@ -0,0 +1,55 @@
+import BaseModel from "./BaseModel";
+import ErrorModel from "./ErrorModel";
+import Job from "./Job";
+import TaskTypes from "./TaskTypes";
+import TaskStatus from "./TaskStatus";
+
+const FIELDS = [
+  "taskId",
+  {
+    name: "taskType",
+    type: TaskTypes
+  },
+  "parentProcessId",
+  {
+    name: "creationTime",
+    type: Date
+  },
+  {
+    name: "lastUpdateTime",
+    type: Date
+  },
+  {
+    name: "taskStatuses",
+    type: TaskStatus,
+    list: true
+  },
+  "taskDetail",
+  "subTaskModel",
+  {
+    name: "taskErrors",
+    type: ErrorModel,
+    list: true
+  },
+  {
+    name: "jobs",
+    type: Job,
+    list: true
+  },
+  "maxRetry",
+  "currentRetry"
+];
+
+export default class Task extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+
+  get latestStatus() {
+    if (this.taskStatuses && this.taskStatuses.length > 0) {
+      return this.taskStatuses[this.taskStatuses.length - 1];
+    } else {
+      return null;
+    }
+  }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/TaskState.js b/django_airavata/apps/api/static/django_airavata_api/js/models/TaskState.js
new file mode 100644
index 0000000..9c860fe
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/TaskState.js
@@ -0,0 +1,4 @@
+import BaseEnum from "./BaseEnum";
+
+export default class TaskState extends BaseEnum {}
+TaskState.init(["CREATED", "EXECUTING", "COMPLETED", "FAILED", "CANCELED"]);
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/TaskStatus.js b/django_airavata/apps/api/static/django_airavata_api/js/models/TaskStatus.js
new file mode 100644
index 0000000..bbcc5ef
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/TaskStatus.js
@@ -0,0 +1,22 @@
+
+import BaseModel from "./BaseModel";
+import TaskState from "./TaskState";
+
+const FIELDS = [
+  {
+    name: "state",
+    type: TaskState,
+  },
+  {
+    name: "timeOfStateChange",
+    type: Date
+  },
+  "reason",
+  "statusId"
+];
+
+export default class TaskStatus extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/TaskTypes.js b/django_airavata/apps/api/static/django_airavata_api/js/models/TaskTypes.js
new file mode 100644
index 0000000..0a3918a
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/TaskTypes.js
@@ -0,0 +1,11 @@
+import BaseEnum from "./BaseEnum";
+
+export default class TaskTypes extends BaseEnum {}
+TaskTypes.init([
+  "ENV_SETUP",
+  "DATA_STAGING",
+  "JOB_SUBMISSION",
+  "ENV_CLEANUP",
+  "MONITORING",
+  "OUTPUT_FETCHING"
+]);
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 97b1336..9ba38e8 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
@@ -198,7 +198,6 @@
                       v-else-if="input.type.isFileValueType"
                       :data-product="dp"
                       :input-file="true"
-                      class="data-product"
                       :key="dp.productUri"
                     />
                   </li>
@@ -206,9 +205,16 @@
               </td>
             </tr>
             <tr>
-              <!-- TODO -->
               <th scope="row">Errors</th>
-              <td></td>
+              <td>
+                <b-card
+                  v-for="error in experiment.errors"
+                  :key="error.errorId"
+                  header="Error"
+                >
+                  <p>{{error.userFriendlyMessage}}</p>
+                </b-card>
+              </td>
             </tr>
             </tbody>
             </table>
@@ -376,9 +382,3 @@ export default {
   }
 };
 </script>
-
-<style scoped>
-.data-product + .data-product {
-  margin-left: 0.5em;
-}
-</style>
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 603b35a..0234033 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
@@ -1,7 +1,7 @@
 <template>
   <div>
     <data-product-viewer v-for="dp in dataProducts"
-      :data-product="dp" class="data-product" :key="dp.productUri"/>
+      :data-product="dp" :key="dp.productUri"/>
   </div>
 </template>