FEATURE: Support uploading a csv with either user emails or usernames (#8971)

FEATURE: Support uploading a csv with either user emails or usernames (#8971)

diff --git a/app/controllers/admin/badges_controller.rb b/app/controllers/admin/badges_controller.rb
index f494fb921e..2c1846d3da 100644
--- a/app/controllers/admin/badges_controller.rb
+++ b/app/controllers/admin/badges_controller.rb
@@ -51,6 +51,9 @@ class Admin::BadgesController < Admin::AdminController
     batch = []
 
     File.open(csv_file) do |csv|
+      mode = Email.is_valid?(CSV.parse_line(csv.first).first) ? 'email' : 'username'
+      csv.rewind
+
       csv.each_line do |email_line|
         batch.concat CSV.parse_line(email_line)
         line_number += 1
@@ -60,7 +63,7 @@ class Admin::BadgesController < Admin::AdminController
         last_batch_item = full_batch || csv.eof?
 
         if last_batch_item
-          Jobs.enqueue(:mass_award_badge, user_emails: batch, badge_id: badge.id)
+          Jobs.enqueue(:mass_award_badge, users_batch: batch, badge_id: badge.id, mode: mode)
           batch = []
           batch_number += 1
         end
diff --git a/app/jobs/regular/mass_award_badge.rb b/app/jobs/regular/mass_award_badge.rb
index ae6db5f6f5..ef51289917 100644
--- a/app/jobs/regular/mass_award_badge.rb
+++ b/app/jobs/regular/mass_award_badge.rb
@@ -3,8 +3,16 @@
 module Jobs
   class MassAwardBadge < ::Jobs::Base
     def execute(args)
+      return unless mode = args[:mode]
       badge = Badge.find_by(id: args[:badge_id])
-      users = User.select(:id, :username, :locale).with_email(args[:user_emails])
+
+      users = User.select(:id, :username, :locale)
+
+      if mode == 'email'
+        users = users.with_email(args[:users_batch])
+      else
+        users = users.where(username_lower: args[:users_batch].map!(&:downcase))
+      end
 
       return if users.empty? || badge.nil?
 
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 408b32075d..1211978b66 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -4502,8 +4502,8 @@ en:
           description: Award the same badge to many users at once.
           no_badge_selected: Please select a badge to get started.
           perform: "Award Badge to Users"
-          upload_csv: Upload a CSV with user emails
-          aborted: Please upload a CSV containing user emails
+          upload_csv: Upload a CSV with either user emails or usernames
+          aborted: Please upload a CSV containing either user emails or usernames
           success: Your CSV was received and users will receive their badge shortly.
           replace_owners: Remove the badge from previous owners
 
diff --git a/spec/fixtures/csv/usernames.csv b/spec/fixtures/csv/usernames.csv
new file mode 100644
index 0000000000..93c2848626
--- /dev/null
+++ b/spec/fixtures/csv/usernames.csv
@@ -0,0 +1,3 @@
+username1
+username2
+username3
\ No newline at end of file
diff --git a/spec/jobs/mass_award_badge_spec.rb b/spec/jobs/mass_award_badge_spec.rb
index b617a2e49b..3fcff6faa9 100644
--- a/spec/jobs/mass_award_badge_spec.rb
+++ b/spec/jobs/mass_award_badge_spec.rb
@@ -6,9 +6,10 @@ describe Jobs::MassAwardBadge do
   describe '#execute' do
     fab!(:badge) { Fabricate(:badge) }
     fab!(:user) { Fabricate(:user) }
+    let(:email_mode) { 'email' }
 
     it 'creates the badge for an existing user' do
-      subject.execute(user_emails: [user.email], badge_id:  badge.id)
+      execute_job([user.email])
 
       expect(UserBadge.where(user: user, badge: badge).exists?).to eq(true)
     end
@@ -16,14 +17,14 @@ describe Jobs::MassAwardBadge do
     it 'works with multiple users' do
       user_2 = Fabricate(:user)
 
-      subject.execute(user_emails: [user.email, user_2.email], badge_id:  badge.id)
+      execute_job([user.email, user_2.email])
 
       expect(UserBadge.exists?(user: user, badge: badge)).to eq(true)
       expect(UserBadge.exists?(user: user_2, badge: badge)).to eq(true)
     end
 
     it 'also creates a notification for the user' do
-      subject.execute(user_emails: [user.email], badge_id:  badge.id)
+      execute_job([user.email])
 
       expect(Notification.exists?(user: user)).to eq(true)
       expect(UserBadge.where.not(notification_id: nil).exists?(user: user, badge: badge)).to eq(true)
@@ -34,10 +35,14 @@ describe Jobs::MassAwardBadge do
 
       UserBadge.create!(badge_id: Badge::Member, user: user, granted_by: Discourse.system_user, granted_at: Time.now)
 
-      subject.execute(user_emails: [user.email, user_2.email], badge_id:  badge.id)
+      execute_job([user.email, user_2.email])
 
       expect(UserBadge.find_by(user: user, badge: badge).featured_rank).to eq(2)
       expect(UserBadge.find_by(user: user_2, badge: badge).featured_rank).to eq(1)
     end
+
+    def execute_job(emails)
+      subject.execute(users_batch: emails, badge_id:  badge.id, mode: 'email')
+    end
   end
 end
diff --git a/spec/requests/admin/badges_controller_spec.rb b/spec/requests/admin/badges_controller_spec.rb
index 15d27bf2fd..06d294bcc0 100644
--- a/spec/requests/admin/badges_controller_spec.rb
+++ b/spec/requests/admin/badges_controller_spec.rb
@@ -199,7 +199,7 @@ describe Admin::BadgesController do
         expect(response.status).to eq(400)
       end
 
-      it 'creates the badge for an existing user' do
+      it 'awards the badge using a list of user emails' do
         Jobs.run_immediately!
 
         user = Fabricate(:user, email: 'user1@test.com')
@@ -209,6 +209,17 @@ describe Admin::BadgesController do
 
         expect(UserBadge.exists?(user: user, badge: badge)).to eq(true)
       end
+
+      it 'awards the badge using a list of usernames' do
+        Jobs.run_immediately!
+
+        user = Fabricate(:user, username: 'username1')
+        file = file_from_fixtures('usernames.csv', 'csv')
+
+        post "/admin/badges/award/#{badge.id}.json", params: { file: fixture_file_upload(file) }
+
+        expect(UserBadge.exists?(user: user, badge: badge)).to eq(true)
+      end
     end
   end
 end

GitHub sha: 9441362c

This commit appears in #8971 which was approved by ZogStriP. It was merged by romanrizzi.