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/05/13 15:59:57 UTC
[airavata-django-portal] branch master updated: AIRAVATA-3039
Handle deleted application when viewing/editing experiment
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
The following commit(s) were added to refs/heads/master by this push:
new 0266b7e AIRAVATA-3039 Handle deleted application when viewing/editing experiment
0266b7e is described below
commit 0266b7e9d0e0cb091e8b6ed30930bdf86e69c008
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Mon May 13 11:59:08 2019 -0400
AIRAVATA-3039 Handle deleted application when viewing/editing experiment
Don't allow editing or cloning an experiment where the application
is no longer available.
---
.../django_airavata_api/js/models/Experiment.js | 8 ++
.../js/models/FullExperiment.js | 4 +
django_airavata/apps/api/views.py | 26 +++++--
.../js/components/experiment/ExperimentSummary.vue | 16 +++-
.../js/containers/EditExperimentContainer.vue | 25 +++++-
.../js/containers/ExperimentListContainer.vue | 88 +++++++++++++++++-----
.../js/containers/RecentExperimentsContainer.vue | 58 ++++++++------
7 files changed, 166 insertions(+), 59 deletions(-)
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/Experiment.js b/django_airavata/apps/api/static/django_airavata_api/js/models/Experiment.js
index 3ff8c35..7052299 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/Experiment.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/Experiment.js
@@ -127,6 +127,14 @@ export default class Experiment extends BaseModel {
);
}
+ get resourceHostId() {
+ return this.userConfigurationData &&
+ this.userConfigurationData.computationalResourceScheduling
+ ? this.userConfigurationData.computationalResourceScheduling
+ .resourceHostId
+ : null;
+ }
+
populateInputsOutputsFromApplicationInterface(applicationInterface) {
// Copy application inputs and outputs to the experiment
this.experimentInputs = applicationInterface.applicationInputs.map(input =>
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/FullExperiment.js b/django_airavata/apps/api/static/django_airavata_api/js/models/FullExperiment.js
index 62b8700..60b0cfb 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/FullExperiment.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/FullExperiment.js
@@ -59,6 +59,10 @@ export default class FullExperiment extends BaseModel {
return this.computeResource ? this.computeResource.hostName : null;
}
+ get resourceHostId() {
+ return this.experiment.resourceHostId;
+ }
+
get experimentStatus() {
return this.experiment.latestStatus;
}
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index 75151a9..989e4ae 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -404,19 +404,29 @@ class FullExperimentViewSet(mixins.RetrieveModelMixin,
for dp in inp.value.split(',')
if inp.value.startswith('airavata-dp')]
appInterfaceId = experimentModel.executionId
- applicationInterface = self.request.airavata_client \
- .getApplicationInterface(self.authz_token, appInterfaceId)
- appModuleId = applicationInterface.applicationModules[0]
- applicationModule = self.request.airavata_client \
- .getApplicationModule(self.authz_token, appModuleId)
+ try:
+ applicationInterface = self.request.airavata_client \
+ .getApplicationInterface(self.authz_token, appInterfaceId)
+ appModuleId = applicationInterface.applicationModules[0]
+ applicationModule = self.request.airavata_client \
+ .getApplicationModule(self.authz_token, appModuleId)
+ except Exception as e:
+ log.exception("Failed to load app interface/module")
+ applicationModule = None
+
compute_resource_id = None
user_conf = experimentModel.userConfigurationData
if user_conf and user_conf.computationalResourceScheduling:
comp_res_sched = user_conf.computationalResourceScheduling
compute_resource_id = comp_res_sched.resourceHostId
- compute_resource = self.request.airavata_client.getComputeResource(
- self.authz_token, compute_resource_id) \
- if compute_resource_id else None
+ try:
+ compute_resource = self.request.airavata_client.getComputeResource(
+ self.authz_token, compute_resource_id) \
+ if compute_resource_id else None
+ except Exception as e:
+ log.exception("Failed to load compute resource for {}".format(
+ compute_resource_id))
+ compute_resource = None
try:
project = self.request.airavata_client.getProject(
self.authz_token, experimentModel.projectId)
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 f2003d5..65320e6 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
@@ -8,11 +8,11 @@
</div>
<div class="col-auto">
<share-button :entity-id="experiment.experimentId" />
- <b-link v-if="experiment.isEditable" class="btn btn-primary" :href="editLink">
+ <b-link v-if="isEditable" class="btn btn-primary" :href="editLink">
Edit
<i class="fa fa-edit" aria-hidden="true"></i>
</b-link>
- <b-btn variant="primary" @click="clone">
+ <b-btn v-if="isClonable" variant="primary" @click="clone">
Clone
<i class="fa fa-copy" aria-hidden="true"></i>
</b-btn>
@@ -77,11 +77,13 @@
</tr>
<tr>
<th scope="row">Application</th>
- <td>{{ localFullExperiment.applicationName }}</td>
+ <td v-if="localFullExperiment.applicationName">{{ localFullExperiment.applicationName }}</td>
+ <td v-else class="font-italic text-muted">Unable to load interface {{ localFullExperiment.experiment.executionId }}</td>
</tr>
<tr>
<th scope="row">Compute Resource</th>
- <td>{{ localFullExperiment.computeHostName }}</td>
+ <td v-if="localFullExperiment.computeHostName">{{ localFullExperiment.computeHostName }}</td>
+ <td v-else class="font-italic text-muted">Unable to load compute resource {{ localFullExperiment.resourceHostId }}</td>
</tr>
<tr>
<th scope="row">Experiment Status</th>
@@ -248,6 +250,12 @@ export default {
},
editLink() {
return urls.editExperiment(this.experiment);
+ },
+ isEditable() {
+ return this.experiment.isEditable && this.localFullExperiment.applicationName;
+ },
+ isClonable() {
+ return this.localFullExperiment.applicationName;
}
},
methods: {
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/EditExperimentContainer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/EditExperimentContainer.vue
index 4c9f67b..02afbf2 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/EditExperimentContainer.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/EditExperimentContainer.vue
@@ -12,6 +12,7 @@
<script>
import { services } from "django-airavata-api";
+import { notifications } from "django-airavata-common-ui";
import ExperimentEditor from "../components/experiment/ExperimentEditor.vue";
import urls from "../utils/urls";
@@ -39,7 +40,7 @@ export default {
},
handleSavedAndLaunchedExperiment: function(experiment) {
// Redirect to experiment view
- urls.navigateToViewExperiment(experiment, {launching: true});
+ urls.navigateToViewExperiment(experiment, { launching: true });
}
},
computed: {},
@@ -48,9 +49,14 @@ export default {
.then(experiment => {
this.experiment = experiment;
const appInterfaceId = experiment.executionId;
- return services.ApplicationInterfaceService.retrieve({
- lookup: appInterfaceId
- });
+ return services.ApplicationInterfaceService.retrieve(
+ {
+ lookup: appInterfaceId
+ },
+ {
+ ignoreErrors: true
+ }
+ );
})
.then(appInterface => {
const appModuleId = appInterface.applicationModules[0];
@@ -60,6 +66,17 @@ export default {
})
.then(appModule => {
this.appModule = appModule;
+ })
+ .catch(() => {
+ notifications.NotificationList.add(
+ new notifications.Notification({
+ type: "ERROR",
+ message:
+ "Unable to load application interface (" +
+ this.experiment.executionId +
+ ") or module. If it has been deleted then you won't be able to edit this experiment."
+ })
+ );
});
}
};
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/ExperimentListContainer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/ExperimentListContainer.vue
index 3e20399..90170ee 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/ExperimentListContainer.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/ExperimentListContainer.vue
@@ -21,9 +21,18 @@
</tr>
</thead>
<tbody>
- <tr v-for="experiment in experiments" :key="experiment.experimentId">
- <td><b-link :href="viewLink(experiment)">{{experiment.name}}</b-link></td>
- <td>{{applicationName(experiment)}}</td>
+ <tr
+ v-for="experiment in experiments"
+ :key="experiment.experimentId"
+ >
+ <td>
+ <b-link :href="viewLink(experiment)">{{experiment.name}}</b-link>
+ </td>
+ <td v-if="applicationName(experiment)">{{applicationName(experiment)}}</td>
+ <td
+ v-else
+ class="font-italic text-muted"
+ >N/A</td>
<td>{{experiment.userName}}</td>
<td>
<span :title="experiment.creationTime">{{ fromNow(experiment.creationTime) }}</span>
@@ -32,17 +41,40 @@
<experiment-status-badge :statusName="experiment.experimentStatus.name" />
</td>
<td>
- <b-link v-if="experiment.isEditable" :href="editLink(experiment)" class="action-link">Edit
- <i class="fa fa-edit" aria-hidden="true"></i>
- </b-link>
- <b-link v-else @click="clone(experiment)" class="action-link">Clone
- <i class="fa fa-copy" aria-hidden="true"></i>
- </b-link>
+ <!-- if we can't load the application for the experiment
+ (for example, if it was deleted), then user can't edit or
+ clone experiment -->
+ <span v-if="applicationName(experiment)">
+ <b-link
+ v-if="experiment.isEditable && applicationName(experiment)"
+ :href="editLink(experiment)"
+ class="action-link"
+ >Edit
+ <i
+ class="fa fa-edit"
+ aria-hidden="true"
+ ></i>
+ </b-link>
+ <b-link
+ v-else
+ @click="clone(experiment)"
+ class="action-link"
+ >Clone
+ <i
+ class="fa fa-copy"
+ aria-hidden="true"
+ ></i>
+ </b-link>
+ </span>
</td>
</tr>
</tbody>
</table>
- <pager v-bind:paginator="experimentsPaginator" v-on:next="nextExperiments" v-on:previous="previousExperiments"></pager>
+ <pager
+ v-bind:paginator="experimentsPaginator"
+ v-on:next="nextExperiments"
+ v-on:previous="previousExperiments"
+ ></pager>
</div>
</div>
</div>
@@ -57,7 +89,7 @@ import { components as comps } from "django-airavata-common-ui";
import ExperimentStatusBadge from "../components/experiment/ExperimentStatusBadge.vue";
import moment from "moment";
-import urls from '../utils/urls';
+import urls from "../utils/urls";
export default {
props: ["initialExperimentsData"],
@@ -96,13 +128,31 @@ export default {
) {
return this.applicationInterfaces[experiment.executionId]
.applicationName;
+ } else if (
+ this.applicationInterfaces[experiment.executionId] === null
+ ) {
+ return null;
}
} else {
- const request = services.ApplicationInterfaceService.retrieve({
- lookup: experiment.executionId
- }).then(result =>
- this.$set(this.applicationInterfaces, experiment.executionId, result)
- );
+ const request = services.ApplicationInterfaceService.retrieve(
+ {
+ lookup: experiment.executionId
+ },
+ {
+ ignoreErrors: true
+ }
+ )
+ .then(result =>
+ this.$set(
+ this.applicationInterfaces,
+ experiment.executionId,
+ result
+ )
+ )
+ .catch(() => {
+ // Application interface may be deleted
+ this.$set(this.applicationInterfaces, experiment.executionId, null);
+ });
this.$set(this.applicationInterfaces, experiment.executionId, request);
}
return "...";
@@ -123,9 +173,9 @@ export default {
}
},
beforeMount: function() {
- services.ExperimentSearchService.list({initialData: this.initialExperimentsData}).then(
- result => (this.experimentsPaginator = result)
- );
+ services.ExperimentSearchService.list({
+ initialData: this.initialExperimentsData
+ }).then(result => (this.experimentsPaginator = result));
}
};
</script>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/RecentExperimentsContainer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/RecentExperimentsContainer.vue
index 0323fd1..95a6fd9 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/RecentExperimentsContainer.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/RecentExperimentsContainer.vue
@@ -31,7 +31,7 @@ export default {
username: String
},
components: {
- "sidebar": components.Sidebar,
+ sidebar: components.Sidebar,
"sidebar-header": components.SidebarHeader,
"sidebar-feed": components.SidebarFeed,
ExperimentStatusBadge
@@ -41,17 +41,19 @@ export default {
},
methods: {
pollExperiments() {
- this.loadExperiments().then(() => {
- setTimeout(
- function() {
- this.pollExperiments();
- }.bind(this),
- this.refreshDelay
- );
- }).catch(() => {
- // If loading experiments fails, just ignore. This can happen if the
- // user navigates away from the page while a request is executing.
- });
+ this.loadExperiments()
+ .then(() => {
+ setTimeout(
+ function() {
+ this.pollExperiments();
+ }.bind(this),
+ this.refreshDelay
+ );
+ })
+ .catch(() => {
+ // If loading experiments fails, just ignore. This can happen if the
+ // user navigates away from the page while a request is executing.
+ });
},
loadExperiments() {
return services.ExperimentSearchService.list(
@@ -74,17 +76,14 @@ export default {
timestamp: e.statusUpdateTime,
interfaceId: e.executionId,
isProgressing: e.convertToExperiment().isProgressing,
- type:
- e.executionId in this.applicationInterfaces
- ? this.applicationInterfaces[e.executionId].applicationName
- : null
+ type: null
};
});
// Load any application interfaces that haven't been loaded yet, so that
// we can display the applicationName of each experiment
const unloadedInterfaceIds = {};
this.feedItems
- .filter(i => i.type === null)
+ .filter(i => !(i.interfaceId in this.applicationInterfaces))
.forEach(i => (unloadedInterfaceIds[i.interfaceId] = true));
Promise.all(
Object.keys(unloadedInterfaceIds).map(interfaceId => {
@@ -101,19 +100,30 @@ export default {
lookup: interfaceId
},
{
- showSpinner: false
+ showSpinner: false,
+ ignoreErrors: true
}
- ).then(applicationInterface => {
- this.applicationInterfaces[interfaceId] = applicationInterface;
- });
+ )
+ .then(applicationInterface => {
+ this.applicationInterfaces[interfaceId] = applicationInterface;
+ })
+ .catch(() => {
+ // ignore if missing
+ this.applicationInterfaces[interfaceId] = null;
+ });
},
populateApplicationNames() {
this.feedItems
.filter(i => i.type === null)
.forEach(feedItem => {
- feedItem.type = this.applicationInterfaces[
- feedItem.interfaceId
- ].applicationName;
+ if (
+ feedItem.interfaceId in this.applicationInterfaces &&
+ this.applicationInterfaces[feedItem.interfaceId]
+ ) {
+ feedItem.type = this.applicationInterfaces[
+ feedItem.interfaceId
+ ].applicationName;
+ }
});
}
},