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/10/05 19:47:14 UTC

[airavata-django-portal] 03/24: AIRAVATA-3477 Finish converting to Vuex

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

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

commit 38417879f9ff3c85b8350c24be829d80602d2ce3
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Aug 27 14:14:51 2021 -0400

    AIRAVATA-3477 Finish converting to Vuex
---
 .../js/web-components/ComputeResourceSelector.vue  |  45 +-
 .../ExperimentComputeResourceSelector.vue          |  61 +++
 .../js/web-components/ExperimentEditor.vue         | 100 ++++-
 .../GroupResourceProfileSelector.vue               |  37 +-
 .../js/web-components/ProjectSelector.vue          |   9 +-
 .../js/web-components/QueueSettingsEditor.vue      | 142 +++---
 .../js/web-components/ResourceSelectionEditor.vue  |   2 +-
 .../js/web-components/store.js                     |   1 +
 .../js/web-components/vuestore.js                  | 477 ++++++++++++++++++++-
 9 files changed, 716 insertions(+), 158 deletions(-)

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
index 3b28d83..88189f8 100644
--- 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
@@ -15,50 +15,57 @@
 </template>
 
 <script>
-import { getComputeResourceNames } from "./store";
+import vuestore from "./vuestore";
+import { mapGetters } from "vuex";
+
 export default {
   name: "compute-resource-selector",
   props: {
     value: {
       // compute resource host id
       type: String,
-      default: null,
+      required: true,
     },
-    computeResources: {
-      type: Array, // of compute resource host ids
-      default: () => [],
+    includedComputeResources: {
+      type: Array,
+      default: null,
     },
   },
+  store: vuestore,
   data() {
     return {
       resourceHostId: this.value,
-      computeResourceNames: {},
     };
   },
   created() {
-    this.loadComputeResourceNames();
+    this.$store.dispatch("loadComputeResourceNames");
   },
   computed: {
     computeResourceOptions: function () {
-      const computeResourceOptions = this.computeResources.map(
-        (computeHostId) => {
-          return {
-            value: computeHostId,
-            text:
-              computeHostId in this.computeResourceNames
-                ? this.computeResourceNames[computeHostId]
-                : "",
-          };
+      const computeResourceIds = Object.keys(this.computeResourceNames).filter(
+        (crid) => {
+          if (this.includedComputeResources) {
+            return this.includedComputeResources.includes(crid);
+          } else {
+            return true;
+          }
         }
       );
+      const computeResourceOptions = computeResourceIds.map((computeHostId) => {
+        return {
+          value: computeHostId,
+          text:
+            computeHostId in this.computeResourceNames
+              ? this.computeResourceNames[computeHostId]
+              : "",
+        };
+      });
       computeResourceOptions.sort((a, b) => a.text.localeCompare(b.text));
       return computeResourceOptions;
     },
+    ...mapGetters(["computeResourceNames"]),
   },
   methods: {
-    async loadComputeResourceNames() {
-      this.computeResourceNames = await getComputeResourceNames();
-    },
     computeResourceChanged() {
       this.emitValueChanged();
     },
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentComputeResourceSelector.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentComputeResourceSelector.vue
new file mode 100644
index 0000000..d209d57
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentComputeResourceSelector.vue
@@ -0,0 +1,61 @@
+<template>
+  <compute-resource-selector
+    :value="value"
+    :includedComputeResources="computeResources"
+    @input.stop="computeResourceChanged"
+  />
+</template>
+
+<script>
+import vuestore from "./vuestore";
+import { mapGetters } from "vuex";
+import ComputeResourceSelector from "./ComputeResourceSelector.vue";
+
+export default {
+  name: "experiment-compute-resource-selector",
+  props: {
+    value: {
+      // compute resource host id
+      type: String,
+      required: true,
+    },
+  },
+  store: vuestore,
+  components: {
+    ComputeResourceSelector,
+  },
+  data() {
+    return {
+      resourceHostId: this.value,
+    };
+  },
+  computed: {
+    // compute resources for the current set of application deployments
+    ...mapGetters(["computeResources"]),
+  },
+  methods: {
+    computeResourceChanged(event) {
+      const [computeResourceId] = event.detail;
+      this.resourceHostId = computeResourceId;
+      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 5c551b8..12f7287 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
@@ -17,9 +17,54 @@
         <!-- programmatically define slots as native slots (not Vue slots), see #mounted() -->
       </div>
     </template>
-    <div ref="resourceSelectionEditor" @input="updateUserConfigurationData">
-      <!-- programmatically define slot for experiment-resource-selection as
-           native slot (not Vue slots), see #mounted() -->
+    <!-- TODO: programmatically define slot for adpf-group-resource-profile-selector -->
+    <div @input.stop="updateGroupResourceProfileId">
+      <adpf-group-resource-profile-selector
+        :value="experiment.userConfigurationData.groupResourceProfileId"
+      />
+    </div>
+    <!-- TODO: programmatically define slot for adpf-experiment-compute-resource-selector -->
+    <div @input.stop="updateComputeResourceHostId">
+      <adpf-experiment-compute-resource-selector
+        ref="computeResourceSelector"
+        :value="
+          experiment.userConfigurationData.computationalResourceScheduling
+            .resourceHostId
+        "
+      />
+    </div>
+    <!-- TODO: programmatically define slot for adpf-queue-settings-editor -->
+    <div
+      @queue-name-changed="updateQueueName"
+      @node-count-changed="updateNodeCount"
+      @total-cpu-count-change="updateTotalCPUCount"
+      @walltime-limit-changed="updateWallTimeLimit"
+      @total-physical-memory-changed="updateTotalPhysicalMemory"
+    >
+      <adpf-queue-settings-editor
+        ref="queueSettingsEditor"
+        :queue-name="
+          experiment.userConfigurationData.computationalResourceScheduling
+            .queueName
+        "
+        :total-cpu-count="
+          experiment.userConfigurationData.computationalResourceScheduling
+            .totalCPUCount
+        "
+        :node-count="
+          experiment.userConfigurationData.computationalResourceScheduling
+            .nodeCount
+        "
+        :wall-time-limit="
+          experiment.userConfigurationData.computationalResourceScheduling
+            .wallTimeLimit
+        "
+        :total-physical-memory="
+          experiment.userConfigurationData.computationalResourceScheduling
+            .totalPhysicalMemory
+        "
+      />
+
     </div>
     <div ref="experimentButtons">
       <!-- programmatically define slot for experiment-buttons as
@@ -125,19 +170,6 @@ export default {
         this.createSlot("experiment-project", projectSelectorEl)
       );
 
-      const resourceSelectionEditor = document.createElement(
-        "adpf-resource-selection-editor"
-      );
-      this.$refs.resourceSelectionEditor.append(
-        this.createSlot(
-          "experiment-resource-selection",
-          resourceSelectionEditor
-        )
-      );
-      // Can't set objects via attributes, must set as prop
-      resourceSelectionEditor.value = this.experiment.userConfigurationData;
-      resourceSelectionEditor.applicationModuleId = this.applicationId;
-
       /*
        * Experiment (save/launch) Buttons native slot
        */
@@ -202,10 +234,38 @@ export default {
       const [projectId] = event.detail;
       this.$store.dispatch("updateProjectId", { projectId });
     },
-    updateUserConfigurationData(event) {
-      const [userConfigurationData] = event.detail;
-      this.$store.dispatch("updateUserConfigurationData", {
-        userConfigurationData,
+    updateGroupResourceProfileId(event) {
+      const [groupResourceProfileId] = event.detail;
+      this.$store.dispatch("updateGroupResourceProfileId", {
+        groupResourceProfileId,
+      });
+    },
+    updateComputeResourceHostId(event) {
+      const [resourceHostId] = event.detail;
+      this.$store.dispatch("updateComputeResourceHostId", {
+        resourceHostId,
+      });
+    },
+    updateQueueName(event) {
+      const [queueName] = event.detail;
+      this.$store.dispatch("updateQueueName", { queueName });
+    },
+    updateTotalCPUCount(event) {
+      const [totalCPUCount] = event.detail;
+      this.$store.dispatch("updateTotalCPUCount", { totalCPUCount });
+    },
+    updateNodeCount(event) {
+      const [nodeCount] = event.detail;
+      this.$store.dispatch("updateNodeCount", { nodeCount });
+    },
+    updateWallTimeLimit(event) {
+      const [wallTimeLimit] = event.detail;
+      this.$store.dispatch("updateWallTimeLimit", { wallTimeLimit });
+    },
+    updateTotalPhysicalMemory(event) {
+      const [totalPhysicalMemory] = event.detail;
+      this.$store.dispatch("updateTotalPhysicalMemory", {
+        totalPhysicalMemory,
       });
     },
     async onSubmit(event) {
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/GroupResourceProfileSelector.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/GroupResourceProfileSelector.vue
index 9fd33c0..7be711f 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/GroupResourceProfileSelector.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/GroupResourceProfileSelector.vue
@@ -17,12 +17,10 @@
 </template>
 
 <script>
-import {
-  getDefaultGroupResourceProfileId,
-  getGroupResourceProfiles,
-} from "./store";
 import Vue from "vue";
 import { BootstrapVue } from "bootstrap-vue";
+import vuestore from "./vuestore";
+import { mapGetters } from "vuex";
 Vue.use(BootstrapVue);
 
 export default {
@@ -30,23 +28,21 @@ export default {
   props: {
     value: {
       type: String,
+      required: true,
     },
     label: {
       type: String,
       default: "Allocation",
     },
   },
+  store: vuestore,
   data() {
     return {
       groupResourceProfileId: this.value,
-      groupResourceProfiles: [],
-      defaultGroupResourceProfileId: null,
     };
   },
   async mounted() {
-    this.defaultGroupResourceProfileId = await getDefaultGroupResourceProfileId();
-    this.groupResourceProfiles = await getGroupResourceProfiles();
-    this.init();
+    await this.$store.dispatch("loadGroupResourceProfiles");
   },
   computed: {
     groupResourceProfileOptions: function () {
@@ -67,23 +63,9 @@ export default {
         return [];
       }
     },
+    ...mapGetters(["groupResourceProfiles"]),
   },
   methods: {
-    init() {
-      // Default the selected group resource profile
-      if (
-        (!this.value ||
-          !this.selectedValueInGroupResourceProfileList(
-            this.groupResourceProfiles
-          )) &&
-        this.groupResourceProfiles &&
-        this.groupResourceProfiles.length > 0
-      ) {
-        // automatically select the last one user selected
-        this.groupResourceProfileId = this.defaultGroupResourceProfileId;
-        this.emitValueChanged();
-      }
-    },
     groupResourceProfileChanged: function (groupResourceProfileId) {
       this.groupResourceProfileId = groupResourceProfileId;
       this.emitValueChanged();
@@ -96,13 +78,6 @@ export default {
       });
       this.$el.dispatchEvent(inputEvent);
     },
-    selectedValueInGroupResourceProfileList(groupResourceProfiles) {
-      return (
-        groupResourceProfiles
-          .map((grp) => grp.groupResourceProfileId)
-          .indexOf(this.value) >= 0
-      );
-    },
   },
   watch: {
     value() {
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ProjectSelector.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ProjectSelector.vue
index 224da0f..dc4bddf 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ProjectSelector.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ProjectSelector.vue
@@ -47,15 +47,10 @@ export default {
     };
   },
   async mounted() {
-    await this.$store.dispatch('loadProjects');
-    await this.$store.dispatch('loadDefaultProjectId');
-    if (!this.projectId) {
-      this.projectId = this.defaultProjectId;
-      this.$emit("input", this.projectId);
-    }
+    await this.$store.dispatch("loadProjects");
   },
   computed: {
-    ...mapGetters(["projects", "defaultProjectId"]),
+    ...mapGetters(["projects"]),
     sharedProjectOptions: function () {
       return this.projects
         ? this.projects
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
index 27c9381..a4692f4 100644
--- 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
@@ -7,31 +7,27 @@
       >
         <div class="card-body">
           <h5 class="card-title mb-4">
-            Settings for queue {{ computationalResourceScheduling.queueName }}
+            Settings for queue {{ localQueueName }}
           </h5>
           <div class="row">
             <div class="col">
               <h3 class="h5 mb-0">
-                {{ computationalResourceScheduling.nodeCount }}
+                {{ localNodeCount }}
               </h3>
               <span class="text-muted text-uppercase">NODE COUNT</span>
             </div>
             <div class="col">
               <h3 class="h5 mb-0">
-                {{ computationalResourceScheduling.totalCPUCount }}
+                {{ localTotalCPUCount }}
               </h3>
               <span class="text-muted text-uppercase">CORE COUNT</span>
             </div>
             <div class="col">
-              <h3 class="h5 mb-0">
-                {{ computationalResourceScheduling.wallTimeLimit }} minutes
-              </h3>
+              <h3 class="h5 mb-0">{{ localWallTimeLimit }} minutes</h3>
               <span class="text-muted text-uppercase">TIME LIMIT</span>
             </div>
             <div class="col" v-if="maxMemory > 0">
-              <h3 class="h5 mb-0">
-                {{ computationalResourceScheduling.totalPhysicalMemory }} MB
-              </h3>
+              <h3 class="h5 mb-0">{{ localTotalPhysicalMemory }} MB</h3>
               <span class="text-muted text-uppercase">PHYSICAL MEMORY</span>
             </div>
           </div>
@@ -42,7 +38,7 @@
       <b-form-group label="Select a Queue" label-for="queue">
         <b-form-select
           id="queue"
-          v-model="computationalResourceScheduling.queueName"
+          v-model="localQueueName"
           :options="queueOptions"
           required
           @change="queueChanged"
@@ -56,9 +52,11 @@
           type="number"
           min="1"
           :max="maxAllowedNodes"
-          v-model="computationalResourceScheduling.nodeCount"
+          v-model="localNodeCount"
           required
-          @input.native.stop="emitValueChanged"
+          @input.native.stop="
+            emitValueChanged('node-count-changed', localNodeCount)
+          "
         >
         </b-form-input>
         <div slot="description">
@@ -72,9 +70,11 @@
           type="number"
           min="1"
           :max="maxAllowedCores"
-          v-model="computationalResourceScheduling.totalCPUCount"
+          v-model="localTotalCPUCount"
           required
-          @input.native.stop="emitValueChanged"
+          @input.native.stop="
+            emitValueChanged('total-cpu-count-changed', localTotalCPUCount)
+          "
         >
         </b-form-input>
         <div slot="description">
@@ -89,9 +89,11 @@
             type="number"
             min="1"
             :max="maxAllowedWalltime"
-            v-model="computationalResourceScheduling.wallTimeLimit"
+            v-model="localWallTimeLimit"
             required
-            @input.native.stop="emitValueChanged"
+            @input.native.stop="
+              emitValueChanged('walltime-limit-changed', localWallTimeLimit)
+            "
           >
           </b-form-input>
         </b-input-group>
@@ -111,8 +113,13 @@
             type="number"
             min="0"
             :max="maxMemory"
-            v-model="computationalResourceScheduling.totalPhysicalMemory"
-            @input.native.stop="emitValueChanged"
+            v-model="localTotalPhysicalMemory"
+            @input.native.stop="
+              emitValueChanged(
+                'total-physical-memory-changed',
+                localTotalPhysicalMemory
+              )
+            "
           >
           </b-form-input>
         </b-input-group>
@@ -132,8 +139,10 @@
 </template>
 
 <script>
-import { models, utils } from "django-airavata-api";
+import { utils } from "django-airavata-api";
 import Vue from "vue";
+import vuestore from "./vuestore";
+import { mapGetters } from "vuex";
 import { BootstrapVue } from "bootstrap-vue";
 Vue.use(BootstrapVue);
 
@@ -146,34 +155,32 @@ config.autoAddCss = false;
 
 export default {
   props: {
-    value: {
-      type: models.ComputationalResourceSchedulingModel,
-      // required: true,
-    },
-    queues: {
-      type: Array, // of BatchQueue
-      // required: true,
+    queueName: {
+      type: String,
+      required: true,
     },
-    maxAllowedNodes: {
+    totalCpuCount: {
       type: Number,
       required: true,
     },
-    maxAllowedCores: {
+    nodeCount: {
       type: Number,
       required: true,
     },
-    maxAllowedWalltime: {
+    wallTimeLimit: {
       type: Number,
       required: true,
     },
-    maxMemory: {
+    totalPhysicalMemory: {
       type: Number,
-      required: true,
+      default: 0,
+      // required: true,
     },
   },
   components: {
     FontAwesomeIcon,
   },
+  store: vuestore,
   mounted() {
     // Add font awesome styles
     // https://github.com/FortAwesome/vue-fontawesome#web-components-with-vue-web-component-wrapper
@@ -188,12 +195,25 @@ export default {
     }
   },
   data() {
+    console.log("QueueSettingsEditor.vue: queueName", this.queueName);
     return {
-      computationalResourceScheduling: this.cloneValue(),
+      localQueueName: this.queueName,
+      localTotalCPUCount: this.totalCpuCount,
+      localNodeCount: this.nodeCount,
+      localWallTimeLimit: this.wallTimeLimit,
+      localTotalPhysicalMemory: this.totalPhysicalMemory,
       showConfiguration: false,
     };
   },
   computed: {
+    ...mapGetters([
+      "queue",
+      "queues",
+      "maxAllowedCores",
+      "maxAllowedNodes",
+      "maxAllowedWalltime",
+      "maxMemory",
+    ]),
     queueOptions() {
       if (!this.queues) {
         return [];
@@ -207,50 +227,52 @@ export default {
       utils.StringUtils.sortIgnoreCase(queueOptions, (q) => q.text);
       return queueOptions;
     },
-    queue() {
-      return this.queues
-        ? this.queues.find((q) => q.queueName === this.queueName)
-        : null;
-    },
-    queueName() {
-      return this.computationalResourceScheduling
-        ? this.computationalResourceScheduling.queueName
-        : null;
-    },
-    queueDescription() {
-      return this.queue ? this.queue.queueDescription : null;
-    },
     closeIcon() {
       return faTimes;
     },
     infoIcon() {
       return faInfoCircle;
-    }
+    },
   },
   methods: {
-    cloneValue() {
-      return this.value
-        ? this.value.clone()
-        : new models.ComputationalResourceSchedulingModel();
-    },
-    emitValueChanged() {
-      const inputEvent = new CustomEvent("input", {
-        detail: [this.computationalResourceScheduling.clone()],
+    emitValueChanged(eventName, value) {
+      console.log(
+        "QueueSettingsEditor: emitValueChanged(",
+        eventName,
+        value,
+        ")"
+      );
+      const inputEvent = new CustomEvent(eventName, {
+        detail: [value],
         composed: true,
         bubbles: true,
       });
       this.$el.dispatchEvent(inputEvent);
     },
     queueChanged() {
-      this.emitValueChanged();
+      this.emitValueChanged("queue-name-changed", this.localQueueName);
     },
   },
   watch: {
-    value: {
-      handler() {
-        this.computationalResourceScheduling = this.cloneValue();
-      },
-      deep: true,
+    queueName(value) {
+      console.log("QueueSettingsEditor: watch(queueName)", value);
+      this.localQueueName = value;
+    },
+    // nodes(value, oldValue) {
+    //   console.log("QueueSettingsEditor: watch(nodes)", value, oldValue);
+    //   this.localNodeCount = value;
+    // },
+    nodeCount(value) {
+      this.localNodeCount = value;
+    },
+    totalCpuCount(value) {
+      this.localTotalCPUCount = value;
+    },
+    wallTimeLimit(value) {
+      this.localWallTimeLimit = value;
+    },
+    totalPhysicalMemory(value) {
+      this.localTotalPhysicalMemory = value;
     },
   },
 };
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
index 8791b32..9557db9 100644
--- 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
@@ -1,4 +1,5 @@
 <template>
+<!-- TODO: this is no longer needed -->
   <div v-if="userConfigurationData">
     <div @input.stop="updateGroupResourceProfileId">
       <adpf-group-resource-profile-selector
@@ -395,7 +396,6 @@ export default {
     },
     bindWebComponentProps() {
       this.$nextTick(() => {
-        this.$refs.computeResourceSelector.computeResources = this.computeResources;
         this.$refs.computeResourceSelector.value = this.resourceHostId;
         this.$refs.queueSettingsEditor.value = this.userConfigurationData.computationalResourceScheduling;
         this.$refs.queueSettingsEditor.queues = this.queues;
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 321a5a0..1f54748 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,3 +1,4 @@
+// TODO: this is no longer needed
 import { errors, services, utils } from "django-airavata-api";
 const CACHE = {
   APPLICATION_MODULES: {},
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/vuestore.js b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/vuestore.js
index 0681867..bb51992 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/vuestore.js
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/vuestore.js
@@ -1,15 +1,21 @@
-import { services } from "django-airavata-api";
+import { errors, services, utils } from "django-airavata-api";
 import Vue from "vue";
 import Vuex from "vuex";
 
 Vue.use(Vuex);
 
+const PROMISES = {
+  workspacePreferences: null,
+};
 export default new Vuex.Store({
   state: {
     experiment: null,
     projects: null,
-    defaultProjectId: null,
-    computeResourceNames: null,
+    computeResourceNames: {},
+    applicationDeployments: [],
+    groupResourceProfiles: null,
+    applicationModuleId: null,
+    appDeploymentQueues: [],
   },
   mutations: {
     setExperiment(state, { experiment }) {
@@ -27,21 +33,51 @@ export default new Vuex.Store({
     updateProjectId(state, { projectId }) {
       state.experiment.projectId = projectId;
     },
-    updateUserConfigurationData(state, { userConfigurationData }) {
-      state.experiment.userConfigurationData = userConfigurationData;
+    updateGroupResourceProfileId(state, { groupResourceProfileId }) {
+      state.experiment.userConfigurationData.groupResourceProfileId = groupResourceProfileId;
+    },
+    updateResourceHostId(state, { resourceHostId }) {
+      state.experiment.userConfigurationData.computationalResourceScheduling.resourceHostId = resourceHostId;
+    },
+    updateQueueName(state, { queueName }) {
+      state.experiment.userConfigurationData.computationalResourceScheduling.queueName = queueName;
+    },
+    updateTotalCPUCount(state, { totalCPUCount }) {
+      state.experiment.userConfigurationData.computationalResourceScheduling.totalCPUCount = totalCPUCount;
+    },
+    updateNodeCount(state, { nodeCount }) {
+      state.experiment.userConfigurationData.computationalResourceScheduling.nodeCount = nodeCount;
+    },
+    updateWallTimeLimit(state, { wallTimeLimit }) {
+      state.experiment.userConfigurationData.computationalResourceScheduling.wallTimeLimit = wallTimeLimit;
+    },
+    updateTotalPhysicalMemory(state, { totalPhysicalMemory }) {
+      state.experiment.userConfigurationData.computationalResourceScheduling.totalPhysicalMemory = totalPhysicalMemory;
     },
     setProjects(state, { projects }) {
       state.projects = projects;
     },
-    setDefaultProjectId(state, { defaultProjectId }) {
-      state.defaultProjectId = defaultProjectId;
-    },
     setComputeResourceNames(state, { computeResourceNames }) {
       state.computeResourceNames = computeResourceNames;
     },
+    setGroupResourceProfiles(state, { groupResourceProfiles }) {
+      state.groupResourceProfiles = groupResourceProfiles;
+    },
+    setWorkspacePreferences(state, { workspacePreferences }) {
+      state.workspacePreferences = workspacePreferences;
+    },
+    setApplicationModuleId(state, { applicationModuleId }) {
+      state.applicationModuleId = applicationModuleId;
+    },
+    setApplicationDeployments(state, { applicationDeployments }) {
+      state.applicationDeployments = applicationDeployments;
+    },
+    setAppDeploymentQueues(state, { appDeploymentQueues }) {
+      state.appDeploymentQueues = appDeploymentQueues;
+    },
   },
   actions: {
-    async loadNewExperiment({ commit }, { applicationId }) {
+    async loadNewExperiment({ commit, dispatch }, { applicationId }) {
       const applicationModule = await services.ApplicationModuleService.retrieve(
         {
           lookup: applicationId,
@@ -58,13 +94,50 @@ export default new Vuex.Store({
         timeStyle: "short",
       });
       experiment.experimentName = `${applicationModule.appModuleName} on ${currentDate}`;
-      commit("setExperiment", { experiment });
+      commit("setApplicationModuleId", { applicationModuleId: applicationId });
+      await dispatch("setExperiment", { experiment });
     },
-    async loadExperiment({ commit }, { experimentId }) {
+    async loadExperiment({ commit, dispatch }, { experimentId }) {
       const experiment = await services.ExperimentService.retrieve({
         lookup: experimentId,
       });
+      const appInterface = await services.ApplicationInterfaceService.retrieve({
+        lookup: experiment.executionId,
+      });
+      commit("setApplicationModuleId", {
+        applicationModuleId: appInterface.applicationModuleId,
+      });
+      await dispatch("setExperiment", { experiment });
+    },
+    async setExperiment({ commit, dispatch }, { experiment }) {
       commit("setExperiment", { experiment });
+      await dispatch("loadExperimentData");
+    },
+    async loadExperimentData({ commit, dispatch, getters }) {
+      await Promise.all([
+        dispatch("loadProjects"),
+        dispatch("loadWorkspacePreferences"),
+        dispatch("loadGroupResourceProfiles"),
+      ]);
+
+      if (!getters.experiment.projectId) {
+        commit("updateProjectId", { projectId: getters.defaultProjectId });
+      }
+      // If there is no groupResourceProfileId set on the experiment, or there
+      // is one set but it is no longer in the list of accessible
+      // groupResourceProfiles, set to the default one
+      if (!getters.groupResourceProfileId || !getters.groupResourceProfile) {
+        commit("updateGroupResourceProfileId", {
+          groupResourceProfileId: getters.defaultGroupResourceProfileId,
+        });
+      }
+      // If experiment has a group resource profile and user has access to it,
+      // load additional necessary data and re-apply group resource profile
+      if (getters.groupResourceProfile) {
+        await dispatch("loadApplicationDeployments");
+        await dispatch("loadAppDeploymentQueues");
+        await dispatch("applyGroupResourceProfile");
+      }
     },
     updateExperimentName({ commit }, { name }) {
       commit("updateExperimentName", { name });
@@ -75,8 +148,181 @@ export default new Vuex.Store({
     updateProjectId({ commit }, { projectId }) {
       commit("updateProjectId", { projectId });
     },
-    updateUserConfigurationData({ commit }, { userConfigurationData }) {
-      commit("updateUserConfigurationData", { userConfigurationData });
+    async updateGroupResourceProfileId(
+      { commit, dispatch },
+      { groupResourceProfileId }
+    ) {
+      commit("updateGroupResourceProfileId", { groupResourceProfileId });
+      await dispatch("loadApplicationDeployments");
+      await dispatch("applyGroupResourceProfile");
+    },
+    async updateComputeResourceHostId(
+      { commit, dispatch, getters },
+      { resourceHostId }
+    ) {
+      if (getters.resourceHostId !== resourceHostId) {
+        commit("updateResourceHostId", { resourceHostId });
+        await dispatch("loadAppDeploymentQueues");
+        await dispatch("setDefaultQueue");
+      }
+    },
+    updateQueueName({ commit, dispatch }, { queueName }) {
+      commit("updateQueueName", { queueName });
+      dispatch("initializeQueue");
+    },
+    updateTotalCPUCount({ commit }, { totalCPUCount }) {
+      commit("updateTotalCPUCount", { totalCPUCount });
+    },
+    updateNodeCount({ commit }, { nodeCount }) {
+      commit("updateNodeCount", { nodeCount });
+    },
+    updateWallTimeLimit({ commit }, { wallTimeLimit }) {
+      commit("updateWallTimeLimit", { wallTimeLimit });
+    },
+    updateTotalPhysicalMemory({ commit }, { totalPhysicalMemory }) {
+      commit("updateTotalPhysicalMemory", { totalPhysicalMemory });
+    },
+    async loadApplicationDeployments({ commit, getters, state }) {
+      const applicationDeployments = await services.ApplicationDeploymentService.list(
+        {
+          appModuleId: state.applicationModuleId,
+          groupResourceProfileId: getters.groupResourceProfileId,
+        },
+        { ignoreErrors: true }
+      )
+        .catch((error) => {
+          // Ignore unauthorized errors, force user to pick another GroupResourceProfile
+          if (!errors.ErrorUtils.isUnauthorizedError(error)) {
+            return Promise.reject(error);
+          } else {
+            return Promise.resolve([]);
+          }
+        })
+        // Report all other error types
+        .catch(utils.FetchUtils.reportError);
+      commit("setApplicationDeployments", { applicationDeployments });
+    },
+    async applyGroupResourceProfile({ dispatch, getters }) {
+      // Make sure that resource host id is in the list of app deployments
+      const computeResourceChanged = await dispatch("initializeResourceHostId");
+      if (computeResourceChanged) {
+        await dispatch("loadAppDeploymentQueues");
+        await dispatch("setDefaultQueue");
+      } else if (!getters.queue) {
+        // allowed queues may have changed. If selected queue isn't in the list
+        // of allowed queues, reset to the default
+        await dispatch("setDefaultQueue");
+      } else {
+        // reapply batchQueueResourcePolicy maximums since they may have changed
+        dispatch("applyBatchQueueResourcePolicy");
+      }
+    },
+    async initializeResourceHostId({ commit, dispatch, getters }) {
+      // if there isn't a selected compute resource or there is but it isn't in
+      // the list of app deployments, set a default one
+      // Returns true if the resourceHostId changed
+      if (
+        !getters.resourceHostId ||
+        !getters.computeResources.find(
+          (crid) => crid === getters.resourceHostId
+        )
+      ) {
+        const defaultResourceHostId = await dispatch(
+          "getDefaultResourceHostId"
+        );
+        commit("updateResourceHostId", {
+          resourceHostId: defaultResourceHostId,
+        });
+        return true;
+      }
+      return false;
+    },
+    async getDefaultResourceHostId({ dispatch, getters }) {
+      await dispatch("loadDefaultComputeResourceId");
+      if (
+        getters.defaultComputeResourceId &&
+        getters.computeResources.find(
+          (crid) => crid === getters.defaultComputeResourceId
+        )
+      ) {
+        return getters.defaultComputeResourceId;
+      } else if (getters.computeResources.length > 0) {
+        // Just pick the first one
+        return getters.computeResources[0];
+      } else {
+        return null;
+      }
+    },
+    async loadDefaultComputeResourceId({ dispatch }) {
+      await dispatch("loadWorkspacePreferences");
+    },
+    async loadAppDeploymentQueues({ commit, getters }) {
+      const applicationDeployment = getters.applicationDeployment;
+      if (applicationDeployment) {
+        const appDeploymentQueues = await services.ApplicationDeploymentService.getQueues(
+          {
+            lookup: applicationDeployment.appDeploymentId,
+          }
+        );
+        commit("setAppDeploymentQueues", { appDeploymentQueues });
+      } else {
+        commit("setAppDeploymentQueues", { appDeploymentQueues: [] });
+      }
+    },
+    async setDefaultQueue({ commit, dispatch, getters }) {
+      // set to the default queue or the first one
+      const defaultQueue = getters.defaultQueue;
+      if (defaultQueue) {
+        commit("updateQueueName", { queueName: defaultQueue.queueName });
+      } else {
+        commit("updateQueueName", { queueName: null });
+      }
+      dispatch("initializeQueue");
+    },
+    initializeQueue({ commit, getters }) {
+      const queue = getters.queue;
+      if (queue) {
+        commit("updateTotalCPUCount", {
+          totalCPUCount: getters.getDefaultCPUCount(queue),
+        });
+        commit("updateNodeCount", {
+          nodeCount: getters.getDefaultNodeCount(queue),
+        });
+        commit("updateWallTimeLimit", {
+          wallTimeLimit: getters.getDefaultWalltime(queue),
+        });
+        commit("updateTotalPhysicalMemory", { totalPhysicalMemory: 0 });
+      } else {
+        commit("updateTotalCPUCount", { totalCPUCount: 0 });
+        commit("updateNodeCount", { nodeCount: 0 });
+        commit("updateWallTimeLimit", { wallTimeLimit: 0 });
+        commit("updateTotalPhysicalMemory", { totalPhysicalMemory: 0 });
+      }
+    },
+    applyBatchQueueResourcePolicy({ commit, getters }) {
+      if (getters.batchQueueResourcePolicy) {
+        const crs =
+          getters.experiment.userConfigurationData
+            .computationalResourceScheduling;
+        commit("updateTotalCPUCount", {
+          totalCPUCount: Math.min(
+            crs.totalCPUCount,
+            getters.batchQueueResourcePolicy.maxAllowedCores
+          ),
+        });
+        commit("updateNodeCount", {
+          nodeCount: Math.min(
+            crs.nodeCount,
+            getters.batchQueueResourcePolicy.maxAllowedNodes
+          ),
+        });
+        commit("updateWallTimeLimit", {
+          wallTimeLimit: Math.min(
+            crs.wallTimeLimit,
+            getters.batchQueueResourcePolicy.maxAllowedWalltime
+          ),
+        });
+      }
     },
     async saveExperiment({ commit, getters }) {
       if (getters.experiment.experimentId) {
@@ -98,19 +344,36 @@ export default new Vuex.Store({
       });
     },
     async loadProjects({ commit }) {
-      const projects = await services.ProjectService.listAll();
+      if (!PROMISES.projects) {
+        PROMISES.projects = services.ProjectService.listAll();
+      }
+      const projects = await PROMISES.projects;
       commit("setProjects", { projects });
     },
-    async loadDefaultProjectId({ commit }) {
-      // TODO: cache the workspace preferences so they aren't loaded more than once
-      const prefs = await services.WorkspacePreferencesService.get();
-      const defaultProjectId = prefs.most_recent_project_id;
-      commit("setDefaultProjectId", { defaultProjectId });
+    async loadWorkspacePreferences({ commit }) {
+      if (!PROMISES.workspacePreferences) {
+        PROMISES.workspacePreferences = services.WorkspacePreferencesService.get();
+      }
+      const workspacePreferences = await PROMISES.workspacePreferences;
+      commit("setWorkspacePreferences", { workspacePreferences });
+    },
+    async loadDefaultProjectId({ dispatch }) {
+      await dispatch("loadWorkspacePreferences");
     },
     async loadComputeResourceNames({ commit }) {
       const computeResourceNames = await services.ComputeResourceService.names();
       commit("setComputeResourceNames", { computeResourceNames });
     },
+    async loadDefaultGroupResourceProfileId({ dispatch }) {
+      await dispatch("loadWorkspacePreferences");
+    },
+    async loadGroupResourceProfiles({ commit }) {
+      if (!PROMISES.groupResourceProfiles) {
+        PROMISES.groupResourceProfiles = services.GroupResourceProfileService.list();
+      }
+      const groupResourceProfiles = await PROMISES.groupResourceProfiles;
+      commit("setGroupResourceProfiles", { groupResourceProfiles });
+    },
   },
   getters: {
     getExperimentInputByName: (state) => (name) => {
@@ -129,7 +392,181 @@ export default new Vuex.Store({
     },
     experiment: (state) => state.experiment,
     projects: (state) => state.projects,
-    defaultProjectId: (state) => state.defaultProjectId,
+    defaultProjectId: (state) =>
+      state.workspacePreferences
+        ? state.workspacePreferences.most_recent_project_id
+        : null,
+    defaultGroupResourceProfileId: (state) =>
+      state.workspacePreferences
+        ? state.workspacePreferences.most_recent_group_resource_profile_id
+        : null,
+    defaultComputeResourceId: (state) =>
+      state.workspacePreferences
+        ? state.workspacePreferences.most_recent_compute_resource_id
+        : null,
     computeResourceNames: (state) => state.computeResourceNames,
+    groupResourceProfiles: (state) => state.groupResourceProfiles,
+    groupResourceProfileId: (state) =>
+      state.experiment
+        ? state.experiment.userConfigurationData.groupResourceProfileId
+        : null,
+    groupResourceProfile: (state, getters) =>
+      state.groupResourceProfiles
+        ? state.groupResourceProfiles.find(
+            (g) => g.groupResourceProfileId === getters.groupResourceProfileId
+          )
+        : null,
+    resourceHostId: (state) =>
+      state.experiment &&
+      state.experiment.userConfigurationData &&
+      state.experiment.userConfigurationData.computationalResourceScheduling
+        ? state.experiment.userConfigurationData.computationalResourceScheduling
+            .resourceHostId
+        : null,
+    computeResources: (state) =>
+      state.applicationDeployments.map((dep) => dep.computeHostId),
+    applicationDeployment: (state, getters) => {
+      if (state.applicationDeployments && getters.resourceHostId) {
+        return state.applicationDeployments.find(
+          (ad) => ad.computeHostId === getters.resourceHostId
+        );
+      } else {
+        return null;
+      }
+    },
+    isQueueInComputeResourcePolicy: (state, getters) => (queueName) => {
+      if (!getters.computeResourcePolicy) {
+        return true;
+      }
+      return getters.computeResourcePolicy.allowedBatchQueues.includes(
+        queueName
+      );
+    },
+    queues: (state, getters) => {
+      return state.appDeploymentQueues
+        ? state.appDeploymentQueues.filter((q) =>
+            getters.isQueueInComputeResourcePolicy(q.queueName)
+          )
+        : [];
+    },
+    defaultQueue: (state, getters) => {
+      const defaultQueue = getters.queues.find((q) => q.isDefaultQueue);
+      if (defaultQueue) {
+        return defaultQueue;
+      } else if (getters.queues.length > 0) {
+        return getters.queues[0];
+      } else {
+        return null;
+      }
+    },
+    queueName: (state) => {
+      return state.experiment &&
+        state.experiment.userConfigurationData &&
+        state.experiment.userConfigurationData.computationalResourceScheduling
+        ? state.experiment.userConfigurationData.computationalResourceScheduling
+            .queueName
+        : null;
+    },
+    queue: (state, getters) => {
+      return getters.queues && getters.queueName
+        ? getters.queues.find((q) => q.queueName === getters.queueName)
+        : null;
+    },
+    getDefaultCPUCount: (state, getters) => (queue) => {
+      const batchQueueResourcePolicy = getters.batchQueueResourcePolicy;
+      if (batchQueueResourcePolicy) {
+        return Math.min(
+          batchQueueResourcePolicy.maxAllowedCores,
+          queue.defaultCPUCount
+        );
+      }
+      return queue.defaultCPUCount;
+    },
+    getDefaultNodeCount: (state, getters) => (queue) => {
+      const batchQueueResourcePolicy = getters.batchQueueResourcePolicy;
+      if (batchQueueResourcePolicy) {
+        return Math.min(
+          batchQueueResourcePolicy.maxAllowedNodes,
+          queue.defaultNodeCount
+        );
+      }
+      return queue.defaultNodeCount;
+    },
+    getDefaultWalltime: (state, getters) => (queue) => {
+      const batchQueueResourcePolicy = getters.batchQueueResourcePolicy;
+      if (batchQueueResourcePolicy) {
+        return Math.min(
+          batchQueueResourcePolicy.maxAllowedWalltime,
+          queue.defaultWalltime
+        );
+      }
+      return queue.defaultWalltime;
+    },
+    computeResourcePolicy: (state, getters) => {
+      if (!getters.groupResourceProfile || !getters.resourceHostId) {
+        return null;
+      }
+      return getters.groupResourceProfile.computeResourcePolicies.find(
+        (crp) => crp.computeResourceId === getters.resourceHostId
+      );
+    },
+    batchQueueResourcePolicies: (state, getters) => {
+      if (!getters.groupResourceProfile || !getters.resourceHostId) {
+        return null;
+      }
+      return getters.groupResourceProfile.batchQueueResourcePolicies.filter(
+        (bqrp) => bqrp.computeResourceId === getters.resourceHostId
+      );
+    },
+    batchQueueResourcePolicy: (state, getters) => {
+      if (!getters.batchQueueResourcePolicies || !getters.queueName) {
+        return null;
+      }
+      return getters.batchQueueResourcePolicies.find(
+        (bqrp) => bqrp.queuename === getters.queueName
+      );
+    },
+    maxAllowedCores: (state, getters) => {
+      if (!getters.queue) {
+        return 0;
+      }
+      const batchQueueResourcePolicy = getters.batchQueueResourcePolicy;
+      if (batchQueueResourcePolicy) {
+        return Math.min(
+          batchQueueResourcePolicy.maxAllowedCores,
+          getters.queue.maxProcessors
+        );
+      }
+      return getters.queue.maxProcessors;
+    },
+    maxAllowedNodes: (state, getters) => {
+      if (!getters.queue) {
+        return 0;
+      }
+      const batchQueueResourcePolicy = getters.batchQueueResourcePolicy;
+      if (batchQueueResourcePolicy) {
+        return Math.min(
+          batchQueueResourcePolicy.maxAllowedNodes,
+          getters.queue.maxNodes
+        );
+      }
+      return getters.queue.maxNodes;
+    },
+    maxAllowedWalltime: (state, getters) => {
+      if (!getters.queue) {
+        return 0;
+      }
+      const batchQueueResourcePolicy = getters.batchQueueResourcePolicy;
+      if (batchQueueResourcePolicy) {
+        return Math.min(
+          batchQueueResourcePolicy.maxAllowedWalltime,
+          getters.queue.maxRunTime
+        );
+      }
+      return getters.queue.maxRunTime;
+    },
+    maxMemory: (state, getters) => {
+      return getters.queue ? getters.queue.maxMemory : 0;
+    },
   },
 });