You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2021/10/06 13:41:29 UTC

[airavata-django-portal] 05/13: AIRAVATA-3319 Add admin UI for updating user's username

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

machristie pushed a commit to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 4ce2dd26cbe093b611f4749add918d04e05984f2
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Jul 6 12:55:54 2021 -0400

    AIRAVATA-3319 Add admin UI for updating user's username
---
 django_airavata/apps/admin/package.json            |  1 +
 .../src/components/users/ChangeUsernamePanel.vue   | 92 ++++++++++++++++++++++
 .../IdentityServiceUserManagementContainer.vue     |  9 +++
 .../src/components/users/UserDetailsContainer.vue  |  7 ++
 django_airavata/apps/admin/yarn.lock               |  5 ++
 .../django_airavata_api/js/service_config.js       |  8 ++
 .../common/js/components/ConfirmationButton.vue    | 52 ++++++++++++
 django_airavata/static/common/js/index.js          |  2 +
 8 files changed, 176 insertions(+)

diff --git a/django_airavata/apps/admin/package.json b/django_airavata/apps/admin/package.json
index 670f5f5..94fde2f 100644
--- a/django_airavata/apps/admin/package.json
+++ b/django_airavata/apps/admin/package.json
@@ -26,6 +26,7 @@
     "vue-resource": "^1.3.4",
     "vue-router": "^2.7.0",
     "vuedraggable": "^2.16.0",
+    "vuelidate": "^0.7.6",
     "vuex": "^2.4.0",
     "weekstart": "^1.0.0"
   },
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ChangeUsernamePanel.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ChangeUsernamePanel.vue
new file mode 100644
index 0000000..42bc64b
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ChangeUsernamePanel.vue
@@ -0,0 +1,92 @@
+<template>
+  <b-card header="Change Username">
+    <p class="card-text">
+      This will change the user's username in the identity service. NOTE: if
+      this user already has an Airavata User Profile, giving the user a new
+      username will result in the user getting a new Airavata User Profile and
+      losing the old one. Also, after updating the username the user will need
+      to log out and log back in.
+    </p>
+    <b-form-group label="New Username" label-for="new-username">
+      <b-input-group>
+        <b-form-input
+          id="new-username"
+          v-model="$v.newUsername.$model"
+          :state="validateState($v.newUsername)"
+        />
+        <b-input-group-append>
+          <b-button @click="newUsername = email">Copy Email Address</b-button>
+        </b-input-group-append>
+      </b-input-group>
+      <b-form-invalid-feedback
+        :state="validateState($v.newUsername)"
+        v-if="!$v.newUsername.emailOrMatchesRegex"
+      >
+        Username can only contain lowercase letters, numbers, underscores and
+        hyphens OR it can be the same as the email address.
+      </b-form-invalid-feedback>
+    </b-form-group>
+    <confirmation-button
+      variant="primary"
+      @confirmed="updateUsername"
+      :disabled="$v.$invalid"
+      dialog-title="Please confirm username change"
+    >
+      Please confirm that you want to change the user's username to
+      <strong>{{ newUsername }}</strong
+      >. NOTE: if this user already has an Airavata User Profile, giving the
+      user a new username will result in
+      <strong
+        >the user getting a new Airavata User Profile and losing the old
+        one</strong
+      >. Also, after updating the username the user will need to log out and log
+      back in.
+    </confirmation-button>
+  </b-card>
+</template>
+
+<script>
+import { components, errors } from "django-airavata-common-ui";
+import { validationMixin } from "vuelidate";
+import { helpers, or, required, sameAs } from "vuelidate/lib/validators";
+export default {
+  name: "change-username-panel",
+  mixins: [validationMixin],
+  props: {
+    username: {
+      type: String,
+      required: true,
+    },
+    email: {
+      type: String,
+      required: true,
+    },
+  },
+  components: {
+    "confirmation-button": components.ConfirmationButton,
+  },
+  data() {
+    return {
+      newUsername: this.username,
+    };
+  },
+  validations() {
+    const usernameRegex = helpers.regex("newUsername", /^[a-z0-9_-]+$/);
+    const emailOrMatchesRegex = or(usernameRegex, sameAs("email"));
+    return {
+      newUsername: {
+        required,
+        emailOrMatchesRegex,
+      },
+    };
+  },
+  methods: {
+    updateUsername() {
+      if (!this.$v.$invalid) {
+        this.$emit("update-username", [this.username, this.newUsername]);
+      }
+    },
+    validateState: errors.vuelidateHelpers.validateState,
+  },
+};
+</script>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
index f7785da..458d718 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
@@ -47,6 +47,7 @@
                   @groups-updated="groupsUpdated"
                   @enable-user="enableUser"
                   @delete-user="deleteUser"
+                  @update-username="updateUsername(data.item, ...$event)"
                 />
               </template>
             </b-table>
@@ -204,6 +205,14 @@ export default {
         this.reloadUserProfiles()
       );
     },
+    updateUsername(userProfile, username, newUsername) {
+      const updatedUserProfile = userProfile.clone();
+      updatedUserProfile.userId = newUsername;
+      services.IAMUserProfileService.updateUsername({
+        lookup: username,
+        data: updatedUserProfile,
+      }).finally(() => this.reloadUserProfiles());
+    },
   },
 };
 </script>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserDetailsContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserDetailsContainer.vue
index 48b9516..e59428f 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserDetailsContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserDetailsContainer.vue
@@ -34,6 +34,11 @@
       :username="iamUserProfile.userId"
       @delete-user="$emit('delete-user', $event)"
     />
+    <change-username-panel
+      :username="iamUserProfile.userId"
+      :email="iamUserProfile.email"
+      @update-username="$emit('update-username', $event)"
+    />
   </div>
 </template>
 <script>
@@ -42,6 +47,7 @@ import UserGroupMembershipEditor from "./UserGroupMembershipEditor";
 import ActivateUserPanel from "./ActivateUserPanel";
 import EnableUserPanel from "./EnableUserPanel";
 import DeleteUserPanel from "./DeleteUserPanel";
+import ChangeUsernamePanel from "./ChangeUsernamePanel.vue";
 
 export default {
   name: "user-details-container",
@@ -60,6 +66,7 @@ export default {
     EnableUserPanel,
     DeleteUserPanel,
     ActivateUserPanel,
+    ChangeUsernamePanel,
   },
   data() {
     return {
diff --git a/django_airavata/apps/admin/yarn.lock b/django_airavata/apps/admin/yarn.lock
index 1e34aef..8b1d3b2 100644
--- a/django_airavata/apps/admin/yarn.lock
+++ b/django_airavata/apps/admin/yarn.lock
@@ -8574,6 +8574,11 @@ vuedraggable@^2.16.0:
   dependencies:
     sortablejs "^1.10.1"
 
+vuelidate@^0.7.6:
+  version "0.7.6"
+  resolved "https://registry.yarnpkg.com/vuelidate/-/vuelidate-0.7.6.tgz#84100c13b943470660d0416642845cd2a1edf4b2"
+  integrity sha512-suzIuet1jGcyZ4oUSW8J27R2tNrJ9cIfklAh63EbAkFjE380iv97BAiIeolRYoB9bF9usBXCu4BxftWN1Dkn3g==
+
 vuex@^2.4.0:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/vuex/-/vuex-2.5.0.tgz#20f0265ade6c9a5ac6724a405d3ffdb4726c9741"
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 64ad6b1..d3c82b0 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
@@ -287,6 +287,14 @@ export default {
         requestType: "post",
         modelClass: IAMUserProfile,
       },
+      updateUsername: {
+        url: "/api/iam-user-profiles/<lookup>/update_username/",
+        bodyParams: {
+          name: "data",
+        },
+        requestType: "put",
+        modelClass: IAMUserProfile,
+      },
     },
     queryParams: ["limit", "offset", "search"],
     modelClass: IAMUserProfile,
diff --git a/django_airavata/static/common/js/components/ConfirmationButton.vue b/django_airavata/static/common/js/components/ConfirmationButton.vue
new file mode 100644
index 0000000..c327045
--- /dev/null
+++ b/django_airavata/static/common/js/components/ConfirmationButton.vue
@@ -0,0 +1,52 @@
+<template>
+  <div class="confirmation-button">
+    <b-button
+      :variant="variant"
+      @click="$refs.modal.show()"
+      :disabled="disabled"
+    >
+      {{ label }}
+    </b-button>
+    <confirmation-dialog
+      ref="modal"
+      :title="dialogTitle"
+      @ok="$emit('confirmed')"
+    >
+      <slot></slot>
+    </confirmation-dialog>
+  </div>
+</template>
+<script>
+import ConfirmationDialog from "./ConfirmationDialog.vue";
+
+export default {
+  name: "confirmation-button",
+  props: {
+    dialogTitle: {
+      type: String,
+      default: "Please confirm",
+    },
+    disabled: {
+      type: Boolean,
+      default: false,
+    },
+    label: {
+      type: String,
+      default: "Update",
+    },
+    variant: {
+      type: String,
+      default: "danger",
+    },
+  },
+  components: {
+    ConfirmationDialog,
+  },
+};
+</script>
+
+<style scoped>
+.confirmation-button {
+  display: inline-block;
+}
+</style>
diff --git a/django_airavata/static/common/js/index.js b/django_airavata/static/common/js/index.js
index ac905a9..b4bcc5e 100644
--- a/django_airavata/static/common/js/index.js
+++ b/django_airavata/static/common/js/index.js
@@ -4,6 +4,7 @@ import AutocompleteTextInput from "./components/AutocompleteTextInput.vue";
 import ClipboardCopyButton from "./components/ClipboardCopyButton.vue";
 import ClipboardCopyLink from "./components/ClipboardCopyLink.vue";
 import ComputeResourceName from "./components/ComputeResourceName";
+import ConfirmationButton from "./components/ConfirmationButton.vue";
 import ConfirmationDialog from "./components/ConfirmationDialog.vue";
 import DataProductViewer from "./components/DataProductViewer";
 import DeleteButton from "./components/DeleteButton.vue";
@@ -47,6 +48,7 @@ const components = {
   ClipboardCopyButton,
   ClipboardCopyLink,
   ComputeResourceName,
+  ConfirmationButton,
   ConfirmationDialog,
   DataProductViewer,
   DeleteButton,