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:11 UTC

[airavata-django-portal] branch develop updated (a5e61bf -> 69da810)

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

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


    from a5e61bf  AIRAVATA-3517 Include IsAuthenticated is required permissions for IAMUserViewSet and UnverifiedEmailUserViewSet
     new 1b923ad  AIRAVATA-3477 StringInputEditor web component
     new 887397c  AIRAVATA-3477 Convert to Vuex for complex shared state between web components
     new 3841787  AIRAVATA-3477 Finish converting to Vuex
     new 8081d38  AIRAVATA-3477 bottom margin on QueueSettingsEditor
     new fa82a91  AIRAVATA-3477 Programmatically define slots and make compute and queue editors standalone
     new 9d3931f  AIRAVATA-3477 Fix issue with native input event colliding with web component input event
     new 99eaba7  AIRAVATA-3477 Fixes to setting initial project and GRP id
     new bdf764c  AIRAVATA-3477 Cleaning up after Vuex refactor
     new 154bce8  AIRAVATA-3477 radio button input editor web component
     new 639ad16  AIRAVATA-3477 Unit tests for web comp vuex store
     new c9e6e26  AIRAVATA-3477 Mixin for input editor web components
     new 19f0ba2  AIRAVATA-3477 Wrapped FileInputEditor as web component
     new 06c38f1  AIRAVATA-3477 Wrapped CheckboxInputEditor as web component
     new 60989a9  AIRAVATA-3477 Wrapped MultiFileInputEditor as web component
     new 9b4b123  AIRAVATA-3477 Wrapped RangeSliderInputEditor as web component
     new 49dfb23  AIRAVATA-3477 Wrapped SelectInputEditor as web component
     new b7f2134  AIRAVATA-3477 Wrapped SliderInputEditor as web component
     new 139a596  AIRAVATA-3477 Wrapped TextareaInputEditor as web component
     new ea6d5b9  AIRAVATA-3477 test logic around initializing experiment's GRP
     new 989bbc3  AIRAVATA-3477 more tests for initializing compute resource and queue settings
     new 13df7fa  AIRAVATA-3477 Support inline options markup
     new e881310  AIRAVATA-3477 Support setting queue name via web component attribute
     new 9aeecfc  AIRAVATA-3477 add backwards compat for updateInputValue
     new 69da810  Merge branch 'airavata-3477' into develop

The 24 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     |   2 +
 .../js/input-editors/InputEditorMixin.js           |   4 +-
 django_airavata/apps/workspace/package.json        |   7 +-
 .../input-editors/CheckboxInputEditor.vue          |  27 +-
 .../experiment/input-editors/FileInputEditor.vue   |   7 +
 .../input-editors/RadioButtonInputEditor.vue       |  26 +-
 .../input-editors/RangeSliderInputEditor.vue       |  71 +-
 .../experiment/input-editors/SelectInputEditor.vue |  22 +-
 .../experiment/input-editors/SliderInputEditor.vue |  61 +-
 .../experiment/input-editors/StringInputEditor.vue |   3 +
 .../input-editors/TextareaInputEditor.vue          |  14 +-
 .../storage/UserStorageFileSelectionContainer.vue  |   1 +
 .../storage/storage-edit/UserStorageLink.vue       |  30 +-
 .../js/web-components/ComputeResourceSelector.vue  |  51 +-
 .../ExperimentComputeResourceSelector.vue          |  43 ++
 .../js/web-components/ExperimentEditor.vue         | 148 ++--
 .../GroupResourceProfileSelector.vue               |  44 +-
 .../js/web-components/ProjectSelector.vue          |  20 +-
 .../js/web-components/QueueSettingsEditor.vue      | 177 ++---
 .../js/web-components/ResourceSelectionEditor.vue  | 426 ------------
 .../input-editors/CheckboxInputEditor.vue          |  50 ++
 .../input-editors/FileInputEditor.vue              |  43 ++
 .../input-editors/InlineOptionsMixin.js            |  50 ++
 .../input-editors/MultiFileInputEditor.vue         |  43 ++
 .../input-editors/RadioButtonInputEditor.vue       |  50 ++
 .../input-editors/RangeSliderInputEditor.vue       |  66 ++
 .../input-editors/SelectInputEditor.vue            |  48 ++
 .../input-editors/SliderInputEditor.vue            |  64 ++
 .../input-editors/StringInputEditor.vue            |  41 ++
 .../input-editors/TextareaInputEditor.vue          |  45 ++
 .../input-editors/WebComponentInputEditorMixin.js  |  62 ++
 .../js/web-components/store.js                     | 755 +++++++++++++++++----
 .../js/web-components/{styles.css => styles.scss}  |   2 +
 .../tests/unit/web-components/store.spec.js        | 708 +++++++++++++++++++
 django_airavata/apps/workspace/yarn.lock           |   5 +
 35 files changed, 2360 insertions(+), 856 deletions(-)
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentComputeResourceSelector.vue
 delete mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ResourceSelectionEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/CheckboxInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/FileInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/InlineOptionsMixin.js
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/MultiFileInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RangeSliderInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/SelectInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/SliderInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/TextareaInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/WebComponentInputEditorMixin.js
 rename django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/{styles.css => styles.scss} (61%)
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/web-components/store.spec.js

[airavata-django-portal] 14/24: AIRAVATA-3477 Wrapped MultiFileInputEditor as web component

Posted by ma...@apache.org.
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 60989a9c50491a1b24d4a2365f5adc660513e543
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu Sep 9 11:45:17 2021 -0400

    AIRAVATA-3477 Wrapped MultiFileInputEditor as web component
---
 .../input-editors/MultiFileInputEditor.vue         | 43 ++++++++++++++++++++++
 1 file changed, 43 insertions(+)

diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/MultiFileInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/MultiFileInputEditor.vue
new file mode 100644
index 0000000..420774c
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/MultiFileInputEditor.vue
@@ -0,0 +1,43 @@
+<template>
+  <!-- NOTE: experimentInput is late bound, don't create component until it is available -->
+  <div>
+    <multi-file-input-editor
+      v-if="experimentInput"
+      :id="id"
+      :value="data"
+      :experiment-input="experimentInput"
+      :read-only="readOnly"
+      @input="valueChanged"
+    />
+  </div>
+</template>
+
+<script>
+import MultiFileInputEditor from "../../components/experiment/input-editors/MultiFileInputEditor.vue";
+import WebComponentInputEditorMixin from "./WebComponentInputEditorMixin.js";
+
+export default {
+  mixins: [WebComponentInputEditorMixin],
+  props: {
+    // Explicit copy props from mixin, workaround for bug, see
+    // https://github.com/vuejs/vue-web-component-wrapper/issues/30#issuecomment-427350734
+    // for more details
+    ...WebComponentInputEditorMixin.props,
+  },
+  components: {
+    MultiFileInputEditor,
+  },
+};
+</script>
+
+<style>
+@import "../styles.css";
+@import "~@uppy/core/dist/style.min.css";
+@import "~@uppy/status-bar/dist/style.min.css";
+@import "~@uppy/drag-drop/dist/style.min.css";
+@import "~codemirror/lib/codemirror.css";
+@import "~codemirror/theme/abcdef.css";
+:host {
+  display: block;
+}
+</style>

[airavata-django-portal] 13/24: AIRAVATA-3477 Wrapped CheckboxInputEditor as web component

Posted by ma...@apache.org.
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 06c38f12ee992154c21a4c366cd9fa9637d7aeff
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu Sep 9 09:42:58 2021 -0400

    AIRAVATA-3477 Wrapped CheckboxInputEditor as web component
---
 .../input-editors/CheckboxInputEditor.vue          | 27 ++++++++++++++--------
 ...ttonInputEditor.vue => CheckboxInputEditor.vue} |  9 +++++---
 .../input-editors/RadioButtonInputEditor.vue       |  3 +++
 .../input-editors/StringInputEditor.vue            |  3 +++
 4 files changed, 29 insertions(+), 13 deletions(-)

diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/CheckboxInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/CheckboxInputEditor.vue
index 5777e87..1c363de 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/CheckboxInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/CheckboxInputEditor.vue
@@ -1,12 +1,15 @@
 <template>
+  <!-- stop propagation of native input events so that they don't conflict with
+  native input events generated by web component wrappers -->
   <b-form-checkbox-group
     :id="id"
     :checked="selectedOptions"
-    :options="options"
+    :options="checkboxOptions"
     stacked
     :state="componentValidState"
     :disabled="readOnly"
     @input="selectionsChanged"
+    @input.native.stop
   />
 </template>
 
@@ -23,17 +26,21 @@ export default {
     value: {
       type: String,
     },
+    options: {
+      type: Array,
+      default: null,
+    },
   },
   computed: {
-    options: function () {
-      return "options" in this.editorConfig
-        ? this.editorConfig["options"].map((option) => {
-            return {
-              text: option[CONFIG_OPTION_TEXT_KEY],
-              value: option[CONFIG_OPTION_VALUE_KEY],
-            };
-          })
-        : [];
+    checkboxOptions: function () {
+      // passed in options take precedence over options in input metadata
+      const options = this.options || this.editorConfig.options || [];
+      return options.map((option) => {
+        return {
+          text: option[CONFIG_OPTION_TEXT_KEY],
+          value: option[CONFIG_OPTION_VALUE_KEY],
+        };
+      });
     },
     selectedOptions() {
       return this.data ? this.data.split(",") : [];
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/CheckboxInputEditor.vue
similarity index 67%
copy from django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
copy to django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/CheckboxInputEditor.vue
index cbfaa9e..e44132a 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/CheckboxInputEditor.vue
@@ -1,6 +1,9 @@
 <template>
+  <!-- Important that input editor is wrapped in div. Input editor stops
+  propagation on native input events, but we need for this component's input
+  events to propagate. So the input editor should not be the root component. -->
   <div>
-    <radio-button-input-editor
+    <checkbox-input-editor
       v-if="experimentInput"
       :id="id"
       :value="data"
@@ -13,7 +16,7 @@
 </template>
 
 <script>
-import RadioButtonInputEditor from "../../components/experiment/input-editors/RadioButtonInputEditor.vue";
+import CheckboxInputEditor from "../../components/experiment/input-editors/CheckboxInputEditor.vue";
 import WebComponentInputEditorMixin from "./WebComponentInputEditorMixin.js";
 
 export default {
@@ -29,7 +32,7 @@ export default {
     },
   },
   components: {
-    RadioButtonInputEditor,
+    CheckboxInputEditor,
   },
 };
 </script>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
index cbfaa9e..9acf8d2 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
@@ -1,4 +1,7 @@
 <template>
+  <!-- Important that input editor is wrapped in div. Input editor stops
+  propagation on native input events, but we need for this component's input
+  events to propagate. So the input editor should not be the root component. -->
   <div>
     <radio-button-input-editor
       v-if="experimentInput"
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
index 43cf0b4..ee825cf 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
@@ -1,5 +1,8 @@
 <template>
   <!-- NOTE: experimentInput is late bound, don't create component until it is available -->
+  <!-- Important that input editor is wrapped in div. Input editor stops
+  propagation on native input events, but we need for this component's input
+  events to propagate. So the input editor should not be the root component. -->
   <div>
     <string-input-editor
       v-if="experimentInput"

[airavata-django-portal] 07/24: AIRAVATA-3477 Fixes to setting initial project and GRP id

Posted by ma...@apache.org.
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 99eaba7cafeb1850c8eb8e88f8b8440d60d7478c
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Sep 1 13:31:50 2021 -0400

    AIRAVATA-3477 Fixes to setting initial project and GRP id
    
    Vuex getters get cached so it works better to get the values directly from state.
---
 .../js/web-components/vuestore.js                  | 29 ++++++++++++++++------
 1 file changed, 21 insertions(+), 8 deletions(-)

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 fd5de65..a2128f5 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
@@ -8,6 +8,7 @@ const PROMISES = {
   workspacePreferences: null,
 };
 export default new Vuex.Store({
+  strict: process.env.NODE_ENV !== "production",
   state: {
     experiment: null,
     projects: null,
@@ -113,27 +114,37 @@ export default new Vuex.Store({
       commit("setExperiment", { experiment });
       await dispatch("loadExperimentData");
     },
-    async loadExperimentData({ commit, dispatch, getters }) {
+    async loadExperimentData({ commit, dispatch, getters, state }) {
       await Promise.all([
         dispatch("loadProjects"),
         dispatch("loadWorkspacePreferences"),
         dispatch("loadGroupResourceProfiles"),
       ]);
 
-      if (!getters.experiment.projectId) {
-        commit("updateProjectId", { projectId: getters.defaultProjectId });
+      if (!state.experiment.projectId) {
+        commit("updateProjectId", {
+          projectId: state.workspacePreferences.most_recent_project_id,
+        });
       }
       // 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) {
+      let groupResourceProfileId =
+        state.experiment.userConfigurationData.groupResourceProfileId;
+      if (
+        !groupResourceProfileId ||
+        !getters.findGroupResourceProfile(groupResourceProfileId)
+      ) {
         commit("updateGroupResourceProfileId", {
-          groupResourceProfileId: getters.defaultGroupResourceProfileId,
+          groupResourceProfileId:
+            state.workspacePreferences.most_recent_group_resource_profile_id,
         });
       }
+      groupResourceProfileId =
+        state.experiment.userConfigurationData.groupResourceProfileId;
       // 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) {
+      if (getters.findGroupResourceProfile(groupResourceProfileId)) {
         await dispatch("loadApplicationDeployments");
         await dispatch("loadAppDeploymentQueues");
         await dispatch("applyGroupResourceProfile");
@@ -410,12 +421,14 @@ export default new Vuex.Store({
       state.experiment
         ? state.experiment.userConfigurationData.groupResourceProfileId
         : null,
-    groupResourceProfile: (state, getters) =>
+    findGroupResourceProfile: (state) => (groupResourceProfileId) =>
       state.groupResourceProfiles
         ? state.groupResourceProfiles.find(
-            (g) => g.groupResourceProfileId === getters.groupResourceProfileId
+            (g) => g.groupResourceProfileId === groupResourceProfileId
           )
         : null,
+    groupResourceProfile: (state, getters) =>
+      getters.findGroupResourceProfile(getters.groupResourceProfileId),
     resourceHostId: (state) =>
       state.experiment &&
       state.experiment.userConfigurationData &&

[airavata-django-portal] 18/24: AIRAVATA-3477 Wrapped TextareaInputEditor as web component

Posted by ma...@apache.org.
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 139a596dbd3ea1015471ff7fedb169656eb85781
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Sep 15 12:00:33 2021 -0400

    AIRAVATA-3477 Wrapped TextareaInputEditor as web component
---
 .../input-editors/TextareaInputEditor.vue          | 14 ++++---
 .../input-editors/TextareaInputEditor.vue          | 45 ++++++++++++++++++++++
 2 files changed, 54 insertions(+), 5 deletions(-)

diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/TextareaInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/TextareaInputEditor.vue
index dbb64a7..8056840 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/TextareaInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/TextareaInputEditor.vue
@@ -1,11 +1,14 @@
 <template>
+  <!-- stop propagation of native input events so that they don't conflict with
+  native input events generated by web component wrappers -->
   <b-form-textarea
     :id="id"
     v-model="data"
-    :rows="rows"
+    :rows="editorRows"
     :disabled="readOnly"
     :state="componentValidState"
     @input="valueChanged"
+    @input.native.stop
   />
 </template>
 
@@ -21,12 +24,13 @@ export default {
     value: {
       type: String,
     },
+    rows: {
+      type: Number,
+    },
   },
   computed: {
-    rows: function () {
-      return "rows" in this.editorConfig
-        ? this.editorConfig["rows"]
-        : DEFAULT_ROWS;
+    editorRows: function () {
+      return this.rows || this.editorConfig.rows || DEFAULT_ROWS;
     },
   },
 };
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/TextareaInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/TextareaInputEditor.vue
new file mode 100644
index 0000000..6cf96e4
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/TextareaInputEditor.vue
@@ -0,0 +1,45 @@
+<template>
+  <!-- NOTE: experimentInput is late bound, don't create component until it is available -->
+  <!-- Important that input editor is wrapped in div. Input editor stops
+  propagation on native input events, but we need for this component's input
+  events to propagate. So the input editor should not be the root component. -->
+  <div>
+    <textarea-input-editor
+      v-if="experimentInput"
+      :id="id"
+      :value="data"
+      :experiment-input="experimentInput"
+      :read-only="readOnly"
+      :rows="rows"
+      @input="valueChanged"
+    />
+  </div>
+</template>
+
+<script>
+import TextareaInputEditor from "../../components/experiment/input-editors/TextareaInputEditor.vue";
+import WebComponentInputEditorMixin from "./WebComponentInputEditorMixin.js";
+
+export default {
+  mixins: [WebComponentInputEditorMixin],
+  props: {
+    // Explicit copy props from mixin, workaround for bug, see
+    // https://github.com/vuejs/vue-web-component-wrapper/issues/30#issuecomment-427350734
+    // for more details
+    ...WebComponentInputEditorMixin.props,
+    rows: {
+      type: Number,
+    },
+  },
+  components: {
+    TextareaInputEditor,
+  },
+};
+</script>
+
+<style lang="scss">
+@import "../styles";
+:host {
+  display: block;
+}
+</style>

[airavata-django-portal] 22/24: AIRAVATA-3477 Support setting queue name via web component attribute

Posted by ma...@apache.org.
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 e88131069d8b10a55297765afc783faa4d8e3acc
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Mon Oct 4 12:34:44 2021 -0400

    AIRAVATA-3477 Support setting queue name via web component attribute
---
 .../js/web-components/QueueSettingsEditor.vue      | 51 +++++++++++++++-------
 .../js/web-components/store.js                     | 21 +++++++--
 2 files changed, 52 insertions(+), 20 deletions(-)

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 5c69076..3c2a83d 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
@@ -1,12 +1,14 @@
 <template>
-  <div v-if="queue">
+  <div>
     <div class="card border-default">
       <b-link
         @click="showConfiguration = !showConfiguration"
         class="card-link text-dark"
       >
         <div class="card-body">
-          <h5 class="card-title mb-4">Settings for queue {{ queueName }}</h5>
+          <h5 class="card-title mb-4">
+            Settings for queue {{ selectedQueueName }}
+          </h5>
           <div class="row">
             <div class="col">
               <h3 class="h5 mb-0">
@@ -36,7 +38,7 @@
       <b-form-group label="Select a Queue" label-for="queue">
         <b-form-select
           id="queue"
-          :value="queueName"
+          :value="selectedQueueName"
           :options="queueOptions"
           required
           @change="queueChanged"
@@ -135,25 +137,35 @@ Vue.use(BootstrapVue);
 
 export default {
   store: store,
+  props: {
+    queueName: {
+      type: String,
+    },
+  },
+  created() {
+    if (this.queueName && this.selectedQueueName !== this.queueName) {
+      this.queueChanged(this.queueName);
+    }
+  },
   data() {
     return {
       showConfiguration: false,
     };
   },
   computed: {
-    ...mapGetters([
-      "queue",
-      "queues",
-      "maxAllowedCores",
-      "maxAllowedNodes",
-      "maxAllowedWalltime",
-      "maxMemory",
-      "queueName",
-      "totalCPUCount",
-      "nodeCount",
-      "wallTimeLimit",
-      "totalPhysicalMemory",
-    ]),
+    ...mapGetters({
+      queue: "queue",
+      queues: "queues",
+      maxAllowedCores: "maxAllowedCores",
+      maxAllowedNodes: "maxAllowedNodes",
+      maxAllowedWalltime: "maxAllowedWalltime",
+      maxMemory: "maxMemory",
+      selectedQueueName: "queueName",
+      totalCPUCount: "totalCPUCount",
+      nodeCount: "nodeCount",
+      wallTimeLimit: "wallTimeLimit",
+      totalPhysicalMemory: "totalPhysicalMemory",
+    }),
     queueOptions() {
       if (!this.queues) {
         return [];
@@ -196,6 +208,13 @@ export default {
       });
     },
   },
+  watch: {
+    queueName(value) {
+      if (value && this.selectedQueueName !== value) {
+        this.queueChanged(value);
+      }
+    },
+  },
 };
 </script>
 
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 a0e7e4d..3ebdf43 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
@@ -32,6 +32,9 @@ export const mutations = {
   updateQueueName(state, { queueName }) {
     state.experiment.userConfigurationData.computationalResourceScheduling.queueName = queueName;
   },
+  setLazyQueueName(state, { queueName }) {
+    state.queueName = queueName;
+  },
   updateTotalCPUCount(state, { totalCPUCount }) {
     state.experiment.userConfigurationData.computationalResourceScheduling.totalCPUCount = totalCPUCount;
   },
@@ -97,9 +100,13 @@ export const actions = {
     });
     await dispatch("setExperiment", { experiment });
   },
-  async setExperiment({ commit, dispatch }, { experiment }) {
+  async setExperiment({ commit, dispatch, state }, { experiment }) {
     commit("setExperiment", { experiment });
     await dispatch("loadExperimentData");
+    // Check lazy experiment state properties and apply them
+    if (state.queueName) {
+      dispatch("updateQueueName", { queueName: state.queueName });
+    }
   },
   async loadExperimentData({ commit, dispatch, state }) {
     await Promise.all([
@@ -183,9 +190,13 @@ export const actions = {
       await dispatch("setDefaultQueue");
     }
   },
-  updateQueueName({ commit, dispatch }, { queueName }) {
-    commit("updateQueueName", { queueName });
-    dispatch("initializeQueue");
+  updateQueueName({ commit, dispatch, state }, { queueName }) {
+    if (state.experiment) {
+      commit("updateQueueName", { queueName });
+      dispatch("initializeQueue");
+    } else {
+      commit("setLazyQueueName", { queueName });
+    }
   },
   updateTotalCPUCount({ commit }, { totalCPUCount }) {
     commit("updateTotalCPUCount", { totalCPUCount });
@@ -634,6 +645,8 @@ export default new Vuex.Store({
     groupResourceProfiles: null,
     applicationModuleId: null,
     appDeploymentQueues: [],
+    // Lazy state fields that will be copied to the experiment once it is loaded
+    queueName: null,
   },
   mutations,
   actions,

[airavata-django-portal] 09/24: AIRAVATA-3477 radio button input editor web component

Posted by ma...@apache.org.
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 154bce88cef385d365fdda6a4b44d9a628a05f4d
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu Sep 2 13:07:20 2021 -0400

    AIRAVATA-3477 radio button input editor web component
---
 .../input-editors/RadioButtonInputEditor.vue       | 26 +++++++++------
 .../experiment/input-editors/StringInputEditor.vue |  3 ++
 ...gInputEditor.vue => RadioButtonInputEditor.vue} | 39 +++++++++++++---------
 .../input-editors/StringInputEditor.vue            | 20 +++--------
 4 files changed, 47 insertions(+), 41 deletions(-)

diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/RadioButtonInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/RadioButtonInputEditor.vue
index 6d38414..3a19b17 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/RadioButtonInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/RadioButtonInputEditor.vue
@@ -1,12 +1,15 @@
 <template>
+  <!-- stop propagation of native input events so that they don't conflict with
+  native input events generated by web component wrappers -->
   <b-form-radio-group
     :id="id"
     v-model="data"
-    :options="options"
+    :options="radioOptions"
     stacked
     :state="componentValidState"
     :disabled="readOnly"
     @input="valueChanged"
+    @input.native.stop
   />
 </template>
 
@@ -23,17 +26,20 @@ export default {
     value: {
       type: String,
     },
+    options: {
+      type: Array,
+    },
   },
   computed: {
-    options: function () {
-      return "options" in this.editorConfig
-        ? this.editorConfig["options"].map((option) => {
-            return {
-              text: option[CONFIG_OPTION_TEXT_KEY],
-              value: option[CONFIG_OPTION_VALUE_KEY],
-            };
-          })
-        : [];
+    radioOptions: function () {
+      // passed in options take precedence over options in input metadata
+      const options = this.options || this.editorConfig.options || [];
+      return options.map((option) => {
+        return {
+          text: option[CONFIG_OPTION_TEXT_KEY],
+          value: option[CONFIG_OPTION_VALUE_KEY],
+        };
+      });
     },
   },
 };
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/StringInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/StringInputEditor.vue
index 988fe22..a272f2c 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/StringInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/StringInputEditor.vue
@@ -1,4 +1,6 @@
 <template>
+  <!-- stop propagation of native input events so that they don't conflict with
+  native input events generated by web component wrappers -->
   <b-form-input
     :id="id"
     type="text"
@@ -6,6 +8,7 @@
     :state="componentValidState"
     :disabled="readOnly"
     @input="valueChanged"
+    @input.native.stop
   />
 </template>
 
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
similarity index 63%
copy from django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
copy to django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
index bed4a8c..5e03681 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
@@ -1,20 +1,20 @@
 <template>
-  <!-- NOTE: experimentInput is late bound, don't create component until it is available -->
   <div>
-    <string-input-editor
-      ref="inputEditor"
+    <radio-button-input-editor
       v-if="experimentInput"
       :id="id"
       :value="data"
       :experiment-input="experimentInput"
       :read-only="readOnly"
+      :options="options"
       @input="onInput"
     />
   </div>
 </template>
 
 <script>
-import StringInputEditor from "../../components/experiment/input-editors/StringInputEditor.vue";
+import RadioButtonInputEditor from "../../components/experiment/input-editors/RadioButtonInputEditor.vue";
+
 import Vue from "vue";
 import { BootstrapVue } from "bootstrap-vue";
 import AsyncComputed from "vue-async-computed";
@@ -26,23 +26,28 @@ Vue.use(AsyncComputed);
 export default {
   props: {
     value: String,
-    // experimentInput: Object,
     name: String,
+    options: {
+      type: Array,
+      default: null,
+    },
   },
+  store: store,
   components: {
-    StringInputEditor,
+    RadioButtonInputEditor,
   },
-  store: store,
   mounted() {
     this.$nextTick(() => {
-      // Stop wrapped input editor 'input' event from bubbling up so it doesn't
-      // conflict with this component's 'input' event. (see #onInput)
-      this.$refs.inputEditor.$el.addEventListener('input', this.stopPropagation);
+      for (const key of Object.keys(this.$props)) {
+        // workaround for issues around setting props before WC connected,
+        // see https://github.com/vuejs/vue-web-component-wrapper/pull/81
+
+        // copy properties set on host element to wrapper component
+        // (mostly this is done so that the options array can be set by client code)
+        this.$parent.props[key] = this.$el.getRootNode().host[key];
+      }
     })
   },
-  destroyed() {
-    this.$refs.inputEditor.$el.removeEventListener('input', this.stopPropagation);
-  },
   data() {
     return {
       data: this.value,
@@ -71,9 +76,11 @@ export default {
         this.$el.dispatchEvent(inputEvent);
       }
     },
-    stopPropagation(event) {
-      event.stopPropagation();
-    }
+  },
+  watch: {
+    value(value) {
+      this.data = value;
+    },
   },
 };
 </script>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
index bed4a8c..c68385b 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
@@ -2,7 +2,6 @@
   <!-- NOTE: experimentInput is late bound, don't create component until it is available -->
   <div>
     <string-input-editor
-      ref="inputEditor"
       v-if="experimentInput"
       :id="id"
       :value="data"
@@ -26,23 +25,12 @@ Vue.use(AsyncComputed);
 export default {
   props: {
     value: String,
-    // experimentInput: Object,
     name: String,
   },
   components: {
     StringInputEditor,
   },
   store: store,
-  mounted() {
-    this.$nextTick(() => {
-      // Stop wrapped input editor 'input' event from bubbling up so it doesn't
-      // conflict with this component's 'input' event. (see #onInput)
-      this.$refs.inputEditor.$el.addEventListener('input', this.stopPropagation);
-    })
-  },
-  destroyed() {
-    this.$refs.inputEditor.$el.removeEventListener('input', this.stopPropagation);
-  },
   data() {
     return {
       data: this.value,
@@ -71,10 +59,12 @@ export default {
         this.$el.dispatchEvent(inputEvent);
       }
     },
-    stopPropagation(event) {
-      event.stopPropagation();
-    }
   },
+  watch: {
+    value(value) {
+      this.data = value;
+    }
+  }
 };
 </script>
 

[airavata-django-portal] 17/24: AIRAVATA-3477 Wrapped SliderInputEditor as web component

Posted by ma...@apache.org.
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 b7f21340af2f1b639e1d005af5d8c3033cbe506a
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Sep 15 11:59:40 2021 -0400

    AIRAVATA-3477 Wrapped SliderInputEditor as web component
---
 .../experiment/input-editors/SliderInputEditor.vue | 61 ++++++++++++++++-----
 .../input-editors/SliderInputEditor.vue            | 64 ++++++++++++++++++++++
 2 files changed, 112 insertions(+), 13 deletions(-)

diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/SliderInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/SliderInputEditor.vue
index 0c2b0c9..fd1df8c 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/SliderInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/SliderInputEditor.vue
@@ -4,9 +4,9 @@
     @change="onChange"
     :state="componentValidState"
     :disabled="readOnly"
-    :min="min"
-    :max="max"
-    :interval="step"
+    :min="sliderMin"
+    :max="sliderMax"
+    :interval="sliderStep"
     tooltip="always"
     :tooltip-formatter="tooltipFormatter"
   />
@@ -23,6 +23,21 @@ export default {
     value: {
       type: String,
     },
+    min: Number,
+    max: Number,
+    step: Number,
+    valueFormat: {
+      type: String,
+      validator(value) {
+        return ["percentage"].indexOf(value) !== -1;
+      },
+    },
+    displayFormat: {
+      type: String,
+      validator(value) {
+        return ["percentage"].indexOf(value) !== -1;
+      },
+    },
   },
   components: {
     VueSlider,
@@ -36,14 +51,26 @@ export default {
     this.initializeSliderValue();
   },
   computed: {
-    min: function () {
-      return "min" in this.editorConfig ? this.editorConfig.min : 0;
+    sliderMin: function () {
+      return typeof this.min !== "undefined"
+        ? this.min
+        : "min" in this.editorConfig
+        ? this.editorConfig.min
+        : 0;
     },
-    max: function () {
-      return "max" in this.editorConfig ? this.editorConfig.max : 100;
+    sliderMax: function () {
+      return typeof this.max !== "undefined"
+        ? this.max
+        : "max" in this.editorConfig
+        ? this.editorConfig.max
+        : 100;
     },
-    step: function () {
-      return "step" in this.editorConfig ? this.editorConfig.step : 1;
+    sliderStep: function () {
+      return typeof this.step !== "undefined"
+        ? this.step
+        : "step" in this.editorConfig
+        ? this.editorConfig.step
+        : 1;
     },
   },
   methods: {
@@ -57,15 +84,19 @@ export default {
     },
     parseValue(value) {
       // Just remove any percentage signs
-      const result = parseInt(value.replaceAll("%", ""));
-      return !isNaN(result) ? result : this.min;
+      const result = value ? parseInt(value.replaceAll("%", "")) : NaN;
+      return !isNaN(result) ? result : this.sliderMin;
     },
     onChange(value) {
       this.data = this.formatValue(value);
       this.valueChanged();
     },
     tooltipFormatter(value) {
-      if ("displayFormat" in this.editorConfig) {
+      if (this.displayFormat) {
+        if (this.displayFormat === "percentage") {
+          return `${value}%`;
+        }
+      } else if ("displayFormat" in this.editorConfig) {
         if (this.editorConfig.displayFormat.percentage) {
           return `${value}%`;
         }
@@ -73,7 +104,11 @@ export default {
       return value;
     },
     formatValue(value) {
-      if ("valueFormat" in this.editorConfig) {
+      if (this.valueFormat) {
+        if (this.valueFormat === "percentage") {
+          return `${value}%`;
+        }
+      } else if ("valueFormat" in this.editorConfig) {
         if (this.editorConfig.valueFormat.percentage) {
           return `${value}%`;
         }
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/SliderInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/SliderInputEditor.vue
new file mode 100644
index 0000000..bec91e9
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/SliderInputEditor.vue
@@ -0,0 +1,64 @@
+<template>
+  <slider-input-editor
+    v-if="experimentInput"
+    :id="id"
+    :value="data"
+    :experiment-input="experimentInput"
+    :read-only="readOnly"
+    :min="min"
+    :max="max"
+    :step="step"
+    :value-format="valueFormat"
+    :display-format="displayFormat"
+    @input="valueChanged"
+  />
+</template>
+
+<script>
+import SliderInputEditor from "../../components/experiment/input-editors/SliderInputEditor.vue";
+import WebComponentInputEditorMixin from "./WebComponentInputEditorMixin.js";
+
+export default {
+  mixins: [WebComponentInputEditorMixin],
+  props: {
+    // Explicit copy props from mixin, workaround for bug, see
+    // https://github.com/vuejs/vue-web-component-wrapper/issues/30#issuecomment-427350734
+    // for more details
+    ...WebComponentInputEditorMixin.props,
+    min: {
+      type: Number,
+    },
+    max: {
+      type: Number,
+    },
+    step: {
+      type: Number,
+    },
+    valueFormat: {
+      type: String,
+      validator(value) {
+        return ["percentage"].indexOf(value) !== -1;
+      },
+    },
+    displayFormat: {
+      type: String,
+      validator(value) {
+        return ["percentage"].indexOf(value) !== -1;
+      },
+    },
+  },
+  components: {
+    SliderInputEditor,
+  },
+};
+</script>
+
+<style lang="scss">
+@import "../styles";
+// Need to explicitly import VueSlider's CSS because importing component scss doesn't work
+// https://github.com/vuejs/vue-web-component-wrapper/issues/12
+@import "~vue-slider-component/dist-css/vue-slider-component.css";
+:host {
+  display: block;
+}
+</style>

[airavata-django-portal] 04/24: AIRAVATA-3477 bottom margin on QueueSettingsEditor

Posted by ma...@apache.org.
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 8081d3873e3b5c0613ec70218901f7028acc31d5
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Mon Aug 30 12:13:30 2021 -0400

    AIRAVATA-3477 bottom margin on QueueSettingsEditor
---
 .../js/web-components/QueueSettingsEditor.vue           | 17 +++++------------
 1 file changed, 5 insertions(+), 12 deletions(-)

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 a4692f4..8fbe8c1 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
@@ -195,7 +195,6 @@ export default {
     }
   },
   data() {
-    console.log("QueueSettingsEditor.vue: queueName", this.queueName);
     return {
       localQueueName: this.queueName,
       localTotalCPUCount: this.totalCpuCount,
@@ -236,12 +235,6 @@ export default {
   },
   methods: {
     emitValueChanged(eventName, value) {
-      console.log(
-        "QueueSettingsEditor: emitValueChanged(",
-        eventName,
-        value,
-        ")"
-      );
       const inputEvent = new CustomEvent(eventName, {
         detail: [value],
         composed: true,
@@ -255,13 +248,8 @@ export default {
   },
   watch: {
     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;
     },
@@ -280,4 +268,9 @@ export default {
 
 <style>
 @import url("./styles.css");
+
+:host {
+  display: block;
+  margin-bottom: 1rem;
+}
 </style>

[airavata-django-portal] 20/24: AIRAVATA-3477 more tests for initializing compute resource and queue settings

Posted by ma...@apache.org.
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 989bbc35e0b8a4d0d01286d1ec86fdb8ff32e2d0
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Sep 29 11:07:00 2021 -0400

    AIRAVATA-3477 more tests for initializing compute resource and queue settings
---
 .../tests/unit/web-components/store.spec.js        | 457 +++++++++++++++++++--
 1 file changed, 416 insertions(+), 41 deletions(-)

diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/web-components/store.spec.js b/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/web-components/store.spec.js
index 615a806..f3cf381 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/web-components/store.spec.js
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/web-components/store.spec.js
@@ -16,17 +16,20 @@ test("setExperiment sets state", () => {
  */
 const testAction = (
   action,
-  payload,
-  state,
-  getters,
-  expectedMutations,
-  done
+  {
+    payload = null,
+    state = {},
+    getters = {},
+    expectedMutations = [],
+    done,
+    expectedActions = [],
+  }
 ) => {
-  let count = 0;
+  let mutationCount = 0;
 
   // mock commit
   const commit = (type, payload) => {
-    const mutation = expectedMutations[count] || {};
+    const mutation = expectedMutations[mutationCount] || {};
     try {
       expect(type).toEqual(mutation.type);
       expect(payload).toEqual(mutation.payload);
@@ -34,20 +37,40 @@ const testAction = (
       done(error);
     }
 
-    count++;
-    if (count >= expectedMutations.length) {
+    mutationCount++;
+    if (mutationCount >= expectedMutations.length) {
+      done();
+    }
+  };
+
+  // mock dispatch
+  let actionCount = 0;
+  const dispatch = (type, payload) => {
+    const action = expectedActions[actionCount] || {};
+    try {
+      expect(type).toEqual(action.type);
+      expect(payload).toEqual(action.payload);
+    } catch (error) {
+      done(error);
+    }
+
+    actionCount++;
+    if (actionCount >= expectedActions.length) {
       done();
     }
+    return action.result;
   };
 
   // call the action with mocked store and arguments
-  action({ commit, state, getters }, payload);
+  const result = action({ commit, dispatch, state, getters }, payload);
 
-  // check if no mutations should have been dispatched
-  if (expectedMutations.length === 0) {
-    expect(count).toEqual(0);
+  // check if no expectedMutations should have been dispatched
+  if (expectedMutations.length === 0 && expectedActions.length === 0) {
+    expect(mutationCount).toEqual(0);
+    expect(actionCount).toEqual(0);
     done();
   }
+  return result;
 };
 
 const testApplyBatchQueueResourcePolicy = ({
@@ -67,14 +90,12 @@ const testApplyBatchQueueResourcePolicy = ({
     experiment: state.experiment,
     batchQueueResourcePolicy: bqrp,
   };
-  testAction(
-    actions.applyBatchQueueResourcePolicy,
-    null,
+  testAction(actions.applyBatchQueueResourcePolicy, {
     state,
     getters,
     expectedMutations,
-    done
-  );
+    done,
+  });
 };
 
 test("applyBatchQueueResourcePolicy: maxAllowedCores caps totalCPUCount", (done) => {
@@ -210,14 +231,12 @@ test("initializeGroupResourceProfileId: set to most recent group resource profil
       },
     },
   ];
-  testAction(
-    actions.initializeGroupResourceProfileId,
-    null,
+  testAction(actions.initializeGroupResourceProfileId, {
     state,
-    g,
+    getters: g,
     expectedMutations,
-    done
-  );
+    done,
+  });
 });
 
 test("initializeGroupResourceProfileId: set to most recent group resource profile when no longer has access to grp", (done) => {
@@ -256,14 +275,12 @@ test("initializeGroupResourceProfileId: set to most recent group resource profil
       },
     },
   ];
-  testAction(
-    actions.initializeGroupResourceProfileId,
-    null,
+  testAction(actions.initializeGroupResourceProfileId, {
     state,
-    g,
+    getters: g,
     expectedMutations,
-    done
-  );
+    done,
+  });
 });
 
 test("initializeGroupResourceProfileId: set to first group resource profile when no most recent grp", (done) => {
@@ -291,14 +308,12 @@ test("initializeGroupResourceProfileId: set to first group resource profile when
       },
     },
   ];
-  testAction(
-    actions.initializeGroupResourceProfileId,
-    null,
+  testAction(actions.initializeGroupResourceProfileId, {
     state,
-    g,
+    getters: g,
     expectedMutations,
-    done
-  );
+    done,
+  });
 });
 
 test("initializeGroupResourceProfileId: set to null when no longer has access", (done) => {
@@ -322,12 +337,372 @@ test("initializeGroupResourceProfileId: set to null when no longer has access",
       },
     },
   ];
-  testAction(
-    actions.initializeGroupResourceProfileId,
-    null,
+  testAction(actions.initializeGroupResourceProfileId, {
+    state,
+    getters: g,
+    expectedMutations,
+    done,
+  });
+});
+
+test("applyGroupResourceProfile: when compute resource changes, dispatches loadAppDeploymentQueues and setDefaultQueue", (done) => {
+  const expectedActions = [
+    {
+      type: "initializeResourceHostId",
+      result: true,
+    },
+    {
+      type: "loadAppDeploymentQueues",
+    },
+    {
+      type: "setDefaultQueue",
+    },
+  ];
+  testAction(actions.applyGroupResourceProfile, {
+    done,
+    expectedActions,
+  });
+});
+
+test("applyGroupResourceProfile: when compute resource doesn't change, but queue no longer allowed, dispatches setDefaultQueue", (done) => {
+  const expectedActions = [
+    {
+      type: "initializeResourceHostId",
+      result: false,
+    },
+    {
+      type: "setDefaultQueue",
+    },
+  ];
+  testAction(actions.applyGroupResourceProfile, {
+    done,
+    expectedActions,
+  });
+});
+
+test("applyGroupResourceProfile: when compute resource doesn't change, and queue doesn't change, dispatches applyBatchQueueResourcePolicy", (done) => {
+  const getters = {
+    queue: new models.BatchQueue({ queueName: "shared" }),
+  };
+  const expectedActions = [
+    {
+      type: "initializeResourceHostId",
+      result: false,
+    },
+    {
+      type: "applyBatchQueueResourcePolicy",
+    },
+  ];
+  testAction(actions.applyGroupResourceProfile, {
+    getters,
+    done,
+    expectedActions,
+  });
+});
+
+test("initializeResourceHostId: experiment has no resourceHostId, should dispatch getDefaultResourceHostId, return true", (done) => {
+  const state = {};
+  state.experiment = new models.Experiment();
+  state.experiment.userConfigurationData.computationalResourceScheduling = new models.ComputationalResourceSchedulingModel();
+  const mockGetters = {
+    resourceHostId: getters.resourceHostId(state),
+  };
+  const expectedActions = [
+    {
+      type: "getDefaultResourceHostId",
+      result: "resourceHostId1",
+    },
+  ];
+  const expectedMutations = [
+    {
+      type: "updateResourceHostId",
+      payload: { resourceHostId: "resourceHostId1" },
+    },
+  ];
+  const result = testAction(actions.initializeResourceHostId, {
+    state,
+    getters: mockGetters,
+    done,
+    expectedActions,
+    expectedMutations,
+  });
+  expect(result).resolves.toBe(true);
+});
+
+test("initializeResourceHostId: experiment has resourceHostId but not in list of app deployments, should dispatch getDefaultResourceHostId, return true", (done) => {
+  const state = {};
+  state.experiment = new models.Experiment();
+  state.experiment.userConfigurationData.computationalResourceScheduling = new models.ComputationalResourceSchedulingModel(
+    {
+      resourceHostId: "resourceHostId1",
+    }
+  );
+  // experiment's resourceHostId1 isn't in list of app deployments
+  state.applicationDeployments = [
+    new models.ApplicationDeploymentDescription({
+      computeHostId: "resourceHostId2",
+    }),
+    new models.ApplicationDeploymentDescription({
+      computeHostId: "resourceHostId3",
+    }),
+  ];
+  const mockGetters = {
+    resourceHostId: getters.resourceHostId(state),
+    computeResources: getters.computeResources(state),
+  };
+  expect(mockGetters.resourceHostId).toBe("resourceHostId1");
+  expect(mockGetters.computeResources).toEqual([
+    "resourceHostId2",
+    "resourceHostId3",
+  ]);
+  const expectedActions = [
+    {
+      type: "getDefaultResourceHostId",
+      result: "resourceHostId2",
+    },
+  ];
+  const expectedMutations = [
+    {
+      type: "updateResourceHostId",
+      payload: { resourceHostId: "resourceHostId2" },
+    },
+  ];
+  const result = testAction(actions.initializeResourceHostId, {
     state,
-    g,
+    getters: mockGetters,
+    done,
+    expectedActions,
     expectedMutations,
-    done
+  });
+  expect(result).resolves.toBe(true);
+});
+
+test("initializeResourceHostId: experiment has resourceHostId and in list of app deployments, should return false", (done) => {
+  const state = {};
+  state.experiment = new models.Experiment();
+  state.experiment.userConfigurationData.computationalResourceScheduling = new models.ComputationalResourceSchedulingModel(
+    {
+      resourceHostId: "resourceHostId1",
+    }
   );
+  state.applicationDeployments = [
+    new models.ApplicationDeploymentDescription({
+      computeHostId: "resourceHostId1",
+    }),
+    new models.ApplicationDeploymentDescription({
+      computeHostId: "resourceHostId2",
+    }),
+    new models.ApplicationDeploymentDescription({
+      computeHostId: "resourceHostId3",
+    }),
+  ];
+  const mockGetters = {
+    resourceHostId: getters.resourceHostId(state),
+    computeResources: getters.computeResources(state),
+  };
+  expect(mockGetters.resourceHostId).toBe("resourceHostId1");
+  expect(mockGetters.computeResources).toEqual([
+    "resourceHostId1",
+    "resourceHostId2",
+    "resourceHostId3",
+  ]);
+  const result = testAction(actions.initializeResourceHostId, {
+    state,
+    getters: mockGetters,
+    done,
+  });
+  expect(result).resolves.toBe(false);
+});
+
+test("getDefaultResourceHostId: dispatch loadDefaultComputeResourceId, return defaultComputeResourceId when in deployments list", (done) => {
+  const state = {};
+  state.workspacePreferences = new models.WorkspacePreferences({
+    most_recent_compute_resource_id: "resourceHostId1",
+  });
+  state.applicationDeployments = [
+    new models.ApplicationDeploymentDescription({
+      computeHostId: "resourceHostId1",
+    }),
+    new models.ApplicationDeploymentDescription({
+      computeHostId: "resourceHostId2",
+    }),
+    new models.ApplicationDeploymentDescription({
+      computeHostId: "resourceHostId3",
+    }),
+  ];
+  const mockGetters = {
+    defaultComputeResourceId: getters.defaultComputeResourceId(state),
+    computeResources: getters.computeResources(state),
+  };
+  expect(mockGetters.defaultComputeResourceId).toBe("resourceHostId1");
+  expect(mockGetters.computeResources).toEqual([
+    "resourceHostId1",
+    "resourceHostId2",
+    "resourceHostId3",
+  ]);
+  const expectedActions = [{ type: "loadDefaultComputeResourceId" }];
+  const result = testAction(actions.getDefaultResourceHostId, {
+    state,
+    getters: mockGetters,
+    expectedActions,
+    done,
+  });
+  expect(result).resolves.toBe("resourceHostId1");
+});
+
+test("getDefaultResourceHostId: dispatch loadDefaultComputeResourceId, return first compute resource when defaultComputeResourceId not in deployments list", (done) => {
+  const state = {};
+  state.workspacePreferences = new models.WorkspacePreferences({
+    most_recent_compute_resource_id: "resourceHostId1",
+  });
+  state.applicationDeployments = [
+    new models.ApplicationDeploymentDescription({
+      computeHostId: "resourceHostId2",
+    }),
+    new models.ApplicationDeploymentDescription({
+      computeHostId: "resourceHostId3",
+    }),
+  ];
+  const mockGetters = {
+    defaultComputeResourceId: getters.defaultComputeResourceId(state),
+    computeResources: getters.computeResources(state),
+  };
+  expect(mockGetters.defaultComputeResourceId).toBe("resourceHostId1");
+  expect(mockGetters.computeResources).toEqual([
+    "resourceHostId2",
+    "resourceHostId3",
+  ]);
+  const expectedActions = [{ type: "loadDefaultComputeResourceId" }];
+  const result = testAction(actions.getDefaultResourceHostId, {
+    state,
+    getters: mockGetters,
+    expectedActions,
+    done,
+  });
+  expect(result).resolves.toBe("resourceHostId2");
+});
+
+test("getDefaultResourceHostId: dispatch loadDefaultComputeResourceId, return null when no compute resources", (done) => {
+  const state = {};
+  state.workspacePreferences = new models.WorkspacePreferences({
+    most_recent_compute_resource_id: "resourceHostId1",
+  });
+  state.applicationDeployments = [];
+  const mockGetters = {
+    defaultComputeResourceId: getters.defaultComputeResourceId(state),
+    computeResources: getters.computeResources(state),
+  };
+  expect(mockGetters.defaultComputeResourceId).toBe("resourceHostId1");
+  expect(mockGetters.computeResources).toEqual([]);
+  const expectedActions = [{ type: "loadDefaultComputeResourceId" }];
+  const result = testAction(actions.getDefaultResourceHostId, {
+    state,
+    getters: mockGetters,
+    expectedActions,
+    done,
+  });
+  expect(result).resolves.toBe(null);
+});
+
+test("initializeQueue: when queue selected, when defaults are less than batch queue policy limits, queue settings use defaults", (done) => {
+  const mockGetters = {
+    queue: new models.BatchQueue({
+      queueName: "shared",
+      defaultNodeCount: 1,
+      defaultCPUCount: 8,
+      defaultWalltime: 30,
+    }),
+    batchQueueResourcePolicy: new models.BatchQueueResourcePolicy({
+      queuename: "shared",
+      maxAllowedNodes: 2,
+      maxAllowedCores: 32,
+      maxAllowedWalltime: 60,
+    }),
+  };
+  mockGetters.getDefaultCPUCount = getters.getDefaultCPUCount(
+    null,
+    mockGetters
+  );
+  mockGetters.getDefaultNodeCount = getters.getDefaultNodeCount(
+    null,
+    mockGetters
+  );
+  mockGetters.getDefaultWalltime = getters.getDefaultWalltime(
+    null,
+    mockGetters
+  );
+  expect(mockGetters.getDefaultCPUCount(mockGetters.queue)).toBe(8);
+  expect(mockGetters.getDefaultNodeCount(mockGetters.queue)).toBe(1);
+  expect(mockGetters.getDefaultWalltime(mockGetters.queue)).toBe(30);
+  const expectedMutations = [
+    { type: "updateTotalCPUCount", payload: { totalCPUCount: 8 } },
+    { type: "updateNodeCount", payload: { nodeCount: 1 } },
+    { type: "updateWallTimeLimit", payload: { wallTimeLimit: 30 } },
+    { type: "updateTotalPhysicalMemory", payload: { totalPhysicalMemory: 0 } },
+  ];
+  testAction(actions.initializeQueue, {
+    getters: mockGetters,
+    expectedMutations,
+    done,
+  });
+});
+
+test("initializeQueue: when queue selected, when defaults are more than batch queue policy limits, queue settings use batch queue policy limits", (done) => {
+  const mockGetters = {
+    queue: new models.BatchQueue({
+      queueName: "shared",
+      defaultNodeCount: 4,
+      defaultCPUCount: 16,
+      defaultWalltime: 120,
+    }),
+    batchQueueResourcePolicy: new models.BatchQueueResourcePolicy({
+      queuename: "shared",
+      maxAllowedNodes: 2,
+      maxAllowedCores: 12,
+      maxAllowedWalltime: 45,
+    }),
+  };
+  mockGetters.getDefaultCPUCount = getters.getDefaultCPUCount(
+    null,
+    mockGetters
+  );
+  mockGetters.getDefaultNodeCount = getters.getDefaultNodeCount(
+    null,
+    mockGetters
+  );
+  mockGetters.getDefaultWalltime = getters.getDefaultWalltime(
+    null,
+    mockGetters
+  );
+  expect(mockGetters.getDefaultCPUCount(mockGetters.queue)).toBe(12);
+  expect(mockGetters.getDefaultNodeCount(mockGetters.queue)).toBe(2);
+  expect(mockGetters.getDefaultWalltime(mockGetters.queue)).toBe(45);
+  const expectedMutations = [
+    { type: "updateTotalCPUCount", payload: { totalCPUCount: 12 } },
+    { type: "updateNodeCount", payload: { nodeCount: 2 } },
+    { type: "updateWallTimeLimit", payload: { wallTimeLimit: 45 } },
+    { type: "updateTotalPhysicalMemory", payload: { totalPhysicalMemory: 0 } },
+  ];
+  testAction(actions.initializeQueue, {
+    getters: mockGetters,
+    expectedMutations,
+    done,
+  });
+});
+
+test("initializeQueue: when no queue selected, settings are set to 0", (done) => {
+  const mockGetters = {
+    queue: null,
+  };
+  const expectedMutations = [
+    { type: "updateTotalCPUCount", payload: { totalCPUCount: 0 } },
+    { type: "updateNodeCount", payload: { nodeCount: 0 } },
+    { type: "updateWallTimeLimit", payload: { wallTimeLimit: 0 } },
+    { type: "updateTotalPhysicalMemory", payload: { totalPhysicalMemory: 0 } },
+  ];
+  testAction(actions.initializeQueue, {
+    getters: mockGetters,
+    expectedMutations,
+    done,
+  });
 });

[airavata-django-portal] 12/24: AIRAVATA-3477 Wrapped FileInputEditor as web component

Posted by ma...@apache.org.
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 19f0ba2fefa1363be2c6a2f017c3438cf3a2e2f9
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Sep 8 11:29:33 2021 -0400

    AIRAVATA-3477 Wrapped FileInputEditor as web component
---
 .../experiment/input-editors/FileInputEditor.vue   |  7 ++++
 .../storage/UserStorageFileSelectionContainer.vue  |  1 +
 .../storage/storage-edit/UserStorageLink.vue       | 30 +++++++++--------
 .../js/web-components/QueueSettingsEditor.vue      | 39 +++-------------------
 ...ioButtonInputEditor.vue => FileInputEditor.vue} | 20 ++++++-----
 .../input-editors/RadioButtonInputEditor.vue       |  3 ++
 .../input-editors/StringInputEditor.vue            |  6 ++++
 .../js/web-components/styles.css                   |  1 +
 8 files changed, 52 insertions(+), 55 deletions(-)

diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/FileInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/FileInputEditor.vue
index 775b3fa..0781e90 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/FileInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/FileInputEditor.vue
@@ -141,6 +141,13 @@ export default {
       this.$emit("uploadend");
     },
   },
+  watch: {
+    value(value, oldValue) {
+      if (this.isDataProductURI && value !== oldValue) {
+        this.loadDataProduct(value);
+      }
+    }
+  }
 };
 </script>
 
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/UserStorageFileSelectionContainer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/UserStorageFileSelectionContainer.vue
index 19bb3e7..1193a41 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/UserStorageFileSelectionContainer.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/UserStorageFileSelectionContainer.vue
@@ -3,6 +3,7 @@
     <user-storage-path-viewer
       v-if="userStoragePath"
       :user-storage-path="userStoragePath"
+      :storage-path="storagePath"
       @directory-selected="directorySelected"
       @file-selected="fileSelected"
       :include-delete-action="false"
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/storage-edit/UserStorageLink.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/storage-edit/UserStorageLink.vue
index e0c7c0a..de4674e 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/storage-edit/UserStorageLink.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/storage/storage-edit/UserStorageLink.vue
@@ -1,7 +1,9 @@
 <template>
-  <b-link :href="storageFileViewRouteUrl()" @click="showFilePreview($event)">
-    {{ fileName }}
-    <b-modal :title="fileName" ref="modal" scrollable size="lg">
+  <div>
+    <b-link :href="storageFileViewRouteUrl()" @click="showFilePreview($event)">
+      {{ fileName }}
+    </b-link>
+    <b-modal :title="fileName" ref="modal" scrollable size="lg" static lazy>
       <user-storage-file-edit-viewer
         :file-name="fileName"
         :data-product-uri="dataProductUri"
@@ -11,10 +13,12 @@
         "
       />
       <template slot="modal-footer">
-        <a :href="storageFileViewRouteUrl()" target="_blank">Open in a new window</a>
+        <a :href="storageFileViewRouteUrl()" target="_blank"
+          >Open in a new window</a
+        >
       </template>
     </b-modal>
-  </b-link>
+  </div>
 </template>
 
 <script>
@@ -22,21 +26,21 @@ import UserStorageFileEditViewer from "./UserStorageEditViewer";
 
 export default {
   name: "user-storage-link",
-  components: {UserStorageFileEditViewer},
+  components: { UserStorageFileEditViewer },
   props: {
     fileName: {
-      required: true
+      required: true,
     },
     dataProductUri: {
-      required: true
+      required: true,
     },
     mimeType: {
-      required: true
+      required: true,
     },
     allowPreview: {
       default: true,
-      required: false
-    }
+      required: false,
+    },
   },
   methods: {
     showFilePreview(event) {
@@ -48,7 +52,7 @@ export default {
     storageFileViewRouteUrl() {
       // This endpoint can handle XHR upload or a TUS uploadURL
       return `/workspace/storage/~?dataProductUri=${this.dataProductUri}`;
-    }
-  }
+    },
+  },
 };
 </script>
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 09f8bd6..61f2be1 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
@@ -56,7 +56,7 @@
         >
         </b-form-input>
         <div slot="description">
-          <font-awesome-icon :icon="infoIcon" />
+          <i class="fa fa-info-circle" aria-hidden="true"></i>
           Max Allowed Nodes = {{ maxAllowedNodes }}
         </div>
       </b-form-group>
@@ -72,7 +72,7 @@
         >
         </b-form-input>
         <div slot="description">
-          <font-awesome-icon :icon="infoIcon" />
+          <i class="fa fa-info-circle" aria-hidden="true"></i>
           Max Allowed Cores = {{ maxAllowedCores }}
         </div>
       </b-form-group>
@@ -90,7 +90,7 @@
           </b-form-input>
         </b-input-group>
         <div slot="description">
-          <font-awesome-icon :icon="infoIcon" />
+          <i class="fa fa-info-circle" aria-hidden="true"></i>
           Max Allowed Wall Time = {{ maxAllowedWalltime }} minutes
         </div>
       </b-form-group>
@@ -111,13 +111,13 @@
           </b-form-input>
         </b-input-group>
         <div slot="description">
-          <font-awesome-icon :icon="infoIcon" />
+          <i class="fa fa-info-circle" aria-hidden="true"></i>
           Max Physical Memory = {{ maxMemory }} MB
         </div>
       </b-form-group>
       <div>
         <b-link class="text-secondary" @click="showConfiguration = false">
-          <font-awesome-icon :icon="closeIcon" />
+          <i class="fa fa-times" aria-hidden="true"></i>
           Hide Settings</b-link
         >
       </div>
@@ -133,31 +133,8 @@ import { mapGetters } from "vuex";
 import { BootstrapVue } from "bootstrap-vue";
 Vue.use(BootstrapVue);
 
-import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
-import { config, dom } from "@fortawesome/fontawesome-svg-core";
-import { faInfoCircle, faTimes } from "@fortawesome/free-solid-svg-icons";
-
-// Make sure you tell Font Awesome to skip auto-inserting CSS into the <head>
-config.autoAddCss = false;
-
 export default {
-  components: {
-    FontAwesomeIcon,
-  },
   store: store,
-  mounted() {
-    // Add font awesome styles
-    // https://github.com/FortAwesome/vue-fontawesome#web-components-with-vue-web-component-wrapper
-    const { shadowRoot } = this.$parent.$options;
-    const id = "fa-styles";
-
-    if (!shadowRoot.getElementById(`${id}`)) {
-      const faStyles = document.createElement("style");
-      faStyles.setAttribute("id", id);
-      faStyles.textContent = dom.css();
-      shadowRoot.appendChild(faStyles);
-    }
-  },
   data() {
     return {
       showConfiguration: false,
@@ -193,12 +170,6 @@ export default {
     queueDescription() {
       return this.queue ? this.queue.queueDescription : null;
     },
-    closeIcon() {
-      return faTimes;
-    },
-    infoIcon() {
-      return faInfoCircle;
-    },
   },
   methods: {
     queueChanged(queueName) {
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/FileInputEditor.vue
similarity index 58%
copy from django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
copy to django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/FileInputEditor.vue
index b97e820..868415c 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/FileInputEditor.vue
@@ -1,19 +1,19 @@
 <template>
+  <!-- NOTE: experimentInput is late bound, don't create component until it is available -->
   <div>
-    <radio-button-input-editor
+    <file-input-editor
       v-if="experimentInput"
       :id="id"
       :value="data"
       :experiment-input="experimentInput"
       :read-only="readOnly"
-      :options="options"
       @input="valueChanged"
     />
   </div>
 </template>
 
 <script>
-import RadioButtonInputEditor from "../../components/experiment/input-editors/RadioButtonInputEditor.vue";
+import FileInputEditor from "../../components/experiment/input-editors/FileInputEditor";
 import WebComponentInputEditorMixin from "./WebComponentInputEditorMixin.js";
 
 export default {
@@ -23,17 +23,21 @@ export default {
     // https://github.com/vuejs/vue-web-component-wrapper/issues/30#issuecomment-427350734
     // for more details
     ...WebComponentInputEditorMixin.props,
-    options: {
-      type: Array,
-      default: null,
-    },
   },
   components: {
-    RadioButtonInputEditor,
+    FileInputEditor,
   },
 };
 </script>
 
 <style>
 @import "../styles.css";
+@import "~@uppy/core/dist/style.min.css";
+@import "~@uppy/status-bar/dist/style.min.css";
+@import "~@uppy/drag-drop/dist/style.min.css";
+@import "~codemirror/lib/codemirror.css";
+@import "~codemirror/theme/abcdef.css";
+:host {
+  display: block;
+}
 </style>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
index b97e820..cbfaa9e 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
@@ -36,4 +36,7 @@ export default {
 
 <style>
 @import "../styles.css";
+:host {
+  display: block;
+}
 </style>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
index be3fc77..43cf0b4 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
@@ -19,6 +19,9 @@ import WebComponentInputEditorMixin from "./WebComponentInputEditorMixin.js";
 export default {
   mixins: [WebComponentInputEditorMixin],
   props: {
+    // Explicit copy props from mixin, workaround for bug, see
+    // https://github.com/vuejs/vue-web-component-wrapper/issues/30#issuecomment-427350734
+    // for more details
     ...WebComponentInputEditorMixin.props,
   },
   components: {
@@ -29,4 +32,7 @@ export default {
 
 <style>
 @import "../styles.css";
+:host {
+  display: block;
+}
 </style>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/styles.css b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/styles.css
index 91acb53..bf593cb 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/styles.css
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/styles.css
@@ -1,3 +1,4 @@
 /* common styles for all web components */
 @import "~bootstrap/dist/css/bootstrap.css";
 @import "~bootstrap-vue/dist/bootstrap-vue.css";
+@import "~@fortawesome/fontawesome-free/css/all.css";

[airavata-django-portal] 02/24: AIRAVATA-3477 Convert to Vuex for complex shared state between web components

Posted by ma...@apache.org.
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 887397c8ad55b0c3442af62f4be2a95a1d84d9f8
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Aug 3 10:00:14 2021 -0400

    AIRAVATA-3477 Convert to Vuex for complex shared state between web components
---
 .../js/input-editors/InputEditorMixin.js           |   4 +-
 django_airavata/apps/workspace/package.json        |   3 +-
 .../js/web-components/ExperimentEditor.vue         |  81 +++++--------
 .../js/web-components/ProjectSelector.vue          |  12 +-
 .../input-editors/StringInputEditor.vue            |  29 +++--
 .../js/web-components/store.js                     |   2 +-
 .../js/web-components/vuestore.js                  | 135 +++++++++++++++++++++
 django_airavata/apps/workspace/yarn.lock           |   5 +
 8 files changed, 200 insertions(+), 71 deletions(-)

diff --git a/django_airavata/apps/workspace/django-airavata-workspace-plugin-api/js/input-editors/InputEditorMixin.js b/django_airavata/apps/workspace/django-airavata-workspace-plugin-api/js/input-editors/InputEditorMixin.js
index e767b01..0b0a504 100644
--- a/django_airavata/apps/workspace/django-airavata-workspace-plugin-api/js/input-editors/InputEditorMixin.js
+++ b/django_airavata/apps/workspace/django-airavata-workspace-plugin-api/js/input-editors/InputEditorMixin.js
@@ -13,7 +13,7 @@ export default {
     },
     experiment: {
       type: models.Experiment,
-      required: true,
+      required: false,
     },
     id: {
       type: String,
@@ -31,7 +31,7 @@ export default {
     };
   },
   asyncComputed: {
-    validationResults: {      
+    validationResults: {
       get () {
         let results = this.experimentInput.validate(this.data);
         let value = []
diff --git a/django_airavata/apps/workspace/package.json b/django_airavata/apps/workspace/package.json
index 1eb2f74..0e2d6ba 100644
--- a/django_airavata/apps/workspace/package.json
+++ b/django_airavata/apps/workspace/package.json
@@ -34,7 +34,8 @@
     "vue": "^2.5.22",
     "vue-flatpickr-component": "^8.1.2",
     "vue-router": "^3.0.6",
-    "vue-slider-component": "^3.2.9-1"
+    "vue-slider-component": "^3.2.9-1",
+    "vuex": "^3.6.2"
   },
   "devDependencies": {
     "@vue/cli-plugin-babel": "^3.1.1",
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 4067db2..5c551b8 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
@@ -29,16 +29,9 @@
 </template>
 
 <script>
-import {
-  getApplicationModule,
-  getApplicationInterfaceForModule,
-  saveExperiment,
-  getExperiment,
-  launchExperiment,
-} from "./store";
-import { utils } from "django-airavata-common-ui";
-
 import Vue from "vue";
+import vuestore from "./vuestore";
+import { mapGetters } from "vuex";
 import { BootstrapVue } from "bootstrap-vue";
 import urls from "../utils/urls";
 Vue.use(BootstrapVue);
@@ -55,13 +48,18 @@ export default {
       required: false,
     },
   },
+  store: vuestore,
   async created() {},
   async mounted() {
-    this.applicationModule = await getApplicationModule(this.applicationId);
-    this.appInterface = await getApplicationInterfaceForModule(
-      this.applicationId
-    );
-    this.experiment = await this.loadExperiment();
+    if (this.experimentId) {
+      await this.$store.dispatch("loadExperiment", {
+        experimentId: this.experimentId,
+      });
+    } else {
+      await this.$store.dispatch("loadNewExperiment", {
+        applicationId: this.applicationId,
+      });
+    }
     // vue-web-component-wrapper clones native slots and turns them into Vue
     // slots which means they lose any event listeners and they basically aren't
     // in the DOM any more.  As a workaround, programmatically create native
@@ -74,14 +72,13 @@ export default {
           slot.textContent = `${input.name} `;
           const textInput = document.createElement("adpf-string-input-editor");
           textInput.setAttribute("value", input.value);
+          textInput.setAttribute("name", input.name);
           slot.appendChild(textInput);
           this.$refs[input.name][0].append(slot);
-          textInput.experimentInput = input;
-          textInput.experiment = this.experiment;
-          textInput.id = utils.sanitizeHTMLId(input.name);
         }
         // TODO: add support for other input types
       }
+      // this.injectPropsIntoSlottedInputs();
 
       /*
        * Experiment Name native slot
@@ -180,37 +177,36 @@ export default {
       );
     });
   },
-  data() {
-    return {
-      applicationModule: null,
-      appInterface: null,
-      experiment: null,
-    };
+  computed: {
+    ...mapGetters(["experiment"]),
   },
   methods: {
     updateExperimentName(event) {
-      this.experiment.experimentName = event.target.value;
+      this.$store.dispatch("updateExperimentName", {
+        name: event.target.value,
+      });
     },
     updateInputValue(inputName, event) {
       if (event.inputType) {
         // Ignore these fine-grained events about the type of change made
         return;
       }
-      const experimentInput = this.experiment.experimentInputs.find(
-        (i) => i.name === inputName
-      );
       // web component input events have the current value in a detail array,
       // native input events have the current value in target.value
-      const value = Array.isArray(event.detail) ? event.detail[0] : event.target.value;
-      experimentInput.value = value;
+      const value = Array.isArray(event.detail)
+        ? event.detail[0]
+        : event.target.value;
+      this.$store.dispatch("updateExperimentInputValue", { inputName, value });
     },
     updateProjectId(event) {
       const [projectId] = event.detail;
-      this.experiment.projectId = projectId;
+      this.$store.dispatch("updateProjectId", { projectId });
     },
     updateUserConfigurationData(event) {
       const [userConfigurationData] = event.detail;
-      this.experiment.userConfigurationData = userConfigurationData;
+      this.$store.dispatch("updateUserConfigurationData", {
+        userConfigurationData,
+      });
     },
     async onSubmit(event) {
       // console.log(event);
@@ -226,14 +222,14 @@ export default {
         return;
       }
       if (event.submitter.name === "save-experiment-button") {
-        await saveExperiment(this.experiment);
+        await this.$store.dispatch("saveExperiment");
         this.postSave();
         return;
       } else {
         // Default submit button handling is save and launch
-        const experiment = await saveExperiment(this.experiment);
-        await launchExperiment(experiment.experimentId);
-        this.postSaveAndLaunch(experiment);
+        await this.$store.dispatch("saveExperiment");
+        await this.$store.dispatch("launchExperiment");
+        this.postSaveAndLaunch(this.experiment);
         return;
       }
     },
@@ -266,21 +262,6 @@ export default {
       }
       urls.navigateToViewExperiment(experiment, { launching: true });
     },
-    async loadExperiment() {
-      if (this.experimentId) {
-        const experiment = await getExperiment(this.experimentId);
-        this.$emit("loaded", experiment);
-        return experiment;
-      } else {
-        const experiment = this.appInterface.createExperiment();
-        experiment.experimentName =
-          this.applicationModule.appModuleName +
-          " on " +
-          new Date().toLocaleString();
-        this.$emit("loaded", experiment);
-        return experiment;
-      }
-    },
     createSlot(name, ...children) {
       const slot = document.createElement("slot");
       slot.setAttribute("name", name);
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 7d5c116..224da0f 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
@@ -27,8 +27,9 @@
 </template>
 
 <script>
-import { getDefaultProjectId, getProjects } from "./store";
 import Vue from "vue";
+import vuestore from "./vuestore";
+import { mapGetters } from "vuex";
 import { BootstrapVue } from "bootstrap-vue";
 Vue.use(BootstrapVue);
 
@@ -39,21 +40,22 @@ export default {
       default: null,
     },
   },
+  store: vuestore,
   data() {
     return {
       projectId: this.value,
-      projects: null,
     };
   },
   async mounted() {
-    this.projects = await getProjects();
-    const defaultProjectId = await getDefaultProjectId();
+    await this.$store.dispatch('loadProjects');
+    await this.$store.dispatch('loadDefaultProjectId');
     if (!this.projectId) {
-      this.projectId = defaultProjectId;
+      this.projectId = this.defaultProjectId;
       this.$emit("input", this.projectId);
     }
   },
   computed: {
+    ...mapGetters(["projects", "defaultProjectId"]),
     sharedProjectOptions: function () {
       return this.projects
         ? this.projects
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
index 905469e..106eca9 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
@@ -1,12 +1,10 @@
 <template>
-<!-- NOTE: regarding v-if="ready": experimentInput/experiment/id are late bound,
-     don't create component until they is available -->
+  <!-- NOTE: experimentInput is late bound, don't create component until it is available -->
   <string-input-editor
-    v-if="ready"
+    v-if="experimentInput"
     :id="id"
     :value="data"
     :experiment-input="experimentInput"
-    :experiment="experiment"
     :read-only="readOnly"
     @input="onInput"
   />
@@ -17,29 +15,36 @@ import StringInputEditor from "../../components/experiment/input-editors/StringI
 import Vue from "vue";
 import { BootstrapVue } from "bootstrap-vue";
 import AsyncComputed from "vue-async-computed";
+import { utils } from "django-airavata-common-ui";
+import vuestore from "../vuestore";
 Vue.use(BootstrapVue);
 Vue.use(AsyncComputed);
 
 export default {
   props: {
     value: String,
-    experimentInput: Object,
-    experiment: Object,
-    readOnly: Boolean,
-    id: String,
+    // experimentInput: Object,
+    name: String,
   },
   components: {
     StringInputEditor,
   },
+  store: vuestore,
   data() {
     return {
       data: this.value,
     };
   },
   computed: {
-    ready() {
-      return this.experiment && this.experimentInput && this.id;
-    }
+    readOnly() {
+      return this.experimentInput.isReadOnly;
+    },
+    id() {
+      return utils.sanitizeHTMLId(this.experimentInput.name);
+    },
+    experimentInput() {
+      return this.$store.getters.getExperimentInputByName(this.name);
+    },
   },
   methods: {
     onInput(value) {
@@ -52,7 +57,7 @@ export default {
         });
         this.$el.dispatchEvent(inputEvent);
       }
-    }
+    },
   },
 };
 </script>
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 c14469a..321a5a0 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
@@ -26,7 +26,7 @@ export async function getApplicationInterfaceForModule(applicationId) {
 
 export async function saveExperiment(experiment) {
   if (experiment.experimentId) {
-    return await services.ExperimentService.update({
+    await services.ExperimentService.update({
       data: experiment,
       lookup: experiment.experimentId,
     });
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
new file mode 100644
index 0000000..0681867
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/vuestore.js
@@ -0,0 +1,135 @@
+import { services } from "django-airavata-api";
+import Vue from "vue";
+import Vuex from "vuex";
+
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+  state: {
+    experiment: null,
+    projects: null,
+    defaultProjectId: null,
+    computeResourceNames: null,
+  },
+  mutations: {
+    setExperiment(state, { experiment }) {
+      state.experiment = experiment;
+    },
+    updateExperimentName(state, { name }) {
+      state.experiment.experimentName = name;
+    },
+    updateExperimentInputValue(state, { inputName, value }) {
+      const experimentInput = state.experiment.experimentInputs.find(
+        (i) => i.name === inputName
+      );
+      experimentInput.value = value;
+    },
+    updateProjectId(state, { projectId }) {
+      state.experiment.projectId = projectId;
+    },
+    updateUserConfigurationData(state, { userConfigurationData }) {
+      state.experiment.userConfigurationData = userConfigurationData;
+    },
+    setProjects(state, { projects }) {
+      state.projects = projects;
+    },
+    setDefaultProjectId(state, { defaultProjectId }) {
+      state.defaultProjectId = defaultProjectId;
+    },
+    setComputeResourceNames(state, { computeResourceNames }) {
+      state.computeResourceNames = computeResourceNames;
+    },
+  },
+  actions: {
+    async loadNewExperiment({ commit }, { applicationId }) {
+      const applicationModule = await services.ApplicationModuleService.retrieve(
+        {
+          lookup: applicationId,
+        }
+      );
+      const appInterface = await services.ApplicationModuleService.getApplicationInterface(
+        {
+          lookup: applicationId,
+        }
+      );
+      const experiment = appInterface.createExperiment();
+      const currentDate = new Date().toLocaleString([], {
+        dateStyle: "medium",
+        timeStyle: "short",
+      });
+      experiment.experimentName = `${applicationModule.appModuleName} on ${currentDate}`;
+      commit("setExperiment", { experiment });
+    },
+    async loadExperiment({ commit }, { experimentId }) {
+      const experiment = await services.ExperimentService.retrieve({
+        lookup: experimentId,
+      });
+      commit("setExperiment", { experiment });
+    },
+    updateExperimentName({ commit }, { name }) {
+      commit("updateExperimentName", { name });
+    },
+    updateExperimentInputValue({ commit }, { inputName, value }) {
+      commit("updateExperimentInputValue", { inputName, value });
+    },
+    updateProjectId({ commit }, { projectId }) {
+      commit("updateProjectId", { projectId });
+    },
+    updateUserConfigurationData({ commit }, { userConfigurationData }) {
+      commit("updateUserConfigurationData", { userConfigurationData });
+    },
+    async saveExperiment({ commit, getters }) {
+      if (getters.experiment.experimentId) {
+        const experiment = await services.ExperimentService.update({
+          data: getters.experiment,
+          lookup: getters.experiment.experimentId,
+        });
+        commit("setExperiment", { experiment });
+      } else {
+        const experiment = await services.ExperimentService.create({
+          data: getters.experiment,
+        });
+        commit("setExperiment", { experiment });
+      }
+    },
+    async launchExperiment({ getters }) {
+      await services.ExperimentService.launch({
+        lookup: getters.experiment.experimentId,
+      });
+    },
+    async loadProjects({ commit }) {
+      const projects = await services.ProjectService.listAll();
+      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 loadComputeResourceNames({ commit }) {
+      const computeResourceNames = await services.ComputeResourceService.names();
+      commit("setComputeResourceNames", { computeResourceNames });
+    },
+  },
+  getters: {
+    getExperimentInputByName: (state) => (name) => {
+      if (!state.experiment) {
+        return null;
+      }
+      const experimentInputs = state.experiment.experimentInputs;
+      if (experimentInputs) {
+        for (const experimentInput of experimentInputs) {
+          if (experimentInput.name === name) {
+            return experimentInput;
+          }
+        }
+      }
+      return null;
+    },
+    experiment: (state) => state.experiment,
+    projects: (state) => state.projects,
+    defaultProjectId: (state) => state.defaultProjectId,
+    computeResourceNames: (state) => state.computeResourceNames,
+  },
+});
diff --git a/django_airavata/apps/workspace/yarn.lock b/django_airavata/apps/workspace/yarn.lock
index 9db3ee8..618a524 100644
--- a/django_airavata/apps/workspace/yarn.lock
+++ b/django_airavata/apps/workspace/yarn.lock
@@ -10534,6 +10534,11 @@ vue@^2.5.22:
   resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
   integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
 
+vuex@^3.6.2:
+  version "3.6.2"
+  resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.6.2.tgz#236bc086a870c3ae79946f107f16de59d5895e71"
+  integrity sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==
+
 w3c-hr-time@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045"

[airavata-django-portal] 24/24: Merge branch 'airavata-3477' into develop

Posted by ma...@apache.org.
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 69da8106a71fa2309d48aee5c724437fc2a5a8c6
Merge: a5e61bf 9aeecfc
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Oct 5 15:46:59 2021 -0400

    Merge branch 'airavata-3477' into develop

 .../api/static/django_airavata_api/js/index.js     |   2 +
 .../js/input-editors/InputEditorMixin.js           |   4 +-
 django_airavata/apps/workspace/package.json        |   7 +-
 .../input-editors/CheckboxInputEditor.vue          |  27 +-
 .../experiment/input-editors/FileInputEditor.vue   |   7 +
 .../input-editors/RadioButtonInputEditor.vue       |  26 +-
 .../input-editors/RangeSliderInputEditor.vue       |  71 +-
 .../experiment/input-editors/SelectInputEditor.vue |  22 +-
 .../experiment/input-editors/SliderInputEditor.vue |  61 +-
 .../experiment/input-editors/StringInputEditor.vue |   3 +
 .../input-editors/TextareaInputEditor.vue          |  14 +-
 .../storage/UserStorageFileSelectionContainer.vue  |   1 +
 .../storage/storage-edit/UserStorageLink.vue       |  30 +-
 .../js/web-components/ComputeResourceSelector.vue  |  51 +-
 .../ExperimentComputeResourceSelector.vue          |  43 ++
 .../js/web-components/ExperimentEditor.vue         | 148 ++--
 .../GroupResourceProfileSelector.vue               |  44 +-
 .../js/web-components/ProjectSelector.vue          |  20 +-
 .../js/web-components/QueueSettingsEditor.vue      | 177 ++---
 .../js/web-components/ResourceSelectionEditor.vue  | 426 ------------
 .../input-editors/CheckboxInputEditor.vue          |  50 ++
 .../input-editors/FileInputEditor.vue              |  43 ++
 .../input-editors/InlineOptionsMixin.js            |  50 ++
 .../input-editors/MultiFileInputEditor.vue         |  43 ++
 .../input-editors/RadioButtonInputEditor.vue       |  50 ++
 .../input-editors/RangeSliderInputEditor.vue       |  66 ++
 .../input-editors/SelectInputEditor.vue            |  48 ++
 .../input-editors/SliderInputEditor.vue            |  64 ++
 .../input-editors/StringInputEditor.vue            |  41 ++
 .../input-editors/TextareaInputEditor.vue          |  45 ++
 .../input-editors/WebComponentInputEditorMixin.js  |  62 ++
 .../js/web-components/store.js                     | 755 +++++++++++++++++----
 .../js/web-components/{styles.css => styles.scss}  |   2 +
 .../tests/unit/web-components/store.spec.js        | 708 +++++++++++++++++++
 django_airavata/apps/workspace/yarn.lock           |   5 +
 35 files changed, 2360 insertions(+), 856 deletions(-)

[airavata-django-portal] 19/24: AIRAVATA-3477 test logic around initializing experiment's GRP

Posted by ma...@apache.org.
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 ea6d5b9abd028346b9e2527299b59f03c93847a5
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Sep 28 13:57:34 2021 -0400

    AIRAVATA-3477 test logic around initializing experiment's GRP
---
 .../api/static/django_airavata_api/js/index.js     |   2 +
 .../js/web-components/store.js                     |  56 +++++---
 .../tests/unit/web-components/store.spec.js        | 156 ++++++++++++++++++++-
 3 files changed, 191 insertions(+), 23 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 7728bea..ca08f1d 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
@@ -38,6 +38,7 @@ import StoragePreference from "./models/StoragePreference";
 import SummaryType from "./models/SummaryType";
 import UserConfigurationData from "./models/UserConfigurationData";
 import UserPermission from "./models/UserPermission";
+import WorkspacePreferences from "./models/WorkspacePreferences";
 
 import CloudJobSubmissionService from "./services/CloudJobSubmissionService";
 import GlobusJobSubmissionService from "./services/GlobusJobSubmissionService";
@@ -99,6 +100,7 @@ const models = {
   SummaryType,
   UserConfigurationData,
   UserPermission,
+  WorkspacePreferences,
 };
 
 const services = {
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 cfc98ab..a0e7e4d 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
@@ -101,7 +101,7 @@ export const actions = {
     commit("setExperiment", { experiment });
     await dispatch("loadExperimentData");
   },
-  async loadExperimentData({ commit, dispatch, getters, state }) {
+  async loadExperimentData({ commit, dispatch, state }) {
     await Promise.all([
       dispatch("loadProjects"),
       dispatch("loadWorkspacePreferences"),
@@ -113,28 +113,47 @@ export const actions = {
         projectId: state.workspacePreferences.most_recent_project_id,
       });
     }
+
+    dispatch("initializeGroupResourceProfileId");
+    const groupResourceProfileId =
+      state.experiment.userConfigurationData.groupResourceProfileId;
+    // If experiment has a group resource profile, load additional necessary
+    // data and re-apply group resource profile
+    if (groupResourceProfileId) {
+      await dispatch("loadApplicationDeployments");
+      await dispatch("loadAppDeploymentQueues");
+      await dispatch("applyGroupResourceProfile");
+    }
+  },
+  initializeGroupResourceProfileId({ commit, getters, state }) {
     // 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
+    // groupResourceProfiles, set to the default one, or the first one
     let groupResourceProfileId =
       state.experiment.userConfigurationData.groupResourceProfileId;
     if (
       !groupResourceProfileId ||
       !getters.findGroupResourceProfile(groupResourceProfileId)
     ) {
-      commit("updateGroupResourceProfileId", {
-        groupResourceProfileId:
-          state.workspacePreferences.most_recent_group_resource_profile_id,
-      });
-    }
-    groupResourceProfileId =
-      state.experiment.userConfigurationData.groupResourceProfileId;
-    // 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.findGroupResourceProfile(groupResourceProfileId)) {
-      await dispatch("loadApplicationDeployments");
-      await dispatch("loadAppDeploymentQueues");
-      await dispatch("applyGroupResourceProfile");
+      if (
+        getters.findGroupResourceProfile(
+          state.workspacePreferences.most_recent_group_resource_profile_id
+        )
+      ) {
+        commit("updateGroupResourceProfileId", {
+          groupResourceProfileId:
+            state.workspacePreferences.most_recent_group_resource_profile_id,
+        });
+      } else if (state.groupResourceProfiles.length > 0) {
+        commit("updateGroupResourceProfileId", {
+          groupResourceProfileId:
+            state.groupResourceProfiles[0].groupResourceProfileId,
+        });
+      } else {
+        commit("updateGroupResourceProfileId", {
+          groupResourceProfileId: null,
+        });
+      }
     }
   },
   updateExperimentName({ commit }, { name }) {
@@ -263,15 +282,14 @@ export const actions = {
       commit("setAppDeploymentQueues", { appDeploymentQueues: [] });
     }
   },
-  async setDefaultQueue({ commit, dispatch, getters }) {
+  async setDefaultQueue({ dispatch, getters }) {
     // set to the default queue or the first one
     const defaultQueue = getters.defaultQueue;
     if (defaultQueue) {
-      commit("updateQueueName", { queueName: defaultQueue.queueName });
+      dispatch("updateQueueName", { queueName: defaultQueue.queueName });
     } else {
-      commit("updateQueueName", { queueName: null });
+      dispatch("updateQueueName", { queueName: null });
     }
-    dispatch("initializeQueue");
   },
   initializeQueue({ commit, getters }) {
     const queue = getters.queue;
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/web-components/store.spec.js b/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/web-components/store.spec.js
index 8bc1606..615a806 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/web-components/store.spec.js
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/web-components/store.spec.js
@@ -1,4 +1,4 @@
-import { actions, mutations } from "@/web-components/store.js";
+import { actions, getters, mutations } from "@/web-components/store.js";
 import { models } from "django-airavata-api";
 
 /*
@@ -160,7 +160,7 @@ test("applyBatchQueueResourcePolicy: maxAllowedWalltime caps wallTimeLimit", (do
       maxAllowedWalltime: 6,
     },
     expectedMutations: [
-      { type: "updateWallTimeLimit", payload: { wallTimeLimit: 6} },
+      { type: "updateWallTimeLimit", payload: { wallTimeLimit: 6 } },
     ],
     done,
   });
@@ -178,8 +178,156 @@ test("applyBatchQueueResourcePolicy: maxAllowedWalltime doesn't affect wallTimeL
       maxAllowedNodes: 2,
       maxAllowedWalltime: 120,
     },
-    expectedMutations: [
-    ],
+    expectedMutations: [],
     done,
   });
 });
+
+test("initializeGroupResourceProfileId: set to most recent group resource profile when null", (done) => {
+  const state = {};
+  state.experiment = new models.Experiment();
+  state.workspacePreferences = new models.WorkspacePreferences();
+  state.workspacePreferences.most_recent_group_resource_profile_id =
+    "ec50a69d-54ea-4b7c-a578-9a2a8da09ba0";
+  state.groupResourceProfiles = [
+    new models.GroupResourceProfile({
+      groupResourceProfileId:
+        state.workspacePreferences.most_recent_group_resource_profile_id,
+    }),
+  ];
+
+  const g = {
+    experiment: state.experiment,
+    findGroupResourceProfile: (groupResourceProfileId) =>
+      getters.findGroupResourceProfile(state)(groupResourceProfileId),
+  };
+  const expectedMutations = [
+    {
+      type: "updateGroupResourceProfileId",
+      payload: {
+        groupResourceProfileId:
+          state.workspacePreferences.most_recent_group_resource_profile_id,
+      },
+    },
+  ];
+  testAction(
+    actions.initializeGroupResourceProfileId,
+    null,
+    state,
+    g,
+    expectedMutations,
+    done
+  );
+});
+
+test("initializeGroupResourceProfileId: set to most recent group resource profile when no longer has access to grp", (done) => {
+  const state = {};
+  state.experiment = new models.Experiment();
+  state.experiment.userConfigurationData.groupResourceProfileId =
+    "2580d4e6-7a8d-444e-b259-a8e6ae886d66";
+  state.workspacePreferences = new models.WorkspacePreferences();
+  state.workspacePreferences.most_recent_group_resource_profile_id =
+    "ec50a69d-54ea-4b7c-a578-9a2a8da09ba0";
+  state.groupResourceProfiles = [
+    new models.GroupResourceProfile({
+      groupResourceProfileId:
+        state.workspacePreferences.most_recent_group_resource_profile_id,
+    }),
+  ];
+  // experiment's grp isn't in the list of available grps
+  expect(
+    state.groupResourceProfiles.find(
+      (grp) =>
+        grp.groupResourceProfileId ===
+        state.experiment.userConfigurationData.groupResourceProfileId
+    )
+  ).toBeUndefined();
+  const g = {
+    experiment: state.experiment,
+    findGroupResourceProfile: (groupResourceProfileId) =>
+      getters.findGroupResourceProfile(state)(groupResourceProfileId),
+  };
+  const expectedMutations = [
+    {
+      type: "updateGroupResourceProfileId",
+      payload: {
+        groupResourceProfileId:
+          state.workspacePreferences.most_recent_group_resource_profile_id,
+      },
+    },
+  ];
+  testAction(
+    actions.initializeGroupResourceProfileId,
+    null,
+    state,
+    g,
+    expectedMutations,
+    done
+  );
+});
+
+test("initializeGroupResourceProfileId: set to first group resource profile when no most recent grp", (done) => {
+  const state = {};
+  state.experiment = new models.Experiment();
+  state.experiment.userConfigurationData.groupResourceProfileId =
+    "2580d4e6-7a8d-444e-b259-a8e6ae886d66";
+  state.workspacePreferences = new models.WorkspacePreferences();
+  state.workspacePreferences.most_recent_group_resource_profile_id = null;
+  state.groupResourceProfiles = [
+    new models.GroupResourceProfile({
+      groupResourceProfileId: "c84da77b-8ce6-457f-b5c7-c72a663d7f77",
+    }),
+  ];
+  const g = {
+    experiment: state.experiment,
+    findGroupResourceProfile: (groupResourceProfileId) =>
+      getters.findGroupResourceProfile(state)(groupResourceProfileId),
+  };
+  const expectedMutations = [
+    {
+      type: "updateGroupResourceProfileId",
+      payload: {
+        groupResourceProfileId: "c84da77b-8ce6-457f-b5c7-c72a663d7f77",
+      },
+    },
+  ];
+  testAction(
+    actions.initializeGroupResourceProfileId,
+    null,
+    state,
+    g,
+    expectedMutations,
+    done
+  );
+});
+
+test("initializeGroupResourceProfileId: set to null when no longer has access", (done) => {
+  const state = {};
+  state.experiment = new models.Experiment();
+  state.experiment.userConfigurationData.groupResourceProfileId =
+    "2580d4e6-7a8d-444e-b259-a8e6ae886d66";
+  state.workspacePreferences = new models.WorkspacePreferences();
+  state.workspacePreferences.most_recent_group_resource_profile_id = null;
+  state.groupResourceProfiles = [];
+  const g = {
+    experiment: state.experiment,
+    findGroupResourceProfile: (groupResourceProfileId) =>
+      getters.findGroupResourceProfile(state)(groupResourceProfileId),
+  };
+  const expectedMutations = [
+    {
+      type: "updateGroupResourceProfileId",
+      payload: {
+        groupResourceProfileId: null,
+      },
+    },
+  ];
+  testAction(
+    actions.initializeGroupResourceProfileId,
+    null,
+    state,
+    g,
+    expectedMutations,
+    done
+  );
+});

[airavata-django-portal] 01/24: AIRAVATA-3477 StringInputEditor web component

Posted by ma...@apache.org.
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 1b923ad8c2f5d511a72d6d57a2e71072d33da169
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Jul 9 16:09:29 2021 -0400

    AIRAVATA-3477 StringInputEditor web component
---
 django_airavata/apps/workspace/package.json        |  4 +-
 .../js/web-components/ExperimentEditor.vue         | 20 +++++--
 .../input-editors/StringInputEditor.vue            | 62 ++++++++++++++++++++++
 3 files changed, 79 insertions(+), 7 deletions(-)

diff --git a/django_airavata/apps/workspace/package.json b/django_airavata/apps/workspace/package.json
index 0e61e88..1eb2f74 100644
--- a/django_airavata/apps/workspace/package.json
+++ b/django_airavata/apps/workspace/package.json
@@ -14,8 +14,8 @@
     "test:unit": "vue-cli-service test:unit",
     "test:unit:watch": "vue-cli-service test:unit --watch",
     "format": "prettier --write .",
-    "build:wc": "cross-env WC_MODE='true' vue-cli-service build --target wc --inline-vue --name adpf './static/django_airavata_workspace/js/web-components/*.vue' --dest ./static/django_airavata_workspace/wc",
-    "build:wc:watch": "cross-env WC_MODE='true' vue-cli-service build --target wc --inline-vue --name adpf './static/django_airavata_workspace/js/web-components/*.vue' --dest ./static/django_airavata_workspace/wc --watch"
+    "build:wc": "cross-env WC_MODE='true' vue-cli-service build --target wc --inline-vue --name adpf './static/django_airavata_workspace/js/web-components/**/*.vue' --dest ./static/django_airavata_workspace/wc",
+    "build:wc:watch": "cross-env WC_MODE='true' vue-cli-service build --target wc --inline-vue --name adpf './static/django_airavata_workspace/js/web-components/**/*.vue' --dest ./static/django_airavata_workspace/wc --watch"
   },
   "dependencies": {
     "@fortawesome/fontawesome-svg-core": "^1.2.35",
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 816bdd7..4067db2 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
@@ -12,7 +12,7 @@
       <div
         :ref="input.name"
         :key="input.name"
-        @input="updateInputValue(input.name, $event.target.value)"
+        @input="updateInputValue(input.name, $event)"
       >
         <!-- programmatically define slots as native slots (not Vue slots), see #mounted() -->
       </div>
@@ -36,6 +36,7 @@ import {
   getExperiment,
   launchExperiment,
 } from "./store";
+import { utils } from "django-airavata-common-ui";
 
 import Vue from "vue";
 import { BootstrapVue } from "bootstrap-vue";
@@ -71,13 +72,15 @@ export default {
         slot.setAttribute("name", input.name);
         if (input.type.name === "STRING") {
           slot.textContent = `${input.name} `;
-          const textInput = document.createElement("input");
-          textInput.setAttribute("type", "text");
+          const textInput = document.createElement("adpf-string-input-editor");
           textInput.setAttribute("value", input.value);
           slot.appendChild(textInput);
+          this.$refs[input.name][0].append(slot);
+          textInput.experimentInput = input;
+          textInput.experiment = this.experiment;
+          textInput.id = utils.sanitizeHTMLId(input.name);
         }
         // TODO: add support for other input types
-        this.$refs[input.name][0].append(slot);
       }
 
       /*
@@ -188,10 +191,17 @@ export default {
     updateExperimentName(event) {
       this.experiment.experimentName = event.target.value;
     },
-    updateInputValue(inputName, value) {
+    updateInputValue(inputName, event) {
+      if (event.inputType) {
+        // Ignore these fine-grained events about the type of change made
+        return;
+      }
       const experimentInput = this.experiment.experimentInputs.find(
         (i) => i.name === inputName
       );
+      // web component input events have the current value in a detail array,
+      // native input events have the current value in target.value
+      const value = Array.isArray(event.detail) ? event.detail[0] : event.target.value;
       experimentInput.value = value;
     },
     updateProjectId(event) {
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
new file mode 100644
index 0000000..905469e
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
@@ -0,0 +1,62 @@
+<template>
+<!-- NOTE: regarding v-if="ready": experimentInput/experiment/id are late bound,
+     don't create component until they is available -->
+  <string-input-editor
+    v-if="ready"
+    :id="id"
+    :value="data"
+    :experiment-input="experimentInput"
+    :experiment="experiment"
+    :read-only="readOnly"
+    @input="onInput"
+  />
+</template>
+
+<script>
+import StringInputEditor from "../../components/experiment/input-editors/StringInputEditor.vue";
+import Vue from "vue";
+import { BootstrapVue } from "bootstrap-vue";
+import AsyncComputed from "vue-async-computed";
+Vue.use(BootstrapVue);
+Vue.use(AsyncComputed);
+
+export default {
+  props: {
+    value: String,
+    experimentInput: Object,
+    experiment: Object,
+    readOnly: Boolean,
+    id: String,
+  },
+  components: {
+    StringInputEditor,
+  },
+  data() {
+    return {
+      data: this.value,
+    };
+  },
+  computed: {
+    ready() {
+      return this.experiment && this.experimentInput && this.id;
+    }
+  },
+  methods: {
+    onInput(value) {
+      if (value !== this.data) {
+        this.data = value;
+        const inputEvent = new CustomEvent("input", {
+          detail: [this.data],
+          composed: true,
+          bubbles: true,
+        });
+        this.$el.dispatchEvent(inputEvent);
+      }
+    }
+  },
+};
+</script>
+
+<style>
+@import "../styles.css";
+</style>

[airavata-django-portal] 10/24: AIRAVATA-3477 Unit tests for web comp vuex store

Posted by ma...@apache.org.
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 639ad1655abde50d6818435b85ff87c443a61887
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Sep 3 14:22:16 2021 -0400

    AIRAVATA-3477 Unit tests for web comp vuex store
---
 .../js/web-components/store.js                     | 1164 ++++++++++----------
 .../tests/unit/web-components/store.spec.js        |  185 ++++
 2 files changed, 770 insertions(+), 579 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 a2128f5..cfc98ab 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
@@ -7,611 +7,617 @@ Vue.use(Vuex);
 const PROMISES = {
   workspacePreferences: null,
 };
-export default new Vuex.Store({
-  strict: process.env.NODE_ENV !== "production",
-  state: {
-    experiment: null,
-    projects: null,
-    computeResourceNames: {},
-    applicationDeployments: [],
-    groupResourceProfiles: null,
-    applicationModuleId: null,
-    appDeploymentQueues: [],
+export const mutations = {
+  setExperiment(state, { experiment }) {
+    state.experiment = experiment;
   },
-  mutations: {
-    setExperiment(state, { experiment }) {
-      state.experiment = experiment;
-    },
-    updateExperimentName(state, { name }) {
-      state.experiment.experimentName = name;
-    },
-    updateExperimentInputValue(state, { inputName, value }) {
-      const experimentInput = state.experiment.experimentInputs.find(
-        (i) => i.name === inputName
-      );
-      experimentInput.value = value;
-    },
-    updateProjectId(state, { projectId }) {
-      state.experiment.projectId = projectId;
-    },
-    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;
-    },
-    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, dispatch }, { applicationId }) {
-      const applicationModule = await services.ApplicationModuleService.retrieve(
-        {
-          lookup: applicationId,
+  updateExperimentName(state, { name }) {
+    state.experiment.experimentName = name;
+  },
+  updateExperimentInputValue(state, { inputName, value }) {
+    const experimentInput = state.experiment.experimentInputs.find(
+      (i) => i.name === inputName
+    );
+    experimentInput.value = value;
+  },
+  updateProjectId(state, { projectId }) {
+    state.experiment.projectId = projectId;
+  },
+  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;
+  },
+  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;
+  },
+};
+export const actions = {
+  async loadNewExperiment({ commit, dispatch }, { applicationId }) {
+    const applicationModule = await services.ApplicationModuleService.retrieve({
+      lookup: applicationId,
+    });
+    const appInterface = await services.ApplicationModuleService.getApplicationInterface(
+      {
+        lookup: applicationId,
+      }
+    );
+    const experiment = appInterface.createExperiment();
+    const currentDate = new Date().toLocaleString([], {
+      dateStyle: "medium",
+      timeStyle: "short",
+    });
+    experiment.experimentName = `${applicationModule.appModuleName} on ${currentDate}`;
+    commit("setApplicationModuleId", { applicationModuleId: applicationId });
+    await dispatch("setExperiment", { experiment });
+  },
+  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, state }) {
+    await Promise.all([
+      dispatch("loadProjects"),
+      dispatch("loadWorkspacePreferences"),
+      dispatch("loadGroupResourceProfiles"),
+    ]);
+
+    if (!state.experiment.projectId) {
+      commit("updateProjectId", {
+        projectId: state.workspacePreferences.most_recent_project_id,
+      });
+    }
+    // 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
+    let groupResourceProfileId =
+      state.experiment.userConfigurationData.groupResourceProfileId;
+    if (
+      !groupResourceProfileId ||
+      !getters.findGroupResourceProfile(groupResourceProfileId)
+    ) {
+      commit("updateGroupResourceProfileId", {
+        groupResourceProfileId:
+          state.workspacePreferences.most_recent_group_resource_profile_id,
+      });
+    }
+    groupResourceProfileId =
+      state.experiment.userConfigurationData.groupResourceProfileId;
+    // 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.findGroupResourceProfile(groupResourceProfileId)) {
+      await dispatch("loadApplicationDeployments");
+      await dispatch("loadAppDeploymentQueues");
+      await dispatch("applyGroupResourceProfile");
+    }
+  },
+  updateExperimentName({ commit }, { name }) {
+    commit("updateExperimentName", { name });
+  },
+  updateExperimentInputValue({ commit }, { inputName, value }) {
+    commit("updateExperimentInputValue", { inputName, value });
+  },
+  updateProjectId({ commit }, { projectId }) {
+    commit("updateProjectId", { projectId });
+  },
+  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([]);
         }
-      );
-      const appInterface = await services.ApplicationModuleService.getApplicationInterface(
+      })
+      // 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: applicationId,
+          lookup: applicationDeployment.appDeploymentId,
         }
       );
-      const experiment = appInterface.createExperiment();
-      const currentDate = new Date().toLocaleString([], {
-        dateStyle: "medium",
-        timeStyle: "short",
-      });
-      experiment.experimentName = `${applicationModule.appModuleName} on ${currentDate}`;
-      commit("setApplicationModuleId", { applicationModuleId: applicationId });
-      await dispatch("setExperiment", { experiment });
-    },
-    async loadExperiment({ commit, dispatch }, { experimentId }) {
-      const experiment = await services.ExperimentService.retrieve({
-        lookup: experimentId,
+      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),
       });
-      const appInterface = await services.ApplicationInterfaceService.retrieve({
-        lookup: experiment.executionId,
+      commit("updateNodeCount", {
+        nodeCount: getters.getDefaultNodeCount(queue),
       });
-      commit("setApplicationModuleId", {
-        applicationModuleId: appInterface.applicationModuleId,
+      commit("updateWallTimeLimit", {
+        wallTimeLimit: getters.getDefaultWalltime(queue),
       });
-      await dispatch("setExperiment", { experiment });
-    },
-    async setExperiment({ commit, dispatch }, { experiment }) {
-      commit("setExperiment", { experiment });
-      await dispatch("loadExperimentData");
-    },
-    async loadExperimentData({ commit, dispatch, getters, state }) {
-      await Promise.all([
-        dispatch("loadProjects"),
-        dispatch("loadWorkspacePreferences"),
-        dispatch("loadGroupResourceProfiles"),
-      ]);
-
-      if (!state.experiment.projectId) {
-        commit("updateProjectId", {
-          projectId: state.workspacePreferences.most_recent_project_id,
-        });
-      }
-      // 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
-      let groupResourceProfileId =
-        state.experiment.userConfigurationData.groupResourceProfileId;
-      if (
-        !groupResourceProfileId ||
-        !getters.findGroupResourceProfile(groupResourceProfileId)
-      ) {
-        commit("updateGroupResourceProfileId", {
-          groupResourceProfileId:
-            state.workspacePreferences.most_recent_group_resource_profile_id,
-        });
-      }
-      groupResourceProfileId =
-        state.experiment.userConfigurationData.groupResourceProfileId;
-      // 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.findGroupResourceProfile(groupResourceProfileId)) {
-        await dispatch("loadApplicationDeployments");
-        await dispatch("loadAppDeploymentQueues");
-        await dispatch("applyGroupResourceProfile");
-      }
-    },
-    updateExperimentName({ commit }, { name }) {
-      commit("updateExperimentName", { name });
-    },
-    updateExperimentInputValue({ commit }, { inputName, value }) {
-      commit("updateExperimentInputValue", { inputName, value });
-    },
-    updateProjectId({ commit }, { projectId }) {
-      commit("updateProjectId", { projectId });
-    },
-    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("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;
+      const totalCPUCount = Math.min(
+        crs.totalCPUCount,
+        getters.batchQueueResourcePolicy.maxAllowedCores
+      );
+      if (totalCPUCount !== crs.totalCPUCount) {
         commit("updateTotalCPUCount", {
-          totalCPUCount: getters.getDefaultCPUCount(queue),
-        });
-        commit("updateNodeCount", {
-          nodeCount: getters.getDefaultNodeCount(queue),
-        });
-        commit("updateWallTimeLimit", {
-          wallTimeLimit: getters.getDefaultWalltime(queue),
+          totalCPUCount,
         });
-        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
-          ),
-        });
+      const nodeCount = Math.min(
+        crs.nodeCount,
+        getters.batchQueueResourcePolicy.maxAllowedNodes
+      );
+      if (nodeCount !== crs.nodeCount) {
         commit("updateNodeCount", {
-          nodeCount: Math.min(
-            crs.nodeCount,
-            getters.batchQueueResourcePolicy.maxAllowedNodes
-          ),
-        });
-        commit("updateWallTimeLimit", {
-          wallTimeLimit: Math.min(
-            crs.wallTimeLimit,
-            getters.batchQueueResourcePolicy.maxAllowedWalltime
-          ),
+          nodeCount,
         });
       }
-    },
-    async saveExperiment({ commit, getters }) {
-      if (getters.experiment.experimentId) {
-        const experiment = await services.ExperimentService.update({
-          data: getters.experiment,
-          lookup: getters.experiment.experimentId,
-        });
-        commit("setExperiment", { experiment });
-      } else {
-        const experiment = await services.ExperimentService.create({
-          data: getters.experiment,
+      const wallTimeLimit = Math.min(
+        crs.wallTimeLimit,
+        getters.batchQueueResourcePolicy.maxAllowedWalltime
+      );
+      if (wallTimeLimit !== crs.wallTimeLimit) {
+        commit("updateWallTimeLimit", {
+          wallTimeLimit,
         });
-        commit("setExperiment", { experiment });
       }
-    },
-    async launchExperiment({ getters }) {
-      await services.ExperimentService.launch({
+    }
+  },
+  async saveExperiment({ commit, getters }) {
+    if (getters.experiment.experimentId) {
+      const experiment = await services.ExperimentService.update({
+        data: getters.experiment,
         lookup: getters.experiment.experimentId,
       });
-    },
-    async loadProjects({ commit }) {
-      if (!PROMISES.projects) {
-        PROMISES.projects = services.ProjectService.listAll();
-      }
-      const projects = await PROMISES.projects;
-      commit("setProjects", { projects });
-    },
-    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) => {
-      if (!state.experiment) {
-        return null;
-      }
-      const experimentInputs = state.experiment.experimentInputs;
-      if (experimentInputs) {
-        for (const experimentInput of experimentInputs) {
-          if (experimentInput.name === name) {
-            return experimentInput;
-          }
+      commit("setExperiment", { experiment });
+    } else {
+      const experiment = await services.ExperimentService.create({
+        data: getters.experiment,
+      });
+      commit("setExperiment", { experiment });
+    }
+  },
+  async launchExperiment({ getters }) {
+    await services.ExperimentService.launch({
+      lookup: getters.experiment.experimentId,
+    });
+  },
+  async loadProjects({ commit }) {
+    if (!PROMISES.projects) {
+      PROMISES.projects = services.ProjectService.listAll();
+    }
+    const projects = await PROMISES.projects;
+    commit("setProjects", { projects });
+  },
+  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 });
+  },
+};
+
+export const getters = {
+  getExperimentInputByName: (state) => (name) => {
+    if (!state.experiment) {
+      return null;
+    }
+    const experimentInputs = state.experiment.experimentInputs;
+    if (experimentInputs) {
+      for (const experimentInput of experimentInputs) {
+        if (experimentInput.name === name) {
+          return experimentInput;
         }
       }
+    }
+    return null;
+  },
+  experiment: (state) => state.experiment,
+  projects: (state) => state.projects,
+  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,
+  findGroupResourceProfile: (state) => (groupResourceProfileId) =>
+    state.groupResourceProfiles
+      ? state.groupResourceProfiles.find(
+          (g) => g.groupResourceProfileId === groupResourceProfileId
+        )
+      : null,
+  groupResourceProfile: (state, getters) =>
+    getters.findGroupResourceProfile(getters.groupResourceProfileId),
+  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;
-    },
-    experiment: (state) => state.experiment,
-    projects: (state) => state.projects,
-    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,
-    findGroupResourceProfile: (state) => (groupResourceProfileId) =>
-      state.groupResourceProfiles
-        ? state.groupResourceProfiles.find(
-            (g) => g.groupResourceProfileId === groupResourceProfileId
-          )
-        : null,
-    groupResourceProfile: (state, getters) =>
-      getters.findGroupResourceProfile(getters.groupResourceProfileId),
-    resourceHostId: (state) =>
-      state.experiment &&
+    }
+  },
+  queueName: (state) => {
+    return 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
+      ? state.experiment.userConfigurationData.computationalResourceScheduling
+          .queueName
+      : null;
+  },
+  totalCPUCount: (state) => {
+    return state.experiment &&
+      state.experiment.userConfigurationData &&
+      state.experiment.userConfigurationData.computationalResourceScheduling
+      ? state.experiment.userConfigurationData.computationalResourceScheduling
+          .totalCPUCount
+      : null;
+  },
+  nodeCount: (state) => {
+    return state.experiment &&
+      state.experiment.userConfigurationData &&
+      state.experiment.userConfigurationData.computationalResourceScheduling
+      ? state.experiment.userConfigurationData.computationalResourceScheduling
+          .nodeCount
+      : null;
+  },
+  wallTimeLimit: (state) => {
+    return state.experiment &&
+      state.experiment.userConfigurationData &&
+      state.experiment.userConfigurationData.computationalResourceScheduling
+      ? state.experiment.userConfigurationData.computationalResourceScheduling
+          .wallTimeLimit
+      : null;
+  },
+  totalPhysicalMemory: (state) => {
+    return state.experiment &&
+      state.experiment.userConfigurationData &&
+      state.experiment.userConfigurationData.computationalResourceScheduling
+      ? state.experiment.userConfigurationData.computationalResourceScheduling
+          .totalPhysicalMemory
+      : 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
       );
-    },
-    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;
-    },
-    totalCPUCount: (state) => {
-      return state.experiment &&
-        state.experiment.userConfigurationData &&
-        state.experiment.userConfigurationData.computationalResourceScheduling
-        ? state.experiment.userConfigurationData.computationalResourceScheduling
-            .totalCPUCount
-        : null;
-    },
-    nodeCount: (state) => {
-      return state.experiment &&
-        state.experiment.userConfigurationData &&
-        state.experiment.userConfigurationData.computationalResourceScheduling
-        ? state.experiment.userConfigurationData.computationalResourceScheduling
-            .nodeCount
-        : null;
-    },
-    wallTimeLimit: (state) => {
-      return state.experiment &&
-        state.experiment.userConfigurationData &&
-        state.experiment.userConfigurationData.computationalResourceScheduling
-        ? state.experiment.userConfigurationData.computationalResourceScheduling
-            .wallTimeLimit
-        : null;
-    },
-    totalPhysicalMemory: (state) => {
-      return state.experiment &&
-        state.experiment.userConfigurationData &&
-        state.experiment.userConfigurationData.computationalResourceScheduling
-        ? state.experiment.userConfigurationData.computationalResourceScheduling
-            .totalPhysicalMemory
-        : 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
+    }
+    return queue.defaultCPUCount;
+  },
+  getDefaultNodeCount: (state, getters) => (queue) => {
+    const batchQueueResourcePolicy = getters.batchQueueResourcePolicy;
+    if (batchQueueResourcePolicy) {
+      return Math.min(
+        batchQueueResourcePolicy.maxAllowedNodes,
+        queue.defaultNodeCount
       );
-    },
-    batchQueueResourcePolicies: (state, getters) => {
-      if (!getters.groupResourceProfile || !getters.resourceHostId) {
-        return null;
-      }
-      return getters.groupResourceProfile.batchQueueResourcePolicies.filter(
-        (bqrp) => bqrp.computeResourceId === getters.resourceHostId
+    }
+    return queue.defaultNodeCount;
+  },
+  getDefaultWalltime: (state, getters) => (queue) => {
+    const batchQueueResourcePolicy = getters.batchQueueResourcePolicy;
+    if (batchQueueResourcePolicy) {
+      return Math.min(
+        batchQueueResourcePolicy.maxAllowedWalltime,
+        queue.defaultWalltime
       );
-    },
-    batchQueueResourcePolicy: (state, getters) => {
-      if (!getters.batchQueueResourcePolicies || !getters.queueName) {
-        return null;
-      }
-      return getters.batchQueueResourcePolicies.find(
-        (bqrp) => bqrp.queuename === getters.queueName
+    }
+    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
       );
-    },
-    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;
-    },
+    }
+    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;
+  },
+};
+
+export default new Vuex.Store({
+  strict: process.env.NODE_ENV !== "production",
+  state: {
+    experiment: null,
+    projects: null,
+    computeResourceNames: {},
+    applicationDeployments: [],
+    groupResourceProfiles: null,
+    applicationModuleId: null,
+    appDeploymentQueues: [],
   },
+  mutations,
+  actions,
+  getters,
 });
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/web-components/store.spec.js b/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/web-components/store.spec.js
new file mode 100644
index 0000000..8bc1606
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/web-components/store.spec.js
@@ -0,0 +1,185 @@
+import { actions, mutations } from "@/web-components/store.js";
+import { models } from "django-airavata-api";
+
+/*
+ * Test MUTATIONS
+ */
+test("setExperiment sets state", () => {
+  const state = {};
+  const experiment = {};
+  mutations.setExperiment(state, { experiment });
+  expect(state.experiment).toBe(experiment);
+});
+
+/*
+ * Test ACTIONS
+ */
+const testAction = (
+  action,
+  payload,
+  state,
+  getters,
+  expectedMutations,
+  done
+) => {
+  let count = 0;
+
+  // mock commit
+  const commit = (type, payload) => {
+    const mutation = expectedMutations[count] || {};
+    try {
+      expect(type).toEqual(mutation.type);
+      expect(payload).toEqual(mutation.payload);
+    } catch (error) {
+      done(error);
+    }
+
+    count++;
+    if (count >= expectedMutations.length) {
+      done();
+    }
+  };
+
+  // call the action with mocked store and arguments
+  action({ commit, state, getters }, payload);
+
+  // check if no mutations should have been dispatched
+  if (expectedMutations.length === 0) {
+    expect(count).toEqual(0);
+    done();
+  }
+};
+
+const testApplyBatchQueueResourcePolicy = ({
+  computationalResourceScheduling,
+  batchQueueResourcePolicy,
+  expectedMutations,
+  done,
+}) => {
+  const state = {};
+  state.experiment = new models.Experiment();
+  state.experiment.userConfigurationData.computationalResourceScheduling = new models.ComputationalResourceSchedulingModel(
+    computationalResourceScheduling
+  );
+  const bqrp = new models.BatchQueueResourcePolicy(batchQueueResourcePolicy);
+
+  const getters = {
+    experiment: state.experiment,
+    batchQueueResourcePolicy: bqrp,
+  };
+  testAction(
+    actions.applyBatchQueueResourcePolicy,
+    null,
+    state,
+    getters,
+    expectedMutations,
+    done
+  );
+};
+
+test("applyBatchQueueResourcePolicy: maxAllowedCores caps totalCPUCount", (done) => {
+  testApplyBatchQueueResourcePolicy({
+    computationalResourceScheduling: {
+      totalCPUCount: 10,
+      nodeCount: 1,
+      wallTimeLimit: 30,
+    },
+    batchQueueResourcePolicy: {
+      maxAllowedCores: 5,
+      maxAllowedNodes: 2,
+      maxAllowedWalltime: 120,
+    },
+    expectedMutations: [
+      { type: "updateTotalCPUCount", payload: { totalCPUCount: 5 } },
+    ],
+    done,
+  });
+});
+
+test("applyBatchQueueResourcePolicy: maxAllowedCores doesn't affect totalCPUCount", (done) => {
+  testApplyBatchQueueResourcePolicy({
+    computationalResourceScheduling: {
+      totalCPUCount: 10,
+      nodeCount: 1,
+      wallTimeLimit: 30,
+    },
+    batchQueueResourcePolicy: {
+      maxAllowedCores: 50,
+      maxAllowedNodes: 2,
+      maxAllowedWalltime: 120,
+    },
+    expectedMutations: [],
+    done,
+  });
+});
+
+test("applyBatchQueueResourcePolicy: maxAllowedNodes caps nodeCount", (done) => {
+  testApplyBatchQueueResourcePolicy({
+    computationalResourceScheduling: {
+      totalCPUCount: 10,
+      nodeCount: 11,
+      wallTimeLimit: 30,
+    },
+    batchQueueResourcePolicy: {
+      maxAllowedCores: 50,
+      maxAllowedNodes: 4,
+      maxAllowedWalltime: 120,
+    },
+    expectedMutations: [{ type: "updateNodeCount", payload: { nodeCount: 4 } }],
+    done,
+  });
+});
+
+test("applyBatchQueueResourcePolicy: maxAllowedNodes doesn't affect nodeCount", (done) => {
+  testApplyBatchQueueResourcePolicy({
+    computationalResourceScheduling: {
+      totalCPUCount: 10,
+      nodeCount: 7,
+      wallTimeLimit: 30,
+    },
+    batchQueueResourcePolicy: {
+      maxAllowedCores: 50,
+      maxAllowedNodes: 52,
+      maxAllowedWalltime: 120,
+    },
+    expectedMutations: [],
+    done,
+  });
+});
+
+test("applyBatchQueueResourcePolicy: maxAllowedWalltime caps wallTimeLimit", (done) => {
+  testApplyBatchQueueResourcePolicy({
+    computationalResourceScheduling: {
+      totalCPUCount: 10,
+      nodeCount: 1,
+      wallTimeLimit: 8,
+    },
+    batchQueueResourcePolicy: {
+      maxAllowedCores: 50,
+      maxAllowedNodes: 10,
+      maxAllowedWalltime: 6,
+    },
+    expectedMutations: [
+      { type: "updateWallTimeLimit", payload: { wallTimeLimit: 6} },
+    ],
+    done,
+  });
+});
+
+test("applyBatchQueueResourcePolicy: maxAllowedWalltime doesn't affect wallTimeLimit", (done) => {
+  testApplyBatchQueueResourcePolicy({
+    computationalResourceScheduling: {
+      totalCPUCount: 10,
+      nodeCount: 1,
+      wallTimeLimit: 30,
+    },
+    batchQueueResourcePolicy: {
+      maxAllowedCores: 24,
+      maxAllowedNodes: 2,
+      maxAllowedWalltime: 120,
+    },
+    expectedMutations: [
+    ],
+    done,
+  });
+});

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

Posted by ma...@apache.org.
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;
+    },
   },
 });

[airavata-django-portal] 08/24: AIRAVATA-3477 Cleaning up after Vuex refactor

Posted by ma...@apache.org.
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 bdf764c95f283caa69b55641ea56c75469a0b96c
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Sep 1 13:36:20 2021 -0400

    AIRAVATA-3477 Cleaning up after Vuex refactor
---
 .../js/web-components/ComputeResourceSelector.vue  |   4 +-
 .../ExperimentComputeResourceSelector.vue          |   4 +-
 .../js/web-components/ExperimentEditor.vue         |   4 +-
 .../GroupResourceProfileSelector.vue               |   4 +-
 .../js/web-components/ProjectSelector.vue          |   4 +-
 .../js/web-components/QueueSettingsEditor.vue      |   4 +-
 .../js/web-components/ResourceSelectionEditor.vue  | 426 ------------
 .../input-editors/StringInputEditor.vue            |   4 +-
 .../js/web-components/store.js                     | 733 +++++++++++++++++----
 .../js/web-components/vuestore.js                  | 617 -----------------
 10 files changed, 625 insertions(+), 1179 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 c438aa3..463d6f8 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
@@ -16,7 +16,7 @@
 </template>
 
 <script>
-import vuestore from "./vuestore";
+import store from "./store";
 import { mapGetters } from "vuex";
 
 export default {
@@ -32,7 +32,7 @@ export default {
       default: null,
     },
   },
-  store: vuestore,
+  store: store,
   data() {
     return {
       resourceHostId: this.value,
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
index 715f63e..fe17520 100644
--- 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
@@ -7,13 +7,13 @@
 </template>
 
 <script>
-import vuestore from "./vuestore";
+import store from "./store";
 import { mapGetters } from "vuex";
 import ComputeResourceSelector from "./ComputeResourceSelector.vue";
 
 export default {
   name: "experiment-compute-resource-selector",
-  store: vuestore,
+  store: store,
   components: {
     ComputeResourceSelector,
   },
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 6bb75d0..339e02e 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
@@ -38,7 +38,7 @@
 
 <script>
 import Vue from "vue";
-import vuestore from "./vuestore";
+import store from "./store";
 import { mapGetters } from "vuex";
 import { BootstrapVue } from "bootstrap-vue";
 import urls from "../utils/urls";
@@ -56,7 +56,7 @@ export default {
       required: false,
     },
   },
-  store: vuestore,
+  store: store,
   async created() {},
   async mounted() {
     if (this.experimentId) {
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 7be711f..2858629 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
@@ -19,7 +19,7 @@
 <script>
 import Vue from "vue";
 import { BootstrapVue } from "bootstrap-vue";
-import vuestore from "./vuestore";
+import store from "./store";
 import { mapGetters } from "vuex";
 Vue.use(BootstrapVue);
 
@@ -35,7 +35,7 @@ export default {
       default: "Allocation",
     },
   },
-  store: vuestore,
+  store: store,
   data() {
     return {
       groupResourceProfileId: this.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 dc4bddf..797f4e3 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
@@ -28,7 +28,7 @@
 
 <script>
 import Vue from "vue";
-import vuestore from "./vuestore";
+import store from "./store";
 import { mapGetters } from "vuex";
 import { BootstrapVue } from "bootstrap-vue";
 Vue.use(BootstrapVue);
@@ -40,7 +40,7 @@ export default {
       default: null,
     },
   },
-  store: vuestore,
+  store: store,
   data() {
     return {
       projectId: this.value,
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 5bb644a..09f8bd6 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
@@ -128,7 +128,7 @@
 <script>
 import { utils } from "django-airavata-api";
 import Vue from "vue";
-import vuestore from "./vuestore";
+import store from "./store";
 import { mapGetters } from "vuex";
 import { BootstrapVue } from "bootstrap-vue";
 Vue.use(BootstrapVue);
@@ -144,7 +144,7 @@ export default {
   components: {
     FontAwesomeIcon,
   },
-  store: vuestore,
+  store: store,
   mounted() {
     // Add font awesome styles
     // https://github.com/FortAwesome/vue-fontawesome#web-components-with-vue-web-component-wrapper
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
deleted file mode 100644
index 9557db9..0000000
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ResourceSelectionEditor.vue
+++ /dev/null
@@ -1,426 +0,0 @@
-<template>
-<!-- TODO: this is no longer needed -->
-  <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
-        ref="queueSettingsEditor"
-        slot="resource-selection-queue-settings"
-        :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 {
-  getAppDeploymentQueues,
-  getApplicationDeployments,
-  getDefaultComputeResourceId,
-  getDefaultGroupResourceProfileId,
-  getGroupResourceProfile,
-} 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: [],
-      appDeploymentQueues: [],
-      groupResourceProfile: 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;
-    },
-    applicationDeployment() {
-      if (this.applicationDeployments && this.resourceHostId) {
-        return this.applicationDeployments.find(
-          (ad) => ad.computeHostId === this.resourceHostId
-        );
-      } else {
-        return null;
-      }
-    },
-    computeResourcePolicy() {
-      if (!this.groupResourceProfile || !this.resourceHostId) {
-        return null;
-      }
-      return this.groupResourceProfile.computeResourcePolicies.find(
-        (crp) => crp.computeResourceId === this.resourceHostId
-      );
-    },
-    batchQueueResourcePolicies: function () {
-      if (!this.groupResourceProfile || !this.resourceHostId) {
-        return null;
-      }
-      return this.groupResourceProfile.batchQueueResourcePolicies.filter(
-        (bqrp) => bqrp.computeResourceId === this.resourceHostId
-      );
-    },
-    batchQueueResourcePolicy() {
-      if (!this.batchQueueResourcePolicies || !this.queueName) {
-        return null;
-      }
-      return this.batchQueueResourcePolicies.find(
-        (bqrp) => bqrp.queuename === this.queueName
-      );
-    },
-    queueName() {
-      return this.userConfigurationData &&
-        this.userConfigurationData.computationalResourceScheduling
-        ? this.userConfigurationData.computationalResourceScheduling.queueName
-        : null;
-    },
-    queues() {
-      return this.appDeploymentQueues
-        ? this.appDeploymentQueues.filter((q) =>
-            this.isQueueInComputeResourcePolicy(q.queueName)
-          )
-        : [];
-    },
-    queue() {
-      return this.queues && this.queueName
-        ? this.queues.find((q) => q.queueName === this.queueName)
-        : null;
-    },
-    maxAllowedCores: function () {
-      if (!this.queue) {
-        return 0;
-      }
-      const batchQueueResourcePolicy = this.batchQueueResourcePolicy;
-      if (batchQueueResourcePolicy) {
-        return Math.min(
-          batchQueueResourcePolicy.maxAllowedCores,
-          this.queue.maxProcessors
-        );
-      }
-      return this.queue.maxProcessors;
-    },
-    maxAllowedNodes: function () {
-      if (!this.queue) {
-        return 0;
-      }
-      const batchQueueResourcePolicy = this.batchQueueResourcePolicy;
-      if (batchQueueResourcePolicy) {
-        return Math.min(
-          batchQueueResourcePolicy.maxAllowedNodes,
-          this.queue.maxNodes
-        );
-      }
-      return this.queue.maxNodes;
-    },
-    maxAllowedWalltime: function () {
-      if (!this.queue) {
-        return 0;
-      }
-      const batchQueueResourcePolicy = this.batchQueueResourcePolicy;
-      if (batchQueueResourcePolicy) {
-        return Math.min(
-          batchQueueResourcePolicy.maxAllowedWalltime,
-          this.queue.maxRunTime
-        );
-      }
-      return this.queue.maxRunTime;
-    },
-    maxMemory() {
-      return this.queue ? this.queue.maxMemory : 0;
-    },
-  },
-  methods: {
-    emitValueChanged: function () {
-      const inputEvent = new CustomEvent("input", {
-        detail: [this.userConfigurationData.clone()],
-        composed: true,
-        bubbles: true,
-      });
-      this.$el.dispatchEvent(inputEvent);
-    },
-    async updateGroupResourceProfileId(event) {
-      const [groupResourceProfileId] = event.detail;
-      this.userConfigurationData.groupResourceProfileId = groupResourceProfileId;
-      await this.loadGroupResourceProfile();
-      await this.loadApplicationDeployments();
-      await this.applyGroupResourceProfile();
-      this.emitValueChanged();
-    },
-    async updateComputeResourceHostId(event) {
-      const [computeResourceHostId] = event.detail;
-      if (
-        this.userConfigurationData.computationalResourceScheduling
-          .resourceHostId !== computeResourceHostId
-      ) {
-        this.userConfigurationData.computationalResourceScheduling.resourceHostId = computeResourceHostId;
-        await this.loadAppDeploymentQueues();
-        this.setDefaultQueue();
-        this.emitValueChanged();
-      }
-    },
-    updateComputationalResourceScheduling(event) {
-      const [computationalResourceScheduling] = event.detail;
-      const queueChanged =
-        this.queueName !== computationalResourceScheduling.queueName;
-      this.userConfigurationData.computationalResourceScheduling = computationalResourceScheduling;
-      if (queueChanged) {
-        this.initializeQueue();
-      }
-      this.emitValueChanged();
-    },
-    async loadApplicationDeployments() {
-      this.applicationDeployments = await getApplicationDeployments(
-        this.applicationModuleId,
-        this.groupResourceProfileId
-      );
-    },
-    async initializeGroupResourceProfileId() {
-      this.userConfigurationData.groupResourceProfileId = await getDefaultGroupResourceProfileId();
-    },
-    async applyGroupResourceProfile() {
-      // Make sure that resource host id is in the list of app deployments
-      const computeResourceChanged = await this.initializeResourceHostId();
-      if (computeResourceChanged) {
-        await this.loadAppDeploymentQueues();
-        this.setDefaultQueue();
-      } else if (!this.queue) {
-        // allowed queues may have changed. If selected queue isn't in the list
-        // of allowed queues, reset to the default
-        this.setDefaultQueue();
-      } else {
-        // reapply batchQueueResourcePolicy maximums since they may have changed
-        this.applyBatchQueueResourcePolicy();
-      }
-    },
-    async initializeResourceHostId() {
-      // 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 (
-        !this.resourceHostId ||
-        !this.computeResources.find((crid) => crid === this.resourceHostId)
-      ) {
-        this.userConfigurationData.computationalResourceScheduling.resourceHostId = await this.getDefaultResourceHostId();
-        return true;
-      }
-      return false;
-    },
-    async loadAppDeploymentQueues() {
-      const applicationDeployment = this.applicationDeployment;
-      if (applicationDeployment) {
-        this.appDeploymentQueues = await getAppDeploymentQueues(
-          applicationDeployment.appDeploymentId
-        );
-      } else {
-        this.appDeploymentQueues = [];
-      }
-    },
-    setDefaultQueue() {
-      // set to the default queue or the first one
-      const defaultQueue = this.getDefaultQueue();
-      if (defaultQueue) {
-        this.userConfigurationData.computationalResourceScheduling.queueName =
-          defaultQueue.queueName;
-      } else {
-        this.userConfigurationData.computationalResourceScheduling.queueName = null;
-      }
-      this.initializeQueue();
-    },
-    isQueueInComputeResourcePolicy: function (queueName) {
-      if (!this.computeResourcePolicy) {
-        return true;
-      }
-      return this.computeResourcePolicy.allowedBatchQueues.includes(queueName);
-    },
-    initializeQueue() {
-      const queue = this.queue;
-      if (queue) {
-        const crs = this.userConfigurationData.computationalResourceScheduling;
-        crs.queueName = queue.queueName;
-        crs.totalCPUCount = this.getDefaultCPUCount(queue);
-        crs.nodeCount = this.getDefaultNodeCount(queue);
-        crs.wallTimeLimit = this.getDefaultWalltime(queue);
-        crs.totalPhysicalMemory = 0;
-      } else {
-        const crs = this.userConfigurationData.computationalResourceScheduling;
-        crs.queueName = null;
-        crs.totalCPUCount = 0;
-        crs.nodeCount = 0;
-        crs.wallTimeLimit = 0;
-        crs.totalPhysicalMemory = 0;
-      }
-    },
-    getDefaultQueue() {
-      const defaultQueue = this.queues.find((q) => q.isDefaultQueue);
-      if (defaultQueue) {
-        return defaultQueue;
-      } else if (this.queues.length > 0) {
-        return this.queues[0];
-      } else {
-        return null;
-      }
-    },
-    getDefaultCPUCount(queue) {
-      const batchQueueResourcePolicy = this.batchQueueResourcePolicy;
-      if (batchQueueResourcePolicy) {
-        return Math.min(
-          batchQueueResourcePolicy.maxAllowedCores,
-          queue.defaultCPUCount
-        );
-      }
-      return queue.defaultCPUCount;
-    },
-    getDefaultNodeCount(queue) {
-      const batchQueueResourcePolicy = this.batchQueueResourcePolicy;
-      if (batchQueueResourcePolicy) {
-        return Math.min(
-          batchQueueResourcePolicy.maxAllowedNodes,
-          queue.defaultNodeCount
-        );
-      }
-      return queue.defaultNodeCount;
-    },
-    getDefaultWalltime(queue) {
-      const batchQueueResourcePolicy = this.batchQueueResourcePolicy;
-      if (batchQueueResourcePolicy) {
-        return Math.min(
-          batchQueueResourcePolicy.maxAllowedWalltime,
-          queue.defaultWalltime
-        );
-      }
-      return queue.defaultWalltime;
-    },
-    applyBatchQueueResourcePolicy() {
-      if (this.batchQueueResourcePolicy) {
-        const crs = this.userConfigurationData.computationalResourceScheduling;
-        crs.totalCPUCount = Math.min(
-          crs.totalCPUCount,
-          this.batchQueueResourcePolicy.maxAllowedCores
-        );
-        crs.nodeCount = Math.min(
-          crs.nodeCount,
-          this.batchQueueResourcePolicy.maxAllowedNodes
-        );
-        crs.wallTimeLimit = Math.min(
-          crs.wallTimeLimit,
-          this.batchQueueResourcePolicy.maxAllowedWalltime
-        );
-      }
-    },
-    cloneValue() {
-      return this.value ? this.value.clone() : null;
-    },
-    async loadData() {
-      if (this.groupResourceProfileId) {
-        let groupResourceProfile = await this.loadGroupResourceProfile();
-        // handle user no longer has access to GRP
-        if (!groupResourceProfile) {
-          await this.initializeGroupResourceProfileId();
-          groupResourceProfile = await this.loadGroupResourceProfile();
-        }
-        if (groupResourceProfile) {
-          await this.loadApplicationDeployments();
-          await this.loadAppDeploymentQueues();
-          await this.applyGroupResourceProfile();
-          // If existing values are no longer selectable, the userConfigurationData
-          // may have changed
-          this.emitValueChanged();
-        }
-      } else {
-        await this.initializeGroupResourceProfileId();
-        if (this.groupResourceProfileId) {
-          await this.loadGroupResourceProfile();
-          await this.loadApplicationDeployments();
-          await this.applyGroupResourceProfile();
-          this.emitValueChanged();
-        }
-      }
-    },
-    async loadGroupResourceProfile() {
-      this.groupResourceProfile = await getGroupResourceProfile(
-        this.groupResourceProfileId
-      );
-      return this.groupResourceProfile;
-    },
-    async getDefaultResourceHostId() {
-      const defaultComputeResourceId = await getDefaultComputeResourceId();
-      if (
-        defaultComputeResourceId &&
-        this.computeResources.find((crid) => crid === defaultComputeResourceId)
-      ) {
-        return defaultComputeResourceId;
-      } else if (this.computeResources.length > 0) {
-        // Just pick the first one
-        return this.computeResources[0];
-      } else {
-        return null;
-      }
-    },
-    bindWebComponentProps() {
-      this.$nextTick(() => {
-        this.$refs.computeResourceSelector.value = this.resourceHostId;
-        this.$refs.queueSettingsEditor.value = this.userConfigurationData.computationalResourceScheduling;
-        this.$refs.queueSettingsEditor.queues = this.queues;
-      });
-    },
-  },
-  watch: {
-    value() {
-      this.userConfigurationData = this.cloneValue();
-      this.loadData();
-    },
-    computeResources: "bindWebComponentProps",
-    resourceHostId: "bindWebComponentProps",
-    "userConfigurationData.computationalResourceScheduling":
-      "bindWebComponentProps",
-    queues: "bindWebComponentProps",
-  },
-};
-</script>
-
-<style>
-@import "./styles.css";
-
-:host {
-  display: block;
-  margin-bottom: 1rem;
-}
-</style>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
index 8f8cfd4..bed4a8c 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
@@ -19,7 +19,7 @@ import Vue from "vue";
 import { BootstrapVue } from "bootstrap-vue";
 import AsyncComputed from "vue-async-computed";
 import { utils } from "django-airavata-common-ui";
-import vuestore from "../vuestore";
+import store from "../store";
 Vue.use(BootstrapVue);
 Vue.use(AsyncComputed);
 
@@ -32,7 +32,7 @@ export default {
   components: {
     StringInputEditor,
   },
-  store: vuestore,
+  store: store,
   mounted() {
     this.$nextTick(() => {
       // Stop wrapped input editor 'input' event from bubbling up so it doesn't
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 1f54748..a2128f5 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,128 +1,617 @@
-// TODO: this is no longer needed
 import { errors, services, utils } from "django-airavata-api";
-const CACHE = {
-  APPLICATION_MODULES: {},
-  APPLICATION_INTERFACES: {},
-  WORKSPACE_PREFERENCES: null,
-};
-export async function getApplicationModule(applicationId) {
-  if (!(applicationId in CACHE.APPLICATION_MODULES)) {
-    const promise = services.ApplicationModuleService.retrieve({
-      lookup: applicationId,
-    });
-    CACHE.APPLICATION_MODULES[applicationId] = promise;
-  }
-  return await CACHE.APPLICATION_MODULES[applicationId];
-}
-
-export async function getApplicationInterfaceForModule(applicationId) {
-  if (!(applicationId in CACHE.APPLICATION_INTERFACES)) {
-    const promise = services.ApplicationModuleService.getApplicationInterface({
-      lookup: applicationId,
-    });
-    CACHE.APPLICATION_INTERFACES[applicationId] = promise;
-  }
-  return await CACHE.APPLICATION_INTERFACES[applicationId];
-}
-
-export async function saveExperiment(experiment) {
-  if (experiment.experimentId) {
-    await services.ExperimentService.update({
-      data: experiment,
-      lookup: experiment.experimentId,
-    });
-  } else {
-    return await services.ExperimentService.create({ data: experiment });
-  }
-}
-
-export async function launchExperiment(experimentId) {
-  return await services.ExperimentService.launch({
-    lookup: experimentId,
-  });
-}
-
-export async function getWorkspacePreferences() {
-  if (!CACHE.WORKSPACE_PREFERENCES) {
-    CACHE.WORKSPACE_PREFERENCES = services.WorkspacePreferencesService.get();
-  }
-  return await CACHE.WORKSPACE_PREFERENCES;
-}
-
-export async function getDefaultProjectId() {
-  const prefs = await getWorkspacePreferences();
-  return prefs.most_recent_project_id;
-}
-
-export async function getDefaultGroupResourceProfileId() {
-  const prefs = await getWorkspacePreferences();
-  return prefs.most_recent_group_resource_profile_id;
-}
-
-export async function getDefaultComputeResourceId() {
-  const prefs = await getWorkspacePreferences();
-  return prefs.most_recent_compute_resource_id;
-}
+import Vue from "vue";
+import Vuex from "vuex";
 
-export async function getExperiment(experimentId) {
-  return await services.ExperimentService.retrieve({ lookup: experimentId });
-}
+Vue.use(Vuex);
 
-export async function getProjects() {
-  return await services.ProjectService.listAll();
-}
-
-export async function getGroupResourceProfiles() {
-  return await services.GroupResourceProfileService.list();
-}
+const PROMISES = {
+  workspacePreferences: null,
+};
+export default new Vuex.Store({
+  strict: process.env.NODE_ENV !== "production",
+  state: {
+    experiment: null,
+    projects: null,
+    computeResourceNames: {},
+    applicationDeployments: [],
+    groupResourceProfiles: null,
+    applicationModuleId: null,
+    appDeploymentQueues: [],
+  },
+  mutations: {
+    setExperiment(state, { experiment }) {
+      state.experiment = experiment;
+    },
+    updateExperimentName(state, { name }) {
+      state.experiment.experimentName = name;
+    },
+    updateExperimentInputValue(state, { inputName, value }) {
+      const experimentInput = state.experiment.experimentInputs.find(
+        (i) => i.name === inputName
+      );
+      experimentInput.value = value;
+    },
+    updateProjectId(state, { projectId }) {
+      state.experiment.projectId = projectId;
+    },
+    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;
+    },
+    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, dispatch }, { applicationId }) {
+      const applicationModule = await services.ApplicationModuleService.retrieve(
+        {
+          lookup: applicationId,
+        }
+      );
+      const appInterface = await services.ApplicationModuleService.getApplicationInterface(
+        {
+          lookup: applicationId,
+        }
+      );
+      const experiment = appInterface.createExperiment();
+      const currentDate = new Date().toLocaleString([], {
+        dateStyle: "medium",
+        timeStyle: "short",
+      });
+      experiment.experimentName = `${applicationModule.appModuleName} on ${currentDate}`;
+      commit("setApplicationModuleId", { applicationModuleId: applicationId });
+      await dispatch("setExperiment", { experiment });
+    },
+    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, state }) {
+      await Promise.all([
+        dispatch("loadProjects"),
+        dispatch("loadWorkspacePreferences"),
+        dispatch("loadGroupResourceProfiles"),
+      ]);
 
-export async function getGroupResourceProfile(groupResourceProfileId) {
-  return await services.GroupResourceProfileService.retrieve(
-    {
-      lookup: groupResourceProfileId,
-    },
-    { ignoreErrors: true }
-  )
-    .catch((error) => {
-      // Ignore unauthorized errors, force user to pick a different GroupResourceProfile
-      if (!errors.ErrorUtils.isUnauthorizedError(error)) {
-        return Promise.reject(error);
+      if (!state.experiment.projectId) {
+        commit("updateProjectId", {
+          projectId: state.workspacePreferences.most_recent_project_id,
+        });
+      }
+      // 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
+      let groupResourceProfileId =
+        state.experiment.userConfigurationData.groupResourceProfileId;
+      if (
+        !groupResourceProfileId ||
+        !getters.findGroupResourceProfile(groupResourceProfileId)
+      ) {
+        commit("updateGroupResourceProfileId", {
+          groupResourceProfileId:
+            state.workspacePreferences.most_recent_group_resource_profile_id,
+        });
+      }
+      groupResourceProfileId =
+        state.experiment.userConfigurationData.groupResourceProfileId;
+      // 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.findGroupResourceProfile(groupResourceProfileId)) {
+        await dispatch("loadApplicationDeployments");
+        await dispatch("loadAppDeploymentQueues");
+        await dispatch("applyGroupResourceProfile");
+      }
+    },
+    updateExperimentName({ commit }, { name }) {
+      commit("updateExperimentName", { name });
+    },
+    updateExperimentInputValue({ commit }, { inputName, value }) {
+      commit("updateExperimentInputValue", { inputName, value });
+    },
+    updateProjectId({ commit }, { projectId }) {
+      commit("updateProjectId", { projectId });
+    },
+    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 {
-        return Promise.resolve(null);
+        // reapply batchQueueResourcePolicy maximums since they may have changed
+        dispatch("applyBatchQueueResourcePolicy");
       }
-    })
-    // Report all other error types
-    .catch(utils.FetchUtils.reportError);
-}
-
-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();
-}
-
-export async function getAppDeploymentQueues(appDeploymentId) {
-  return await services.ApplicationDeploymentService.getQueues({
-    lookup: appDeploymentId,
-  });
-}
+    },
+    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) {
+        const experiment = await services.ExperimentService.update({
+          data: getters.experiment,
+          lookup: getters.experiment.experimentId,
+        });
+        commit("setExperiment", { experiment });
+      } else {
+        const experiment = await services.ExperimentService.create({
+          data: getters.experiment,
+        });
+        commit("setExperiment", { experiment });
+      }
+    },
+    async launchExperiment({ getters }) {
+      await services.ExperimentService.launch({
+        lookup: getters.experiment.experimentId,
+      });
+    },
+    async loadProjects({ commit }) {
+      if (!PROMISES.projects) {
+        PROMISES.projects = services.ProjectService.listAll();
+      }
+      const projects = await PROMISES.projects;
+      commit("setProjects", { projects });
+    },
+    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) => {
+      if (!state.experiment) {
+        return null;
+      }
+      const experimentInputs = state.experiment.experimentInputs;
+      if (experimentInputs) {
+        for (const experimentInput of experimentInputs) {
+          if (experimentInput.name === name) {
+            return experimentInput;
+          }
+        }
+      }
+      return null;
+    },
+    experiment: (state) => state.experiment,
+    projects: (state) => state.projects,
+    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,
+    findGroupResourceProfile: (state) => (groupResourceProfileId) =>
+      state.groupResourceProfiles
+        ? state.groupResourceProfiles.find(
+            (g) => g.groupResourceProfileId === groupResourceProfileId
+          )
+        : null,
+    groupResourceProfile: (state, getters) =>
+      getters.findGroupResourceProfile(getters.groupResourceProfileId),
+    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;
+    },
+    totalCPUCount: (state) => {
+      return state.experiment &&
+        state.experiment.userConfigurationData &&
+        state.experiment.userConfigurationData.computationalResourceScheduling
+        ? state.experiment.userConfigurationData.computationalResourceScheduling
+            .totalCPUCount
+        : null;
+    },
+    nodeCount: (state) => {
+      return state.experiment &&
+        state.experiment.userConfigurationData &&
+        state.experiment.userConfigurationData.computationalResourceScheduling
+        ? state.experiment.userConfigurationData.computationalResourceScheduling
+            .nodeCount
+        : null;
+    },
+    wallTimeLimit: (state) => {
+      return state.experiment &&
+        state.experiment.userConfigurationData &&
+        state.experiment.userConfigurationData.computationalResourceScheduling
+        ? state.experiment.userConfigurationData.computationalResourceScheduling
+            .wallTimeLimit
+        : null;
+    },
+    totalPhysicalMemory: (state) => {
+      return state.experiment &&
+        state.experiment.userConfigurationData &&
+        state.experiment.userConfigurationData.computationalResourceScheduling
+        ? state.experiment.userConfigurationData.computationalResourceScheduling
+            .totalPhysicalMemory
+        : 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;
+    },
+  },
+});
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
deleted file mode 100644
index a2128f5..0000000
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/vuestore.js
+++ /dev/null
@@ -1,617 +0,0 @@
-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({
-  strict: process.env.NODE_ENV !== "production",
-  state: {
-    experiment: null,
-    projects: null,
-    computeResourceNames: {},
-    applicationDeployments: [],
-    groupResourceProfiles: null,
-    applicationModuleId: null,
-    appDeploymentQueues: [],
-  },
-  mutations: {
-    setExperiment(state, { experiment }) {
-      state.experiment = experiment;
-    },
-    updateExperimentName(state, { name }) {
-      state.experiment.experimentName = name;
-    },
-    updateExperimentInputValue(state, { inputName, value }) {
-      const experimentInput = state.experiment.experimentInputs.find(
-        (i) => i.name === inputName
-      );
-      experimentInput.value = value;
-    },
-    updateProjectId(state, { projectId }) {
-      state.experiment.projectId = projectId;
-    },
-    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;
-    },
-    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, dispatch }, { applicationId }) {
-      const applicationModule = await services.ApplicationModuleService.retrieve(
-        {
-          lookup: applicationId,
-        }
-      );
-      const appInterface = await services.ApplicationModuleService.getApplicationInterface(
-        {
-          lookup: applicationId,
-        }
-      );
-      const experiment = appInterface.createExperiment();
-      const currentDate = new Date().toLocaleString([], {
-        dateStyle: "medium",
-        timeStyle: "short",
-      });
-      experiment.experimentName = `${applicationModule.appModuleName} on ${currentDate}`;
-      commit("setApplicationModuleId", { applicationModuleId: applicationId });
-      await dispatch("setExperiment", { experiment });
-    },
-    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, state }) {
-      await Promise.all([
-        dispatch("loadProjects"),
-        dispatch("loadWorkspacePreferences"),
-        dispatch("loadGroupResourceProfiles"),
-      ]);
-
-      if (!state.experiment.projectId) {
-        commit("updateProjectId", {
-          projectId: state.workspacePreferences.most_recent_project_id,
-        });
-      }
-      // 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
-      let groupResourceProfileId =
-        state.experiment.userConfigurationData.groupResourceProfileId;
-      if (
-        !groupResourceProfileId ||
-        !getters.findGroupResourceProfile(groupResourceProfileId)
-      ) {
-        commit("updateGroupResourceProfileId", {
-          groupResourceProfileId:
-            state.workspacePreferences.most_recent_group_resource_profile_id,
-        });
-      }
-      groupResourceProfileId =
-        state.experiment.userConfigurationData.groupResourceProfileId;
-      // 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.findGroupResourceProfile(groupResourceProfileId)) {
-        await dispatch("loadApplicationDeployments");
-        await dispatch("loadAppDeploymentQueues");
-        await dispatch("applyGroupResourceProfile");
-      }
-    },
-    updateExperimentName({ commit }, { name }) {
-      commit("updateExperimentName", { name });
-    },
-    updateExperimentInputValue({ commit }, { inputName, value }) {
-      commit("updateExperimentInputValue", { inputName, value });
-    },
-    updateProjectId({ commit }, { projectId }) {
-      commit("updateProjectId", { projectId });
-    },
-    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) {
-        const experiment = await services.ExperimentService.update({
-          data: getters.experiment,
-          lookup: getters.experiment.experimentId,
-        });
-        commit("setExperiment", { experiment });
-      } else {
-        const experiment = await services.ExperimentService.create({
-          data: getters.experiment,
-        });
-        commit("setExperiment", { experiment });
-      }
-    },
-    async launchExperiment({ getters }) {
-      await services.ExperimentService.launch({
-        lookup: getters.experiment.experimentId,
-      });
-    },
-    async loadProjects({ commit }) {
-      if (!PROMISES.projects) {
-        PROMISES.projects = services.ProjectService.listAll();
-      }
-      const projects = await PROMISES.projects;
-      commit("setProjects", { projects });
-    },
-    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) => {
-      if (!state.experiment) {
-        return null;
-      }
-      const experimentInputs = state.experiment.experimentInputs;
-      if (experimentInputs) {
-        for (const experimentInput of experimentInputs) {
-          if (experimentInput.name === name) {
-            return experimentInput;
-          }
-        }
-      }
-      return null;
-    },
-    experiment: (state) => state.experiment,
-    projects: (state) => state.projects,
-    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,
-    findGroupResourceProfile: (state) => (groupResourceProfileId) =>
-      state.groupResourceProfiles
-        ? state.groupResourceProfiles.find(
-            (g) => g.groupResourceProfileId === groupResourceProfileId
-          )
-        : null,
-    groupResourceProfile: (state, getters) =>
-      getters.findGroupResourceProfile(getters.groupResourceProfileId),
-    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;
-    },
-    totalCPUCount: (state) => {
-      return state.experiment &&
-        state.experiment.userConfigurationData &&
-        state.experiment.userConfigurationData.computationalResourceScheduling
-        ? state.experiment.userConfigurationData.computationalResourceScheduling
-            .totalCPUCount
-        : null;
-    },
-    nodeCount: (state) => {
-      return state.experiment &&
-        state.experiment.userConfigurationData &&
-        state.experiment.userConfigurationData.computationalResourceScheduling
-        ? state.experiment.userConfigurationData.computationalResourceScheduling
-            .nodeCount
-        : null;
-    },
-    wallTimeLimit: (state) => {
-      return state.experiment &&
-        state.experiment.userConfigurationData &&
-        state.experiment.userConfigurationData.computationalResourceScheduling
-        ? state.experiment.userConfigurationData.computationalResourceScheduling
-            .wallTimeLimit
-        : null;
-    },
-    totalPhysicalMemory: (state) => {
-      return state.experiment &&
-        state.experiment.userConfigurationData &&
-        state.experiment.userConfigurationData.computationalResourceScheduling
-        ? state.experiment.userConfigurationData.computationalResourceScheduling
-            .totalPhysicalMemory
-        : 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;
-    },
-  },
-});

[airavata-django-portal] 16/24: AIRAVATA-3477 Wrapped SelectInputEditor as web component

Posted by ma...@apache.org.
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 49dfb23291c7199d6ee5920f3f06370434ab1c1d
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Sep 10 15:33:57 2021 -0400

    AIRAVATA-3477 Wrapped SelectInputEditor as web component
---
 .../experiment/input-editors/SelectInputEditor.vue | 22 ++++++------
 .../input-editors/SelectInputEditor.vue            | 40 ++++++++++++++++++++++
 2 files changed, 52 insertions(+), 10 deletions(-)

diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/SelectInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/SelectInputEditor.vue
index d13069e..dbfe101 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/SelectInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/SelectInputEditor.vue
@@ -2,7 +2,7 @@
   <b-form-select
     :id="id"
     v-model="data"
-    :options="options"
+    :options="selectOptions"
     stacked
     :disabled="readOnly"
     :state="componentValidState"
@@ -23,17 +23,19 @@ export default {
     value: {
       type: String,
     },
+    options: {
+      type: Array,
+    },
   },
   computed: {
-    options: function () {
-      return "options" in this.editorConfig
-        ? this.editorConfig["options"].map((option) => {
-            return {
-              text: option[CONFIG_OPTION_TEXT_KEY],
-              value: option[CONFIG_OPTION_VALUE_KEY],
-            };
-          })
-        : [];
+    selectOptions: function () {
+      const options = this.options || this.editorConfig.options || [];
+      return options.map((option) => {
+        return {
+          text: option[CONFIG_OPTION_TEXT_KEY],
+          value: option[CONFIG_OPTION_VALUE_KEY],
+        };
+      });
     },
   },
 };
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/SelectInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/SelectInputEditor.vue
new file mode 100644
index 0000000..59b0681
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/SelectInputEditor.vue
@@ -0,0 +1,40 @@
+<template>
+  <select-input-editor
+    v-if="experimentInput"
+    :id="id"
+    :value="data"
+    :experiment-input="experimentInput"
+    :read-only="readOnly"
+    :options="options"
+    @input="valueChanged"
+  />
+</template>
+
+<script>
+import SelectInputEditor from "../../components/experiment/input-editors/SelectInputEditor.vue";
+import WebComponentInputEditorMixin from "./WebComponentInputEditorMixin.js";
+
+export default {
+  mixins: [WebComponentInputEditorMixin],
+  props: {
+    // Explicit copy props from mixin, workaround for bug, see
+    // https://github.com/vuejs/vue-web-component-wrapper/issues/30#issuecomment-427350734
+    // for more details
+    ...WebComponentInputEditorMixin.props,
+    options: {
+      type: Array,
+      default: null,
+    },
+  },
+  components: {
+    SelectInputEditor,
+  },
+};
+</script>
+
+<style lang="scss">
+@import "../styles";
+:host {
+  display: block;
+}
+</style>

[airavata-django-portal] 15/24: AIRAVATA-3477 Wrapped RangeSliderInputEditor as web component

Posted by ma...@apache.org.
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 9b4b123ae81e3a19cce4451a8a16e248ebef25ff
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Sep 10 15:24:34 2021 -0400

    AIRAVATA-3477 Wrapped RangeSliderInputEditor as web component
---
 .../input-editors/RangeSliderInputEditor.vue       | 71 ++++++++++++++++------
 .../js/web-components/ComputeResourceSelector.vue  |  7 ++-
 .../ExperimentComputeResourceSelector.vue          |  7 ++-
 .../js/web-components/ExperimentEditor.vue         |  4 +-
 .../GroupResourceProfileSelector.vue               |  7 ++-
 .../js/web-components/ProjectSelector.vue          |  7 ++-
 .../js/web-components/QueueSettingsEditor.vue      |  4 +-
 .../input-editors/CheckboxInputEditor.vue          |  4 +-
 .../input-editors/FileInputEditor.vue              |  4 +-
 .../input-editors/MultiFileInputEditor.vue         |  4 +-
 .../input-editors/RadioButtonInputEditor.vue       |  4 +-
 .../input-editors/RangeSliderInputEditor.vue       | 66 ++++++++++++++++++++
 .../input-editors/StringInputEditor.vue            |  4 +-
 .../js/web-components/{styles.css => styles.scss}  |  1 +
 14 files changed, 155 insertions(+), 39 deletions(-)

diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/RangeSliderInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/RangeSliderInputEditor.vue
index 8276bb2..cf8ec18 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/RangeSliderInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/RangeSliderInputEditor.vue
@@ -4,9 +4,9 @@
     @change="onChange"
     :state="componentValidState"
     :disabled="readOnly"
-    :min="min"
-    :max="max"
-    :interval="step"
+    :min="sliderMin"
+    :max="sliderMax"
+    :interval="sliderStep"
     tooltip="always"
     :tooltip-formatter="tooltipFormatter"
     :enable-cross="false"
@@ -24,6 +24,22 @@ export default {
     value: {
       type: String,
     },
+    min: Number,
+    max: Number,
+    step: Number,
+    valueFormat: {
+      type: String,
+      validator(value) {
+        return ["percentage"].indexOf(value) !== -1;
+      },
+    },
+    displayFormat: {
+      type: String,
+      validator(value) {
+        return ["percentage"].indexOf(value) !== -1;
+      },
+    },
+    delimiter: String,
   },
   components: {
     VueSlider,
@@ -37,17 +53,31 @@ export default {
     this.initializeSliderValues();
   },
   computed: {
-    min: function () {
-      return "min" in this.editorConfig ? this.editorConfig.min : 0;
+    sliderMin: function () {
+      return typeof this.min !== "undefined"
+        ? this.min
+        : "min" in this.editorConfig
+        ? this.editorConfig.min
+        : 0;
     },
-    max: function () {
-      return "max" in this.editorConfig ? this.editorConfig.max : 100;
+    sliderMax: function () {
+      return typeof this.max !== "undefined"
+        ? this.max
+        : "max" in this.editorConfig
+        ? this.editorConfig.max
+        : 100;
     },
-    step: function () {
-      return "step" in this.editorConfig ? this.editorConfig.step : 1;
+    sliderStep: function () {
+      return typeof this.step !== "undefined"
+        ? this.step
+        : "step" in this.editorConfig
+        ? this.editorConfig.step
+        : 1;
     },
-    delimiter() {
-      return "delimiter" in this.editorConfig
+    sliderDelimiter() {
+      return this.delimiter
+        ? this.delimiter
+        : "delimiter" in this.editorConfig
         ? this.editorConfig.delimiter
         : "-";
     },
@@ -64,9 +94,8 @@ export default {
     parseValue(value) {
       // Just remove any percentage signs
       const result = value
-        .replaceAll("%", "")
-        .split(this.delimiter)
-        .map(parseFloat);
+        ? value.replaceAll("%", "").split(this.sliderDelimiter).map(parseFloat)
+        : [];
       return result.length === 2 && !isNaN(result[0]) && !isNaN(result[1])
         ? result
         : [this.min, this.max];
@@ -76,7 +105,11 @@ export default {
       this.valueChanged();
     },
     tooltipFormatter(value) {
-      if ("displayFormat" in this.editorConfig) {
+      if (this.displayFormat) {
+        if (this.displayFormat === "percentage") {
+          return `${value}%`;
+        }
+      } else if ("displayFormat" in this.editorConfig) {
         if (this.editorConfig.displayFormat.percentage) {
           return `${value}%`;
         }
@@ -85,12 +118,16 @@ export default {
     },
     formatValue(value) {
       let values = value.map(String);
-      if ("valueFormat" in this.editorConfig) {
+      if (this.valueFormat) {
+        if (this.valueFormat === "percentage") {
+          values = values.map((v) => `${v}%`);
+        }
+      } else if ("valueFormat" in this.editorConfig) {
         if (this.editorConfig.valueFormat.percentage) {
           values = values.map((v) => `${v}%`);
         }
       }
-      return values.join(this.delimiter);
+      return values.join(this.sliderDelimiter);
     },
   },
   watch: {
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 463d6f8..60ff2a1 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
@@ -87,6 +87,9 @@ export default {
 };
 </script>
 
-<style>
-@import "./styles.css";
+<style lang="scss">
+@import "./styles";
+:host {
+  display: block;
+}
 </style>
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
index fe17520..1121fe0 100644
--- 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
@@ -35,6 +35,9 @@ export default {
 };
 </script>
 
-<style>
-@import "./styles.css";
+<style lang="scss">
+@import "./styles";
+:host {
+  display: block;
+}
 </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 339e02e..e235303 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
@@ -297,8 +297,8 @@ export default {
 };
 </script>
 
-<style>
-@import "./styles.css";
+<style lang="scss">
+@import "./styles";
 
 :host {
   display: block;
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 2858629..bb58621 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
@@ -87,6 +87,9 @@ export default {
 };
 </script>
 
-<style>
-@import url("./styles.css");
+<style lang="scss">
+@import "./styles";
+:host {
+  display: block;
+}
 </style>
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 797f4e3..801453c 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
@@ -87,6 +87,9 @@ export default {
 };
 </script>
 
-<style>
-@import url("./styles.css");
+<style lang="scss">
+@import "./styles";
+:host {
+  display: block;
+}
 </style>
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 61f2be1..5c69076 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
@@ -199,8 +199,8 @@ export default {
 };
 </script>
 
-<style>
-@import url("./styles.css");
+<style lang="scss">
+@import "./styles";
 
 :host {
   display: block;
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/CheckboxInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/CheckboxInputEditor.vue
index e44132a..7d54188 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/CheckboxInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/CheckboxInputEditor.vue
@@ -37,8 +37,8 @@ export default {
 };
 </script>
 
-<style>
-@import "../styles.css";
+<style lang="scss">
+@import "../styles";
 :host {
   display: block;
 }
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/FileInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/FileInputEditor.vue
index 868415c..c8201ab 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/FileInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/FileInputEditor.vue
@@ -30,8 +30,8 @@ export default {
 };
 </script>
 
-<style>
-@import "../styles.css";
+<style lang="scss">
+@import "../styles";
 @import "~@uppy/core/dist/style.min.css";
 @import "~@uppy/status-bar/dist/style.min.css";
 @import "~@uppy/drag-drop/dist/style.min.css";
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/MultiFileInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/MultiFileInputEditor.vue
index 420774c..c8ee002 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/MultiFileInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/MultiFileInputEditor.vue
@@ -30,8 +30,8 @@ export default {
 };
 </script>
 
-<style>
-@import "../styles.css";
+<style lang="scss">
+@import "../styles";
 @import "~@uppy/core/dist/style.min.css";
 @import "~@uppy/status-bar/dist/style.min.css";
 @import "~@uppy/drag-drop/dist/style.min.css";
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
index 9acf8d2..6dd9ca5 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
@@ -37,8 +37,8 @@ export default {
 };
 </script>
 
-<style>
-@import "../styles.css";
+<style lang="scss">
+@import "../styles";
 :host {
   display: block;
 }
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RangeSliderInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RangeSliderInputEditor.vue
new file mode 100644
index 0000000..296b264
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RangeSliderInputEditor.vue
@@ -0,0 +1,66 @@
+<template>
+  <range-slider-input-editor
+    v-if="experimentInput"
+    :id="id"
+    :value="data"
+    :experiment-input="experimentInput"
+    :read-only="readOnly"
+    :min="min"
+    :max="max"
+    :step="step"
+    :value-format="valueFormat"
+    :display-format="displayFormat"
+    :delimiter="delimiter"
+    @input="valueChanged"
+  />
+</template>
+
+<script>
+import RangeSliderInputEditor from "../../components/experiment/input-editors/RangeSliderInputEditor.vue";
+import WebComponentInputEditorMixin from "./WebComponentInputEditorMixin.js";
+
+export default {
+  mixins: [WebComponentInputEditorMixin],
+  props: {
+    // Explicit copy props from mixin, workaround for bug, see
+    // https://github.com/vuejs/vue-web-component-wrapper/issues/30#issuecomment-427350734
+    // for more details
+    ...WebComponentInputEditorMixin.props,
+    min: {
+      type: Number,
+    },
+    max: {
+      type: Number,
+    },
+    step: {
+      type: Number,
+    },
+    valueFormat: {
+      type: String,
+      validator(value) {
+        return ["percentage"].indexOf(value) !== -1;
+      },
+    },
+    displayFormat: {
+      type: String,
+      validator(value) {
+        return ["percentage"].indexOf(value) !== -1;
+      },
+    },
+    delimiter: String,
+  },
+  components: {
+    RangeSliderInputEditor,
+  },
+};
+</script>
+
+<style lang="scss">
+@import "../styles";
+// Need to explicitly import VueSlider's CSS because importing component scss doesn't work
+// https://github.com/vuejs/vue-web-component-wrapper/issues/12
+@import "~vue-slider-component/dist-css/vue-slider-component.css";
+:host {
+  display: block;
+}
+</style>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
index ee825cf..2f34fd1 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
@@ -33,8 +33,8 @@ export default {
 };
 </script>
 
-<style>
-@import "../styles.css";
+<style lang="scss">
+@import "../styles";
 :host {
   display: block;
 }
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/styles.css b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/styles.scss
similarity index 85%
rename from django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/styles.css
rename to django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/styles.scss
index bf593cb..62e5639 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/styles.css
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/styles.scss
@@ -2,3 +2,4 @@
 @import "~bootstrap/dist/css/bootstrap.css";
 @import "~bootstrap-vue/dist/bootstrap-vue.css";
 @import "~@fortawesome/fontawesome-free/css/all.css";
+@import "../../../scss/styles";

[airavata-django-portal] 06/24: AIRAVATA-3477 Fix issue with native input event colliding with web component input event

Posted by ma...@apache.org.
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 9d3931fdccdacb88c7e2f08a0e83d05f59b43b84
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Sep 1 13:27:15 2021 -0400

    AIRAVATA-3477 Fix issue with native input event colliding with web component input event
---
 .../js/web-components/ExperimentEditor.vue         |  4 ---
 .../input-editors/StringInputEditor.vue            | 32 ++++++++++++++++------
 2 files changed, 24 insertions(+), 12 deletions(-)

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 a766124..6bb75d0 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
@@ -216,10 +216,6 @@ export default {
       });
     },
     updateInputValue(inputName, event) {
-      if (event.inputType) {
-        // Ignore these fine-grained events about the type of change made
-        return;
-      }
       // web component input events have the current value in a detail array,
       // native input events have the current value in target.value
       const value = Array.isArray(event.detail)
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
index 106eca9..8f8cfd4 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
@@ -1,13 +1,16 @@
 <template>
   <!-- NOTE: experimentInput is late bound, don't create component until it is available -->
-  <string-input-editor
-    v-if="experimentInput"
-    :id="id"
-    :value="data"
-    :experiment-input="experimentInput"
-    :read-only="readOnly"
-    @input="onInput"
-  />
+  <div>
+    <string-input-editor
+      ref="inputEditor"
+      v-if="experimentInput"
+      :id="id"
+      :value="data"
+      :experiment-input="experimentInput"
+      :read-only="readOnly"
+      @input="onInput"
+    />
+  </div>
 </template>
 
 <script>
@@ -30,6 +33,16 @@ export default {
     StringInputEditor,
   },
   store: vuestore,
+  mounted() {
+    this.$nextTick(() => {
+      // Stop wrapped input editor 'input' event from bubbling up so it doesn't
+      // conflict with this component's 'input' event. (see #onInput)
+      this.$refs.inputEditor.$el.addEventListener('input', this.stopPropagation);
+    })
+  },
+  destroyed() {
+    this.$refs.inputEditor.$el.removeEventListener('input', this.stopPropagation);
+  },
   data() {
     return {
       data: this.value,
@@ -58,6 +71,9 @@ export default {
         this.$el.dispatchEvent(inputEvent);
       }
     },
+    stopPropagation(event) {
+      event.stopPropagation();
+    }
   },
 };
 </script>

[airavata-django-portal] 05/24: AIRAVATA-3477 Programmatically define slots and make compute and queue editors standalone

Posted by ma...@apache.org.
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 fa82a91eb44428c8333fd1c2b6d45cc20c15f31c
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Mon Aug 30 13:33:59 2021 -0400

    AIRAVATA-3477 Programmatically define slots and make compute and queue editors standalone
---
 .../js/web-components/ComputeResourceSelector.vue  |   3 +-
 .../ExperimentComputeResourceSelector.vue          |  37 ++-----
 .../js/web-components/ExperimentEditor.vue         | 119 ++++++++-------------
 .../js/web-components/QueueSettingsEditor.vue      | 118 +++++++-------------
 .../js/web-components/vuestore.js                  |  32 ++++++
 5 files changed, 126 insertions(+), 183 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 88189f8..c438aa3 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
@@ -6,6 +6,7 @@
       :options="computeResourceOptions"
       required
       @input="computeResourceChanged"
+      :disabled="computeResourceOptions.length === 0"
     >
       <template slot="first">
         <option :value="null" disabled>Select a Compute Resource</option>
@@ -24,7 +25,7 @@ export default {
     value: {
       // compute resource host id
       type: String,
-      required: true,
+      default: null,
     },
     includedComputeResources: {
       type: Array,
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
index d209d57..715f63e 100644
--- 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
@@ -1,6 +1,6 @@
 <template>
   <compute-resource-selector
-    :value="value"
+    :value="resourceHostId"
     :includedComputeResources="computeResources"
     @input.stop="computeResourceChanged"
   />
@@ -13,44 +13,23 @@ 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"]),
+    ...mapGetters([
+      // compute resources for the current set of application deployments
+      "computeResources",
+      "resourceHostId",
+    ]),
   },
   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.$store.dispatch("updateComputeResourceHostId", {
+        computeResourceId,
       });
-      this.$el.dispatchEvent(inputEvent);
-    },
-  },
-  watch: {
-    value() {
-      this.resourceHostId = this.value;
     },
   },
 };
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 12f7287..a766124 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,54 +17,17 @@
         <!-- programmatically define slots as native slots (not Vue slots), see #mounted() -->
       </div>
     </template>
-    <!-- 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"
+      ref="groupResourceProfileSelector"
+      @input.stop="updateGroupResourceProfileId"
     >
-      <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
-        "
-      />
-
+      <!-- programmatically define slot for adpf-group-resource-profile-selector -->
+    </div>
+    <div ref="computeResourceSelector">
+      <!-- programmatically define slot for adpf-experiment-compute-resource-selector -->
+    </div>
+    <div ref="queueSettingsEditor">
+      <!-- programmatically define slot for adpf-queue-settings-editor -->
     </div>
     <div ref="experimentButtons">
       <!-- programmatically define slot for experiment-buttons as
@@ -105,6 +68,7 @@ export default {
         applicationId: this.applicationId,
       });
     }
+    this.$emit("loaded", this.experiment);
     // vue-web-component-wrapper clones native slots and turns them into Vue
     // slots which means they lose any event listeners and they basically aren't
     // in the DOM any more.  As a workaround, programmatically create native
@@ -170,6 +134,39 @@ export default {
         this.createSlot("experiment-project", projectSelectorEl)
       );
 
+      const groupResourceProfileSelectorEl = document.createElement(
+        "adpf-group-resource-profile-selector"
+      );
+      if (this.groupResourceProfileId) {
+        groupResourceProfileSelectorEl.setAttribute(
+          "value",
+          this.groupResourceProfileId
+        );
+      }
+      this.$refs.groupResourceProfileSelector.append(
+        this.createSlot(
+          "experiment-group-resource-profile",
+          groupResourceProfileSelectorEl
+        )
+      );
+
+      const computeResourceSelectorEl = document.createElement(
+        "adpf-experiment-compute-resource-selector"
+      );
+      this.$refs.computeResourceSelector.append(
+        this.createSlot(
+          "experiment-compute-resource",
+          computeResourceSelectorEl
+        )
+      );
+
+      const queueSettingsEditorEl = document.createElement(
+        "adpf-queue-settings-editor"
+      );
+      this.$refs.queueSettingsEditor.append(
+        this.createSlot("experiment-queue-settings", queueSettingsEditorEl)
+      );
+
       /*
        * Experiment (save/launch) Buttons native slot
        */
@@ -210,7 +207,7 @@ export default {
     });
   },
   computed: {
-    ...mapGetters(["experiment"]),
+    ...mapGetters(["experiment", "groupResourceProfileId"]),
   },
   methods: {
     updateExperimentName(event) {
@@ -240,34 +237,6 @@ export default {
         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) {
       // console.log(event);
       // 'save' event is cancelable. Listener can call .preventDefault() on the event to cancel.
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 8fbe8c1..5bb644a 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
@@ -6,28 +6,26 @@
         class="card-link text-dark"
       >
         <div class="card-body">
-          <h5 class="card-title mb-4">
-            Settings for queue {{ localQueueName }}
-          </h5>
+          <h5 class="card-title mb-4">Settings for queue {{ queueName }}</h5>
           <div class="row">
             <div class="col">
               <h3 class="h5 mb-0">
-                {{ localNodeCount }}
+                {{ nodeCount }}
               </h3>
               <span class="text-muted text-uppercase">NODE COUNT</span>
             </div>
             <div class="col">
               <h3 class="h5 mb-0">
-                {{ localTotalCPUCount }}
+                {{ totalCPUCount }}
               </h3>
               <span class="text-muted text-uppercase">CORE COUNT</span>
             </div>
             <div class="col">
-              <h3 class="h5 mb-0">{{ localWallTimeLimit }} minutes</h3>
+              <h3 class="h5 mb-0">{{ wallTimeLimit }} minutes</h3>
               <span class="text-muted text-uppercase">TIME LIMIT</span>
             </div>
             <div class="col" v-if="maxMemory > 0">
-              <h3 class="h5 mb-0">{{ localTotalPhysicalMemory }} MB</h3>
+              <h3 class="h5 mb-0">{{ totalPhysicalMemory }} MB</h3>
               <span class="text-muted text-uppercase">PHYSICAL MEMORY</span>
             </div>
           </div>
@@ -38,7 +36,7 @@
       <b-form-group label="Select a Queue" label-for="queue">
         <b-form-select
           id="queue"
-          v-model="localQueueName"
+          :value="queueName"
           :options="queueOptions"
           required
           @change="queueChanged"
@@ -52,11 +50,9 @@
           type="number"
           min="1"
           :max="maxAllowedNodes"
-          v-model="localNodeCount"
+          :value="nodeCount"
           required
-          @input.native.stop="
-            emitValueChanged('node-count-changed', localNodeCount)
-          "
+          @input.native.stop="updateNodeCount"
         >
         </b-form-input>
         <div slot="description">
@@ -70,11 +66,9 @@
           type="number"
           min="1"
           :max="maxAllowedCores"
-          v-model="localTotalCPUCount"
+          :value="totalCPUCount"
           required
-          @input.native.stop="
-            emitValueChanged('total-cpu-count-changed', localTotalCPUCount)
-          "
+          @input.native.stop="updateTotalCPUCount"
         >
         </b-form-input>
         <div slot="description">
@@ -89,11 +83,9 @@
             type="number"
             min="1"
             :max="maxAllowedWalltime"
-            v-model="localWallTimeLimit"
+            :value="wallTimeLimit"
             required
-            @input.native.stop="
-              emitValueChanged('walltime-limit-changed', localWallTimeLimit)
-            "
+            @input.native.stop="updateWallTimeLimit"
           >
           </b-form-input>
         </b-input-group>
@@ -113,13 +105,8 @@
             type="number"
             min="0"
             :max="maxMemory"
-            v-model="localTotalPhysicalMemory"
-            @input.native.stop="
-              emitValueChanged(
-                'total-physical-memory-changed',
-                localTotalPhysicalMemory
-              )
-            "
+            :value="totalPhysicalMemory"
+            @input.native.stop="updateTotalPhysicalMemory"
           >
           </b-form-input>
         </b-input-group>
@@ -154,29 +141,6 @@ import { faInfoCircle, faTimes } from "@fortawesome/free-solid-svg-icons";
 config.autoAddCss = false;
 
 export default {
-  props: {
-    queueName: {
-      type: String,
-      required: true,
-    },
-    totalCpuCount: {
-      type: Number,
-      required: true,
-    },
-    nodeCount: {
-      type: Number,
-      required: true,
-    },
-    wallTimeLimit: {
-      type: Number,
-      required: true,
-    },
-    totalPhysicalMemory: {
-      type: Number,
-      default: 0,
-      // required: true,
-    },
-  },
   components: {
     FontAwesomeIcon,
   },
@@ -196,11 +160,6 @@ export default {
   },
   data() {
     return {
-      localQueueName: this.queueName,
-      localTotalCPUCount: this.totalCpuCount,
-      localNodeCount: this.nodeCount,
-      localWallTimeLimit: this.wallTimeLimit,
-      localTotalPhysicalMemory: this.totalPhysicalMemory,
       showConfiguration: false,
     };
   },
@@ -212,6 +171,11 @@ export default {
       "maxAllowedNodes",
       "maxAllowedWalltime",
       "maxMemory",
+      "queueName",
+      "totalCPUCount",
+      "nodeCount",
+      "wallTimeLimit",
+      "totalPhysicalMemory",
     ]),
     queueOptions() {
       if (!this.queues) {
@@ -226,6 +190,9 @@ export default {
       utils.StringUtils.sortIgnoreCase(queueOptions, (q) => q.text);
       return queueOptions;
     },
+    queueDescription() {
+      return this.queue ? this.queue.queueDescription : null;
+    },
     closeIcon() {
       return faTimes;
     },
@@ -234,33 +201,28 @@ export default {
     },
   },
   methods: {
-    emitValueChanged(eventName, value) {
-      const inputEvent = new CustomEvent(eventName, {
-        detail: [value],
-        composed: true,
-        bubbles: true,
-      });
-      this.$el.dispatchEvent(inputEvent);
+    queueChanged(queueName) {
+      this.$store.dispatch("updateQueueName", { queueName });
     },
-    queueChanged() {
-      this.emitValueChanged("queue-name-changed", this.localQueueName);
-    },
-  },
-  watch: {
-    queueName(value) {
-      this.localQueueName = value;
-    },
-    nodeCount(value) {
-      this.localNodeCount = value;
+    updateNodeCount(event) {
+      this.$store.dispatch("updateNodeCount", {
+        nodeCount: event.target.value,
+      });
     },
-    totalCpuCount(value) {
-      this.localTotalCPUCount = value;
+    updateTotalCPUCount(event) {
+      this.$store.dispatch("updateTotalCPUCount", {
+        totalCPUCount: event.target.value,
+      });
     },
-    wallTimeLimit(value) {
-      this.localWallTimeLimit = value;
+    updateWallTimeLimit(event) {
+      this.$store.dispatch("updateWallTimeLimit", {
+        wallTimeLimit: event.target.value,
+      });
     },
-    totalPhysicalMemory(value) {
-      this.localTotalPhysicalMemory = value;
+    updateTotalPhysicalMemory(event) {
+      this.$store.dispatch("updateTotalPhysicalMemory", {
+        totalPhysicalMemory: event.target.value,
+      });
     },
   },
 };
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 bb51992..fd5de65 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
@@ -467,6 +467,38 @@ export default new Vuex.Store({
             .queueName
         : null;
     },
+    totalCPUCount: (state) => {
+      return state.experiment &&
+        state.experiment.userConfigurationData &&
+        state.experiment.userConfigurationData.computationalResourceScheduling
+        ? state.experiment.userConfigurationData.computationalResourceScheduling
+            .totalCPUCount
+        : null;
+    },
+    nodeCount: (state) => {
+      return state.experiment &&
+        state.experiment.userConfigurationData &&
+        state.experiment.userConfigurationData.computationalResourceScheduling
+        ? state.experiment.userConfigurationData.computationalResourceScheduling
+            .nodeCount
+        : null;
+    },
+    wallTimeLimit: (state) => {
+      return state.experiment &&
+        state.experiment.userConfigurationData &&
+        state.experiment.userConfigurationData.computationalResourceScheduling
+        ? state.experiment.userConfigurationData.computationalResourceScheduling
+            .wallTimeLimit
+        : null;
+    },
+    totalPhysicalMemory: (state) => {
+      return state.experiment &&
+        state.experiment.userConfigurationData &&
+        state.experiment.userConfigurationData.computationalResourceScheduling
+        ? state.experiment.userConfigurationData.computationalResourceScheduling
+            .totalPhysicalMemory
+        : null;
+    },
     queue: (state, getters) => {
       return getters.queues && getters.queueName
         ? getters.queues.find((q) => q.queueName === getters.queueName)

[airavata-django-portal] 23/24: AIRAVATA-3477 add backwards compat for updateInputValue

Posted by ma...@apache.org.
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 9aeecfc3b0e56339c658acf3f71eabab0107aa1b
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Oct 5 15:46:37 2021 -0400

    AIRAVATA-3477 add backwards compat for updateInputValue
---
 .../django_airavata_workspace/js/web-components/ExperimentEditor.vue  | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

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 e235303..4377ca0 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
@@ -220,7 +220,9 @@ export default {
       // native input events have the current value in target.value
       const value = Array.isArray(event.detail)
         ? event.detail[0]
-        : event.target.value;
+        : event.target // Backwards compatibility: second argument changed from the value to the 'event'
+        ? event.target.value
+        : event;
       this.$store.dispatch("updateExperimentInputValue", { inputName, value });
     },
     updateProjectId(event) {

[airavata-django-portal] 11/24: AIRAVATA-3477 Mixin for input editor web components

Posted by ma...@apache.org.
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 c9e6e2612b907d40e46d1d7009dd79b2c0d6f7cc
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Sep 8 09:30:17 2021 -0400

    AIRAVATA-3477 Mixin for input editor web components
---
 .../input-editors/RadioButtonInputEditor.vue       | 65 +++-------------------
 .../input-editors/StringInputEditor.vue            | 49 ++--------------
 ...tEditor.vue => WebComponentInputEditorMixin.js} | 30 +---------
 3 files changed, 12 insertions(+), 132 deletions(-)

diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
index 5e03681..b97e820 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
@@ -7,81 +7,30 @@
       :experiment-input="experimentInput"
       :read-only="readOnly"
       :options="options"
-      @input="onInput"
+      @input="valueChanged"
     />
   </div>
 </template>
 
 <script>
 import RadioButtonInputEditor from "../../components/experiment/input-editors/RadioButtonInputEditor.vue";
-
-import Vue from "vue";
-import { BootstrapVue } from "bootstrap-vue";
-import AsyncComputed from "vue-async-computed";
-import { utils } from "django-airavata-common-ui";
-import store from "../store";
-Vue.use(BootstrapVue);
-Vue.use(AsyncComputed);
+import WebComponentInputEditorMixin from "./WebComponentInputEditorMixin.js";
 
 export default {
+  mixins: [WebComponentInputEditorMixin],
   props: {
-    value: String,
-    name: String,
+    // Explicit copy props from mixin, workaround for bug, see
+    // https://github.com/vuejs/vue-web-component-wrapper/issues/30#issuecomment-427350734
+    // for more details
+    ...WebComponentInputEditorMixin.props,
     options: {
       type: Array,
       default: null,
     },
   },
-  store: store,
   components: {
     RadioButtonInputEditor,
   },
-  mounted() {
-    this.$nextTick(() => {
-      for (const key of Object.keys(this.$props)) {
-        // workaround for issues around setting props before WC connected,
-        // see https://github.com/vuejs/vue-web-component-wrapper/pull/81
-
-        // copy properties set on host element to wrapper component
-        // (mostly this is done so that the options array can be set by client code)
-        this.$parent.props[key] = this.$el.getRootNode().host[key];
-      }
-    })
-  },
-  data() {
-    return {
-      data: this.value,
-    };
-  },
-  computed: {
-    readOnly() {
-      return this.experimentInput.isReadOnly;
-    },
-    id() {
-      return utils.sanitizeHTMLId(this.experimentInput.name);
-    },
-    experimentInput() {
-      return this.$store.getters.getExperimentInputByName(this.name);
-    },
-  },
-  methods: {
-    onInput(value) {
-      if (value !== this.data) {
-        this.data = value;
-        const inputEvent = new CustomEvent("input", {
-          detail: [this.data],
-          composed: true,
-          bubbles: true,
-        });
-        this.$el.dispatchEvent(inputEvent);
-      }
-    },
-  },
-  watch: {
-    value(value) {
-      this.data = value;
-    },
-  },
 };
 </script>
 
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
index c68385b..be3fc77 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
@@ -7,64 +7,23 @@
       :value="data"
       :experiment-input="experimentInput"
       :read-only="readOnly"
-      @input="onInput"
+      @input="valueChanged"
     />
   </div>
 </template>
 
 <script>
 import StringInputEditor from "../../components/experiment/input-editors/StringInputEditor.vue";
-import Vue from "vue";
-import { BootstrapVue } from "bootstrap-vue";
-import AsyncComputed from "vue-async-computed";
-import { utils } from "django-airavata-common-ui";
-import store from "../store";
-Vue.use(BootstrapVue);
-Vue.use(AsyncComputed);
+import WebComponentInputEditorMixin from "./WebComponentInputEditorMixin.js";
 
 export default {
+  mixins: [WebComponentInputEditorMixin],
   props: {
-    value: String,
-    name: String,
+    ...WebComponentInputEditorMixin.props,
   },
   components: {
     StringInputEditor,
   },
-  store: store,
-  data() {
-    return {
-      data: this.value,
-    };
-  },
-  computed: {
-    readOnly() {
-      return this.experimentInput.isReadOnly;
-    },
-    id() {
-      return utils.sanitizeHTMLId(this.experimentInput.name);
-    },
-    experimentInput() {
-      return this.$store.getters.getExperimentInputByName(this.name);
-    },
-  },
-  methods: {
-    onInput(value) {
-      if (value !== this.data) {
-        this.data = value;
-        const inputEvent = new CustomEvent("input", {
-          detail: [this.data],
-          composed: true,
-          bubbles: true,
-        });
-        this.$el.dispatchEvent(inputEvent);
-      }
-    },
-  },
-  watch: {
-    value(value) {
-      this.data = value;
-    }
-  }
 };
 </script>
 
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/WebComponentInputEditorMixin.js
similarity index 72%
copy from django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
copy to django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/WebComponentInputEditorMixin.js
index 5e03681..da7bf54 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/WebComponentInputEditorMixin.js
@@ -1,19 +1,3 @@
-<template>
-  <div>
-    <radio-button-input-editor
-      v-if="experimentInput"
-      :id="id"
-      :value="data"
-      :experiment-input="experimentInput"
-      :read-only="readOnly"
-      :options="options"
-      @input="onInput"
-    />
-  </div>
-</template>
-
-<script>
-import RadioButtonInputEditor from "../../components/experiment/input-editors/RadioButtonInputEditor.vue";
 
 import Vue from "vue";
 import { BootstrapVue } from "bootstrap-vue";
@@ -27,15 +11,8 @@ export default {
   props: {
     value: String,
     name: String,
-    options: {
-      type: Array,
-      default: null,
-    },
   },
   store: store,
-  components: {
-    RadioButtonInputEditor,
-  },
   mounted() {
     this.$nextTick(() => {
       for (const key of Object.keys(this.$props)) {
@@ -65,7 +42,7 @@ export default {
     },
   },
   methods: {
-    onInput(value) {
+    valueChanged(value) {
       if (value !== this.data) {
         this.data = value;
         const inputEvent = new CustomEvent("input", {
@@ -83,8 +60,3 @@ export default {
     },
   },
 };
-</script>
-
-<style>
-@import "../styles.css";
-</style>

[airavata-django-portal] 21/24: AIRAVATA-3477 Support inline options markup

Posted by ma...@apache.org.
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 13df7fa71853ef8a870a8d5fe2db28d83439ca21
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Mon Oct 4 10:05:58 2021 -0400

    AIRAVATA-3477 Support inline options markup
---
 .../input-editors/CheckboxInputEditor.vue          |  9 +++-
 .../input-editors/InlineOptionsMixin.js            | 50 ++++++++++++++++++++++
 .../input-editors/RadioButtonInputEditor.vue       |  9 +++-
 .../input-editors/SelectInputEditor.vue            | 28 +++++++-----
 4 files changed, 82 insertions(+), 14 deletions(-)

diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/CheckboxInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/CheckboxInputEditor.vue
index 7d54188..46cb957 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/CheckboxInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/CheckboxInputEditor.vue
@@ -9,18 +9,20 @@
       :value="data"
       :experiment-input="experimentInput"
       :read-only="readOnly"
-      :options="options"
+      :options="allOptions"
       @input="valueChanged"
     />
+    <div ref="optionsSlot" class="options-slot"></div>
   </div>
 </template>
 
 <script>
 import CheckboxInputEditor from "../../components/experiment/input-editors/CheckboxInputEditor.vue";
 import WebComponentInputEditorMixin from "./WebComponentInputEditorMixin.js";
+import InlineOptionsMixin from "./InlineOptionsMixin.js";
 
 export default {
-  mixins: [WebComponentInputEditorMixin],
+  mixins: [WebComponentInputEditorMixin, InlineOptionsMixin],
   props: {
     // Explicit copy props from mixin, workaround for bug, see
     // https://github.com/vuejs/vue-web-component-wrapper/issues/30#issuecomment-427350734
@@ -42,4 +44,7 @@ export default {
 :host {
   display: block;
 }
+:host .options-slot {
+  display: none;
+}
 </style>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/InlineOptionsMixin.js b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/InlineOptionsMixin.js
new file mode 100644
index 0000000..dca7683
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/InlineOptionsMixin.js
@@ -0,0 +1,50 @@
+export default {
+  data() {
+    return {
+      inlineOptions: [],
+    };
+  },
+  computed: {
+    allOptions() {
+      // Copy options
+      const result = this.options ? this.options.slice() : [];
+      // Copy inlineOptions into result
+      result.push(...this.inlineOptions);
+      // return null if empty
+      return result.length > 0 ? result : null;
+    },
+  },
+  mounted() {
+    this.$nextTick(() => {
+      // Create default slot programmatically
+      this.$refs.optionsSlot.append(document.createElement("slot"));
+      this.readInlineOptions();
+      this.addInlineOptionsChangeListener();
+    });
+  },
+  destroyed() {
+    this.removeInlineOptionsChangeListener();
+  },
+  methods: {
+    readInlineOptions() {
+      // Find options in slot and load them in
+      const slot = this.$el.querySelector("slot");
+      const els = slot.assignedElements();
+      this.inlineOptions = [];
+      for (const el of els) {
+        if (el.tagName === "OPTION") {
+          this.inlineOptions.push({ text: el.textContent, value: el.value });
+        }
+      }
+    },
+    addInlineOptionsChangeListener() {
+      const slot = this.$el.querySelector("slot");
+      // listen for changing options https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement#examples
+      slot.addEventListener("slotchange", this.readInlineOptions);
+    },
+    removeInlineOptionsChangeListener() {
+      const slot = this.$el.querySelector("slot");
+      slot.removeEventListener("slotchange", this.readInlineOptions);
+    },
+  },
+};
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
index 6dd9ca5..bdf0ed2 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
@@ -9,18 +9,20 @@
       :value="data"
       :experiment-input="experimentInput"
       :read-only="readOnly"
-      :options="options"
+      :options="allOptions"
       @input="valueChanged"
     />
+    <div ref="optionsSlot" class="options-slot"></div>
   </div>
 </template>
 
 <script>
 import RadioButtonInputEditor from "../../components/experiment/input-editors/RadioButtonInputEditor.vue";
 import WebComponentInputEditorMixin from "./WebComponentInputEditorMixin.js";
+import InlineOptionsMixin from "./InlineOptionsMixin.js";
 
 export default {
-  mixins: [WebComponentInputEditorMixin],
+  mixins: [WebComponentInputEditorMixin, InlineOptionsMixin],
   props: {
     // Explicit copy props from mixin, workaround for bug, see
     // https://github.com/vuejs/vue-web-component-wrapper/issues/30#issuecomment-427350734
@@ -42,4 +44,7 @@ export default {
 :host {
   display: block;
 }
+:host .options-slot {
+  display: none;
+}
 </style>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/SelectInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/SelectInputEditor.vue
index 59b0681..03ef339 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/SelectInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/SelectInputEditor.vue
@@ -1,21 +1,26 @@
 <template>
-  <select-input-editor
-    v-if="experimentInput"
-    :id="id"
-    :value="data"
-    :experiment-input="experimentInput"
-    :read-only="readOnly"
-    :options="options"
-    @input="valueChanged"
-  />
+  <div>
+    <select-input-editor
+      v-if="experimentInput"
+      :id="id"
+      :value="data"
+      :experiment-input="experimentInput"
+      :read-only="readOnly"
+      :options="allOptions"
+      @input="valueChanged"
+    >
+    </select-input-editor>
+    <div ref="optionsSlot" class="options-slot"></div>
+  </div>
 </template>
 
 <script>
 import SelectInputEditor from "../../components/experiment/input-editors/SelectInputEditor.vue";
 import WebComponentInputEditorMixin from "./WebComponentInputEditorMixin.js";
+import InlineOptionsMixin from "./InlineOptionsMixin.js";
 
 export default {
-  mixins: [WebComponentInputEditorMixin],
+  mixins: [WebComponentInputEditorMixin, InlineOptionsMixin],
   props: {
     // Explicit copy props from mixin, workaround for bug, see
     // https://github.com/vuejs/vue-web-component-wrapper/issues/30#issuecomment-427350734
@@ -37,4 +42,7 @@ export default {
 :host {
   display: block;
 }
+:host .options-slot {
+  display: none;
+}
 </style>