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 2021/05/11 20:42:18 UTC

[airavata-django-portal] branch airavata-3453 updated (f4224c0 -> 982cada)

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a change to branch airavata-3453
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git.


    from f4224c0  AIRAVATA-3453 lint error
     new b29b545  AIRAVATA-3453 Initial version of resource selection components
     new 982cada  AIRAVATA-3453 Fixed async caching logic

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../api/static/django_airavata_api/js/index.js     |   6 +-
 .../js/web-components/ComputeResourceSelector.vue  |  84 +++++++++++
 .../js/web-components/ExperimentEditor.vue         |  18 +--
 .../js/web-components/QueueSettingsEditor.vue      |  13 ++
 .../js/web-components/ResourceSelectionEditor.vue  | 160 +++++++++++++++++++++
 .../js/web-components/store.js                     |  63 +++++---
 6 files changed, 318 insertions(+), 26 deletions(-)
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ComputeResourceSelector.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/QueueSettingsEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ResourceSelectionEditor.vue

[airavata-django-portal] 01/02: AIRAVATA-3453 Initial version of resource selection components

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch airavata-3453
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit b29b54580939ed6ad4a3d27f6cdb141a56a2d144
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue May 11 16:36:16 2021 -0400

    AIRAVATA-3453 Initial version of resource selection components
---
 .../api/static/django_airavata_api/js/index.js     |   6 +-
 .../js/web-components/ComputeResourceSelector.vue  |  84 +++++++++++
 .../js/web-components/ExperimentEditor.vue         |  18 +--
 .../js/web-components/QueueSettingsEditor.vue      |  13 ++
 .../js/web-components/ResourceSelectionEditor.vue  | 160 +++++++++++++++++++++
 .../js/web-components/store.js                     |  35 ++++-
 6 files changed, 306 insertions(+), 10 deletions(-)

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 13d130f..489589d 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
@@ -35,6 +35,7 @@ import SetEnvPaths from "./models/SetEnvPaths";
 import SharedEntity from "./models/SharedEntity";
 import StoragePreference from "./models/StoragePreference";
 import SummaryType from "./models/SummaryType";
+import UserConfigurationData from "./models/UserConfigurationData";
 import UserPermission from "./models/UserPermission";
 
 import CloudJobSubmissionService from "./services/CloudJobSubmissionService";
@@ -93,6 +94,7 @@ const models = {
   SharedEntity,
   StoragePreference,
   SummaryType,
+  UserConfigurationData,
   UserPermission,
 };
 
@@ -110,7 +112,9 @@ const services = {
   ExperimentSearchService: ServiceFactory.service("ExperimentSearch"),
   ExperimentService: ServiceFactory.service("Experiments"),
   ExperimentStatisticsService: ServiceFactory.service("ExperimentStatistics"),
-  ExperimentStoragePathService: ServiceFactory.service("ExperimentStoragePaths"),
+  ExperimentStoragePathService: ServiceFactory.service(
+    "ExperimentStoragePaths"
+  ),
   FullExperimentService: ServiceFactory.service("FullExperiments"),
   GatewayResourceProfileService: ServiceFactory.service(
     "GatewayResourceProfile"
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ComputeResourceSelector.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ComputeResourceSelector.vue
new file mode 100644
index 0000000..3b28d83
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ComputeResourceSelector.vue
@@ -0,0 +1,84 @@
+<template>
+  <b-form-group label="Compute Resource" label-for="compute-resource">
+    <b-form-select
+      id="compute-resource"
+      v-model="resourceHostId"
+      :options="computeResourceOptions"
+      required
+      @input="computeResourceChanged"
+    >
+      <template slot="first">
+        <option :value="null" disabled>Select a Compute Resource</option>
+      </template>
+    </b-form-select>
+  </b-form-group>
+</template>
+
+<script>
+import { getComputeResourceNames } from "./store";
+export default {
+  name: "compute-resource-selector",
+  props: {
+    value: {
+      // compute resource host id
+      type: String,
+      default: null,
+    },
+    computeResources: {
+      type: Array, // of compute resource host ids
+      default: () => [],
+    },
+  },
+  data() {
+    return {
+      resourceHostId: this.value,
+      computeResourceNames: {},
+    };
+  },
+  created() {
+    this.loadComputeResourceNames();
+  },
+  computed: {
+    computeResourceOptions: function () {
+      const computeResourceOptions = this.computeResources.map(
+        (computeHostId) => {
+          return {
+            value: computeHostId,
+            text:
+              computeHostId in this.computeResourceNames
+                ? this.computeResourceNames[computeHostId]
+                : "",
+          };
+        }
+      );
+      computeResourceOptions.sort((a, b) => a.text.localeCompare(b.text));
+      return computeResourceOptions;
+    },
+  },
+  methods: {
+    async loadComputeResourceNames() {
+      this.computeResourceNames = await getComputeResourceNames();
+    },
+    computeResourceChanged() {
+      this.emitValueChanged();
+    },
+    emitValueChanged: function () {
+      const inputEvent = new CustomEvent("input", {
+        detail: [this.resourceHostId],
+        composed: true,
+        bubbles: true,
+      });
+      this.$el.dispatchEvent(inputEvent);
+    },
+  },
+  watch: {
+    value() {
+      this.resourceHostId = this.value;
+    },
+  },
+};
+</script>
+
+<style>
+@import "./styles.css";
+</style>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentEditor.vue
index 25d77d8..e7acb23 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentEditor.vue
@@ -24,9 +24,9 @@
       <!-- programmatically define slots as native slots (not Vue slots), see #mounted() -->
       </div>
     </template>
-    <div @input="updateGroupResourceProfileId">
-      <slot name="experiment-group-resource-profile">
-        <adpf-group-resource-profile-selector :value="experiment.userConfigurationData.groupResourceProfileId"/>
+    <div @input="updateUserConfigurationData">
+      <slot name="experiment-resource-selection">
+        <adpf-resource-selection-editor ref="resourceSelectionEditor" />
       </slot>
     </div>
     <slot name="save-button">
@@ -45,6 +45,7 @@ import {
 
 export default {
   props: {
+    // TODO: rename to applicationModuleId?
     applicationId: {
       type: String,
       required: true,
@@ -79,6 +80,9 @@ export default {
         // TODO: add support for other input types
         this.$refs[input.name][0].append(slot);
       }
+      // Can't set objects via attributes, must set as prop
+      this.$refs.resourceSelectionEditor.value = this.experiment.userConfigurationData;
+      this.$refs.resourceSelectionEditor.applicationModuleId = this.applicationId;
     });
   },
   data() {
@@ -100,9 +104,9 @@ export default {
       const [projectId] = event.detail;
       this.experiment.projectId = projectId;
     },
-    updateGroupResourceProfileId(event) {
-      const [groupResourceProfileId] = event.detail;
-      this.experiment.userConfigurationData.groupResourceProfileId = groupResourceProfileId;
+    updateUserConfigurationData(event) {
+      const [userConfigurationData] = event.detail;
+      this.experiment.userConfigurationData = userConfigurationData;
     },
     onSubmit(event) {
       // console.log(event);
@@ -137,8 +141,6 @@ export default {
           this.applicationModule.appModuleName +
           " on " +
           new Date().toLocaleString();
-        experiment.userConfigurationData.computationalResourceScheduling.resourceHostId =
-          "js-169-51.jetstream-cloud.org_6672e8fe-8d63-4bbe-8bf8-4ea04092e72f";
         this.$emit("loaded", experiment);
         return experiment;
       }
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/QueueSettingsEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/QueueSettingsEditor.vue
new file mode 100644
index 0000000..a02d2bb
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/QueueSettingsEditor.vue
@@ -0,0 +1,13 @@
+<template>
+  <div>QueueSettingsEditor</div>
+</template>
+
+<script>
+export default {
+
+}
+</script>
+
+<style>
+
+</style>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ResourceSelectionEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ResourceSelectionEditor.vue
new file mode 100644
index 0000000..d6824ec
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ResourceSelectionEditor.vue
@@ -0,0 +1,160 @@
+<template>
+  <div v-if="userConfigurationData">
+    <div @input.stop="updateGroupResourceProfileId">
+      <adpf-group-resource-profile-selector
+        slot="resource-selection-grp"
+        :value="userConfigurationData.groupResourceProfileId"
+      />
+    </div>
+    <div @input.stop="updateComputeResourceHostId">
+      <adpf-compute-resource-selector
+        ref="computeResourceSelector"
+        slot="resource-selection-compute-resource"
+        :value="
+          userConfigurationData.computationalResourceScheduling.resourceHostId
+        "
+      />
+    </div>
+    <div @input.stop="updateComputationalResourceScheduling">
+      <adpf-queue-settings-editor
+        slot="resource-selection-queue-settings"
+        :value="userConfigurationData.computationalResourceScheduling"
+        :queues="queues"
+        :max-allowed-nodes="maxAllowedNodes"
+        :max-allowed-cores="maxAllowedCores"
+        :max-allowed-walltime="maxAllowedWalltime"
+        :max-memory="maxMemory"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+import { models } from "django-airavata-api";
+import {
+  getApplicationDeployments,
+  getDefaultComputeResourceId,
+} from "./store";
+export default {
+  // TODO: better name? UserConfigurationDataEditor?
+  name: "resource-selection-editor",
+  props: {
+    value: {
+      type: models.UserConfigurationData,
+    },
+    applicationModuleId: {
+      type: String,
+    },
+  },
+  data() {
+    return {
+      userConfigurationData: this.cloneValue(),
+      applicationDeployments: [],
+      queues: [],
+      maxAllowedNodes: 0,
+      maxAllowedCores: 0,
+      maxAllowedWalltime: 0,
+      maxMemory: 0,
+      defaultComputeResourceId: null,
+    };
+  },
+  computed: {
+    computeResources() {
+      return this.applicationDeployments.map((dep) => dep.computeHostId);
+    },
+    groupResourceProfileId() {
+      return this.userConfigurationData
+        ? this.userConfigurationData.groupResourceProfileId
+        : null;
+    },
+    resourceHostId() {
+      return this.userConfigurationData &&
+        this.userConfigurationData.computationalResourceScheduling
+        ? this.userConfigurationData.computationalResourceScheduling
+            .resourceHostId
+        : null;
+    },
+  },
+  methods: {
+    emitValueChanged: function () {
+      const inputEvent = new CustomEvent("input", {
+        detail: [this.userConfigurationData],
+        composed: true,
+        bubbles: true,
+      });
+      this.$el.dispatchEvent(inputEvent);
+    },
+    updateGroupResourceProfileId(event) {
+      const [groupResourceProfileId] = event.detail;
+      this.userConfigurationData.groupResourceProfileId = groupResourceProfileId;
+      this.emitValueChanged();
+      this.loadApplicationDeployments();
+    },
+    updateComputeResourceHostId(event) {
+      const [computeResourceHostId] = event.detail;
+      this.userConfigurationData.computationalResourceScheduling.resourceHostId = computeResourceHostId;
+      this.emitValueChanged();
+      // TODO: recalculate queues for the selected host
+    },
+    updateComputationalResourceScheduling(event) {
+      const [computationalResourceScheduling] = event.detail;
+      this.userConfigurationData.computationalResourceScheduling = computationalResourceScheduling;
+      this.emitValueChanged();
+      // TODO: recalculate maxes for the selected queue, etc.
+    },
+    async loadApplicationDeployments() {
+      this.applicationDeployments = await getApplicationDeployments(
+        this.applicationModuleId,
+        this.groupResourceProfileId
+      );
+      if (
+        !this.userConfigurationData.computationalResourceScheduling
+          .computeHostId
+      ) {
+        this.userConfigurationData.computationalResourceScheduling.resourceHostId = this.getDefaultResourceHostId();
+      }
+    },
+    cloneValue() {
+      return this.value ? new models.UserConfigurationData(this.value) : null;
+    },
+    async loadData() {
+      if (this.groupResourceProfileId) {
+        this.loadApplicationDeployments();
+      }
+      this.loadDefaultComputeResourceId();
+    },
+    async loadDefaultComputeResourceId() {
+      this.defaultComputeResourceId = await getDefaultComputeResourceId();
+    },
+    getDefaultResourceHostId() {
+      if (
+        this.defaultComputeResourceId &&
+        this.computeResources.find(
+          (crid) => crid === this.defaultComputeResourceId
+        )
+      ) {
+        return this.defaultComputeResourceId;
+      } else if (this.computeResources.length > 0) {
+        // Just pick the first one
+        return this.computeResources[0];
+      }
+    },
+    bindWebComponentProps() {
+      this.$nextTick(() => {
+        this.$refs.computeResourceSelector.computeResources = this.computeResources;
+        this.$refs.computeResourceSelector.value = this.resourceHostId;
+      });
+    },
+  },
+  watch: {
+    value() {
+      this.userConfigurationData = this.cloneValue();
+      this.loadData();
+    },
+    computeResources: "bindWebComponentProps",
+    resourceHostId: "bindWebComponentProps",
+  },
+};
+</script>
+
+<style></style>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js
index 387ca5a..866fcea 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js
@@ -1,4 +1,4 @@
-import { services } from "django-airavata-api";
+import { errors, services, utils } from "django-airavata-api";
 const CACHE = {
   APPLICATION_MODULES: {},
   APPLICATION_INTERFACES: {},
@@ -16,6 +16,8 @@ export async function getApplicationModule(applicationId) {
 }
 
 export async function getApplicationInterfaceForModule(applicationId) {
+  // TODO: I'm not sure this is the right pattern. Perhaps the promise should be
+  // put in the cache and the cache entry should be 'await'-ed.
   if (applicationId in CACHE.APPLICATION_INTERFACES) {
     return CACHE.APPLICATION_INTERFACES[applicationId];
   }
@@ -54,6 +56,11 @@ export async function getDefaultGroupResourceProfileId() {
   return prefs.most_recent_group_resource_profile_id;
 }
 
+export async function getDefaultComputeResourceId() {
+  const prefs = await getWorkspacePreferences();
+  return prefs.most_recent_compute_resource_id;
+}
+
 export async function getExperiment(experimentId) {
   return await services.ExperimentService.retrieve({ lookup: experimentId });
 }
@@ -65,3 +72,29 @@ export async function getProjects() {
 export async function getGroupResourceProfiles() {
   return await services.GroupResourceProfileService.list();
 }
+
+export async function getApplicationDeployments(
+  applicationId,
+  groupResourceProfileId
+) {
+  return await services.ApplicationDeploymentService.list(
+    {
+      appModuleId: applicationId,
+      groupResourceProfileId: groupResourceProfileId,
+    },
+    { ignoreErrors: true }
+  )
+    .catch((error) => {
+      // Ignore unauthorized errors, force user to pick another GroupResourceProfile
+      if (!errors.ErrorUtils.isUnauthorizedError(error)) {
+        return Promise.reject(error);
+      }
+    })
+    // Report all other error types
+    .catch(utils.FetchUtils.reportError);
+}
+
+export async function getComputeResourceNames() {
+  // TODO: cache these
+  return await services.ComputeResourceService.names();
+}

[airavata-django-portal] 02/02: AIRAVATA-3453 Fixed async caching logic

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch airavata-3453
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 982cada25160dbb17fcabdcee140d0fa2cff3cb5
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue May 11 16:41:58 2021 -0400

    AIRAVATA-3453 Fixed async caching logic
---
 .../js/web-components/store.js                     | 32 ++++++++++------------
 1 file changed, 14 insertions(+), 18 deletions(-)

diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js
index 866fcea..0388289 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js
@@ -5,27 +5,23 @@ const CACHE = {
   WORKSPACE_PREFERENCES: null,
 };
 export async function getApplicationModule(applicationId) {
-  if (applicationId in CACHE.APPLICATION_MODULES) {
-    return CACHE.APPLICATION_MODULES[applicationId];
+  if (!(applicationId in CACHE.APPLICATION_MODULES)) {
+    const promise = services.ApplicationModuleService.retrieve({
+      lookup: applicationId,
+    });
+    CACHE.APPLICATION_MODULES[applicationId] = promise;
   }
-  const result = await services.ApplicationModuleService.retrieve({
-    lookup: applicationId,
-  });
-  CACHE.APPLICATION_MODULES[applicationId] = result;
-  return result;
+  return await CACHE.APPLICATION_MODULES[applicationId];
 }
 
 export async function getApplicationInterfaceForModule(applicationId) {
-  // TODO: I'm not sure this is the right pattern. Perhaps the promise should be
-  // put in the cache and the cache entry should be 'await'-ed.
-  if (applicationId in CACHE.APPLICATION_INTERFACES) {
-    return CACHE.APPLICATION_INTERFACES[applicationId];
+  if (!(applicationId in CACHE.APPLICATION_INTERFACES)) {
+    const promise = services.ApplicationModuleService.getApplicationInterface({
+      lookup: applicationId,
+    });
+    CACHE.APPLICATION_INTERFACES[applicationId] = promise;
   }
-  const result = await services.ApplicationModuleService.getApplicationInterface(
-    { lookup: applicationId }
-  );
-  CACHE.APPLICATION_INTERFACES[applicationId] = result;
-  return result;
+  return await CACHE.APPLICATION_INTERFACES[applicationId];
 }
 
 export async function saveExperiment(experiment) {
@@ -41,9 +37,9 @@ export async function saveExperiment(experiment) {
 
 export async function getWorkspacePreferences() {
   if (!CACHE.WORKSPACE_PREFERENCES) {
-    CACHE.WORKSPACE_PREFERENCES = await services.WorkspacePreferencesService.get();
+    CACHE.WORKSPACE_PREFERENCES = services.WorkspacePreferencesService.get();
   }
-  return CACHE.WORKSPACE_PREFERENCES;
+  return await CACHE.WORKSPACE_PREFERENCES;
 }
 
 export async function getDefaultProjectId() {