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 2022/06/03 20:11:53 UTC

[airavata-django-portal] branch AIRAVATA-3562 updated (2057fb03 -> 64387832)

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

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


    from 2057fb03 AIRAVATA-3564 Hook up admin UI to store/REST API
     new 5b7b9b71 AIRAVATA-3564 Save common extended user profile fields
     new ff79032b AIRAVATA-3564 Add new fields and save
     new 141421a1 AIRAVATA-3564 Add, remove and reorder choices
     new aaf2f2c6 AIRAVATA-3564 Control whether an "Other" option is available
     new 7d5bd315 AIRAVATA-3564 Allow adding and removing Links
     new 70a657a1 AIRAVATA-3564 UI for reordering and deleting fields
     new 0f94300e AIRAVATA-3564 Adding validation to extended user profile fields
     new 64387832 AIRAVATA-3564 Some styling and layout improvements

The 8 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:
 .../users/ExtendedUserProfileContainer.vue         | 149 ++++-----
 .../ExtendedUserProfileFieldEditor.vue             | 366 +++++++++++++++++++++
 .../src/store/modules/extendedUserProfile.js       | 151 ++++++++-
 .../api/static/django_airavata_api/js/index.js     |   6 +
 .../js/models/ExtendedUserProfileField.js          |  36 ++
 .../js/models/ExtendedUserProfileFieldChoice.js    |  21 +-
 .../js/models/ExtendedUserProfileFieldLink.js      |  15 +
 .../js/components/ExtendedUserProfileEditor.vue    |  16 +-
 ... ExtendedUserProfileMultiChoiceValueEditor.vue} |   8 +-
 ...ExtendedUserProfileSingleChoiceValueEditor.vue} |   8 +-
 ....vue => ExtendedUserProfileTextValueEditor.vue} |   8 +-
 ...xtendedUserProfileUserAgreementValueEditor.vue} |   8 +-
 ...itor.vue => ExtendedUserProfileValueEditor.vue} |   0
 .../js/store/modules/extendedUserProfile.js        |   2 +-
 django_airavata/static/common/scss/main.scss       |   5 +
 15 files changed, 676 insertions(+), 123 deletions(-)
 create mode 100644 django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
 rename django_airavata/apps/auth/static/django_airavata_auth/js/components/{ExtendedUserProfileMultiChoiceFieldEditor.vue => ExtendedUserProfileMultiChoiceValueEditor.vue} (94%)
 rename django_airavata/apps/auth/static/django_airavata_auth/js/components/{ExtendedUserProfileSingleChoiceFieldEditor.vue => ExtendedUserProfileSingleChoiceValueEditor.vue} (94%)
 rename django_airavata/apps/auth/static/django_airavata_auth/js/components/{ExtendedUserProfileTextFieldEditor.vue => ExtendedUserProfileTextValueEditor.vue} (86%)
 rename django_airavata/apps/auth/static/django_airavata_auth/js/components/{ExtendedUserProfileUserAgreementFieldEditor.vue => ExtendedUserProfileUserAgreementValueEditor.vue} (89%)
 rename django_airavata/apps/auth/static/django_airavata_auth/js/components/{ExtendedUserProfileFieldEditor.vue => ExtendedUserProfileValueEditor.vue} (100%)


[airavata-django-portal] 02/08: AIRAVATA-3564 Add new fields and save

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

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

commit ff79032b0730c8e6b7362b958d629eb6cc334301
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Jun 3 09:42:12 2022 -0400

    AIRAVATA-3564 Add new fields and save
---
 .../users/ExtendedUserProfileContainer.vue         | 21 +++++++---------
 .../ExtendedUserProfileFieldEditor.vue             | 20 +++++++--------
 .../src/store/modules/extendedUserProfile.js       | 29 ++++++++++++++++++++--
 .../api/static/django_airavata_api/js/index.js     |  2 ++
 4 files changed, 48 insertions(+), 24 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
index 2bd39ae6..118655c2 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
@@ -12,7 +12,12 @@
     </div>
     <div class="row">
       <div class="col">
-        <b-button variant="primary" @click="addField"> Add Field </b-button>
+        <b-dropdown text="Add Field">
+          <b-dropdown-item @click="addField('text')">Text</b-dropdown-item>
+          <b-dropdown-item @click="addField('single_choice')">Single Choice</b-dropdown-item>
+          <b-dropdown-item @click="addField('multi_choice')">Multi Choice</b-dropdown-item>
+          <b-dropdown-item @click="addField('user_agreement')">User Agreement</b-dropdown-item>
+        </b-dropdown>
       </div>
     </div>
     <div class="row mt-4">
@@ -30,7 +35,6 @@ export default {
   components: { ExtendedUserProfileFieldEditor },
   data() {
     return {
-      fields: [],
     };
   },
   created() {
@@ -40,18 +44,11 @@ export default {
     ...mapActions("extendedUserProfile", [
       "loadExtendedUserProfileFields",
       "saveExtendedUserProfileFields",
+      "addExtendedUserProfileField",
     ]),
-    addField() {
+    addField(field_type) {
       // TODO: post an empty field to the API
-      this.fields.push({
-        id: null,
-        name: "",
-        description: "",
-        type: "text",
-        required: false,
-        options: null,
-        links: null,
-      });
+      this.addExtendedUserProfileField({field_type})
     },
     addOption(field) {
       if (!field.options) {
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
index 5b19426b..99ae9765 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
@@ -1,5 +1,5 @@
 <template>
-  <b-card :title="'Field: ' + name">
+  <b-card :title="title">
     <b-form-group label="Name">
       <b-form-input v-model="name" />
     </b-form-group>
@@ -79,15 +79,15 @@ export default {
         this.setRequired({ value, field: this.extendedUserProfileField });
       },
     },
-    // TODO: probably don't need these
-    fieldTypeOptions() {
-      return [
-        { value: "text", text: "Text" },
-        { value: "single_choice", text: "Single Choice" },
-        { value: "multi_choice", text: "Multi Choice" },
-        { value: "user_agreement", text: "User Agreement" },
-      ];
-    },
+    title() {
+      const fieldTypes = {
+        text: "Text",
+        single_choice: "Single Choice",
+        multi_choice: "Multi Choice",
+        user_agreement: "User Agreement",
+      }
+      return `${fieldTypes[this.extendedUserProfileField.field_type]}: ${this.name}`;
+    }
   },
   methods: {
     ...mapMutations("extendedUserProfile", ["setName", "setHelpText", "setRequired"]),
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js b/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
index decd3d89..b3b7c4ec 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
@@ -1,4 +1,4 @@
-import { services } from "django-airavata-api";
+import { models, services } from "django-airavata-api";
 
 const state = () => ({
   extendedUserProfileFields: null,
@@ -13,8 +13,10 @@ const actions = {
     const extendedUserProfileFields = await services.ExtendedUserProfileFieldService.list();
     commit("setExtendedUserProfileFields", { extendedUserProfileFields });
   },
-  async saveExtendedUserProfileFields({ dispatch, state }) {
+  async saveExtendedUserProfileFields({ commit, dispatch, state }) {
+    let order = 1;
     for (const field of state.extendedUserProfileFields) {
+      commit("setOrder", {field, order: order++});
       // Create or update each field
       if (field.id) {
         await services.ExtendedUserProfileFieldService.update({
@@ -28,6 +30,20 @@ const actions = {
     // Reload the fields
     dispatch("loadExtendedUserProfileFields");
   },
+  async addExtendedUserProfileField({ state, commit }, { field_type }) {
+    const field = new models.ExtendedUserProfileField({
+      field_type,
+      name: `New Field ${state.extendedUserProfileFields.length + 1}`,
+      description: "",
+      help_text: "",
+      required: true,
+      links: [],
+      other: false,
+      choices: [],
+      checkbox_label: "",
+    });
+    commit("addExtendedUserProfileField", { field });
+  },
 };
 
 function getField(state, field) {
@@ -54,6 +70,15 @@ const mutations = {
   setRequired(state, { value, field }) {
     setFieldProp(state, field, "required", value);
   },
+  setOrder(state, {order, field}) {
+    setFieldProp(state, field, 'order', order);
+  },
+  addExtendedUserProfileField(state, { field }) {
+    if (!state.extendedUserProfileFields) {
+      state.extendedUserProfileFields = [];
+    }
+    state.extendedUserProfileFields.push(field);
+  },
 };
 
 export default {
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 e177e42f..8aa2bfc3 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
@@ -18,6 +18,7 @@ import DataType from "./models/DataType";
 import Experiment from "./models/Experiment";
 import ExperimentSearchFields from "./models/ExperimentSearchFields";
 import ExperimentState from "./models/ExperimentState";
+import ExtendedUserProfileField from "./models/ExtendedUserProfileField";
 import FullExperiment from "./models/FullExperiment";
 import Group from "./models/Group";
 import GroupComputeResourcePreference from "./models/GroupComputeResourcePreference";
@@ -80,6 +81,7 @@ const models = {
   Experiment,
   ExperimentSearchFields,
   ExperimentState,
+  ExtendedUserProfileField,
   FullExperiment,
   Group,
   GroupComputeResourcePreference,


[airavata-django-portal] 01/08: AIRAVATA-3564 Save common extended user profile fields

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

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

commit 5b7b9b712265108e86f1ab58d371a45986be27f4
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue May 24 12:33:25 2022 -0400

    AIRAVATA-3564 Save common extended user profile fields
---
 .../users/ExtendedUserProfileContainer.vue         | 75 +++--------------
 .../ExtendedUserProfileFieldEditor.vue             | 98 ++++++++++++++++++++++
 .../src/store/modules/extendedUserProfile.js       | 35 ++++++++
 .../js/models/ExtendedUserProfileField.js          | 24 ++++++
 .../js/components/ExtendedUserProfileEditor.vue    | 16 ++--
 ... ExtendedUserProfileMultiChoiceValueEditor.vue} |  8 +-
 ...ExtendedUserProfileSingleChoiceValueEditor.vue} |  8 +-
 ....vue => ExtendedUserProfileTextValueEditor.vue} |  8 +-
 ...xtendedUserProfileUserAgreementValueEditor.vue} |  8 +-
 ...itor.vue => ExtendedUserProfileValueEditor.vue} |  0
 .../js/store/modules/extendedUserProfile.js        |  2 +-
 11 files changed, 192 insertions(+), 90 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
index e813f737..2bd39ae6 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
@@ -7,61 +7,7 @@
     </div>
     <div v-for="field in extendedUserProfileFields" class="row" :key="field.id">
       <div class="col">
-        <b-card :title="'Field: ' + field.name">
-          <b-form-group label="Name">
-            <b-form-input v-model="field.name" />
-          </b-form-group>
-          <b-form-group label="Help text">
-            <b-form-input v-model="field.help_text" />
-          </b-form-group>
-          <b-form-group label="Required">
-            <b-form-checkbox v-model="field.required" />
-          </b-form-group>
-          <b-form-group label="Type">
-            <b-form-select
-              v-model="field.field_type"
-              :options="fieldTypeOptions"
-            />
-          </b-form-group>
-          <b-card
-            title="Options"
-            v-if="
-              field.field_type === 'single_choice' ||
-              field.field_type === 'multi_choice'
-            "
-          >
-            <template v-for="choice in field.choices">
-              <b-input-group :key="choice.id">
-                <b-form-input v-model="choice.display_text" />
-                <b-input-group-append>
-                  <b-button @click="deleteOption(field, choice)"
-                    >Delete</b-button
-                  >
-                </b-input-group-append>
-              </b-input-group>
-            </template>
-            <b-button @click="addOption(field)">Add Option</b-button>
-          </b-card>
-          <template v-if="field.links && field.links.length > 0">
-            <b-card title="Links" v-for="link in field.links" :key="link.id">
-              <b-form-group label="Label">
-                <b-form-input v-model="link.label" />
-              </b-form-group>
-              <b-form-group label="URL">
-                <b-form-input v-model="link.url" />
-              </b-form-group>
-              <b-form-group label="Show as link?">
-                <b-form-checkbox v-model="link.display_link" />
-              </b-form-group>
-              <b-form-group label="Show inline?">
-                <b-form-checkbox v-model="link.display_inline" />
-              </b-form-group>
-            </b-card>
-          </template>
-          <b-button v-if="!field.links" @click="addLink(field)"
-            >Add Link</b-button
-          >
-        </b-card>
+        <extended-user-profile-field-editor :extendedUserProfileField="field" />
       </div>
     </div>
     <div class="row">
@@ -79,7 +25,9 @@
 
 <script>
 import { mapActions, mapGetters } from "vuex";
+import ExtendedUserProfileFieldEditor from "./field-editors/ExtendedUserProfileFieldEditor.vue";
 export default {
+  components: { ExtendedUserProfileFieldEditor },
   data() {
     return {
       fields: [],
@@ -89,7 +37,10 @@ export default {
     this.loadExtendedUserProfileFields();
   },
   methods: {
-    ...mapActions("extendedUserProfile", ["loadExtendedUserProfileFields"]),
+    ...mapActions("extendedUserProfile", [
+      "loadExtendedUserProfileFields",
+      "saveExtendedUserProfileFields",
+    ]),
     addField() {
       // TODO: post an empty field to the API
       this.fields.push({
@@ -138,18 +89,12 @@ export default {
       const i = field.links.indexOf(link);
       field.links.splice(i, 1);
     },
-    save() {},
+    save() {
+      this.saveExtendedUserProfileFields();
+    },
   },
   computed: {
     ...mapGetters("extendedUserProfile", ["extendedUserProfileFields"]),
-    fieldTypeOptions() {
-      return [
-        { value: "text", text: "Text" },
-        { value: "single_choice", text: "Single Choice" },
-        { value: "multi_choice", text: "Multi Choice" },
-        { value: "user_agreement", text: "User Agreement" },
-      ];
-    },
   },
 };
 </script>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
new file mode 100644
index 00000000..5b19426b
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
@@ -0,0 +1,98 @@
+<template>
+  <b-card :title="'Field: ' + name">
+    <b-form-group label="Name">
+      <b-form-input v-model="name" />
+    </b-form-group>
+    <b-form-group label="Help text">
+      <b-form-input v-model="help_text" />
+    </b-form-group>
+    <b-form-group label="Required">
+      <b-form-checkbox v-model="required" />
+    </b-form-group>
+    <!-- <b-card
+            title="Options"
+            v-if="
+              field.field_type === 'single_choice' ||
+              field.field_type === 'multi_choice'
+            "
+          >
+            <template v-for="choice in field.choices">
+              <b-input-group :key="choice.id">
+                <b-form-input v-model="choice.display_text" />
+                <b-input-group-append>
+                  <b-button @click="deleteOption(field, choice)"
+                    >Delete</b-button
+                  >
+                </b-input-group-append>
+              </b-input-group>
+            </template>
+            <b-button @click="addOption(field)">Add Option</b-button>
+          </b-card>
+          <template v-if="field.links && field.links.length > 0">
+            <b-card title="Links" v-for="link in field.links" :key="link.id">
+              <b-form-group label="Label">
+                <b-form-input v-model="link.label" />
+              </b-form-group>
+              <b-form-group label="URL">
+                <b-form-input v-model="link.url" />
+              </b-form-group>
+              <b-form-group label="Show as link?">
+                <b-form-checkbox v-model="link.display_link" />
+              </b-form-group>
+              <b-form-group label="Show inline?">
+                <b-form-checkbox v-model="link.display_inline" />
+              </b-form-group>
+            </b-card>
+          </template>
+          <b-button v-if="!field.links" @click="addLink(field)"
+            >Add Link</b-button
+          > -->
+  </b-card>
+</template>
+
+<script>
+import { mapMutations } from "vuex";
+export default {
+  props: ["extendedUserProfileField"],
+  computed: {
+    name: {
+      get() {
+        return this.extendedUserProfileField.name;
+      },
+      set(value) {
+        this.setName({ value, field: this.extendedUserProfileField });
+      },
+    },
+    help_text: {
+      get() {
+        return this.extendedUserProfileField.help_text;
+      },
+      set(value) {
+        this.setHelpText({ value, field: this.extendedUserProfileField });
+      },
+    },
+    required: {
+      get() {
+        return this.extendedUserProfileField.required;
+      },
+      set(value) {
+        this.setRequired({ value, field: this.extendedUserProfileField });
+      },
+    },
+    // TODO: probably don't need these
+    fieldTypeOptions() {
+      return [
+        { value: "text", text: "Text" },
+        { value: "single_choice", text: "Single Choice" },
+        { value: "multi_choice", text: "Multi Choice" },
+        { value: "user_agreement", text: "User Agreement" },
+      ];
+    },
+  },
+  methods: {
+    ...mapMutations("extendedUserProfile", ["setName", "setHelpText", "setRequired"]),
+  },
+};
+</script>
+
+<style></style>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js b/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
index 2e3eefc2..decd3d89 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
@@ -13,12 +13,47 @@ const actions = {
     const extendedUserProfileFields = await services.ExtendedUserProfileFieldService.list();
     commit("setExtendedUserProfileFields", { extendedUserProfileFields });
   },
+  async saveExtendedUserProfileFields({ dispatch, state }) {
+    for (const field of state.extendedUserProfileFields) {
+      // Create or update each field
+      if (field.id) {
+        await services.ExtendedUserProfileFieldService.update({
+          lookup: field.id,
+          data: field,
+        });
+      } else {
+        await services.ExtendedUserProfileFieldService.create({ data: field });
+      }
+    }
+    // Reload the fields
+    dispatch("loadExtendedUserProfileFields");
+  },
 };
 
+function getField(state, field) {
+  const extendedUserProfileField = state.extendedUserProfileFields.find(
+    (f) => f === field
+  );
+  return extendedUserProfileField;
+}
+function setFieldProp(state, field, prop, value) {
+  const extendedUserProfileField = getField(state, field);
+  extendedUserProfileField[prop] = value;
+}
+
 const mutations = {
   setExtendedUserProfileFields(state, { extendedUserProfileFields }) {
     state.extendedUserProfileFields = extendedUserProfileFields;
   },
+  setName(state, { value, field }) {
+    setFieldProp(state, field, "name", value);
+  },
+  setHelpText(state, { value, field }) {
+    setFieldProp(state, field, "help_text", value);
+  },
+  setRequired(state, { value, field }) {
+    setFieldProp(state, field, "required", value);
+  },
 };
 
 export default {
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js
index 2eec7ac8..bef33e41 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js
@@ -37,4 +37,28 @@ export default class ExtendedUserProfileField extends BaseModel {
   constructor(data = {}) {
     super(FIELDS, data);
   }
+  toJSON() {
+    const copy = Object.assign({}, this);
+    // Remove unnecessary properties
+    switch (this.field_type) {
+      case "text":
+        delete copy["other"];
+        delete copy["choices"];
+        delete copy["checkbox_label"];
+        break;
+      case "single_choice":
+      case "multi_choice":
+        delete copy["checkbox_label"];
+        break;
+      case "user_agreement":
+        delete copy["other"];
+        delete copy["choices"];
+        break;
+      default:
+        // eslint-disable-next-line no-console
+        console.error("Unrecognized field type", this.field_type);
+        break;
+    }
+    return copy;
+  }
 }
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
index 0b0b08d8..aaf9fc7f 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
@@ -15,10 +15,10 @@
 
 <script>
 import { mapGetters } from "vuex";
-import ExtendedUserProfileMultiChoiceFieldEditor from "./ExtendedUserProfileMultiChoiceFieldEditor.vue";
-import ExtendedUserProfileSingleChoiceFieldEditor from "./ExtendedUserProfileSingleChoiceFieldEditor.vue";
-import ExtendedUserProfileTextFieldEditor from "./ExtendedUserProfileTextFieldEditor.vue";
-import ExtendedUserProfileUserAgreementFieldEditor from "./ExtendedUserProfileUserAgreementFieldEditor.vue";
+import ExtendedUserProfileMultiChoiceValueEditor from "./ExtendedUserProfileMultiChoiceValueEditor.vue";
+import ExtendedUserProfileSingleChoiceValueEditor from "./ExtendedUserProfileSingleChoiceValueEditor.vue";
+import ExtendedUserProfileTextValueEditor from "./ExtendedUserProfileTextValueEditor.vue";
+import ExtendedUserProfileUserAgreementValueEditor from "./ExtendedUserProfileUserAgreementValueEditor.vue";
 import { mixins } from "django-airavata-common-ui";
 export default {
   mixins: [mixins.ValidationParent],
@@ -31,10 +31,10 @@ export default {
   methods: {
     getEditor(extendedUserProfileField) {
       const fieldTypeEditors = {
-        text: ExtendedUserProfileTextFieldEditor,
-        single_choice: ExtendedUserProfileSingleChoiceFieldEditor,
-        multi_choice: ExtendedUserProfileMultiChoiceFieldEditor,
-        user_agreement: ExtendedUserProfileUserAgreementFieldEditor,
+        text: ExtendedUserProfileTextValueEditor,
+        single_choice: ExtendedUserProfileSingleChoiceValueEditor,
+        multi_choice: ExtendedUserProfileMultiChoiceValueEditor,
+        user_agreement: ExtendedUserProfileUserAgreementValueEditor,
       };
 
       if (extendedUserProfileField.field_type in fieldTypeEditors) {
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceValueEditor.vue
similarity index 94%
rename from django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue
rename to django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceValueEditor.vue
index 9e99fb7f..9905c89f 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceValueEditor.vue
@@ -1,5 +1,5 @@
 <template>
-  <extended-user-profile-field-editor v-bind="$props">
+  <extended-user-profile-value-editor v-bind="$props">
     <b-form-checkbox-group
       v-model="value"
       :options="options"
@@ -29,7 +29,7 @@
         >Please specify a value for 'Other'.</b-form-invalid-feedback
       >
     </template>
-  </extended-user-profile-field-editor>
+  </extended-user-profile-value-editor>
 </template>
 
 <script>
@@ -37,11 +37,11 @@ import { mapGetters, mapMutations } from "vuex";
 import { validationMixin } from "vuelidate";
 import { required, requiredIf } from "vuelidate/lib/validators";
 import { errors } from "django-airavata-common-ui";
-import ExtendedUserProfileFieldEditor from "./ExtendedUserProfileFieldEditor.vue";
+import ExtendedUserProfileValueEditor from "./ExtendedUserProfileValueEditor.vue";
 const OTHER_OPTION = new Object(); // sentinel value
 export default {
   mixins: [validationMixin],
-  components: { ExtendedUserProfileFieldEditor },
+  components: { ExtendedUserProfileValueEditor },
   props: ["extendedUserProfileField"],
   data() {
     return {
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceValueEditor.vue
similarity index 94%
rename from django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue
rename to django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceValueEditor.vue
index 7bc1f26d..1ca3ef48 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceValueEditor.vue
@@ -1,5 +1,5 @@
 <template>
-  <extended-user-profile-field-editor v-bind="$props">
+  <extended-user-profile-value-editor v-bind="$props">
     <b-form-select
       v-model="value"
       :options="options"
@@ -33,7 +33,7 @@
         >Please specify a value for 'Other'.</b-form-invalid-feedback
       >
     </template>
-  </extended-user-profile-field-editor>
+  </extended-user-profile-value-editor>
 </template>
 
 <script>
@@ -41,12 +41,12 @@ import { mapGetters, mapMutations } from "vuex";
 import { validationMixin } from "vuelidate";
 import { required, requiredIf } from "vuelidate/lib/validators";
 import { errors } from "django-airavata-common-ui";
-import ExtendedUserProfileFieldEditor from "./ExtendedUserProfileFieldEditor.vue";
+import ExtendedUserProfileValueEditor from "./ExtendedUserProfileValueEditor.vue";
 const OTHER_OPTION = new Object(); // sentinel value
 
 export default {
   mixins: [validationMixin],
-  components: { ExtendedUserProfileFieldEditor },
+  components: { ExtendedUserProfileValueEditor },
   props: ["extendedUserProfileField"],
   data() {
     return {
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextValueEditor.vue
similarity index 86%
rename from django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue
rename to django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextValueEditor.vue
index 3e2a8e69..644ed825 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextValueEditor.vue
@@ -1,10 +1,10 @@
 <template>
-  <extended-user-profile-field-editor v-bind="$props">
+  <extended-user-profile-value-editor v-bind="$props">
     <b-form-input v-model="value" :state="validateState($v.value)" />
     <b-form-invalid-feedback :state="validateState($v.value)"
       >This field is required.</b-form-invalid-feedback
     >
-  </extended-user-profile-field-editor>
+  </extended-user-profile-value-editor>
 </template>
 
 <script>
@@ -12,10 +12,10 @@ import { mapGetters, mapMutations } from "vuex";
 import { validationMixin } from "vuelidate";
 import { requiredIf } from "vuelidate/lib/validators";
 import { errors } from "django-airavata-common-ui";
-import ExtendedUserProfileFieldEditor from "./ExtendedUserProfileFieldEditor.vue";
+import ExtendedUserProfileValueEditor from "./ExtendedUserProfileValueEditor.vue";
 export default {
   mixins: [validationMixin],
-  components: { ExtendedUserProfileFieldEditor },
+  components: { ExtendedUserProfileValueEditor },
   props: ["extendedUserProfileField"],
   computed: {
     ...mapGetters("extendedUserProfile", ["getTextValue"]),
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementValueEditor.vue
similarity index 89%
rename from django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementFieldEditor.vue
rename to django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementValueEditor.vue
index d0fe40a4..035044d0 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementFieldEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementValueEditor.vue
@@ -1,5 +1,5 @@
 <template>
-  <extended-user-profile-field-editor v-bind="$props">
+  <extended-user-profile-value-editor v-bind="$props">
     <b-form-checkbox
       v-model="value"
       :unchecked-value="false"
@@ -11,18 +11,18 @@
     <b-form-invalid-feedback :state="validateState($v.value)"
       >This field is required.</b-form-invalid-feedback
     >
-  </extended-user-profile-field-editor>
+  </extended-user-profile-value-editor>
 </template>
 
 <script>
 import { mapGetters, mapMutations } from "vuex";
 import { validationMixin } from "vuelidate";
 import { errors } from "django-airavata-common-ui";
-import ExtendedUserProfileFieldEditor from "./ExtendedUserProfileFieldEditor.vue";
+import ExtendedUserProfileValueEditor from "./ExtendedUserProfileValueEditor.vue";
 
 export default {
   mixins: [validationMixin],
-  components: { ExtendedUserProfileFieldEditor },
+  components: { ExtendedUserProfileValueEditor },
   props: ["extendedUserProfileField"],
   computed: {
     ...mapGetters("extendedUserProfile", ["getUserAgreementValue"]),
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileValueEditor.vue
similarity index 100%
rename from django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileFieldEditor.vue
rename to django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileValueEditor.vue
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js b/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
index 6e264490..9fef5c74 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
@@ -176,7 +176,7 @@ const mutations = {
       });
     }
   },
-  updateExperimentInputValue(state, { extendedUserProfileValue }) {
+  updateExtendedUserProfileValue(state, { extendedUserProfileValue }) {
     const index = state.extendedUserProfileValues.findIndex(
       (v) =>
         v.ext_user_profile_field ===


[airavata-django-portal] 05/08: AIRAVATA-3564 Allow adding and removing Links

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

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

commit 7d5bd3159717cfacfba87f1878b95c69ff330765
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Jun 3 14:08:38 2022 -0400

    AIRAVATA-3564 Allow adding and removing Links
---
 .../users/ExtendedUserProfileContainer.vue         | 17 +++--
 .../ExtendedUserProfileFieldEditor.vue             | 87 +++++++++++++++++-----
 .../src/store/modules/extendedUserProfile.js       | 33 ++++++++
 .../api/static/django_airavata_api/js/index.js     |  2 +
 .../js/models/ExtendedUserProfileFieldLink.js      | 15 ++++
 5 files changed, 128 insertions(+), 26 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
index 118655c2..bf4ab5a5 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
@@ -14,9 +14,15 @@
       <div class="col">
         <b-dropdown text="Add Field">
           <b-dropdown-item @click="addField('text')">Text</b-dropdown-item>
-          <b-dropdown-item @click="addField('single_choice')">Single Choice</b-dropdown-item>
-          <b-dropdown-item @click="addField('multi_choice')">Multi Choice</b-dropdown-item>
-          <b-dropdown-item @click="addField('user_agreement')">User Agreement</b-dropdown-item>
+          <b-dropdown-item @click="addField('single_choice')"
+            >Single Choice</b-dropdown-item
+          >
+          <b-dropdown-item @click="addField('multi_choice')"
+            >Multi Choice</b-dropdown-item
+          >
+          <b-dropdown-item @click="addField('user_agreement')"
+            >User Agreement</b-dropdown-item
+          >
         </b-dropdown>
       </div>
     </div>
@@ -34,8 +40,7 @@ import ExtendedUserProfileFieldEditor from "./field-editors/ExtendedUserProfileF
 export default {
   components: { ExtendedUserProfileFieldEditor },
   data() {
-    return {
-    };
+    return {};
   },
   created() {
     this.loadExtendedUserProfileFields();
@@ -48,7 +53,7 @@ export default {
     ]),
     addField(field_type) {
       // TODO: post an empty field to the API
-      this.addExtendedUserProfileField({field_type})
+      this.addExtendedUserProfileField({ field_type });
     },
     addOption(field) {
       if (!field.options) {
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
index b8bd9793..186b07f6 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
@@ -76,26 +76,52 @@
         Allow user to type in an "Other" option
       </b-form-checkbox>
     </b-card>
-    <!--
-          <template v-if="field.links && field.links.length > 0">
-            <b-card title="Links" v-for="link in field.links" :key="link.id">
-              <b-form-group label="Label">
-                <b-form-input v-model="link.label" />
-              </b-form-group>
-              <b-form-group label="URL">
-                <b-form-input v-model="link.url" />
-              </b-form-group>
-              <b-form-group label="Show as link?">
-                <b-form-checkbox v-model="link.display_link" />
-              </b-form-group>
-              <b-form-group label="Show inline?">
-                <b-form-checkbox v-model="link.display_inline" />
-              </b-form-group>
-            </b-card>
-          </template>
-          <b-button v-if="!field.links" @click="addLink(field)"
-            >Add Link</b-button
-          > -->
+
+    <template
+      v-if="
+        extendedUserProfileField.links &&
+        extendedUserProfileField.links.length > 0
+      "
+    >
+      <transition-group name="fade">
+        <b-card
+          :title="`Link: ${link.label}`"
+          v-for="link in extendedUserProfileField.links"
+          :key="link.key"
+        >
+          <b-form-group label="Label">
+            <b-form-input
+              :value="link.label"
+              @input="handleLinkLabelChanged(link, $event)"
+            />
+          </b-form-group>
+          <b-form-group label="URL">
+            <b-form-input
+              :value="link.url"
+              @input="handleLinkURLChanged(link, $event)"
+            />
+          </b-form-group>
+          <b-form-group label="Show as link?">
+            <b-form-checkbox
+              :checked="link.display_link"
+              @input="handleLinkDisplayLinkChanged(link, $event)"
+            />
+          </b-form-group>
+          <b-form-group label="Show inline?">
+            <b-form-checkbox
+              :checked="link.display_inline"
+              @input="handleLinkDisplayInlineChanged(link, $event)"
+            />
+          </b-form-group>
+          <b-button @click="handleLinkDeleted(link)" variant="danger">
+            Delete Link
+          </b-button>
+        </b-card>
+      </transition-group>
+    </template>
+    <b-button @click="addLink({ field: extendedUserProfileField })"
+      >Add Link</b-button
+    >
   </b-card>
 </template>
 
@@ -158,6 +184,12 @@ export default {
       "updateChoiceDisplayText",
       "deleteChoice",
       "updateChoiceIndex",
+      "addLink",
+      "updateLinkLabel",
+      "updateLinkURL",
+      "updateLinkDisplayLink",
+      "updateLinkDisplayInline",
+      "deleteLink",
     ]),
     handleChoiceDisplayTextChanged(choice, display_text) {
       this.updateChoiceDisplayText({ choice, display_text });
@@ -183,6 +215,21 @@ export default {
         index,
       });
     },
+    handleLinkLabelChanged(link, label) {
+      this.updateLinkLabel({ link, label });
+    },
+    handleLinkURLChanged(link, url) {
+      this.updateLinkURL({ link, url });
+    },
+    handleLinkDisplayLinkChanged(link, display_link) {
+      this.updateLinkDisplayLink({ link, display_link });
+    },
+    handleLinkDisplayInlineChanged(link, display_inline) {
+      this.updateLinkDisplayInline({ link, display_inline });
+    },
+    handleLinkDeleted(link) {
+      this.deleteLink({ field: this.extendedUserProfileField, link });
+    },
   },
 };
 </script>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js b/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
index 044f5749..1a577ad5 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
@@ -23,6 +23,10 @@ const actions = {
           commit("setChoiceOrder", { choice, order: index });
         }
       }
+      for (let index = 0; index < field.links.length; index++) {
+        const link = field.links[index];
+        commit("setLinkOrder", { link, order: index });
+      }
       // Create or update each field
       if (field.id) {
         await services.ExtendedUserProfileFieldService.update({
@@ -110,6 +114,35 @@ const mutations = {
     const index = field.choices.indexOf(choice);
     field.choices.splice(index, 1);
   },
+  addLink(state, { field }) {
+    field.links.push(
+      new models.ExtendedUserProfileFieldLink({
+        label: "",
+        url: "",
+        display_link: true,
+        display_inline: false,
+      })
+    );
+  },
+  updateLinkLabel(state, { link, label }) {
+    link.label = label;
+  },
+  updateLinkURL(state, { link, url }) {
+    link.url = url;
+  },
+  updateLinkDisplayLink(state, { link, display_link }) {
+    link.display_link = display_link;
+  },
+  updateLinkDisplayInline(state, { link, display_inline }) {
+    link.display_inline = display_inline;
+  },
+  setLinkOrder(state, { link, order }) {
+    link.order = order;
+  },
+  deleteLink(state, { field, link }) {
+    const index = field.links.indexOf(link);
+    field.links.splice(index, 1);
+  },
 };
 
 export default {
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 8f9f7597..b7255f03 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
@@ -20,6 +20,7 @@ import ExperimentSearchFields from "./models/ExperimentSearchFields";
 import ExperimentState from "./models/ExperimentState";
 import ExtendedUserProfileField from "./models/ExtendedUserProfileField";
 import ExtendedUserProfileFieldChoice from "./models/ExtendedUserProfileFieldChoice";
+import ExtendedUserProfileFieldLink from "./models/ExtendedUserProfileFieldLink";
 import FullExperiment from "./models/FullExperiment";
 import Group from "./models/Group";
 import GroupComputeResourcePreference from "./models/GroupComputeResourcePreference";
@@ -84,6 +85,7 @@ const models = {
   ExperimentState,
   ExtendedUserProfileField,
   ExtendedUserProfileFieldChoice,
+  ExtendedUserProfileFieldLink,
   FullExperiment,
   Group,
   GroupComputeResourcePreference,
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldLink.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldLink.js
index 5d52803a..31e007ea 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldLink.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldLink.js
@@ -1,4 +1,5 @@
 import BaseModel from "./BaseModel";
+import uuidv4 from "uuid/v4";
 
 const FIELDS = [
   "id",
@@ -12,5 +13,19 @@ const FIELDS = [
 export default class ExtendedUserProfileFieldLink extends BaseModel {
   constructor(data = {}) {
     super(FIELDS, data);
+    this._key = data.key ? data.key : uuidv4();
+  }
+
+  get key() {
+    return this._key;
+  }
+
+  toJSON() {
+    const copy = Object.assign({}, this);
+    // id must either have a value or be missing, it can't be null
+    if (!copy.id) {
+      delete copy.id;
+    }
+    return copy;
   }
 }


[airavata-django-portal] 08/08: AIRAVATA-3564 Some styling and layout improvements

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

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

commit 64387832c7e678e65d7ab7dd440d811cbaf19530
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Jun 3 16:11:43 2022 -0400

    AIRAVATA-3564 Some styling and layout improvements
---
 .../users/ExtendedUserProfileContainer.vue         |  51 ++++-----
 .../ExtendedUserProfileFieldEditor.vue             | 115 ++++++++++++---------
 django_airavata/static/common/scss/main.scss       |   2 +
 3 files changed, 97 insertions(+), 71 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
index e346c3cd..8a38d1bd 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
@@ -1,8 +1,12 @@
 <template>
-  <div>
-    <div class="row">
+  <div class="has-fixed-footer">
+    <div class="row mb-2">
       <div class="col-auto mr-auto">
-        <h1 class="h4 mb-4">Extended User Profile Editor</h1>
+        <h1 class="h4">Extended User Profile Editor</h1>
+        <p class="text-muted small">
+          Add and edit additional user profile fields for gateway users to
+          complete.
+        </p>
       </div>
     </div>
     <transition-group name="fade">
@@ -20,28 +24,23 @@
         </div>
       </div>
     </transition-group>
-    <div class="row">
-      <div class="col">
-        <b-dropdown text="Add Field">
-          <b-dropdown-item @click="addField('text')">Text</b-dropdown-item>
-          <b-dropdown-item @click="addField('single_choice')"
-            >Single Choice</b-dropdown-item
-          >
-          <b-dropdown-item @click="addField('multi_choice')"
-            >Multi Choice</b-dropdown-item
-          >
-          <b-dropdown-item @click="addField('user_agreement')"
-            >User Agreement</b-dropdown-item
-          >
-        </b-dropdown>
-      </div>
-    </div>
-    <div class="row mt-4">
-      <div class="col">
-        <b-button variant="primary" @click="save" :disabled="!valid"
-          >Save</b-button
+    <div ref="bottom" />
+    <div class="fixed-footer">
+      <b-dropdown text="Add Field">
+        <b-dropdown-item @click="addField('text')">Text</b-dropdown-item>
+        <b-dropdown-item @click="addField('single_choice')"
+          >Single Choice</b-dropdown-item
         >
-      </div>
+        <b-dropdown-item @click="addField('multi_choice')"
+          >Multi Choice</b-dropdown-item
+        >
+        <b-dropdown-item @click="addField('user_agreement')"
+          >User Agreement</b-dropdown-item
+        >
+      </b-dropdown>
+      <b-button variant="primary" @click="save" :disabled="!valid" class="ml-2"
+        >Save</b-button
+      >
     </div>
   </div>
 </template>
@@ -66,8 +65,10 @@ export default {
       "addExtendedUserProfileField",
     ]),
     addField(field_type) {
-      // TODO: post an empty field to the API
       this.addExtendedUserProfileField({ field_type });
+      this.$nextTick(() => {
+        this.$refs.bottom.scrollIntoView();
+      });
     },
     addOption(field) {
       if (!field.options) {
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
index 1e8f45ff..7f1371b1 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
@@ -1,16 +1,16 @@
 <template>
   <b-card :title="title">
-    <b-form-group label="Name">
+    <b-form-group label="Name" label-cols="3">
       <b-form-input v-model="name" :state="validateState($v.name)" />
       <b-form-invalid-feedback :state="validateState($v.name)"
         >This field is required.</b-form-invalid-feedback
       >
     </b-form-group>
-    <b-form-group label="Help text">
+    <b-form-group label="Help text" label-cols="3">
       <b-form-input v-model="help_text" />
     </b-form-group>
-    <b-form-group label="Required">
-      <b-form-checkbox v-model="required" />
+    <b-form-group>
+      <b-form-checkbox v-model="required" switch> Required </b-form-checkbox>
     </b-form-group>
     <b-card title="Options" v-if="extendedUserProfileField.supportsChoices">
       <transition-group name="fade">
@@ -65,34 +65,43 @@
             >
           </b-form-group>
         </template>
-        <b-input-group :key="'other'" v-if="extendedUserProfileField.other">
-          <b-form-input placeholder="Please specify" disabled />
-          <b-input-group-append>
-            <b-button disabled>
-              <i class="fa fa-arrow-up" aria-hidden="true"></i>
-            </b-button>
-            <b-button disabled>
-              <i class="fa fa-arrow-down" aria-hidden="true"></i>
-            </b-button>
-            <b-button
-              @click="other = false"
-              variant="danger"
-              v-b-tooltip.hover.left
-              title="Remove Other option"
-            >
-              <i class="fa fa-trash" aria-hidden="true"></i>
-            </b-button>
-          </b-input-group-append>
-        </b-input-group>
+        <b-form-group :key="'other'" v-if="extendedUserProfileField.other">
+          <b-input-group>
+            <b-form-input
+              placeholder="User will see: Other (please specify)"
+              disabled
+            />
+            <b-input-group-append>
+              <b-button disabled>
+                <i class="fa fa-arrow-up" aria-hidden="true"></i>
+              </b-button>
+              <b-button disabled>
+                <i class="fa fa-arrow-down" aria-hidden="true"></i>
+              </b-button>
+              <b-button
+                @click="other = false"
+                variant="danger"
+                v-b-tooltip.hover.left
+                title="Remove Other option"
+              >
+                <i class="fa fa-trash" aria-hidden="true"></i>
+              </b-button>
+            </b-input-group-append>
+          </b-input-group>
+        </b-form-group>
       </transition-group>
-      <div>
-        <b-button @click="addChoice({ field: extendedUserProfileField })"
+      <b-form-group>
+        <b-button
+          @click="addChoice({ field: extendedUserProfileField })"
+          size="sm"
           >Add Option</b-button
         >
-      </div>
-      <b-form-checkbox v-model="other" switch>
-        Allow user to type in an "Other" option
-      </b-form-checkbox>
+      </b-form-group>
+      <b-form-group>
+        <b-form-checkbox v-model="other" switch>
+          Allow user to type in an "Other" option
+        </b-form-checkbox>
+      </b-form-group>
     </b-card>
 
     <template v-if="links && links.length > 0">
@@ -103,7 +112,7 @@
             .$each.$iter"
           :key="link.key"
         >
-          <b-form-group label="Label">
+          <b-form-group label="Label" label-cols="3">
             <b-form-input
               :value="link.label"
               @input="handleLinkLabelChanged(link, $event, $v_label)"
@@ -113,7 +122,7 @@
               >This field is required.</b-form-invalid-feedback
             >
           </b-form-group>
-          <b-form-group label="URL">
+          <b-form-group label="URL" label-cols="3">
             <b-form-input
               :value="link.url"
               @input="handleLinkURLChanged(link, $event, $v_url)"
@@ -123,25 +132,37 @@
               >This field is required.</b-form-invalid-feedback
             >
           </b-form-group>
-          <b-form-group label="Show as link?">
-            <b-form-checkbox
-              :checked="link.display_link"
-              @input="handleLinkDisplayLinkChanged(link, $event)"
-            />
-          </b-form-group>
-          <b-form-group label="Show inline?">
-            <b-form-checkbox
-              :checked="link.display_inline"
-              @input="handleLinkDisplayInlineChanged(link, $event)"
-            />
-          </b-form-group>
-          <b-button @click="handleLinkDeleted(link)" variant="danger">
+          <b-row>
+            <b-col>
+              <b-form-group>
+                <b-form-checkbox
+                  :checked="link.display_link"
+                  @input="handleLinkDisplayLinkChanged(link, $event)"
+                  switch
+                >
+                  Show as link?
+                </b-form-checkbox>
+              </b-form-group>
+            </b-col>
+            <b-col>
+              <b-form-group>
+                <b-form-checkbox
+                  :checked="link.display_inline"
+                  @input="handleLinkDisplayInlineChanged(link, $event)"
+                  switch
+                >
+                  Show inline?
+                </b-form-checkbox>
+              </b-form-group>
+            </b-col>
+          </b-row>
+          <b-button @click="handleLinkDeleted(link)" variant="danger" size="sm">
             Delete Link
           </b-button>
         </b-card>
       </transition-group>
     </template>
-    <b-button @click="addLink({ field: extendedUserProfileField })"
+    <b-button @click="addLink({ field: extendedUserProfileField })" size="sm"
       >Add Link</b-button
     >
     <b-button
@@ -149,6 +170,7 @@
       :disabled="
         extendedUserProfileFields.indexOf(extendedUserProfileField) === 0
       "
+      size="sm"
       >Move Up</b-button
     >
     <b-button
@@ -157,9 +179,10 @@
         extendedUserProfileFields.indexOf(extendedUserProfileField) ===
         extendedUserProfileFields.length - 1
       "
+      size="sm"
       >Move Down</b-button
     >
-    <b-button @click="handleDelete" variant="danger">Delete</b-button>
+    <b-button @click="handleDelete" variant="danger" size="sm">Delete</b-button>
   </b-card>
 </template>
 
diff --git a/django_airavata/static/common/scss/main.scss b/django_airavata/static/common/scss/main.scss
index a5730b87..3314aa3b 100644
--- a/django_airavata/static/common/scss/main.scss
+++ b/django_airavata/static/common/scss/main.scss
@@ -360,6 +360,8 @@ $header_height_with_border: $header_height + 1px;
   bottom: 0px;
   left: 70px;
   right: 0px;
+  // keep fixed-footer above form controls
+  z-index: 3;
   padding-left: calc(1rem + 15px);
   padding-right: calc(1rem + 15px);
   padding-top: 1rem;


[airavata-django-portal] 07/08: AIRAVATA-3564 Adding validation to extended user profile fields

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

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

commit 0f94300ee1e3dc64efe013b3674e0ff350dfc657
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Jun 3 15:38:55 2022 -0400

    AIRAVATA-3564 Adding validation to extended user profile fields
---
 .../users/ExtendedUserProfileContainer.vue         |  11 +-
 .../ExtendedUserProfileFieldEditor.vue             | 168 +++++++++++++++------
 2 files changed, 130 insertions(+), 49 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
index 66524084..e346c3cd 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
@@ -14,6 +14,8 @@
         <div class="col">
           <extended-user-profile-field-editor
             :extendedUserProfileField="field"
+            @valid="recordValidChildComponent(field)"
+            @invalid="recordInvalidChildComponent(field)"
           />
         </div>
       </div>
@@ -36,7 +38,9 @@
     </div>
     <div class="row mt-4">
       <div class="col">
-        <b-button variant="primary" @click="save">Save</b-button>
+        <b-button variant="primary" @click="save" :disabled="!valid"
+          >Save</b-button
+        >
       </div>
     </div>
   </div>
@@ -45,7 +49,9 @@
 <script>
 import { mapActions, mapGetters } from "vuex";
 import ExtendedUserProfileFieldEditor from "./field-editors/ExtendedUserProfileFieldEditor.vue";
+import { mixins } from "django-airavata-common-ui";
 export default {
+  mixins: [mixins.ValidationParent],
   components: { ExtendedUserProfileFieldEditor },
   data() {
     return {};
@@ -105,6 +111,9 @@ export default {
   },
   computed: {
     ...mapGetters("extendedUserProfile", ["extendedUserProfileFields"]),
+    valid() {
+      return this.childComponentsAreValid;
+    },
   },
 };
 </script>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
index e09a8d36..1e8f45ff 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
@@ -1,7 +1,10 @@
 <template>
   <b-card :title="title">
     <b-form-group label="Name">
-      <b-form-input v-model="name" />
+      <b-form-input v-model="name" :state="validateState($v.name)" />
+      <b-form-invalid-feedback :state="validateState($v.name)"
+        >This field is required.</b-form-invalid-feedback
+      >
     </b-form-group>
     <b-form-group label="Help text">
       <b-form-input v-model="help_text" />
@@ -11,41 +14,56 @@
     </b-form-group>
     <b-card title="Options" v-if="extendedUserProfileField.supportsChoices">
       <transition-group name="fade">
-        <template v-for="(choice, index) in extendedUserProfileField.choices">
-          <b-input-group :key="choice.key">
-            <b-form-input
-              :value="choice.display_text"
-              @input="handleChoiceDisplayTextChanged(choice, $event)"
-            />
-            <b-input-group-append>
-              <b-button
-                @click="handleChoiceMoveUp(choice)"
-                :disabled="index === 0"
-                v-b-tooltip.hover.left
-                title="Move Up"
-              >
-                <i class="fa fa-arrow-up" aria-hidden="true"></i>
-              </b-button>
-              <b-button
-                @click="handleChoiceMoveDown(choice)"
-                :disabled="
-                  index === extendedUserProfileField.choices.length - 1
+        <template
+          v-for="({ $model: choice, display_text: $v_display_text },
+          index) in $v.choices.$each.$iter"
+        >
+          <b-form-group :key="choice.key">
+            <b-input-group>
+              <b-form-input
+                :value="choice.display_text"
+                @input="
+                  handleChoiceDisplayTextChanged(
+                    choice,
+                    $event,
+                    $v_display_text
+                  )
                 "
-                v-b-tooltip.hover.left
-                title="Move Down"
-              >
-                <i class="fa fa-arrow-down" aria-hidden="true"></i>
-              </b-button>
-              <b-button
-                @click="handleChoiceDeleted(choice)"
-                variant="danger"
-                v-b-tooltip.hover.left
-                title="Delete Option"
-              >
-                <i class="fa fa-trash" aria-hidden="true"></i>
-              </b-button>
-            </b-input-group-append>
-          </b-input-group>
+                :state="validateState($v_display_text)"
+              />
+              <b-input-group-append>
+                <b-button
+                  @click="handleChoiceMoveUp(choice)"
+                  :disabled="index === 0"
+                  v-b-tooltip.hover.left
+                  title="Move Up"
+                >
+                  <i class="fa fa-arrow-up" aria-hidden="true"></i>
+                </b-button>
+                <b-button
+                  @click="handleChoiceMoveDown(choice)"
+                  :disabled="
+                    index === extendedUserProfileField.choices.length - 1
+                  "
+                  v-b-tooltip.hover.left
+                  title="Move Down"
+                >
+                  <i class="fa fa-arrow-down" aria-hidden="true"></i>
+                </b-button>
+                <b-button
+                  @click="handleChoiceDeleted(choice)"
+                  variant="danger"
+                  v-b-tooltip.hover.left
+                  title="Delete Option"
+                >
+                  <i class="fa fa-trash" aria-hidden="true"></i>
+                </b-button>
+              </b-input-group-append>
+            </b-input-group>
+            <b-form-invalid-feedback :state="validateState($v_display_text)"
+              >This field is required.</b-form-invalid-feedback
+            >
+          </b-form-group>
         </template>
         <b-input-group :key="'other'" v-if="extendedUserProfileField.other">
           <b-form-input placeholder="Please specify" disabled />
@@ -77,29 +95,33 @@
       </b-form-checkbox>
     </b-card>
 
-    <template
-      v-if="
-        extendedUserProfileField.links &&
-        extendedUserProfileField.links.length > 0
-      "
-    >
+    <template v-if="links && links.length > 0">
       <transition-group name="fade">
         <b-card
           :title="`Link: ${link.label}`"
-          v-for="link in extendedUserProfileField.links"
+          v-for="{ $model: link, label: $v_label, url: $v_url } in $v.links
+            .$each.$iter"
           :key="link.key"
         >
           <b-form-group label="Label">
             <b-form-input
               :value="link.label"
-              @input="handleLinkLabelChanged(link, $event)"
+              @input="handleLinkLabelChanged(link, $event, $v_label)"
+              :state="validateState($v_label)"
             />
+            <b-form-invalid-feedback :state="validateState($v_label)"
+              >This field is required.</b-form-invalid-feedback
+            >
           </b-form-group>
           <b-form-group label="URL">
             <b-form-input
               :value="link.url"
-              @input="handleLinkURLChanged(link, $event)"
+              @input="handleLinkURLChanged(link, $event, $v_url)"
+              :state="validateState($v_url)"
             />
+            <b-form-invalid-feedback :state="validateState($v_url)"
+              >This field is required.</b-form-invalid-feedback
+            >
           </b-form-group>
           <b-form-group label="Show as link?">
             <b-form-checkbox
@@ -142,8 +164,12 @@
 </template>
 
 <script>
-import { mapActions, mapGetters, mapMutations } from "vuex";
+import { mapGetters, mapMutations } from "vuex";
+import { validationMixin } from "vuelidate";
+import { required } from "vuelidate/lib/validators";
+import { errors } from "django-airavata-common-ui";
 export default {
+  mixins: [validationMixin],
   props: ["extendedUserProfileField"],
   computed: {
     ...mapGetters("extendedUserProfile", ["extendedUserProfileFields"]),
@@ -153,6 +179,7 @@ export default {
       },
       set(value) {
         this.setName({ value, field: this.extendedUserProfileField });
+        this.$v.name.$touch();
       },
     },
     help_text: {
@@ -190,6 +217,39 @@ export default {
         this.name
       }`;
     },
+    choices() {
+      return this.extendedUserProfileField.choices;
+    },
+    links() {
+      return this.extendedUserProfileField.links;
+    },
+    valid() {
+      return !this.$v.$invalid;
+    },
+  },
+  validations() {
+    return {
+      name: {
+        required,
+      },
+      choices: {
+        $each: {
+          display_text: {
+            required,
+          },
+        },
+      },
+      links: {
+        $each: {
+          label: {
+            required,
+          },
+          url: {
+            required,
+          },
+        },
+      },
+    };
   },
   methods: {
     ...mapMutations("extendedUserProfile", [
@@ -210,8 +270,9 @@ export default {
       "updateFieldIndex",
       "deleteField",
     ]),
-    handleChoiceDisplayTextChanged(choice, display_text) {
+    handleChoiceDisplayTextChanged(choice, display_text, $v) {
       this.updateChoiceDisplayText({ choice, display_text });
+      $v.$touch();
     },
     handleChoiceDeleted(choice) {
       this.deleteChoice({ field: this.extendedUserProfileField, choice });
@@ -234,11 +295,13 @@ export default {
         index,
       });
     },
-    handleLinkLabelChanged(link, label) {
+    handleLinkLabelChanged(link, label, $v) {
       this.updateLinkLabel({ link, label });
+      $v.$touch();
     },
-    handleLinkURLChanged(link, url) {
+    handleLinkURLChanged(link, url, $v) {
       this.updateLinkURL({ link, url });
+      $v.$touch();
     },
     handleLinkDisplayLinkChanged(link, display_link) {
       this.updateLinkDisplayLink({ link, display_link });
@@ -264,6 +327,15 @@ export default {
         field: this.extendedUserProfileField,
       });
     },
+    validateState: errors.vuelidateHelpers.validateState,
+  },
+  watch: {
+    valid: {
+      handler(valid) {
+        this.$emit(valid ? "valid" : "invalid");
+      },
+      immediate: true,
+    },
   },
 };
 </script>


[airavata-django-portal] 06/08: AIRAVATA-3564 UI for reordering and deleting fields

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

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

commit 70a657a1abfb61a9ae94f521a1aa29ca56c70d36
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Jun 3 14:44:11 2022 -0400

    AIRAVATA-3564 UI for reordering and deleting fields
---
 .../users/ExtendedUserProfileContainer.vue         | 16 +++++++---
 .../ExtendedUserProfileFieldEditor.vue             | 36 +++++++++++++++++++++-
 .../src/store/modules/extendedUserProfile.js       | 25 +++++++++++++++
 .../js/models/ExtendedUserProfileField.js          |  7 +++++
 4 files changed, 79 insertions(+), 5 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
index bf4ab5a5..66524084 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ExtendedUserProfileContainer.vue
@@ -5,11 +5,19 @@
         <h1 class="h4 mb-4">Extended User Profile Editor</h1>
       </div>
     </div>
-    <div v-for="field in extendedUserProfileFields" class="row" :key="field.id">
-      <div class="col">
-        <extended-user-profile-field-editor :extendedUserProfileField="field" />
+    <transition-group name="fade">
+      <div
+        v-for="field in extendedUserProfileFields"
+        class="row"
+        :key="field.key"
+      >
+        <div class="col">
+          <extended-user-profile-field-editor
+            :extendedUserProfileField="field"
+          />
+        </div>
       </div>
-    </div>
+    </transition-group>
     <div class="row">
       <div class="col">
         <b-dropdown text="Add Field">
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
index 186b07f6..e09a8d36 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
@@ -122,14 +122,31 @@
     <b-button @click="addLink({ field: extendedUserProfileField })"
       >Add Link</b-button
     >
+    <b-button
+      @click="handleMoveUp({ field: extendedUserProfileField })"
+      :disabled="
+        extendedUserProfileFields.indexOf(extendedUserProfileField) === 0
+      "
+      >Move Up</b-button
+    >
+    <b-button
+      @click="handleMoveDown({ field: extendedUserProfileField })"
+      :disabled="
+        extendedUserProfileFields.indexOf(extendedUserProfileField) ===
+        extendedUserProfileFields.length - 1
+      "
+      >Move Down</b-button
+    >
+    <b-button @click="handleDelete" variant="danger">Delete</b-button>
   </b-card>
 </template>
 
 <script>
-import { mapMutations } from "vuex";
+import { mapActions, mapGetters, mapMutations } from "vuex";
 export default {
   props: ["extendedUserProfileField"],
   computed: {
+    ...mapGetters("extendedUserProfile", ["extendedUserProfileFields"]),
     name: {
       get() {
         return this.extendedUserProfileField.name;
@@ -190,6 +207,8 @@ export default {
       "updateLinkDisplayLink",
       "updateLinkDisplayInline",
       "deleteLink",
+      "updateFieldIndex",
+      "deleteField",
     ]),
     handleChoiceDisplayTextChanged(choice, display_text) {
       this.updateChoiceDisplayText({ choice, display_text });
@@ -230,6 +249,21 @@ export default {
     handleLinkDeleted(link) {
       this.deleteLink({ field: this.extendedUserProfileField, link });
     },
+    handleMoveUp({ field }) {
+      let index = this.extendedUserProfileFields.indexOf(field);
+      index--;
+      this.updateFieldIndex({ field, index });
+    },
+    handleMoveDown({ field }) {
+      let index = this.extendedUserProfileFields.indexOf(field);
+      index++;
+      this.updateFieldIndex({ field, index });
+    },
+    handleDelete() {
+      this.deleteField({
+        field: this.extendedUserProfileField,
+      });
+    },
   },
 };
 </script>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js b/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
index 1a577ad5..9ca0b917 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
@@ -2,6 +2,7 @@ import { models, services } from "django-airavata-api";
 
 const state = () => ({
   extendedUserProfileFields: null,
+  deletedExtendedUserProfileFields: [],
 });
 
 const getters = {
@@ -37,6 +38,14 @@ const actions = {
         await services.ExtendedUserProfileFieldService.create({ data: field });
       }
     }
+    if (state.deletedExtendedUserProfileFields.length > 0) {
+      for (const field of state.deletedExtendedUserProfileFields) {
+        await services.ExtendedUserProfileFieldService.delete({
+          lookup: field.id,
+        });
+      }
+      commit("resetDeletedExtendedUserProfileFields");
+    }
     // Reload the fields
     dispatch("loadExtendedUserProfileFields");
   },
@@ -143,6 +152,22 @@ const mutations = {
     const index = field.links.indexOf(link);
     field.links.splice(index, 1);
   },
+  updateFieldIndex(state, { field, index }) {
+    const currentIndex = state.extendedUserProfileFields.indexOf(field);
+    state.extendedUserProfileFields.splice(currentIndex, 1);
+    state.extendedUserProfileFields.splice(index, 0, field);
+  },
+  deleteField(state, { field }) {
+    const index = state.extendedUserProfileFields.indexOf(field);
+    state.extendedUserProfileFields.splice(index, 1);
+    // later when we save we'll need to sync this delete with the server
+    if (field.id) {
+      state.deletedExtendedUserProfileFields.push(field);
+    }
+  },
+  resetDeletedExtendedUserProfileFields(state) {
+    state.deletedExtendedUserProfileFields = [];
+  },
 };
 
 export default {
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js
index 94d5f8f6..04938ca5 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js
@@ -1,6 +1,7 @@
 import BaseModel from "./BaseModel";
 import ExtendedUserProfileFieldChoice from "./ExtendedUserProfileFieldChoice";
 import ExtendedUserProfileFieldLink from "./ExtendedUserProfileFieldLink";
+import uuidv4 from "uuid/v4";
 
 const FIELDS = [
   "id",
@@ -36,7 +37,13 @@ const FIELDS = [
 export default class ExtendedUserProfileField extends BaseModel {
   constructor(data = {}) {
     super(FIELDS, data);
+    this._key = data.key ? data.key : uuidv4();
   }
+
+  get key() {
+    return this._key;
+  }
+
   toJSON() {
     const copy = Object.assign({}, this);
     // Remove unnecessary properties


[airavata-django-portal] 03/08: AIRAVATA-3564 Add, remove and reorder choices

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

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

commit 141421a19906969dbc852af3b6191e9bfa03e97d
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Jun 3 13:12:18 2022 -0400

    AIRAVATA-3564 Add, remove and reorder choices
---
 .../ExtendedUserProfileFieldEditor.vue             | 105 ++++++++++++++++-----
 .../src/store/modules/extendedUserProfile.js       |  34 ++++++-
 .../api/static/django_airavata_api/js/index.js     |   2 +
 .../js/models/ExtendedUserProfileField.js          |   5 +
 .../js/models/ExtendedUserProfileFieldChoice.js    |  21 ++++-
 django_airavata/static/common/scss/main.scss       |   3 +
 6 files changed, 139 insertions(+), 31 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
index 99ae9765..575c0582 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
@@ -9,25 +9,50 @@
     <b-form-group label="Required">
       <b-form-checkbox v-model="required" />
     </b-form-group>
-    <!-- <b-card
-            title="Options"
-            v-if="
-              field.field_type === 'single_choice' ||
-              field.field_type === 'multi_choice'
-            "
-          >
-            <template v-for="choice in field.choices">
-              <b-input-group :key="choice.id">
-                <b-form-input v-model="choice.display_text" />
-                <b-input-group-append>
-                  <b-button @click="deleteOption(field, choice)"
-                    >Delete</b-button
-                  >
-                </b-input-group-append>
-              </b-input-group>
-            </template>
-            <b-button @click="addOption(field)">Add Option</b-button>
-          </b-card>
+    <b-card title="Options" v-if="extendedUserProfileField.supportsChoices">
+      <transition-group name="fade">
+        <template v-for="(choice, index) in extendedUserProfileField.choices">
+          <b-input-group :key="choice.key">
+            <b-form-input
+              :value="choice.display_text"
+              @input="handleChoiceDisplayTextChanged(choice, $event)"
+            />
+            <b-input-group-append>
+              <b-button
+                @click="handleChoiceMoveUp(choice)"
+                :disabled="index === 0"
+                v-b-tooltip.hover.left
+                title="Move Up"
+              >
+                <i class="fa fa-arrow-up" aria-hidden="true"></i>
+              </b-button>
+              <b-button
+                @click="handleChoiceMoveDown(choice)"
+                :disabled="
+                  index === extendedUserProfileField.choices.length - 1
+                "
+                v-b-tooltip.hover.left
+                title="Move Down"
+              >
+                <i class="fa fa-arrow-down" aria-hidden="true"></i>
+              </b-button>
+              <b-button
+                @click="handleChoiceDeleted(choice)"
+                variant="danger"
+                v-b-tooltip.hover.left
+                title="Delete Option"
+              >
+                <i class="fa fa-trash" aria-hidden="true"></i>
+              </b-button>
+            </b-input-group-append>
+          </b-input-group>
+        </template>
+      </transition-group>
+      <b-button @click="addChoice({ field: extendedUserProfileField })"
+        >Add Option</b-button
+      >
+    </b-card>
+    <!--
           <template v-if="field.links && field.links.length > 0">
             <b-card title="Links" v-for="link in field.links" :key="link.id">
               <b-form-group label="Label">
@@ -85,12 +110,46 @@ export default {
         single_choice: "Single Choice",
         multi_choice: "Multi Choice",
         user_agreement: "User Agreement",
-      }
-      return `${fieldTypes[this.extendedUserProfileField.field_type]}: ${this.name}`;
-    }
+      };
+      return `${fieldTypes[this.extendedUserProfileField.field_type]}: ${
+        this.name
+      }`;
+    },
   },
   methods: {
-    ...mapMutations("extendedUserProfile", ["setName", "setHelpText", "setRequired"]),
+    ...mapMutations("extendedUserProfile", [
+      "setName",
+      "setHelpText",
+      "setRequired",
+      "addChoice",
+      "updateChoiceDisplayText",
+      "deleteChoice",
+      "updateChoiceIndex",
+    ]),
+    handleChoiceDisplayTextChanged(choice, display_text) {
+      this.updateChoiceDisplayText({ choice, display_text });
+    },
+    handleChoiceDeleted(choice) {
+      this.deleteChoice({ field: this.extendedUserProfileField, choice });
+    },
+    handleChoiceMoveUp(choice) {
+      let index = this.extendedUserProfileField.choices.indexOf(choice);
+      index--;
+      this.updateChoiceIndex({
+        field: this.extendedUserProfileField,
+        choice,
+        index,
+      });
+    },
+    handleChoiceMoveDown(choice) {
+      let index = this.extendedUserProfileField.choices.indexOf(choice);
+      index++;
+      this.updateChoiceIndex({
+        field: this.extendedUserProfileField,
+        choice,
+        index,
+      });
+    },
   },
 };
 </script>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js b/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
index b3b7c4ec..ed2df6ac 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
@@ -16,7 +16,13 @@ const actions = {
   async saveExtendedUserProfileFields({ commit, dispatch, state }) {
     let order = 1;
     for (const field of state.extendedUserProfileFields) {
-      commit("setOrder", {field, order: order++});
+      commit("setOrder", { field, order: order++ });
+      if (field.supportsChoices) {
+        for (let index = 0; index < field.choices.length; index++) {
+          const choice = field.choices[index];
+          commit("setChoiceOrder", { choice, order: index });
+        }
+      }
       // Create or update each field
       if (field.id) {
         await services.ExtendedUserProfileFieldService.update({
@@ -70,8 +76,8 @@ const mutations = {
   setRequired(state, { value, field }) {
     setFieldProp(state, field, "required", value);
   },
-  setOrder(state, {order, field}) {
-    setFieldProp(state, field, 'order', order);
+  setOrder(state, { order, field }) {
+    setFieldProp(state, field, "order", order);
   },
   addExtendedUserProfileField(state, { field }) {
     if (!state.extendedUserProfileFields) {
@@ -79,6 +85,28 @@ const mutations = {
     }
     state.extendedUserProfileFields.push(field);
   },
+  addChoice(state, { field }) {
+    field.choices.push(
+      new models.ExtendedUserProfileFieldChoice({
+        display_text: "",
+      })
+    );
+  },
+  setChoiceOrder(state, { choice, order }) {
+    choice.order = order;
+  },
+  updateChoiceDisplayText(state, { choice, display_text }) {
+    choice.display_text = display_text;
+  },
+  updateChoiceIndex(state, { field, choice, index }) {
+    const currentIndex = field.choices.indexOf(choice);
+    field.choices.splice(currentIndex, 1);
+    field.choices.splice(index, 0, choice);
+  },
+  deleteChoice(state, { field, choice }) {
+    const index = field.choices.indexOf(choice);
+    field.choices.splice(index, 1);
+  },
 };
 
 export default {
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 8aa2bfc3..8f9f7597 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
@@ -19,6 +19,7 @@ import Experiment from "./models/Experiment";
 import ExperimentSearchFields from "./models/ExperimentSearchFields";
 import ExperimentState from "./models/ExperimentState";
 import ExtendedUserProfileField from "./models/ExtendedUserProfileField";
+import ExtendedUserProfileFieldChoice from "./models/ExtendedUserProfileFieldChoice";
 import FullExperiment from "./models/FullExperiment";
 import Group from "./models/Group";
 import GroupComputeResourcePreference from "./models/GroupComputeResourcePreference";
@@ -82,6 +83,7 @@ const models = {
   ExperimentSearchFields,
   ExperimentState,
   ExtendedUserProfileField,
+  ExtendedUserProfileFieldChoice,
   FullExperiment,
   Group,
   GroupComputeResourcePreference,
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js
index bef33e41..94d5f8f6 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js
@@ -61,4 +61,9 @@ export default class ExtendedUserProfileField extends BaseModel {
     }
     return copy;
   }
+  get supportsChoices() {
+    return (
+      this.field_type === "single_choice" || this.field_type === "multi_choice"
+    );
+  }
 }
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldChoice.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldChoice.js
index edd0f8a0..3ea1ea52 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldChoice.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldChoice.js
@@ -1,13 +1,24 @@
 import BaseModel from "./BaseModel";
+import uuidv4 from "uuid/v4";
 
-const FIELDS = [
-  "id",
-  "display_text",
-  "order",
-];
+const FIELDS = ["id", "display_text", "order"];
 
 export default class ExtendedUserProfileFieldChoice extends BaseModel {
   constructor(data = {}) {
     super(FIELDS, data);
+    this._key = data.key ? data.key : uuidv4();
+  }
+
+  get key() {
+    return this._key;
+  }
+
+  toJSON() {
+    const copy = Object.assign({}, this);
+    // id must either have a value or be missing, it can't be null
+    if (!copy.id) {
+      delete copy.id;
+    }
+    return copy;
   }
 }
diff --git a/django_airavata/static/common/scss/main.scss b/django_airavata/static/common/scss/main.scss
index 72698d50..a5730b87 100644
--- a/django_airavata/static/common/scss/main.scss
+++ b/django_airavata/static/common/scss/main.scss
@@ -253,6 +253,9 @@ $header_height_with_border: $header_height + 1px;
 .fade-leave-to {
   opacity: 0;
 }
+.fade-move {
+  transition: transform 0.3s;
+}
 
 /**
  * spacing between buttons. .btn-container is a class applied to a wrapper


[airavata-django-portal] 04/08: AIRAVATA-3564 Control whether an "Other" option is available

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

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

commit aaf2f2c6373e194580aaa128d0a3a60e5d4101a1
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Jun 3 13:26:17 2022 -0400

    AIRAVATA-3564 Control whether an "Other" option is available
---
 .../ExtendedUserProfileFieldEditor.vue             | 39 ++++++++++++++++++++--
 .../src/store/modules/extendedUserProfile.js       |  3 ++
 2 files changed, 39 insertions(+), 3 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
index 575c0582..b8bd9793 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/field-editors/ExtendedUserProfileFieldEditor.vue
@@ -47,10 +47,34 @@
             </b-input-group-append>
           </b-input-group>
         </template>
+        <b-input-group :key="'other'" v-if="extendedUserProfileField.other">
+          <b-form-input placeholder="Please specify" disabled />
+          <b-input-group-append>
+            <b-button disabled>
+              <i class="fa fa-arrow-up" aria-hidden="true"></i>
+            </b-button>
+            <b-button disabled>
+              <i class="fa fa-arrow-down" aria-hidden="true"></i>
+            </b-button>
+            <b-button
+              @click="other = false"
+              variant="danger"
+              v-b-tooltip.hover.left
+              title="Remove Other option"
+            >
+              <i class="fa fa-trash" aria-hidden="true"></i>
+            </b-button>
+          </b-input-group-append>
+        </b-input-group>
       </transition-group>
-      <b-button @click="addChoice({ field: extendedUserProfileField })"
-        >Add Option</b-button
-      >
+      <div>
+        <b-button @click="addChoice({ field: extendedUserProfileField })"
+          >Add Option</b-button
+        >
+      </div>
+      <b-form-checkbox v-model="other" switch>
+        Allow user to type in an "Other" option
+      </b-form-checkbox>
     </b-card>
     <!--
           <template v-if="field.links && field.links.length > 0">
@@ -104,6 +128,14 @@ export default {
         this.setRequired({ value, field: this.extendedUserProfileField });
       },
     },
+    other: {
+      get() {
+        return this.extendedUserProfileField.other;
+      },
+      set(value) {
+        this.setOther({ value, field: this.extendedUserProfileField });
+      },
+    },
     title() {
       const fieldTypes = {
         text: "Text",
@@ -121,6 +153,7 @@ export default {
       "setName",
       "setHelpText",
       "setRequired",
+      "setOther",
       "addChoice",
       "updateChoiceDisplayText",
       "deleteChoice",
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js b/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
index ed2df6ac..044f5749 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/store/modules/extendedUserProfile.js
@@ -79,6 +79,9 @@ const mutations = {
   setOrder(state, { order, field }) {
     setFieldProp(state, field, "order", order);
   },
+  setOther(state, { value, field }) {
+    setFieldProp(state, field, "other", value);
+  },
   addExtendedUserProfileField(state, { field }) {
     if (!state.extendedUserProfileFields) {
       state.extendedUserProfileFields = [];