This is an automated email from the ASF dual-hosted git repository. yasithdev pushed a commit to branch feat/generic-experiment-launcher in repository https://gitbox.apache.org/repos/asf/airavata-portals.git
commit 9d90b2d58f8c03575e21076cd30080575a0cfeae Author: yasithdev <[email protected]> AuthorDate: Fri Apr 24 23:26:06 2026 -0400 refactor(launcher): delete old experiment-editor surface Remove CreateExperimentContainer, EditExperimentContainer, ExperimentEditor, ComputationalResourceSchedulingEditor, QueueSettingsEditor, GroupResourceProfileSelector, entry-create-experiment.js, and entry-edit-experiment.js. Drop the matching view functions (create_experiment, edit_experiment, applications, get_custom_template) and orphaned imports from workspace/views.py. Replace the still-routed edit_experiment URL with a 301 redirect to /workspace/launch (missed by Task 5). Drop create-experiment and edit-experiment entries from vite.config.js. --- .../ComputationalResourceSchedulingEditor.vue | 296 ----------- .../js/components/experiment/ExperimentEditor.vue | 423 ---------------- .../experiment/GroupResourceProfileSelector.vue | 114 ----- .../components/experiment/QueueSettingsEditor.vue | 544 --------------------- .../js/containers/CreateExperimentContainer.vue | 99 ---- .../js/containers/EditExperimentContainer.vue | 78 --- .../js/entry-create-experiment.js | 32 -- .../js/entry-edit-experiment.js | 25 - .../django_airavata/apps/workspace/urls.py | 4 +- .../django_airavata/apps/workspace/views.py | 120 ----- .../django_airavata/apps/workspace/vite.config.js | 2 - 11 files changed, 2 insertions(+), 1735 deletions(-) diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ComputationalResourceSchedulingEditor.vue b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ComputationalResourceSchedulingEditor.vue deleted file mode 100644 index 6e7c50454..000000000 --- a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ComputationalResourceSchedulingEditor.vue +++ /dev/null @@ -1,296 +0,0 @@ -<template> - <div> - <div class="row"> - <div class="col"> - <form-group - label="Compute Resource" - label-for="compute-resource" - :feedback="getValidationFeedback('resource_host_id')" - :state="getValidationState('resource_host_id')" - > - <select - id="compute-resource" - v-model="resource_host_id" - class="form-select" - required - :state="getValidationState('resource_host_id')" - :disabled="!computeResourceOptions || computeResourceOptions.length === 0" - @change="computeResourceChanged(($event.target as HTMLSelectElement).value)" - > - <option :value="null" disabled>Select a Compute Resource</option> - <option v-for="opt in computeResourceOptions" :key="opt.value" :value="opt.value"> - {{ opt.text }} - </option> - </select> - </form-group> - </div> - </div> - <div class="row"> - <div class="col"> - <QueueSettingsEditor - v-if="appDeploymentId" - v-model="data" - :app-module-id="appModuleId" - :app-deployment-id="appDeploymentId" - :compute-resource-policy="selectedComputeResourcePolicy" - :batch-queue-resource-policies="batchQueueResourcePolicies" - @input="queueSettingsChanged" - @valid="queueSettingsValidityChanged(true)" - @invalid="queueSettingsValidityChanged(false)" - > - </QueueSettingsEditor> - </div> - </div> - </div> -</template> - -<script setup lang="ts"> -import { ref, computed, watch, onMounted } from "vue"; -import QueueSettingsEditor from "./QueueSettingsEditor.vue"; -import { errors, models, services, utils as apiUtils } from "django-airavata-api"; -import { utils } from "django-airavata-common-ui"; - -type ComputationalResourceSchedulingModel = InstanceType<typeof models.ComputationalResourceSchedulingModel>; - -interface ComputeResourceOption { - value: string; - text: string; -} - -interface WorkspacePreferences { - most_recent_compute_resource_id?: string | null; -} - -interface GroupResourceProfileData { - group_resource_profile_id: string; - compute_resource_policies: Array<{ compute_resource_id: string; [key: string]: unknown }>; - batch_queue_resource_policies: Array<{ compute_resource_id: string; [key: string]: unknown }>; -} - -interface ApplicationDeployment { - compute_host_id: string; - app_deployment_id: string; -} - -const props = defineProps<{ - modelValue: ComputationalResourceSchedulingModel; - appModuleId: string; - groupResourceProfileId: string; -}>(); - -const emit = defineEmits<{ - "update:modelValue": [value: ComputationalResourceSchedulingModel]; - input: [value: ComputationalResourceSchedulingModel]; - valid: []; - invalid: []; -}>(); - -// VModelMixin inline -function copyValue(value: ComputationalResourceSchedulingModel) { - return value instanceof models.BaseModel - ? (value as unknown as { clone: () => ComputationalResourceSchedulingModel }).clone() - : value; -} - -const data = ref<ComputationalResourceSchedulingModel>(copyValue(props.modelValue)); - -watch( - () => props.modelValue, - (newValue) => { - data.value = copyValue(newValue); - }, - { deep: true }, -); - -watch( - data, - (newValue, oldValue) => { - if (typeof props.modelValue === "object" && newValue === oldValue) { - emit("update:modelValue", newValue); - emit("input", newValue); - } else if ( - (props.modelValue === null || typeof props.modelValue !== "object") && - newValue !== oldValue - ) { - emit("update:modelValue", newValue); - emit("input", newValue); - } - }, - { deep: true }, -); - -const computeResources = ref<Record<string, string>>({}); -const applicationDeployments = ref<ApplicationDeployment[]>([]); -const selectedGroupResourceProfileData = ref<GroupResourceProfileData | null>(null); -const resource_host_id = ref<string | null>(props.modelValue.resource_host_id ?? null); -const invalidQueueSettings = ref(false); -const workspacePreferences = ref<WorkspacePreferences | null>(null); - -const localComputationalResourceScheduling = computed(() => data.value); - -const computeResourceOptions = computed<ComputeResourceOption[]>(() => { - const options = applicationDeployments.value.map((dep) => ({ - value: dep.compute_host_id, - text: dep.compute_host_id in computeResources.value ? computeResources.value[dep.compute_host_id] : "", - })); - options.sort((a, b) => a.text.localeCompare(b.text)); - return options; -}); - -const selectedComputeResourcePolicy = computed(() => { - if (selectedGroupResourceProfileData.value === null) return null; - return selectedGroupResourceProfileData.value.compute_resource_policies.find( - (crp) => crp.compute_resource_id === localComputationalResourceScheduling.value.resource_host_id, - ) ?? null; -}); - -interface BatchQueueResourcePolicyLike { - compute_resource_id: string; - queuename: string; - maxAllowedCores: number; - maxAllowedNodes: number; - maxAllowedWalltime: number; - resourcePolicyId?: string; - [key: string]: unknown; -} - -const batchQueueResourcePolicies = computed<BatchQueueResourcePolicyLike[] | null>(() => { - if (selectedGroupResourceProfileData.value === null) return null; - return selectedGroupResourceProfileData.value.batch_queue_resource_policies.filter( - (bqrp) => bqrp.compute_resource_id === localComputationalResourceScheduling.value.resource_host_id, - ) as BatchQueueResourcePolicyLike[]; -}); - -const appDeploymentId = computed<string | null>(() => { - if (!resource_host_id.value || applicationDeployments.value.length === 0) return null; - const selectedDep = applicationDeployments.value.find( - (dep) => dep.compute_host_id === resource_host_id.value, - ); - if (!selectedDep) { - throw new Error("Failed to find application deployment!"); - } - return selectedDep.app_deployment_id; -}); - -const validation = computed(() => { - const queueInfo = {}; - return localComputationalResourceScheduling.value.validate(queueInfo); -}); - -const valid = computed(() => !invalidQueueSettings.value && Object.keys(validation.value).length === 0); - -watch(computeResourceOptions, (newOptions) => { - if (resource_host_id.value !== null && !newOptions.find((opt) => opt.value === resource_host_id.value)) { - resource_host_id.value = null; - } - if ( - resource_host_id.value === null && - workspacePreferences.value?.most_recent_compute_resource_id && - newOptions.find((opt) => opt.value === workspacePreferences.value!.most_recent_compute_resource_id) - ) { - resource_host_id.value = workspacePreferences.value.most_recent_compute_resource_id; - } - if (resource_host_id.value === null && newOptions.length > 0) { - resource_host_id.value = newOptions[0].value; - } - computeResourceChanged(resource_host_id.value); -}); - -watch( - () => props.groupResourceProfileId, - (newGroupResourceProfileId) => { - loadApplicationDeployments(props.appModuleId, newGroupResourceProfileId); - if ( - selectedGroupResourceProfileData.value && - selectedGroupResourceProfileData.value.group_resource_profile_id !== newGroupResourceProfileId - ) { - loadGroupResourceProfile(); - } - }, -); - -onMounted(() => { - loadWorkspacePreferences().then(() => { - loadApplicationDeployments(props.appModuleId, props.groupResourceProfileId); - }); - loadComputeResourceNames(); - loadGroupResourceProfile(); - validate(); -}); - -function computeResourceChanged(selectedComputeResourceId: string | null) { - data.value.resource_host_id = selectedComputeResourceId; -} - -function loadApplicationDeployments(appModuleId: string, groupResourceProfileId: string) { - services.ApplicationDeploymentService.list( - { app_module_id: appModuleId, group_resource_profile_id: groupResourceProfileId }, - { ignoreErrors: true }, - ) - .then((deps: unknown) => { - applicationDeployments.value = deps as ApplicationDeployment[]; - }) - .catch((error: unknown) => { - if (!errors.ErrorUtils.isUnauthorizedError(error)) { - return Promise.reject(error); - } - }) - .catch(apiUtils.FetchUtils.reportError); -} - -function loadGroupResourceProfile() { - services.ProjectResourceProfileService.retrieve( - { lookup: props.groupResourceProfileId }, - { ignoreErrors: true }, - ) - .then((grp: unknown) => { - selectedGroupResourceProfileData.value = grp as GroupResourceProfileData; - }) - .catch((error: unknown) => { - if (!errors.ErrorUtils.isUnauthorizedError(error)) { - return Promise.reject(error); - } - }) - .catch(apiUtils.FetchUtils.reportError); -} - -function loadComputeResourceNames() { - services.ComputeResourceService.names().then( - (names: unknown) => (computeResources.value = names as Record<string, string>), - ); -} - -function loadWorkspacePreferences(): Promise<void> { - return services.WorkspacePreferencesService.get().then( - (prefs: unknown) => (workspacePreferences.value = prefs as WorkspacePreferences), - ); -} - -function queueSettingsChanged() { - localComputationalResourceScheduling.value.resource_host_id = resource_host_id.value; - emit("input", data.value); -} - -function queueSettingsValidityChanged(validValue: boolean) { - invalidQueueSettings.value = !validValue; - validate(); -} - -function validate() { - if (!valid.value) { - emit("invalid"); - } else { - emit("valid"); - } -} - -function getValidationFeedback(properties: string): unknown { - return utils.getProperty(validation.value, properties); -} - -function getValidationState(properties: string): boolean | null { - return getValidationFeedback(properties) ? false : null; -} -</script> - -<style></style> diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentEditor.vue b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentEditor.vue deleted file mode 100644 index be28bbc44..000000000 --- a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentEditor.vue +++ /dev/null @@ -1,423 +0,0 @@ -<template> - <div> - <UnsavedChangesGuard :dirty="dirty" /> - <div class="row"> - <div class="col-auto me-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.app_module_name }} - </div> - <slot name="title">Experiment Editor</slot> - </h1> - </div> - <div class="col-auto"> - <ShareButton - ref="shareButton" - :entity-id="localExperiment.experiment_id" - :entity-label="'Experiment'" - :parent-entity-id="localExperiment.project_id" - :parent-entity-label="'Project'" - :auto-add-default-gateway-users-group="false" - /> - </div> - </div> - <form novalidate> - <div class="row"> - <div class="col"> - <form-group - label="Experiment Name" - label-for="experiment-name" - :feedback="getValidationFeedback('experiment_name')" - :state="getValidationState('experiment_name')" - > - <input - id="experiment-name" - v-model="localExperiment.experiment_name" - class="form-control" - type="text" - required - placeholder="Experiment name" - :state="getValidationState('experiment_name')" - /> - </form-group> - <ExperimentDescriptionEditor v-model="localExperiment.description" /> - </div> - </div> - <div class="row"> - <div class="col"> - <form-group - label="Project" - label-for="project" - :feedback="getValidationFeedback('project_id')" - :state="getValidationState('project_id')" - > - <select - id="project" - v-model="localExperiment.project_id" - class="form-select" - required - :state="getValidationState('project_id')" - > - <option :value="null" disabled>Select a Project</option> - <optgroup label="My Projects"> - <option - v-for="project in myProjectOptions" - :key="project.value" - :value="project.value" - > - {{ project.text }} - </option> - </optgroup> - <optgroup label="Projects Shared With Me"> - <option - v-for="project in sharedProjectOptions" - :key="project.value" - :value="project.value" - > - {{ project.text }} - </option> - </optgroup> - </select> - </form-group> - </div> - </div> - <div class="row"> - <div class="col"> - <WorkspaceNoticesManagementContainer - v-if="appInterface && (appInterface as Record<string, unknown>).application_description" - class="mt-2" - :data="[{ notificationMessage: (appInterface as Record<string, unknown>).application_description as string | undefined }]" - /> - </div> - </div> - <div class="row"> - <div class="col"> - <h1 class="h4 mt-2 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"> - <InputEditorContainer - v-for="experimentInput in localExperiment.experiment_inputs" - v-show="experimentInput.show" - :key="experimentInput.name" - v-model="experimentInput.value" - :experiment-input="experimentInput" - :experiment="localExperiment" - @invalid="recordInvalidInputEditorValue(experimentInput.name)" - @valid="recordValidInputEditorValue(experimentInput.name)" - @input="inputValueChanged" - @uploadstart="uploadStart(experimentInput.name)" - @uploadend="uploadEnd(experimentInput.name)" - /> - </transition-group> - </div> - </div> - </div> - </div> - <GroupResourceProfileSelector - v-model="localExperiment.user_configuration_data.group_resource_profile_id" - @invalid="invalidGroupResourceProfileSelector = true" - @valid="invalidGroupResourceProfileSelector = false" - > - </GroupResourceProfileSelector> - <div class="row"> - <div class="col"> - <ComputationalResourceSchedulingEditor - v-if="localExperiment.user_configuration_data.group_resource_profile_id" - v-model="localExperiment.user_configuration_data.computational_resource_scheduling" - :app-module-id="appModule.app_module_id" - :group-resource-profile-id=" - localExperiment.user_configuration_data.group_resource_profile_id - " - @invalid="invalidComputationalResourceSchedulingEditor = true" - @valid="invalidComputationalResourceSchedulingEditor = false" - > - </ComputationalResourceSchedulingEditor> - </div> - </div> - <div class="row"> - <div class="col"> - <div class="mb-3" label="Email Settings"> - <div class="form-check"> - <input - v-model="localExperiment.enable_email_notification" - class="form-check-input" - type="checkbox" - /> - Receive email notification of experiment status - </div> - </div> - </div> - </div> - <div class="row"> - <div id="col-exp-buttons" class="col"> - <button - class="btn btn-success btn-sm" - :disabled="isSaveDisabled" - @click="saveAndLaunchExperiment" - > - Save and Launch - </button> - <button class="btn btn-primary btn-sm" :disabled="isSaveDisabled" @click="saveExperiment"> - Save - </button> - </div> - </div> - </form> - </div> -</template> - -<script setup lang="ts"> -import { ref, computed, watch, onMounted } from "vue"; -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.vue"; - -const ShareButton = components.ShareButton; -const UnsavedChangesGuard = components.UnsavedChangesGuard; - -type Experiment = InstanceType<typeof models.Experiment>; -type ApplicationModule = InstanceType<typeof models.ApplicationModule>; -type ApplicationInterfaceDefinition = InstanceType<typeof models.ApplicationInterfaceDefinition>; - -interface ProjectOption { - value: string; - text: string; -} - -const props = defineProps<{ - experiment: Experiment; - appModule: ApplicationModule; - appInterface: ApplicationInterfaceDefinition; -}>(); - -const emit = defineEmits<{ - saved: [experiment: Experiment]; - savedAndLaunched: [experiment: Experiment]; -}>(); - -const shareButton = ref<{ mergeAndSave: (_id: string) => Promise<unknown> } | null>(null); - -const projects = ref<unknown[]>([]); -const localExperiment = ref<Experiment>(props.experiment.clone() as Experiment); -const invalidInputs = ref<string[]>([]); -const invalidComputationalResourceSchedulingEditor = ref(false); -const invalidGroupResourceProfileSelector = ref(false); -const edited = ref(false); -const saved_ = ref(false); -const uploadingInputs = ref<string[]>([]); - -const sharedProjectOptions = computed<ProjectOption[]>(() => - (projects.value as Array<Record<string, unknown>>) - .filter((p) => !p.is_owner) - .map((project) => ({ - value: project.project_id as string, - text: project.name + (!project.is_owner ? " (owned by " + project.owner + ")" : ""), - })), -); - -const myProjectOptions = computed<ProjectOption[]>(() => - (projects.value as Array<Record<string, unknown>>) - .filter((p) => p.is_owner) - .map((project) => ({ - value: project.project_id as string, - text: project.name as string, - })), -); - -const valid = computed(() => { - const validation = localExperiment.value.validate(); - return ( - Object.keys(validation).length === 0 && - invalidInputs.value.length === 0 && - !invalidComputationalResourceSchedulingEditor.value && - !invalidGroupResourceProfileSelector.value - ); -}); - -const isSaveDisabled = computed(() => !valid.value || hasUploadingInputs.value); - -const dirty = computed(() => edited.value && !saved_.value); - -const hasUploadingInputs = computed(() => uploadingInputs.value.length > 0); - -watch( - () => props.experiment, - (newValue) => { - localExperiment.value = newValue.clone() as Experiment; - }, -); - -watch( - localExperiment, - () => { - edited.value = true; - }, - { deep: true }, -); - -watch( - () => (props.experiment as unknown as Record<string, unknown>).experiment_inputs, - () => { - experimentInputsChanged(); - }, - { deep: true }, -); - -watch( - () => { - const ucd = (props.experiment as unknown as { - user_configuration_data?: { - computational_resource_scheduling?: { resource_host_id?: string }; - }; - }).user_configuration_data; - return ucd?.computational_resource_scheduling?.resource_host_id; - }, - () => { - resourceHostIdChanged(); - }, -); - -onMounted(() => { - services.ProjectService.listAll().then((projs: unknown) => { - projects.value = projs as unknown[]; - if (!localExperiment.value.project_id) { - services.WorkspacePreferencesService.get().then((workspacePreferences: unknown) => { - const prefs = workspacePreferences as Record<string, unknown>; - if (!localExperiment.value.project_id) { - localExperiment.value.project_id = prefs.most_recent_project_id as string; - } - }); - } - }); -}); - -function saveExperiment() { - return saveOrUpdateExperiment().then((experiment: Experiment) => { - localExperiment.value = experiment; - emit("saved", experiment); - }); -} - -function saveAndLaunchExperiment() { - return saveOrUpdateExperiment().then((experiment: Experiment) => { - localExperiment.value = experiment; - return services.ExperimentService.launch({ - lookup: experiment.experiment_id, - }).then(() => { - emit("savedAndLaunched", experiment); - }); - }); -} - -function saveOrUpdateExperiment(): Promise<Experiment> { - if (localExperiment.value.experiment_id) { - return services.ExperimentService.update({ - lookup: localExperiment.value.experiment_id, - data: localExperiment.value, - }).then((experiment: unknown) => { - saved_.value = true; - return experiment as Experiment; - }); - } else { - return services.ExperimentService.create({ - data: localExperiment.value, - }).then((experiment: unknown) => { - const exp = experiment as Experiment; - saved_.value = true; - return shareButton.value! - .mergeAndSave(exp.experiment_id) - .then(() => exp); - }); - } -} - -function getValidationFeedback(properties: string): unknown { - return utils.getProperty(localExperiment.value.validate(), properties); -} - -function getValidationState(properties: string): boolean | null { - return getValidationFeedback(properties) ? false : null; -} - -function recordInvalidInputEditorValue(experimentInputName: string) { - if (!invalidInputs.value.includes(experimentInputName)) { - invalidInputs.value.push(experimentInputName); - } -} - -function recordValidInputEditorValue(experimentInputName: string) { - if (invalidInputs.value.includes(experimentInputName)) { - const index = invalidInputs.value.indexOf(experimentInputName); - invalidInputs.value.splice(index, 1); - } -} - -function uploadStart(experimentInputName: string) { - if (!uploadingInputs.value.includes(experimentInputName)) { - uploadingInputs.value.push(experimentInputName); - } -} - -function uploadEnd(experimentInputName: string) { - if (uploadingInputs.value.includes(experimentInputName)) { - const index = uploadingInputs.value.indexOf(experimentInputName); - uploadingInputs.value.splice(index, 1); - } -} - -function inputValueChanged() { - (localExperiment.value as unknown as { evaluateInputDependencies: () => void }).evaluateInputDependencies(); -} - -// Inline debounce wrapper for calculateQueueSettings -let calcTimer: ReturnType<typeof setTimeout> | undefined; -function calculateQueueSettings() { - clearTimeout(calcTimer); - calcTimer = setTimeout(async () => { - const queueSettingsUpdate = await services.QueueSettingsCalculatorService.calculate( - { - lookup: (props.appInterface as unknown as Record<string, unknown>).queue_settings_calculator_id, - data: localExperiment.value, - }, - { showSpinner: false }, - ); - Object.assign( - (localExperiment.value as unknown as Record<string, unknown> & { user_configuration_data: Record<string, unknown> }).user_configuration_data.computationalResourceScheduling as object, - queueSettingsUpdate, - ); - }, 500); -} - -function experimentInputsChanged() { - if ((props.appInterface as unknown as Record<string, unknown>).queue_settings_calculator_id) { - calculateQueueSettings(); - } -} - -function resourceHostIdChanged() { - if ((props.appInterface as unknown as Record<string, unknown>).queue_settings_calculator_id) { - calculateQueueSettings(); - } -} -</script> - -<style> -.application-name { - font-size: 12px; -} - -#col-exp-buttons { - text-align: right; -} -</style> diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/GroupResourceProfileSelector.vue b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/GroupResourceProfileSelector.vue deleted file mode 100644 index ca3a591d4..000000000 --- a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/GroupResourceProfileSelector.vue +++ /dev/null @@ -1,114 +0,0 @@ -<template> - <div class="row"> - <div class="col"> - <div class="mb-3" label="Allocation" label-for="group-resource-profile"> - <select - id="group-resource-profile" - v-model="groupResourceProfileId" - class="form-select" - required - @change="emitValueChanged" - > - <option :value="null" disabled>Select an allocation</option> - <option - v-for="option in groupResourceProfileOptions" - :key="option.value" - :value="option.value" - > - {{ option.text }} - </option> - </select> - </div> - </div> - </div> -</template> - -<script setup lang="ts"> -import { ref, computed, onMounted } from "vue"; -import { services } from "django-airavata-api"; - -interface GroupResourceProfile { - group_resource_profile_id: string; - group_resource_profile_name: string; -} - -interface WorkspacePreferences { - most_recent_project_resource_profile_id?: string; -} - -const props = defineProps<{ - modelValue?: string | null; -}>(); - -const emit = defineEmits<{ - "update:modelValue": [value: string | null]; - valid: []; - invalid: []; -}>(); - -const groupResourceProfileId = ref<string | null>(props.modelValue ?? null); -const groupResourceProfiles = ref<GroupResourceProfile[]>([]); -const workspacePreferences = ref<WorkspacePreferences | null>(null); - -const groupResourceProfileOptions = computed(() => { - if (groupResourceProfiles.value.length > 0) { - const options = groupResourceProfiles.value.map((grp) => ({ - value: grp.group_resource_profile_id, - text: grp.group_resource_profile_name, - })); - options.sort((a, b) => a.text.localeCompare(b.text)); - return options; - } - return []; -}); - -const valid = computed(() => !!groupResourceProfileId.value); - -function emitValueChanged() { - validate(); - emit("update:modelValue", groupResourceProfileId.value); -} - -function validate() { - if (!valid.value) { - emit("invalid"); - } else { - emit("valid"); - } -} - -function selectedValueInGroupResourceProfileList(profiles: GroupResourceProfile[]) { - return profiles.map((grp) => grp.group_resource_profile_id).indexOf(props.modelValue ?? "") >= 0; -} - -function loadGroupResourceProfiles() { - return services.ProjectResourceProfileService.list().then( - (profiles: GroupResourceProfile[]) => { - groupResourceProfiles.value = profiles; - if ( - (!props.modelValue || !selectedValueInGroupResourceProfileList(profiles)) && - groupResourceProfiles.value.length > 0 - ) { - // automatically select the last one user selected - groupResourceProfileId.value = - workspacePreferences.value?.most_recent_project_resource_profile_id ?? null; - emitValueChanged(); - } - }, - ); -} - -function loadWorkspacePreferences() { - return services.WorkspacePreferencesService.get().then( - (prefs: WorkspacePreferences) => (workspacePreferences.value = prefs), - ); -} - -onMounted(async () => { - await loadWorkspacePreferences(); - await loadGroupResourceProfiles(); - validate(); -}); -</script> - -<style></style> diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/QueueSettingsEditor.vue b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/QueueSettingsEditor.vue deleted file mode 100644 index d5255364d..000000000 --- a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/QueueSettingsEditor.vue +++ /dev/null @@ -1,544 +0,0 @@ -<template> - <div v-if="showQueueSettings"> - <div class="row"> - <div class="col"> - <div :class="['card border-default', { 'border-danger': !valid, 'is-disabled': disabled }]"> - <a - class="card-link text-dark" - :disabled="disabled" - @click="showConfiguration = !showConfiguration" - > - <div class="card-body"> - <h5 class="card-title mb-4">Settings for queue {{ data.queue_name }}</h5> - <div class="row"> - <div class="col"> - <h3 class="h5 mb-0"> - {{ data.node_count }} - </h3> - <span class="text-muted text-uppercase">NODE COUNT</span> - </div> - <div class="col"> - <h3 class="h5 mb-0"> - {{ data.total_cpu_count }} - </h3> - <span class="text-muted text-uppercase">CORE COUNT</span> - </div> - <div class="col"> - <h3 class="h5 mb-0">{{ data.wall_time_limit }} minutes</h3> - <span class="text-muted text-uppercase">TIME LIMIT</span> - </div> - <div v-if="maxPhysicalMemory > 0" class="col"> - <h3 class="h5 mb-0">{{ data.total_physical_memory }} MB</h3> - <span class="text-muted text-uppercase">PHYSICAL MEMORY</span> - </div> - </div> - </div> - </a> - </div> - </div> - </div> - <div v-if="showConfiguration"> - <div class="row"> - <div class="col"> - <form-group - label="Select a Queue" - label-for="queue" - :invalid-feedback="getValidationFeedback('queue_name')" - :state="getValidationState('queue_name')" - > - <select - id="queue" - v-model="data.queue_name" - class="form-select" - required - @change="queueChanged" - > - <option v-for="opt in queueOptions" :key="opt.value" :value="opt.value"> - {{ opt.text }} - </option> - </select> - <small class="form-text text-muted">{{ queueDescription }}</small> - </form-group> - </div> - <div class="d-flex flex-row"> - <div class="flex-fill"> - <form-group - label="Node Count" - label-for="node-count" - :invalid-feedback="getValidationFeedback('nodeCount')" - :state="getValidationState('nodeCount', true)" - > - <input - id="node-count" - v-model="data.node_count" - class="form-control" - type="number" - min="1" - :max="maxNodes" - required - @input="nodeCountChanged" - /> - <small class="form-text text-muted"> - <i class="fa fa-info-circle" aria-hidden="true"></i> - Max Allowed Nodes = {{ maxNodes }} - </small> - </form-group> - </div> - <div class="flex-fill"> - <form-group - label="Total Core Count" - label-for="core-count" - :invalid-feedback="getValidationFeedback('totalCPUCount')" - :state="getValidationState('totalCPUCount', true)" - > - <input - id="core-count" - v-model="data.total_cpu_count" - class="form-control" - type="number" - min="1" - :max="maxCPUCount" - required - @input="cpuCountChanged" - /> - <small class="form-text text-muted"> - <i class="fa fa-info-circle" aria-hidden="true"></i> - Max Allowed Cores = {{ maxCPUCount - }}<template v-if="selectedQueueDefault && selectedQueueDefault.cpu_per_node > 0" - >. There are {{ selectedQueueDefault.cpu_per_node }} cores per node. - </template> - </small> - </form-group> - </div> - </div> - <div - v-if="selectedQueueDefault && selectedQueueDefault.cpu_per_node > 0" - class="d-flex flex-column" - > - <div - class="flex-fill" - style=" - border: 1px solid #6c757d; - border-top-right-radius: 10px; - margin-top: 51px; - border-left-width: 0px; - border-bottom-width: 0px; - margin-right: 15px; - " - ></div> - <button - class="btn btn-sm btn-outline-secondary rounded-pill" - @click="enableNodeCountToCpuCheck = !enableNodeCountToCpuCheck" - > - <i v-if="enableNodeCountToCpuCheck" class="fa fa-lock" aria-hidden="true"></i> - <i v-else class="fa fa-unlock" aria-hidden="true"></i> - </button> - <div - class="flex-fill" - style=" - border: 1px solid #6c757d; - border-bottom-right-radius: 10px; - margin-bottom: 57px; - border-left-width: 0px; - border-top-width: 0px; - margin-right: 15px; - " - ></div> - </div> - </div> - <form-group - label="Wall Time Limit" - label-for="walltime-limit" - :invalid-feedback="getValidationFeedback('wallTimeLimit')" - :state="getValidationState('wallTimeLimit', true)" - > - <div class="input-group"> - <input - id="walltime-limit" - v-model="data.wall_time_limit" - class="form-control" - type="number" - min="1" - :max="maxWalltime" - required - /> - <span class="input-group-text">minutes</span> - </div> - <small class="form-text text-muted"> - <i class="fa fa-info-circle" aria-hidden="true"></i> - Max Allowed Wall Time = {{ maxWalltime }} minutes - </small> - </form-group> - <form-group - v-if="maxPhysicalMemory > 0" - label="Total Physical Memory" - label-for="total-physical-memory" - :invalid-feedback="getValidationFeedback('totalPhysicalMemory')" - :state="getValidationState('totalPhysicalMemory', true)" - > - <div class="input-group"> - <input - id="total-physical-memory" - v-model="data.total_physical_memory" - class="form-control" - type="number" - min="0" - :max="maxPhysicalMemory" - /> - <span class="input-group-text">MB</span> - </div> - <small class="form-text text-muted"> - <i class="fa fa-info-circle" aria-hidden="true"></i> - Max Physical Memory = {{ maxPhysicalMemory }} MB - </small> - </form-group> - <div> - <a class="text-secondary action-link" href="#" @click.prevent="showConfiguration = false"> - <i class="fa fa-times text-secondary" aria-hidden="true"></i> - Hide Settings</a - > - </div> - </div> - </div> -</template> - -<script setup lang="ts"> -import { ref, computed, watch, onMounted } from "vue"; -import { models, services } from "django-airavata-api"; -import { utils } from "django-airavata-common-ui"; - -type ComputationalResourceSchedulingModel = InstanceType<typeof models.ComputationalResourceSchedulingModel>; -type ComputeResourcePolicy = InstanceType<typeof models.ComputeResourcePolicy>; - -interface QueueDefault { - queue_name: string; - queue_description?: string; - max_processors: number; - max_nodes: number; - max_run_time: number; - max_memory: number; - default_cpu_count: number; - default_node_count: number; - default_walltime: number; - is_default_queue: boolean; - cpu_per_node: number; -} - -interface BatchQueueResourcePolicy { - queuename: string; - maxAllowedCores: number; - maxAllowedNodes: number; - maxAllowedWalltime: number; - resourcePolicyId?: string; -} - -interface ApplicationInterface { - show_queue_settings: boolean; - queue_settings_calculator_id?: string; -} - -const props = defineProps<{ - modelValue: ComputationalResourceSchedulingModel; - appDeploymentId: string; - appModuleId: string; - computeResourcePolicy?: ComputeResourcePolicy | null; - batchQueueResourcePolicies?: BatchQueueResourcePolicy[] | null; -}>(); - -const emit = defineEmits<{ - "update:modelValue": [value: ComputationalResourceSchedulingModel]; - valid: []; - invalid: []; - input: [value: ComputationalResourceSchedulingModel]; -}>(); - -// VModelMixin inline -function copyValue(value: ComputationalResourceSchedulingModel) { - return value instanceof models.BaseModel ? (value as unknown as { clone: () => ComputationalResourceSchedulingModel }).clone() : value; -} - -const data = ref<ComputationalResourceSchedulingModel>(copyValue(props.modelValue)); - -watch( - () => props.modelValue, - (newValue) => { - data.value = copyValue(newValue); - }, - { deep: true }, -); - -watch( - data, - (newValue, oldValue) => { - if (typeof props.modelValue === "object" && newValue === oldValue) { - emit("update:modelValue", newValue); - emit("input", newValue); - } else if ( - (props.modelValue === null || typeof props.modelValue !== "object") && - newValue !== oldValue - ) { - emit("update:modelValue", newValue); - emit("input", newValue); - } - }, - { deep: true }, -); - -const showConfiguration = ref(false); -const appDeploymentQueues = ref<QueueDefault[] | null>(null); -const enableNodeCountToCpuCheck = ref(true); -const applicationInterface = ref<ApplicationInterface | null>(null); - -const queueOptions = computed(() => - queueDefaults.value.map((queueDefault) => ({ - value: queueDefault.queue_name, - text: queueDefault.queue_name, - })), -); - -const selectedQueueDefault = computed<QueueDefault | undefined>(() => - queueDefaults.value.find((queue) => queue.queue_name === data.value.queue_name), -); - -const batchQueueResourcePolicyComputed = computed<BatchQueueResourcePolicy | null>(() => { - if (!selectedQueueDefault.value) return null; - return getBatchQueueResourcePolicy(selectedQueueDefault.value.queue_name); -}); - -const maxCPUCount = computed<number>(() => { - if (!selectedQueueDefault.value) return 0; - const bqrp = batchQueueResourcePolicyComputed.value; - if (bqrp) { - return Math.min(bqrp.maxAllowedCores, selectedQueueDefault.value.max_processors); - } - return selectedQueueDefault.value.max_processors; -}); - -const maxNodes = computed<number>(() => { - if (!selectedQueueDefault.value) return 0; - const bqrp = batchQueueResourcePolicyComputed.value; - if (bqrp) { - return Math.min(bqrp.maxAllowedNodes, selectedQueueDefault.value.max_nodes); - } - return selectedQueueDefault.value.max_nodes; -}); - -const maxWalltime = computed<number>(() => { - if (!selectedQueueDefault.value) return 0; - const bqrp = batchQueueResourcePolicyComputed.value; - if (bqrp) { - return Math.min(bqrp.maxAllowedWalltime, selectedQueueDefault.value.max_run_time); - } - return selectedQueueDefault.value.max_run_time; -}); - -const maxPhysicalMemory = computed<number>(() => - selectedQueueDefault.value ? selectedQueueDefault.value.max_memory : 0, -); - -const queueDefaults = computed<QueueDefault[]>(() => { - if (!appDeploymentQueues.value) return []; - return appDeploymentQueues.value - .filter((q) => isQueueInComputeResourcePolicy(q.queue_name)) - .sort((a, b) => { - if (a.is_default_queue) return -1; - if (b.is_default_queue) return 1; - return a.queue_name.localeCompare(b.queue_name); - }); -}); - -const validation = computed(() => { - if (!selectedQueueDefault.value) { - return data.value.validate(); - } - return data.value.validate(selectedQueueDefault.value, batchQueueResourcePolicyComputed.value); -}); - -const valid = computed(() => Object.keys(validation.value).length === 0); - -const showQueueSettings = computed<boolean>(() => - applicationInterface.value ? applicationInterface.value.show_queue_settings : false, -); - -const disabled = computed<boolean>( - () => !!(applicationInterface.value && applicationInterface.value.queue_settings_calculator_id), -); - -const queueDescription = computed<string | null>(() => - selectedQueueDefault.value ? (selectedQueueDefault.value.queue_description ?? null) : null, -); - -watch(enableNodeCountToCpuCheck, () => { - if (enableNodeCountToCpuCheck.value) { - nodeCountChanged(); - } -}); - -watch( - () => props.appDeploymentId, - () => { - loadAppDeploymentQueues().then(() => setDefaultQueue()); - }, -); - -watch(batchQueueResourcePolicyComputed, (value, oldValue) => { - if (value && (!oldValue || value.resourcePolicyId !== oldValue.resourcePolicyId)) { - applyBatchQueueResourcePolicy(); - } -}); - -watch( - () => props.computeResourcePolicy, - () => { - if (!isQueueInComputeResourcePolicy(data.value.queue_name)) { - setDefaultQueue(); - } - }, -); - -watch( - () => props.modelValue, - () => { - validate(); - }, - { deep: true }, -); - -onMounted(() => { - loadAppDeploymentQueues().then(() => { - if (!props.modelValue.queue_name) { - setDefaultQueue(); - } - }); - validate(); - loadApplicationInterface(); -}); - -function queueChanged(event: Event) { - const queueName = (event.target as HTMLSelectElement).value; - const queueDefault = queueDefaults.value.find((queue) => queue.queue_name === queueName); - if (queueDefault) { - data.value.total_cpu_count = getDefaultCPUCount(queueDefault); - data.value.node_count = getDefaultNodeCount(queueDefault); - data.value.wall_time_limit = getDefaultWalltime(queueDefault); - if (maxPhysicalMemory.value === 0) { - data.value.total_physical_memory = 0; - } - } -} - -function validate() { - if (!valid.value) { - emit("invalid"); - } else { - emit("valid"); - } -} - -function loadAppDeploymentQueues(): Promise<void> { - return services.ApplicationDeploymentService.getQueues({ - lookup: props.appDeploymentId, - }).then((queueDefaults: unknown) => { - appDeploymentQueues.value = queueDefaults as QueueDefault[]; - }); -} - -function setDefaultQueue() { - if (queueDefaults.value.length === 0) { - data.value.queue_name = null; - return; - } - const defaultQueue = queueDefaults.value[0]; - data.value.queue_name = defaultQueue.queue_name; - data.value.total_cpu_count = getDefaultCPUCount(defaultQueue); - data.value.node_count = getDefaultNodeCount(defaultQueue); - data.value.wall_time_limit = getDefaultWalltime(defaultQueue); - if (maxPhysicalMemory.value === 0) { - data.value.total_physical_memory = 0; - } -} - -function isQueueInComputeResourcePolicy(queue_name: string): boolean { - if (!props.computeResourcePolicy) return true; - return (props.computeResourcePolicy as unknown as { allowedBatchQueues: string[] }).allowedBatchQueues.includes(queue_name); -} - -function getBatchQueueResourcePolicy(queueName: string): BatchQueueResourcePolicy | null { - if (!props.batchQueueResourcePolicies || props.batchQueueResourcePolicies.length === 0) { - return null; - } - return props.batchQueueResourcePolicies.find((bqrp) => bqrp.queuename === queueName) ?? null; -} - -function getDefaultCPUCount(queueDefault: QueueDefault): number { - const bqrp = batchQueueResourcePolicyComputed.value; - if (bqrp) { - return Math.min(bqrp.maxAllowedCores, queueDefault.default_cpu_count); - } - return queueDefault.default_cpu_count; -} - -function getDefaultNodeCount(queueDefault: QueueDefault): number { - const bqrp = batchQueueResourcePolicyComputed.value; - if (bqrp) { - return Math.min(bqrp.maxAllowedNodes, queueDefault.default_node_count); - } - return queueDefault.default_node_count; -} - -function getDefaultWalltime(queueDefault: QueueDefault): number { - const bqrp = batchQueueResourcePolicyComputed.value; - if (bqrp) { - return Math.min(bqrp.maxAllowedWalltime, queueDefault.default_walltime); - } - return queueDefault.default_walltime; -} - -function getValidationFeedback(properties: string): unknown { - return utils.getProperty(validation.value, properties); -} - -function getValidationState(properties: string, showValidState?: boolean): boolean | null { - return getValidationFeedback(properties) ? false : showValidState ? true : null; -} - -function applyBatchQueueResourcePolicy() { - if (selectedQueueDefault.value) { - data.value.total_cpu_count = Math.min(data.value.total_cpu_count, maxCPUCount.value); - data.value.node_count = Math.min(data.value.node_count, maxNodes.value); - data.value.wall_time_limit = Math.min(data.value.wall_time_limit, maxWalltime.value); - } -} - -function nodeCountChanged() { - if (enableNodeCountToCpuCheck.value && selectedQueueDefault.value && selectedQueueDefault.value.cpu_per_node > 0) { - const nodeCount = parseInt(String(data.value.node_count)); - data.value.total_cpu_count = Math.min( - nodeCount * selectedQueueDefault.value.cpu_per_node, - maxCPUCount.value, - ); - } -} - -function cpuCountChanged() { - if (enableNodeCountToCpuCheck.value && selectedQueueDefault.value && selectedQueueDefault.value.cpu_per_node > 0) { - const cpuCount = parseInt(String(data.value.total_cpu_count)); - if (cpuCount > 0) { - data.value.node_count = Math.min( - Math.ceil(cpuCount / selectedQueueDefault.value.cpu_per_node), - maxNodes.value, - ); - } - } -} - -function loadApplicationInterface() { - services.ApplicationModuleService.getApplicationInterface({ - lookup: props.appModuleId, - }).then((iface: unknown) => { - applicationInterface.value = iface as ApplicationInterface; - }); -} -</script> - -<style></style> diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/CreateExperimentContainer.vue b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/CreateExperimentContainer.vue deleted file mode 100644 index 7a0bfcaac..000000000 --- a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/CreateExperimentContainer.vue +++ /dev/null @@ -1,99 +0,0 @@ -<template> - <experiment-editor - v-if="experiment" - :experiment="experiment" - :app-module="appModule" - :app-interface="appInterface" - @saved="handleSavedExperiment" - @saved-and-launched="handleSavedAndLaunchedExperiment" - > - <template #title> - <span>Create a New Experiment</span> - </template> - </experiment-editor> -</template> - -<script setup lang="ts"> -import { ref, onMounted } from "vue"; -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"; - -import { formatShort } from "django-airavata-common-ui/js/utils/dates.js"; - -const props = defineProps<{ - appModuleId?: string; - userInputValues?: Record<string, unknown> | null; - experimentDataDir?: string | null; -}>(); - -// Convenience accessors -const appModuleId = () => props.appModuleId; -const userInputValues = () => props.userInputValues; -const experimentDataDir = () => props.experimentDataDir; - -const experiment = ref<unknown>(null); -const appModule = ref<unknown>(null); -const appInterface = ref<unknown>(null); - -onMounted(() => { - const modId = appModuleId(); - if (!modId) return; - const loadAppModule = services.ApplicationModuleService.retrieve( - { lookup: modId }, - { ignoreErrors: true }, - ); - const loadAppInterface = services.ApplicationModuleService.getApplicationInterface( - { lookup: modId }, - { ignoreErrors: true }, - ); - Promise.all([loadAppModule, loadAppInterface]) - .then(([mod, iface]: [unknown, unknown]) => { - const typedMod = mod as { app_module_name: string }; - const typedIface = iface as { - createExperiment(): unknown; - }; - const exp = typedIface.createExperiment() as { - experiment_name: string; - experiment_inputs: Array<{ name: string; value: unknown }>; - user_configuration_data: { experiment_data_dir: string }; - }; - exp.experiment_name = typedMod.app_module_name + " on " + formatShort(new Date()); - appModule.value = mod; - appInterface.value = iface; - const inputValues = userInputValues(); - if (inputValues) { - Object.keys(inputValues).forEach((k) => { - const experimentInput = exp.experiment_inputs.find((inp) => inp.name === k); - if (experimentInput) { - experimentInput.value = inputValues[k]; - } - }); - } - const dataDir = experimentDataDir(); - if (dataDir) { - exp.user_configuration_data.experiment_data_dir = dataDir; - } - experiment.value = exp; - }) - .catch((error: unknown) => { - notifications.NotificationList.addError(error); - }); -}); - -function handleSavedExperiment() { - urls.navigateToExperimentsList(""); -} - -function handleSavedAndLaunchedExperiment(exp: unknown) { - urls.navigateToViewExperiment("", exp as { experiment_id: string }, { launching: true }); -} -</script> - -<style> -/* style the containing div, in base.html template */ -.main-content-wrapper { - background-color: #ffffff; -} -</style> diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/EditExperimentContainer.vue b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/EditExperimentContainer.vue deleted file mode 100644 index a5566322c..000000000 --- a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/EditExperimentContainer.vue +++ /dev/null @@ -1,78 +0,0 @@ -<template> - <experiment-editor - v-if="appModule" - :experiment="experiment" - :app-module="appModule" - :app-interface="appInterface" - @saved="handleSavedExperiment" - @saved-and-launched="handleSavedAndLaunchedExperiment" - > - <template #title> - <span>Edit Experiment</span> - </template> - </experiment-editor> -</template> - -<script setup lang="ts"> -import { ref, onMounted } from "vue"; -import { errors, services } from "django-airavata-api"; -import { notifications } from "django-airavata-common-ui"; -import ExperimentEditor from "../components/experiment/ExperimentEditor.vue"; -import urls from "../utils/urls"; - -const props = defineProps<{ - experimentId: string; -}>(); - -const experiment = ref<unknown>(null); -const appModule = ref<unknown>(null); -const appInterface = ref<unknown>(null); - -onMounted(() => { - services.ExperimentService.retrieve({ lookup: props.experimentId }) - .then((exp: unknown) => { - experiment.value = exp; - const appInterfaceId = (exp as { execution_id: string }).execution_id; - return services.ApplicationInterfaceService.retrieve( - { lookup: appInterfaceId }, - { ignoreErrors: true }, - ); - }) - .then((iface: unknown) => { - appInterface.value = iface; - const appModuleId = (iface as { application_modules: string[] }).application_modules[0]; - return services.ApplicationModuleService.retrieve({ lookup: appModuleId }); - }) - .then((mod: unknown) => { - appModule.value = mod; - }) - .catch((error: unknown) => { - const exp = experiment.value as { execution_id: string } | null; - const message = errors.ErrorUtils.isNotFoundError(error) - ? `Application interface (${exp?.execution_id}) was not found. - If it has been deleted then you won't be able to edit this experiment.` - : `Unable to load application interface (${exp?.execution_id}) or module`; - notifications.NotificationList.add( - new notifications.Notification({ - type: "ERROR", - message, - }), - ); - }); -}); - -function handleSavedExperiment() { - urls.navigateToExperimentsList(""); -} - -function handleSavedAndLaunchedExperiment(exp: unknown) { - urls.navigateToViewExperiment("", exp as { experiment_id: string }, { launching: true }); -} -</script> - -<style> -/* style the containing div, in base.html template */ -.main-content-wrapper { - background-color: #ffffff; -} -</style> diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-create-experiment.js b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-create-experiment.js deleted file mode 100644 index 9d0d999b4..000000000 --- a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-create-experiment.js +++ /dev/null @@ -1,32 +0,0 @@ -import { h } from "vue"; -import { components, entry } from "django-airavata-common-ui"; -import CreateExperimentContainer from "./containers/CreateExperimentContainer.vue"; -import "../../scss/styles.scss"; - -entry(({ createApp }) => { - const el = document.getElementById("create-experiment"); - const appModuleId = el ? el.dataset.appModuleId || null : null; - const userInputValues = - el && el.dataset.userInputValues ? JSON.parse(el.dataset.userInputValues) : null; - const experimentDataDir = el ? el.dataset.experimentDataDir || null : null; - const app = createApp({ - data() { - return { - appModuleId, - userInputValues, - experimentDataDir, - }; - }, - render() { - return h(components.MainLayout, null, { - default: () => - h(CreateExperimentContainer, { - appModuleId: this.appModuleId, - userInputValues: this.userInputValues, - experimentDataDir: this.experimentDataDir, - }), - }); - }, - }); - app.mount("#create-experiment"); -}); diff --git a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-edit-experiment.js b/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-edit-experiment.js deleted file mode 100644 index 4332b258e..000000000 --- a/airavata-django-portal/django_airavata/apps/workspace/static/django_airavata_workspace/js/entry-edit-experiment.js +++ /dev/null @@ -1,25 +0,0 @@ -import { h } from "vue"; -import { components, entry } from "django-airavata-common-ui"; -import EditExperimentContainer from "./containers/EditExperimentContainer.vue"; -import "../../scss/styles.scss"; - -entry(({ createApp }) => { - const el = document.getElementById("edit-experiment"); - const experimentId = el ? el.dataset.experimentId || null : null; - const app = createApp({ - data() { - return { - experimentId, - }; - }, - render() { - return h(components.MainLayout, null, { - default: () => - h(EditExperimentContainer, { - experimentId: this.experimentId, - }), - }); - }, - }); - app.mount("#edit-experiment"); -}); diff --git a/airavata-django-portal/django_airavata/apps/workspace/urls.py b/airavata-django-portal/django_airavata/apps/workspace/urls.py index d493334b5..5e1218575 100644 --- a/airavata-django-portal/django_airavata/apps/workspace/urls.py +++ b/airavata-django-portal/django_airavata/apps/workspace/urls.py @@ -23,8 +23,8 @@ urlpatterns = [ ), re_path( r"^projects/(?P<project_id>[^/]+)/experiments/(?P<experiment_id>[^/]+)/edit$", - views.edit_experiment, - name="edit_experiment", + RedirectView.as_view(url="/workspace/launch", permanent=True), + name="edit_experiment_redirect", ), re_path( r"^projects/(?P<project_id>[^/]+)/experiments/(?P<experiment_id>[^/]+)/$", diff --git a/airavata-django-portal/django_airavata/apps/workspace/views.py b/airavata-django-portal/django_airavata/apps/workspace/views.py index cfdf3010d..54ecdaa1a 100644 --- a/airavata-django-portal/django_airavata/apps/workspace/views.py +++ b/airavata-django-portal/django_airavata/apps/workspace/views.py @@ -1,22 +1,16 @@ import json import logging -from urllib.parse import urlparse from django.conf import settings from django.contrib.auth.decorators import login_required from django.shortcuts import render -from django.utils.module_loading import import_string from rest_framework.renderers import JSONRenderer -from django_airavata.apps.api import models -from django_airavata.apps.api import user_storage as user_storage_sdk from django_airavata.apps.api.views import ( - ApplicationModuleViewSet, ExperimentSearchViewSet, FullExperimentViewSet, ProjectViewSet, ) -from django_airavata.proto_compat import DataType logger = logging.getLogger(__name__) @@ -27,8 +21,6 @@ ENTRY_POINTS = { "project-list": "static/django_airavata_workspace/js/entry-project-list.js", "project-overview": "static/django_airavata_workspace/js/entry-project-overview.js", "edit-project": "static/django_airavata_workspace/js/entry-edit-project.js", - "create-experiment": "static/django_airavata_workspace/js/entry-create-experiment.js", - "edit-experiment": "static/django_airavata_workspace/js/entry-edit-experiment.js", "view-experiment": "static/django_airavata_workspace/js/entry-view-experiment.js", "user-storage": "static/django_airavata_workspace/js/entry-user-storage.js", "compute": "static/django_airavata_workspace/js/entry-compute.js", @@ -110,19 +102,6 @@ def _create_default_project(request): return project_id -@login_required -def applications(request): - request.active_nav_item = "applications" - return render( - request, - "django_airavata_workspace/base.html", - { - "bundle_name": "applications", - "entry_point": ENTRY_POINTS["applications"], - }, - ) - - @login_required def new_application(request): request.active_nav_item = "applications" @@ -185,105 +164,6 @@ def project_overview(request, project_id): ) -@login_required -def create_experiment(request, app_module_id): - request.active_nav_item = "applications" - - # User input files can be passed as query parameters - # <input name>=<path/to/user_file> - # and also as data product URIs - # <input name>=<data product URI> - app_interface = ApplicationModuleViewSet.as_view({"get": "application_interface"})( - request, app_module_id=app_module_id - ) - if app_interface.status_code != 200: - raise Exception("Failed to load application module data: {}".format(app_interface.data["detail"])) - user_input_values = {} - for app_input in app_interface.data.get("application_inputs", []): - if app_input["type"] == DataType.URI and app_input["name"] in request.GET: - user_file_value = request.GET[app_input["name"]] - try: - user_file_url = urlparse(user_file_value) - if user_file_url.scheme == "airavata-dp": - dp_uri = user_file_value - try: - data_product = request.airavata_client.research.get_data_product(dp_uri) - if user_storage_sdk.exists(request, data_product): - user_input_values[app_input["name"]] = dp_uri - except Exception: - logger.exception(f"Failed checking data product uri: {dp_uri}", extra={"request": request}) - except ValueError: - logger.exception(f"Invalid user file value: {user_file_value}", extra={"request": request}) - elif app_input["type"] == DataType.STRING and app_input["name"] in request.GET: - name = app_input["name"] - user_input_values[name] = request.GET[name] - context = { - "bundle_name": "create-experiment", - "entry_point": ENTRY_POINTS["create-experiment"], - "app_module_id": app_module_id, - "user_input_values": json.dumps(user_input_values), - } - if "experiment-data-dir" in request.GET: - context["experiment_data_dir"] = request.GET["experiment-data-dir"] - - template_path = "django_airavata_workspace/create_experiment.html" - # Apply a custom application template if it exists - custom_template_path, custom_context = get_custom_template(request, app_module_id) - if custom_template_path is not None: - logger.debug(f"Applying custom application template {custom_template_path}") - template_path = custom_template_path - context.update(custom_context) - - return render(request, template_path, context) - - -@login_required -def edit_experiment(request, project_id, experiment_id): - request.active_nav_item = "projects" - - project = request.airavata_client.research.get_project(project_id) - experiment = request.airavata_client.research.get_experiment(experiment_id) - applicationInterface = request.airavata_client.research.get_application_interface(experiment.execution_id) - app_module_id = applicationInterface.application_modules[0] - - breadcrumbs = [ - {"label": "Projects", "url": "/workspace/projects"}, - {"label": project.name, "url": f"/workspace/projects/{project_id}/"}, - {"label": "Experiments", "url": f"/workspace/projects/{project_id}/experiments"}, - {"label": "Edit Experiment", "url": None}, - ] - - context = { - "bundle_name": "edit-experiment", - "entry_point": ENTRY_POINTS["edit-experiment"], - "experiment_id": experiment_id, - "app_module_id": app_module_id, - "project_id": project_id, - "breadcrumbs_json": json.dumps(breadcrumbs), - } - template_path = "django_airavata_workspace/edit_experiment.html" - custom_template_path, custom_context = get_custom_template(request, app_module_id) - if custom_template_path is not None: - logger.debug(f"Applying custom application template {custom_template_path}") - template_path = custom_template_path - context.update(custom_context) - - return render(request, template_path, context) - - -def get_custom_template(request, app_module_id): - template_path = None - context = {} - query = models.ApplicationTemplate.objects.filter(application_module_id=app_module_id) - if query.exists(): - application_template = query.get() - template_path = application_template.template_path - for context_processor in application_template.context_processors.all(): - context_processor = import_string(context_processor.callable_path) - context.update(context_processor(request)) - return template_path, context - - @login_required def view_experiment(request, project_id, experiment_id): request.active_nav_item = "projects" diff --git a/airavata-django-portal/django_airavata/apps/workspace/vite.config.js b/airavata-django-portal/django_airavata/apps/workspace/vite.config.js index a73d02e6a..2d23675da 100644 --- a/airavata-django-portal/django_airavata/apps/workspace/vite.config.js +++ b/airavata-django-portal/django_airavata/apps/workspace/vite.config.js @@ -10,10 +10,8 @@ export default defineAppConfig({ dashboard: resolve(srcDir, "entry-dashboard.js"), "project-list": resolve(srcDir, "entry-project-list.js"), applications: resolve(srcDir, "entry-applications.js"), - "create-experiment": resolve(srcDir, "entry-create-experiment.js"), "view-experiment": resolve(srcDir, "entry-view-experiment.js"), "experiment-list": resolve(srcDir, "entry-experiment-list.js"), - "edit-experiment": resolve(srcDir, "entry-edit-experiment.js"), "edit-project": resolve(srcDir, "entry-edit-project.js"), "user-storage": resolve(srcDir, "entry-user-storage.js"), compute: resolve(srcDir, "entry-compute.js"),
