forked from External/greenlight
		
	GRN2-260: Added the ability to merge user accounts (#938)
* Added the ability to merge user accounts * Styling fixes
This commit is contained in:
		| @@ -41,6 +41,49 @@ $(document).on('turbolinks:load', function(){ | ||||
|  | ||||
|         updateTabParams(this.id) | ||||
|       }) | ||||
|  | ||||
|       $('.selectpicker').selectpicker({ | ||||
|         liveSearchPlaceholder: getLocalizedString('javascript.search.start') | ||||
|       }); | ||||
|       // Fixes turbolinks issue with bootstrap select | ||||
|       $(window).trigger('load.bs.select.data-api'); | ||||
|        | ||||
|       // Display merge accounts modal with correct info | ||||
|       $(".merge-user").click(function() { | ||||
|         // Update the path of save button | ||||
|         $("#merge-save-access").attr("data-path", $(this).data("path")) | ||||
|  | ||||
|         let userInfo = $(this).data("info") | ||||
|  | ||||
|         $("#merge-to").html("<span>" + userInfo.name + "</span>" + "<span class='text-muted d-block'>" + userInfo.email + "</span>" + "<span class='text-muted d-block'>" + userInfo.uid + "</span>") | ||||
|   | ||||
|       }) | ||||
|  | ||||
|       $("#mergeUserModal").on("show.bs.modal", function() { | ||||
|         $(".selectpicker").selectpicker('val','') | ||||
|       }) | ||||
|    | ||||
|       $(".bootstrap-select").on("click", function() { | ||||
|         $(".bs-searchbox").siblings().hide() | ||||
|       }) | ||||
|    | ||||
|       $(".bs-searchbox input").on("input", function() { | ||||
|         if ($(".bs-searchbox input").val() == '' || $(".bs-searchbox input").val().length < 3) { | ||||
|           $(".bs-searchbox").siblings().hide() | ||||
|         } else { | ||||
|           $(".bs-searchbox").siblings().show() | ||||
|         } | ||||
|       }) | ||||
|  | ||||
|       // User selects an option from the Room Access dropdown | ||||
|       $(".bootstrap-select").on("changed.bs.select", function(){ | ||||
|         // Get the uid of the selected user | ||||
|         let user = $(".selectpicker").selectpicker('val') | ||||
|         if (user != "") { | ||||
|           userInfo = JSON.parse(user) | ||||
|           $("#merge-from").html("<span>" + userInfo.name + "</span>" + "<span class='text-muted d-block'>" + userInfo.email + "</span>" + "<span id='from-uid' class='text-muted d-block'>" + userInfo.uid + "</span>") | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|     else if(action == "site_settings"){ | ||||
|       loadColourSelectors() | ||||
| @@ -79,6 +122,11 @@ function changeBrandingImage(path) { | ||||
|   $.post(path, {value: url}) | ||||
| } | ||||
|  | ||||
| function mergeUsers() { | ||||
|   let userToMerge = $("#from-uid").text() | ||||
|   $.post($("#merge-save-access").data("path"), {merge: userToMerge}) | ||||
| } | ||||
|  | ||||
| // Filters by role | ||||
| function filterRole(role) { | ||||
|   var search = new URL(location.href).searchParams.get('search') | ||||
|   | ||||
| @@ -69,7 +69,7 @@ $(document).on('turbolinks:load', function(){ | ||||
|     }) | ||||
|  | ||||
|     $('.selectpicker').selectpicker({ | ||||
|       liveSearchPlaceholder: "Start searching..." | ||||
|       liveSearchPlaceholder: getLocalizedString('javascript.search.start') | ||||
|     }); | ||||
|     // Fixes turbolinks issue with bootstrap select | ||||
|     $(window).trigger('load.bs.select.data-api'); | ||||
|   | ||||
| @@ -89,3 +89,11 @@ | ||||
|     cursor: pointer; | ||||
|   } | ||||
| } | ||||
|  | ||||
| #merge-account-arrow { | ||||
|   position: absolute; | ||||
|   top: 47%; | ||||
|   right: 47%; | ||||
|   z-index: 999; | ||||
|   background: white; | ||||
| } | ||||
| @@ -24,7 +24,7 @@ class AdminsController < ApplicationController | ||||
|   include Rolify | ||||
|   include Populator | ||||
|  | ||||
|   manage_users = [:edit_user, :promote, :demote, :ban_user, :unban_user, :approve, :reset] | ||||
|   manage_users = [:edit_user, :promote, :demote, :ban_user, :unban_user, :approve, :reset, :merge_user] | ||||
|   manage_deleted_users = [:undelete] | ||||
|   authorize_resource class: false | ||||
|   before_action :find_user, only: manage_users | ||||
| @@ -41,6 +41,8 @@ class AdminsController < ApplicationController | ||||
|     @role = params[:role] ? Role.find_by(name: params[:role], provider: @user_domain) : nil | ||||
|     @tab = params[:tab] || "active" | ||||
|  | ||||
|     @user_list = merge_user_list | ||||
|  | ||||
|     @pagy, @users = pagy(manage_users_list) | ||||
|   end | ||||
|  | ||||
| @@ -140,6 +142,45 @@ class AdminsController < ApplicationController | ||||
|  | ||||
|     redirect_to redirect_path, flash: { success: I18n.t("administrator.flash.reset_password") } | ||||
|   end | ||||
|  | ||||
|   # POST /admins/merge/:user_uid | ||||
|   def merge_user | ||||
|     begin | ||||
|       # Get uid of user that will be merged into the other account | ||||
|       uid_to_merge = params[:merge] | ||||
|       logger.info "#{current_user.uid} is attempting to merge #{uid_to_merge} into #{@user.uid}" | ||||
|  | ||||
|       # Check to make sure the 2 users are unique | ||||
|       raise "Can not merge the user into themself" if uid_to_merge == @user.uid | ||||
|  | ||||
|       # Find user to merge | ||||
|       user_to_merge = User.find_by(uid: uid_to_merge) | ||||
|  | ||||
|       # Move over user's rooms | ||||
|       user_to_merge.rooms.each do |room| | ||||
|         room.owner = @user | ||||
|  | ||||
|         room.name = "(#{I18n.t('merged')}) #{room.name}" | ||||
|  | ||||
|         room.save! | ||||
|       end | ||||
|  | ||||
|       # Reload user to update merge rooms | ||||
|       user_to_merge.reload | ||||
|  | ||||
|       # Delete merged user | ||||
|       user_to_merge.destroy(true) | ||||
|     rescue => e | ||||
|       logger.info "Failed to merge #{uid_to_merge} into #{@user.uid}: #{e}" | ||||
|       flash[:alert] = I18n.t("administrator.flash.merge_fail") | ||||
|     else | ||||
|       logger.info "#{current_user.uid} successfully merged #{uid_to_merge} into #{@user.uid}" | ||||
|       flash[:success] = I18n.t("administrator.flash.merge_success") | ||||
|     end | ||||
|  | ||||
|     redirect_to admins_path | ||||
|   end | ||||
|  | ||||
|   # SITE SETTINGS | ||||
|  | ||||
|   # POST /admins/update_settings | ||||
|   | ||||
| @@ -77,7 +77,18 @@ module Populator | ||||
|       roles_can_appear << role.name if role.get_permission("can_appear_in_share_list") && role.name != "super_admin" | ||||
|     end | ||||
|  | ||||
|     initial_list = User.where.not(uid: current_user.uid).with_highest_priority_role(roles_can_appear) | ||||
|     initial_list = User.where.not(uid: current_user.uid) | ||||
|                        .without_role(:pending) | ||||
|                        .without_role(:denied) | ||||
|                        .with_highest_priority_role(roles_can_appear) | ||||
|  | ||||
|     return initial_list unless Rails.configuration.loadbalanced_configuration | ||||
|     initial_list.where(provider: @user_domain) | ||||
|   end | ||||
|  | ||||
|   # Returns a list of users that can merged into another user | ||||
|   def merge_user_list | ||||
|     initial_list = User.where.not(uid: current_user.uid).without_role(:super_admin) | ||||
|  | ||||
|     return initial_list unless Rails.configuration.loadbalanced_configuration | ||||
|     initial_list.where(provider: @user_domain) | ||||
|   | ||||
| @@ -36,7 +36,7 @@ class Ability | ||||
|  | ||||
|       if highest_role.get_permission("can_manage_users") | ||||
|         can [:index, :roles, :edit_user, :promote, :demote, :ban_user, :unban_user, | ||||
|              :approve, :invite, :reset, :undelete], :admin | ||||
|              :approve, :invite, :reset, :undelete, :merge_user], :admin | ||||
|       end | ||||
|  | ||||
|       can [:index, :server_recordings, :server_rooms], :admin if highest_role.get_permission("can_manage_rooms_recordings") | ||||
|   | ||||
| @@ -60,6 +60,11 @@ class Room < ApplicationRecord | ||||
|     user.rooms.include?(self) | ||||
|   end | ||||
|  | ||||
|   # Determines whether room is a home room | ||||
|   def home_room? | ||||
|     owner.main_room == self | ||||
|   end | ||||
|  | ||||
|   def shared_users | ||||
|     User.where(id: shared_access.pluck(:user_id)) | ||||
|   end | ||||
|   | ||||
| @@ -124,6 +124,9 @@ | ||||
|                               <%= link_to admin_edit_user_path(user_uid: user.uid), class: "dropdown-item" do %> | ||||
|                                 <i class="dropdown-icon fas fa-user-edit"></i> <%= t("administrator.users.settings.edit") %> | ||||
|                               <% end %> | ||||
|                               <button class= "merge-user dropdown-item" data-path="<%= merge_user_path(user_uid: user.uid) %>" data-info="<%= user.slice(:name, :email, :uid).to_json %>" data-toggle="modal" data-target="#mergeUserModal"> | ||||
|                                 <i class="dropdown-icon fas fa-user-friends"></i> <%= t("administrator.users.settings.merge") %> | ||||
|                               </button> | ||||
|                               <%= button_to admin_ban_path(user_uid: user.uid), class: "dropdown-item", "data-disable": "" do %> | ||||
|                                 <i class="dropdown-icon fas fa-lock"></i> <%= t("administrator.users.settings.ban") %> | ||||
|                               <% end %> | ||||
| @@ -157,3 +160,4 @@ | ||||
|  | ||||
| <%= render "shared/modals/invite_user_modal" %> | ||||
| <%= render "shared/modals/delete_account_modal", delete_location: relative_root %> | ||||
| <%= render "shared/modals/merge_user_modal" %> | ||||
							
								
								
									
										51
									
								
								app/views/shared/modals/_merge_user_modal.html.erb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								app/views/shared/modals/_merge_user_modal.html.erb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| <% | ||||
| # BigBlueButton open source conferencing system - http://www.bigbluebutton.org/. | ||||
| # Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below). | ||||
| # This program is free software; you can redistribute it and/or modify it under the | ||||
| # terms of the GNU Lesser General Public License as published by the Free Software | ||||
| # Foundation; either version 3.0 of the License, or (at your option) any later | ||||
| # version. | ||||
| # | ||||
| # BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY | ||||
| # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A | ||||
| # PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. | ||||
| # You should have received a copy of the GNU Lesser General Public License along | ||||
| # with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. | ||||
| %> | ||||
|  | ||||
| <div class="modal fade" id="mergeUserModal" tabindex="-1" role="dialog"> | ||||
|   <div class="modal-dialog modal-dialog-centered" role="document"> | ||||
|     <div class="modal-content text-center"> | ||||
|       <div class="modal-body"> | ||||
|         <div class="card-body p-6"> | ||||
|           <div class="card-title"> | ||||
|             <h3><%= t("modal.merge_user.title") %></h3> | ||||
|           </div> | ||||
|           <select class="selectpicker" title="<%= t("modal.share_access.select") %>" data-live-search="true" data-virtual-scroll="true" > | ||||
|             <% @user_list.each do |user| %> | ||||
|                 <option value="<%= { uid: user.uid, email: user.email, name: user.name }.to_json %>" data-subtext="<%= user.email %>" ><%= user.name %></option> | ||||
|               <% end %> | ||||
|           </select> | ||||
|           <div class="mt-5 text-left row"> | ||||
|             <div class="list-group-item col-6 text-center"> | ||||
|               <label class="form-label text-primary"><%= t("modal.merge_user.from") %></label> | ||||
|                <div id="merge-from"></div> | ||||
|             </div> | ||||
|             <i id="merge-account-arrow" class="fas fa-2x fa-arrow-circle-right text-primary"></i> | ||||
|             <div class="list-group-item col-6 text-center"> | ||||
|               <label class="form-label text-primary"><%= t("modal.merge_user.to") %></label> | ||||
|                <div id="merge-to"></div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="mt-6"> | ||||
|             <button id="merge-save-access" class="btn btn-primary btn-block" onclick="mergeUsers()" ><%= t("modal.merge_user.save") %></button> | ||||
|             <button class="btn btn-secondary text-primary btn-block" onclick="$('#mergeUserModal').modal('hide')"><%= t("modal.merge_user.cancel") %></button> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="card-footer"> | ||||
|           <p><%= t("modal.merge_user.footer") %></p> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
| @@ -21,7 +21,7 @@ | ||||
|           <div class="card-title"> | ||||
|             <h3><%= t("modal.share_access.title") %></h3> | ||||
|           </div> | ||||
|           <select class="selectpicker" title="<%= t("modal.share_access.select") %>..." data-live-search="true" data-virtual-scroll="true" > | ||||
|           <select class="selectpicker" title="<%= t("modal.share_access.select") %>" data-live-search="true" data-virtual-scroll="true" > | ||||
|             <% @user_list.each do |user| %> | ||||
|                 <option value="<%= user.uid %>" data-subtext="<%= user.uid %>" ><%= user.name %></option> | ||||
|               <% end %> | ||||
|   | ||||
| @@ -84,6 +84,8 @@ en: | ||||
|       demoted: User has been successfully demoted | ||||
|       invite: Invite successfully sent to %{email} | ||||
|       invite_email_verification: Emails must be enabled in order to use this method. Please contact your system administrator. | ||||
|       merge_fail: There was an issue merging the user accounts. Please check the users selected and try again  | ||||
|       merge_success: User accounts merged successfully | ||||
|       perm_deleted: User has been permanently deleted | ||||
|       promoted: User has been successfully promoted | ||||
|       registration_method_updated: Registration method successfully updated | ||||
| @@ -245,6 +247,8 @@ en: | ||||
|         body: 'To view the recording, follow the link below:' | ||||
|         autogenerated: 'This e-mail is auto-generated by BigBlueButton.' | ||||
|         footer: 'BigBlueButton is an open source web conferencing system. For more information on BigBlueButton, see https://bigbluebutton.org/.' | ||||
|     search: | ||||
|       start: Start searching... | ||||
|   landing: | ||||
|     about: "%{href} is a simple front-end for your BigBlueButton open-source web conferencing server. You can create your own rooms to host sessions, or join others using a short and convenient link." | ||||
|     welcome: Welcome to BigBlueButton. | ||||
| @@ -308,6 +312,7 @@ en: | ||||
|   maintenance: | ||||
|     window_alert: Maintenance window scheduled for %{date} | ||||
|   max_concurrent: The maximum number of concurrent sessions allowed has been reached! | ||||
|   merged: Merged | ||||
|   modal: | ||||
|     create_role: | ||||
|       create: Create a new Role | ||||
| @@ -368,6 +373,13 @@ en: | ||||
|       save: Save Changes | ||||
|       cancel_changes: Cancel Changes | ||||
|       select: Select User | ||||
|     merge_user: | ||||
|       cancel: Cancel | ||||
|       from: Account to be Merged | ||||
|       title: Merge User Accounts | ||||
|       to: Primary Account | ||||
|       save: Merge | ||||
|       footer: The rooms of the account to be merged will be transfered over to the Primary Account's room list and then the account will be deleted. | ||||
|   name_update_success: Room name successfully changed! | ||||
|   no_user_email_exists: There is no existing user with the email specified. Please make sure you typed it correctly. | ||||
|   omniauth_error: An error occured while authenticating with omniauth. Please try again or contact an administrator! | ||||
|   | ||||
| @@ -50,6 +50,7 @@ Rails.application.routes.draw do | ||||
|     post '/approve/:user_uid', to: 'admins#approve', as: :admin_approve | ||||
|     get '/reset', to: 'admins#reset', as: :admin_reset | ||||
|     post '/undelete', to: 'admins#undelete', as: :admin_undelete | ||||
|     post '/merge/:user_uid', to: 'admins#merge_user', as: :merge_user | ||||
|     # Site Settings | ||||
|     post '/update_settings', to: 'admins#update_settings', as: :admin_update_settings | ||||
|     post '/registration_method', to: 'admins#registration_method', as: :admin_change_registration | ||||
|   | ||||
| @@ -197,6 +197,42 @@ describe AdminsController, type: :controller do | ||||
|         expect(response).to redirect_to(admins_path) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context "POST #merge_user" do | ||||
|       it "merges the users room to the primary account and deletes the old user" do | ||||
|         @request.session[:user_id] = @admin.id | ||||
|  | ||||
|         @user2 = create(:user) | ||||
|         room1 = create(:room, owner: @user2) | ||||
|         room2 = create(:room, owner: @user2) | ||||
|         room3 = @user2.main_room | ||||
|  | ||||
|         post :merge_user, params: { user_uid: @user.uid, merge: @user2.uid } | ||||
|  | ||||
|         room1.reload | ||||
|         room2.reload | ||||
|         room3.reload | ||||
|  | ||||
|         expect(User.exists?(uid: @user2.uid)).to be false | ||||
|         expect(room1.name).to start_with("(Merged)") | ||||
|         expect(room2.name).to start_with("(Merged)") | ||||
|         expect(room3.name).to start_with("(Merged)") | ||||
|         expect(room1.owner).to eq(@user) | ||||
|         expect(room2.owner).to eq(@user) | ||||
|         expect(room3.owner).to eq(@user) | ||||
|         expect(flash[:success]).to be_present | ||||
|         expect(response).to redirect_to(admins_path) | ||||
|       end | ||||
|  | ||||
|       it "does not merge if trying to merge the same user into themself" do | ||||
|         @request.session[:user_id] = @admin.id | ||||
|  | ||||
|         post :merge_user, params: { user_uid: @user.uid, merge: @user.uid } | ||||
|  | ||||
|         expect(flash[:alert]).to be_present | ||||
|         expect(response).to redirect_to(admins_path) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   describe "User Design" do | ||||
|   | ||||
		Reference in New Issue
	
	Block a user