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>