blob: 45e162ac17bde66b8f90ef44162b631142050f63 [file] [log] [blame]
<template>
<div>
<unsaved-changes-guard :dirty="dirty"/>
<div class="row">
<div class="col-auto mr-auto">
<h1 class="h4 mb-4">
<div
v-if="appModule"
class="application-name text-muted text-uppercase"
>
<i class="fa fa-code" aria-hidden="true"></i>
{{ appModule.appModuleName }}
</div>
<slot name="title">Experiment Editor</slot>
</h1>
</div>
<div class="col-auto">
<share-button
ref="shareButton"
:entity-id="localExperiment.experimentId"
:entity-label="'Experiment'"
:parent-entity-id="localExperiment.projectId"
:parent-entity-label="'Project'"
:auto-add-default-gateway-users-group="false"
/>
</div>
</div>
<div class="w-100">
<workspace-notices-management-container
v-if="appInterface.applicationDescription"
:data="[{notificationMessage: appInterface.applicationDescription}]"/>
</div>
<b-form novalidate>
<div class="row">
<div class="col">
<b-form-group
label="Experiment Name"
label-for="experiment-name"
:feedback="getValidationFeedback('experimentName')"
:state="getValidationState('experimentName')"
>
<b-form-input
id="experiment-name"
type="text"
v-model="localExperiment.experimentName"
required
placeholder="Experiment name"
:state="getValidationState('experimentName')"
></b-form-input>
</b-form-group>
<experiment-description-editor
v-model="localExperiment.description"
/>
</div>
</div>
<div class="row">
<div class="col">
<b-form-group
label="Project"
label-for="project"
:feedback="getValidationFeedback('projectId')"
:state="getValidationState('projectId')"
>
<b-form-select
id="project"
v-model="localExperiment.projectId"
required
:state="getValidationState('projectId')"
>
<template slot="first">
<option :value="null" disabled>Select a Project</option>
</template>
<optgroup label="My Projects">
<option
v-for="project in myProjectOptions"
:value="project.value"
:key="project.value"
>
{{ project.text }}
</option>
</optgroup>
<optgroup label="Projects Shared With Me">
<option
v-for="project in sharedProjectOptions"
:value="project.value"
:key="project.value"
>
{{ project.text }}
</option>
</optgroup>
</b-form-select>
</b-form-group>
</div>
</div>
<div class="row">
<div class="col">
<h1 class="h4 mt-5 mb-4">Application Configuration</h1>
</div>
</div>
<div class="row">
<div class="col">
<div class="card border-default">
<div class="card-body">
<h2 class="h6 mb-3">Application Inputs</h2>
<transition-group name="fade">
<input-editor-container
v-for="experimentInput in localExperiment.experimentInputs"
:experiment-input="experimentInput"
:experiment="localExperiment"
v-model="experimentInput.value"
v-show="experimentInput.show"
:key="experimentInput.name"
@invalid="recordInvalidInputEditorValue(experimentInput.name)"
@valid="recordValidInputEditorValue(experimentInput.name)"
@input="inputValueChanged"
@uploadstart="uploadStart(experimentInput.name)"
@uploadend="uploadEnd(experimentInput.name)"
/>
</transition-group>
</div>
</div>
</div>
</div>
<group-resource-profile-selector
v-model="localExperiment.userConfigurationData.groupResourceProfileId"
>
</group-resource-profile-selector>
<div class="row">
<div class="col">
<computational-resource-scheduling-editor
v-model="
localExperiment.userConfigurationData
.computationalResourceScheduling
"
v-if="localExperiment.userConfigurationData.groupResourceProfileId"
:app-module-id="appModule.appModuleId"
:group-resource-profile-id="
localExperiment.userConfigurationData.groupResourceProfileId
"
@invalid="invalidComputationalResourceSchedulingEditor = true"
@valid="invalidComputationalResourceSchedulingEditor = false"
>
</computational-resource-scheduling-editor>
</div>
</div>
<div class="row">
<div class="col">
<b-form-group
label="Email Settings"
>
<b-form-checkbox
v-model="localExperiment.enableEmailNotification"
>
Receive email notification of experiment status
</b-form-checkbox>
</b-form-group>
</div>
</div>
<div class="row">
<div id="col-exp-buttons" class="col">
<b-button
variant="success"
@click="saveAndLaunchExperiment"
:disabled="isSaveDisabled"
>
Save and Launch
</b-button>
<b-button
variant="primary"
@click="saveExperiment"
:disabled="isSaveDisabled"
>
Save
</b-button>
</div>
</div>
</b-form>
</div>
</template>
<script>
import ComputationalResourceSchedulingEditor from "./ComputationalResourceSchedulingEditor.vue";
import ExperimentDescriptionEditor from "./ExperimentDescriptionEditor.vue";
import GroupResourceProfileSelector from "./GroupResourceProfileSelector.vue";
import InputEditorContainer from "./input-editors/InputEditorContainer.vue";
import {models, services} from "django-airavata-api";
import {components, utils} from "django-airavata-common-ui";
import WorkspaceNoticesManagementContainer from "../notices/WorkspaceNoticesManagementContainer";
export default {
name: "edit-experiment",
props: {
experiment: {
type: models.Experiment,
required: true,
},
appModule: {
type: models.ApplicationModule,
required: true,
},
appInterface: {
type: models.ApplicationInterfaceDefinition,
required: true,
},
},
data() {
return {
projects: [],
localExperiment: this.experiment.clone(),
invalidInputs: [],
invalidComputationalResourceSchedulingEditor: false,
edited: false,
saved: false,
uploadingInputs: [],
};
},
components: {
WorkspaceNoticesManagementContainer,
ComputationalResourceSchedulingEditor,
ExperimentDescriptionEditor,
GroupResourceProfileSelector,
InputEditorContainer,
"share-button": components.ShareButton,
"unsaved-changes-guard": components.UnsavedChangesGuard,
},
mounted: function () {
services.ProjectService.listAll().then((projects) => {
this.projects = projects;
if (!this.localExperiment.projectId) {
services.WorkspacePreferencesService.get().then(
(workspacePreferences) => {
if (!this.localExperiment.projectId) {
this.localExperiment.projectId =
workspacePreferences.most_recent_project_id;
}
}
);
}
});
},
computed: {
sharedProjectOptions: function () {
return this.projects
.filter((p) => !p.isOwner)
.map((project) => ({
value: project.projectID,
text:
project.name +
(!project.isOwner ? " (owned by " + project.owner + ")" : ""),
}));
},
myProjectOptions() {
return this.projects
.filter((p) => p.isOwner)
.map((project) => ({
value: project.projectID,
text: project.name,
}));
},
valid: function () {
const validation = this.localExperiment.validate();
return (
Object.keys(validation).length === 0 &&
this.invalidInputs.length === 0 &&
!this.invalidComputationalResourceSchedulingEditor
);
},
isSaveDisabled: function () {
return !this.valid || this.hasUploadingInputs;
},
dirty() {
return this.edited && !this.saved;
},
hasUploadingInputs() {
return this.uploadingInputs.length > 0;
},
},
methods: {
saveExperiment: function () {
return this.saveOrUpdateExperiment().then((experiment) => {
this.localExperiment = experiment;
this.$emit("saved", experiment);
});
},
saveAndLaunchExperiment: function () {
return this.saveOrUpdateExperiment().then((experiment) => {
this.localExperiment = experiment;
return services.ExperimentService.launch({
lookup: experiment.experimentId,
}).then(() => {
this.$emit("savedAndLaunched", experiment);
});
});
},
saveOrUpdateExperiment: function () {
if (this.localExperiment.experimentId) {
return services.ExperimentService.update({
lookup: this.localExperiment.experimentId,
data: this.localExperiment,
}).then((experiment) => {
this.saved = true;
return experiment;
});
} else {
return services.ExperimentService.create({
data: this.localExperiment,
}).then((experiment) => {
// Can't save sharing settings for a new experiment until it has been
// created
this.saved = true;
return this.$refs.shareButton
.mergeAndSave(experiment.experimentId)
.then(() => experiment);
});
}
},
getValidationFeedback: function (properties) {
return utils.getProperty(this.localExperiment.validate(), properties);
},
getValidationState: function (properties) {
return this.getValidationFeedback(properties) ? false : null;
},
recordInvalidInputEditorValue: function (experimentInputName) {
if (!this.invalidInputs.includes(experimentInputName)) {
this.invalidInputs.push(experimentInputName);
}
},
recordValidInputEditorValue: function (experimentInputName) {
if (this.invalidInputs.includes(experimentInputName)) {
const index = this.invalidInputs.indexOf(experimentInputName);
this.invalidInputs.splice(index, 1);
}
},
uploadStart(experimentInputName) {
if (!this.uploadingInputs.includes(experimentInputName)) {
this.uploadingInputs.push(experimentInputName);
}
},
uploadEnd(experimentInputName) {
if (this.uploadingInputs.includes(experimentInputName)) {
const index = this.uploadingInputs.indexOf(experimentInputName);
this.uploadingInputs.splice(index, 1);
}
},
inputValueChanged: function () {
this.localExperiment.evaluateInputDependencies();
},
},
watch: {
experiment: function (newValue) {
this.localExperiment = newValue.clone();
},
localExperiment: {
handler() {
this.edited = true;
},
deep: true,
},
},
};
</script>
<style>
.application-name {
font-size: 12px;
}
#col-exp-buttons {
text-align: right;
}
</style>