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 493ed1a58f7f88963d397c4bb6cbe2217b3e829e
Author: Marcus Christie <[email protected]>
AuthorDate: Fri Apr 15 17:23:22 2022 -0400

    AIRAVATA-3565 WIP: Ext User Profile UI with load/saving text and 
single_choice values
---
 .../api/static/django_airavata_api/js/index.js     |   6 ++
 .../js/models/ExtendedUserProfileField.js          |  39 ++++++++
 .../js/models/ExtendedUserProfileFieldChoice.js    |  13 +++
 .../js/models/ExtendedUserProfileFieldLink.js      |  16 +++
 .../js/models/ExtendedUserProfileValue.js          |  18 ++++
 .../django_airavata_api/js/service_config.js       |  10 ++
 django_airavata/apps/auth/serializers.py           |   2 +-
 .../js/components/ExtendedUserProfileEditor.vue    |  42 ++++++++
 .../ExtendedUserProfileSingleChoiceFieldEditor.vue |  45 +++++++++
 .../ExtendedUserProfileTextFieldEditor.vue         |  31 ++++++
 .../js/containers/UserProfileContainer.vue         |  12 ++-
 .../static/django_airavata_auth/js/store/index.js  |   2 +
 .../js/store/modules/extendedUserProfile.js        | 108 +++++++++++++++++++++
 django_airavata/apps/auth/urls.py                  |   4 +-
 14 files changed, 344 insertions(+), 4 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 ca08f1d7..e177e42f 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
@@ -120,6 +120,12 @@ const services = {
   ExperimentStoragePathService: ServiceFactory.service(
     "ExperimentStoragePaths"
   ),
+  ExtendedUserProfileFieldService: ServiceFactory.service(
+    "ExtendedUserProfileFields"
+  ),
+  ExtendedUserProfileValueService: ServiceFactory.service(
+    "ExtendedUserProfileValues"
+  ),
   FullExperimentService: ServiceFactory.service("FullExperiments"),
   GatewayResourceProfileService: ServiceFactory.service(
     "GatewayResourceProfile"
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
new file mode 100644
index 00000000..8b719db1
--- /dev/null
+++ 
b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js
@@ -0,0 +1,39 @@
+import BaseModel from "./BaseModel";
+import ExtendedUserProfileFieldChoice from "./ExtendedUserProfileFieldChoice";
+import ExtendedUserProfileFieldLink from "./ExtendedUserProfileFieldLink";
+
+const FIELDS = [
+  "id",
+  "name",
+  "help_text",
+  "order",
+  {
+    name: "created_date",
+    type: "date",
+  },
+  {
+    name: "updated_date",
+    type: "date",
+  },
+  "field_type",
+  {
+    name: "links",
+    list: true,
+    type: ExtendedUserProfileFieldLink,
+  },
+  // For user_agreement type
+  "checkbox_label",
+  // For single_choice and multi_choice types
+  {
+    name: "choices",
+    list: true,
+    type: ExtendedUserProfileFieldChoice,
+  },
+  "other",
+];
+
+export default class ExtendedUserProfileField extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+}
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
new file mode 100644
index 00000000..edd0f8a0
--- /dev/null
+++ 
b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldChoice.js
@@ -0,0 +1,13 @@
+import BaseModel from "./BaseModel";
+
+const FIELDS = [
+  "id",
+  "display_text",
+  "order",
+];
+
+export default class ExtendedUserProfileFieldChoice extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+}
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
new file mode 100644
index 00000000..5d52803a
--- /dev/null
+++ 
b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldLink.js
@@ -0,0 +1,16 @@
+import BaseModel from "./BaseModel";
+
+const FIELDS = [
+  "id",
+  "label",
+  "url",
+  "order",
+  "display_link",
+  "display_inline",
+];
+
+export default class ExtendedUserProfileFieldLink extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+}
diff --git 
a/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileValue.js
 
b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileValue.js
new file mode 100644
index 00000000..471faec7
--- /dev/null
+++ 
b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileValue.js
@@ -0,0 +1,18 @@
+import BaseModel from "./BaseModel";
+
+// TODO: do we need this?
+const FIELDS = [
+  "id",
+  "value_type",
+  "ext_user_profile_field",
+  "text_value",
+  "choices",
+  "other_value",
+  "agreement_value",
+];
+
+export default class ExtendedUserProfileValue extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+}
diff --git 
a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js 
b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
index 152a3961..941ae12c 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
@@ -10,6 +10,7 @@ import ExperimentSearchFields from 
"./models/ExperimentSearchFields";
 import ExperimentStatistics from "./models/ExperimentStatistics";
 import ExperimentStoragePath from "./models/ExperimentStoragePath";
 import ExperimentSummary from "./models/ExperimentSummary";
+import ExtendedUserProfileField from "./models/ExtendedUserProfileField";
 import FullExperiment from "./models/FullExperiment";
 import GatewayResourceProfile from "./models/GatewayResourceProfile";
 import Group from "./models/Group";
@@ -237,6 +238,15 @@ export default {
       },
     },
   },
+  ExtendedUserProfileFields: {
+    url: "/auth/extended-user-profile-fields",
+    viewSet: true,
+    modelClass: ExtendedUserProfileField,
+  },
+  ExtendedUserProfileValues: {
+    url: "/auth/extended-user-profile-values",
+    viewSet: true,
+  },
   FullExperiments: {
     url: "/api/full-experiments",
     viewSet: [
diff --git a/django_airavata/apps/auth/serializers.py 
b/django_airavata/apps/auth/serializers.py
index 66dd54f6..61fc0239 100644
--- a/django_airavata/apps/auth/serializers.py
+++ b/django_airavata/apps/auth/serializers.py
@@ -222,7 +222,7 @@ class 
ExtendedUserProfileFieldSerializer(serializers.ModelSerializer):
 
 class ExtendedUserProfileValueSerializer(serializers.ModelSerializer):
     text_value = serializers.CharField(required=False, allow_blank=True)
-    choices = serializers.ListField(child=serializers.IntegerField(), 
required=False, allow_empty=False, min_length=1)
+    choices = serializers.ListField(child=serializers.IntegerField(), 
required=False)
     other_value = serializers.CharField(required=False, allow_blank=True)
     agreement_value = serializers.BooleanField(required=False)
 
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
new file mode 100644
index 00000000..3b1cfea1
--- /dev/null
+++ 
b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
@@ -0,0 +1,42 @@
+<template>
+  <b-card>
+    <template v-for="extendedUserProfileField in extendedUserProfileFields">
+      <component
+        :key="extendedUserProfileField.id"
+        :is="getEditor(extendedUserProfileField)"
+        :extended-user-profile-field="extendedUserProfileField"
+      />
+    </template>
+  </b-card>
+</template>
+
+<script>
+import { mapGetters } from "vuex";
+import ExtendedUserProfileSingleChoiceFieldEditor from 
"./ExtendedUserProfileSingleChoiceFieldEditor.vue";
+import ExtendedUserProfileTextFieldEditor from 
"./ExtendedUserProfileTextFieldEditor.vue";
+export default {
+  computed: {
+    ...mapGetters("extendedUserProfile", ["extendedUserProfileFields"]),
+  },
+  methods: {
+    getEditor(extendedUserProfileField) {
+      const fieldTypeEditors = {
+        text: ExtendedUserProfileTextFieldEditor,
+        single_choice: ExtendedUserProfileSingleChoiceFieldEditor,
+      };
+
+      if (extendedUserProfileField.field_type in fieldTypeEditors) {
+        return fieldTypeEditors[extendedUserProfileField.field_type];
+      } else {
+        // eslint-disable-next-line no-console
+        console.error(
+          "Unexpected field_type",
+          extendedUserProfileField.field_type
+        );
+      }
+    },
+  },
+};
+</script>
+
+<style></style>
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/ExtendedUserProfileSingleChoiceFieldEditor.vue
new file mode 100644
index 00000000..bf95019f
--- /dev/null
+++ 
b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue
@@ -0,0 +1,45 @@
+<template>
+  <b-form-group
+    :label="extendedUserProfileField.name"
+    :description="extendedUserProfileField.help_text"
+  >
+    <b-form-select v-model="value" :options="options"></b-form-select>
+  </b-form-group>
+</template>
+
+<script>
+import { mapGetters, mapMutations } from "vuex";
+export default {
+  props: ["extendedUserProfileField"],
+  computed: {
+    ...mapGetters("extendedUserProfile", ["getSingleChoiceValue"]),
+    value: {
+      get() {
+        return this.getSingleChoiceValue(this.extendedUserProfileField.id);
+      },
+      set(value) {
+        this.setSingleChoiceValue({
+          value,
+          id: this.extendedUserProfileField.id,
+        });
+      },
+    },
+    options() {
+      return this.extendedUserProfileField &&
+        this.extendedUserProfileField.choices
+        ? this.extendedUserProfileField.choices.map((choice) => {
+            return {
+              value: choice.id,
+              text: choice.display_text,
+            };
+          })
+        : [];
+    },
+  },
+  methods: {
+    ...mapMutations("extendedUserProfile", ["setSingleChoiceValue"]),
+  },
+};
+</script>
+
+<style></style>
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/ExtendedUserProfileTextFieldEditor.vue
new file mode 100644
index 00000000..8f2b9989
--- /dev/null
+++ 
b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue
@@ -0,0 +1,31 @@
+<template>
+  <b-form-group
+    :label="extendedUserProfileField.name"
+    :description="extendedUserProfileField.help_text"
+  >
+    <b-form-input v-model="value" />
+  </b-form-group>
+</template>
+
+<script>
+import { mapGetters, mapMutations } from 'vuex';
+export default {
+  props: ["extendedUserProfileField"],
+  computed: {
+    ...mapGetters('extendedUserProfile', ['getTextValue']),
+    value: {
+      get() {
+        return this.getTextValue(this.extendedUserProfileField.id);
+      },
+      set(value) {
+        this.setTextValue({value, id: this.extendedUserProfileField.id})
+      }
+    }
+  },
+  methods: {
+    ...mapMutations('extendedUserProfile', ['setTextValue']),
+  }
+};
+</script>
+
+<style></style>
diff --git 
a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
 
b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
index 65499d58..4d44405f 100644
--- 
a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
+++ 
b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
@@ -21,6 +21,9 @@
       @save="onSave"
       @resend-email-verification="handleResendEmailVerification"
     />
+    <!-- TODO: include both forms in the same card -->
+    <!-- include extended-user-profile-editor if there are 
extendedUserProfileFields -->
+    <extended-user-profile-editor v-if="extendedUserProfileFields && 
extendedUserProfileFields.length > 0"/>
     <b-link
       v-if="user && user.complete"
       class="text-muted small"
@@ -34,12 +37,15 @@
 import UserProfileEditor from "../components/UserProfileEditor.vue";
 import { notifications } from "django-airavata-common-ui";
 import { mapActions, mapGetters } from "vuex";
+import ExtendedUserProfileEditor from 
'../components/ExtendedUserProfileEditor.vue';
 
 export default {
-  components: { UserProfileEditor },
+  components: { UserProfileEditor, ExtendedUserProfileEditor },
   name: "user-profile-container",
   async created() {
     await this.loadCurrentUser();
+    await this.loadExtendedUserProfileFields();
+    await this.loadExtendedUserProfileValues();
 
     const queryParams = new URLSearchParams(window.location.search);
     if (queryParams.has("code")) {
@@ -60,6 +66,7 @@ export default {
   },
   computed: {
     ...mapGetters("userProfile", ["user"]),
+    ...mapGetters("extendedUserProfile", ["extendedUserProfileFields"]),
   },
   methods: {
     ...mapActions("userProfile", [
@@ -68,7 +75,10 @@ export default {
       "updateUser",
       "resendEmailVerification",
     ]),
+    ...mapActions("extendedUserProfile", ["loadExtendedUserProfileFields", 
"loadExtendedUserProfileValues", "saveExtendedUserProfileValues"]),
     async onSave() {
+      // TODO: only save if both standard and extended user profiles are valid
+      this.saveExtendedUserProfileValues();
       if (this.$refs.userProfileEditor.valid) {
         await this.updateUser();
         notifications.NotificationList.add(
diff --git 
a/django_airavata/apps/auth/static/django_airavata_auth/js/store/index.js 
b/django_airavata/apps/auth/static/django_airavata_auth/js/store/index.js
index bb13e8ac..8dd262a7 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/store/index.js
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/store/index.js
@@ -1,5 +1,6 @@
 import Vuex from "vuex";
 import userProfile from "./modules/userProfile";
+import extendedUserProfile from "./modules/extendedUserProfile";
 
 const debug = process.env.NODE_ENV !== "production";
 
@@ -8,6 +9,7 @@ function createStore(Vue) {
   return new Vuex.Store({
     modules: {
       userProfile,
+      extendedUserProfile,
     },
     strict: debug,
   });
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 e69de29b..85c085d7 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
@@ -0,0 +1,108 @@
+import { services } from "django-airavata-api";
+
+const state = () => ({
+  extendedUserProfileFields: null,
+  extendedUserProfileValues: [],
+});
+
+const getters = {
+  extendedUserProfileFields: (state) => state.extendedUserProfileFields,
+  extendedUserProfileValues: (state) => state.extendedUserProfileValues,
+  getTextValue: (state) => (id) => {
+    const value = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    return value ? value.text_value : null;
+  },
+  getSingleChoiceValue: (state) => (id) => {
+    const value = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    if (value && value.choices && value.choices.length === 1) {
+      return value.choices[0];
+    } else {
+      return null;
+    }
+  },
+};
+
+const actions = {
+  async loadExtendedUserProfileFields({ commit }) {
+    const extendedUserProfileFields = await 
services.ExtendedUserProfileFieldService.list();
+    commit("setExtendedUserProfileFields", { extendedUserProfileFields });
+  },
+  async loadExtendedUserProfileValues({ commit }) {
+    const extendedUserProfileValues = await 
services.ExtendedUserProfileValueService.list();
+    commit("setExtendedUserProfileValues", { extendedUserProfileValues });
+  },
+  async saveExtendedUserProfileValues({ state, commit }) {
+    for (const value of state.extendedUserProfileValues) {
+      // Create or update each value
+      if (value.id) {
+        await services.ExtendedUserProfileValueService.update({
+          lookup: value.id,
+          data: value,
+        });
+      } else {
+        const extendedUserProfileValue = await 
services.ExtendedUserProfileValueService.create(
+          { data: value }
+        );
+        commit("updateExtendedUserProfileValue", { extendedUserProfileValue });
+      }
+    }
+  },
+};
+
+const mutations = {
+  setExtendedUserProfileFields(state, { extendedUserProfileFields }) {
+    state.extendedUserProfileFields = extendedUserProfileFields;
+  },
+  setExtendedUserProfileValues(state, { extendedUserProfileValues }) {
+    state.extendedUserProfileValues = extendedUserProfileValues;
+  },
+  setTextValue(state, { value, id }) {
+    const profileValue = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    if (profileValue) {
+      profileValue.text_value = value;
+    } else {
+      state.extendedUserProfileValues.push({
+        value_type: "text",
+        ext_user_profile_field: id,
+        text_value: value,
+      });
+    }
+  },
+  setSingleChoiceValue(state, { value, id }) {
+    const profileValue = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    if (profileValue) {
+      profileValue.choices = [value];
+      profileValue.other_value = "";
+    } else {
+      state.extendedUserProfileValues.push({
+        value_type: "single_choice",
+        ext_user_profile_field: id,
+        choices: [value],
+      });
+    }
+  },
+  updateExperimentInputValue(state, { extendedUserProfileValue }) {
+    const index = state.extendedUserProfileValues.findIndex(
+      (v) =>
+        v.ext_user_profile_field ===
+        extendedUserProfileValue.ext_user_profile_field
+    );
+    state.extendedUserProfileValues.splice(index, 1, extendedUserProfileValue);
+  },
+};
+
+export default {
+  namespaced: true,
+  state,
+  getters,
+  actions,
+  mutations,
+};
diff --git a/django_airavata/apps/auth/urls.py 
b/django_airavata/apps/auth/urls.py
index 8ca2f532..a7622718 100644
--- a/django_airavata/apps/auth/urls.py
+++ b/django_airavata/apps/auth/urls.py
@@ -7,8 +7,8 @@ from . import views
 
 router = routers.DefaultRouter()
 router.register(r'users', views.UserViewSet, basename='user')
-router.register(r'extended-user-profile-fields', 
views.ExtendedUserProfileFieldViewset, basename='extend-user-profile-field')
-router.register(r'extended-user-profile-values', 
views.ExtendedUserProfileValueViewset, basename='extend-user-profile-value')
+router.register(r'extended-user-profile-fields', 
views.ExtendedUserProfileFieldViewset, basename='extended-user-profile-field')
+router.register(r'extended-user-profile-values', 
views.ExtendedUserProfileValueViewset, basename='extended-user-profile-value')
 app_name = 'django_airavata_auth'
 urlpatterns = [
     re_path(r'^', include(router.urls)),

Reply via email to