diff --git a/app/views/admin/users/export.csv.erb b/app/views/admin/users/export.csv.erb
new file mode 100644
index 000000000..fa3eab4f5
--- /dev/null
+++ b/app/views/admin/users/export.csv.erb
@@ -0,0 +1,6 @@
+<%- headers = ["Name", "Username", "Email address", "Status", "Joining date", "Last activity", "Organizations"] -%>
+<%= CSV.generate_line headers -%>
+<%- @users.each do |user| -%>
+ <%- row = [user.name, user.username, user.email, user_status(user), user.registered_at&.strftime("%d %b, %Y"), user.last_activity&.strftime("%d %b, %Y"), user.organizations.pluck(:name)] -%>
+ <%= CSV.generate_line(row).html_safe -%>
+<%- end -%>
\ No newline at end of file
diff --git a/app/views/admin/users/index/_controls.html.erb b/app/views/admin/users/index/_controls.html.erb
index 3c4f96b4e..4347d30fa 100644
--- a/app/views/admin/users/index/_controls.html.erb
+++ b/app/views/admin/users/index/_controls.html.erb
@@ -24,6 +24,9 @@
<%= render "admin/users/index/filter_role_field", f: f %>
+
+ <%= render "admin/users/index/controls/export", f: f %>
+
<% end %>
@@ -36,6 +39,9 @@
<%= render "admin/users/index/filter_role_field", f: f %>
+
+ <%= render "admin/users/index/controls/export", f: f %>
+
<% end %>
<%= paginate @users, theme: "admin", scope: @users, label: "Paginate users" %>
diff --git a/app/views/admin/users/index/controls/_export.html.erb b/app/views/admin/users/index/controls/_export.html.erb
new file mode 100644
index 000000000..bea5d0636
--- /dev/null
+++ b/app/views/admin/users/index/controls/_export.html.erb
@@ -0,0 +1,5 @@
+
+
+
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 5cfc56fdc..f646a7595 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -45,6 +45,9 @@ namespace :admin do
resources :users, only: %i[index show update destroy] do
resources :email_messages, only: :show
+ collection do
+ get "export"
+ end
member do
post "banish"
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index f8489868b..7880fe09d 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -110,6 +110,14 @@ FactoryBot.define do
after(:build) { |user| user.add_role(:suspended) }
end
+ trait :warned do
+ after(:build) { |user| user.add_role(:warned) }
+ end
+
+ trait :comment_suspended do
+ after(:build) { |user| user.add_role(:comment_suspended) }
+ end
+
trait :invited do
after(:build) do |user|
user.registered = false
diff --git a/spec/helpers/admin/users_helper_spec.rb b/spec/helpers/admin/users_helper_spec.rb
index 77452d0b4..bcc30b52b 100644
--- a/spec/helpers/admin/users_helper_spec.rb
+++ b/spec/helpers/admin/users_helper_spec.rb
@@ -86,4 +86,36 @@ describe Admin::UsersHelper do
expect(role).to be_nil
end
end
+
+ describe "#user_status" do
+ it "renders the proper status for a user that is suspended" do
+ suspended_user = create(:user, :suspended)
+ status = helper.user_status(suspended_user)
+ expect(status).to eq "Suspended"
+ end
+
+ it "renders the proper status for a user that is warned" do
+ warned_user = create(:user, :warned)
+ status = helper.user_status(warned_user)
+ expect(status).to eq "Warned"
+ end
+
+ it "renders the proper status for a user that is comment suspended" do
+ comment_suspended_user = create(:user, :comment_suspended)
+ status = helper.user_status(comment_suspended_user)
+ expect(status).to eq "Comment Suspended"
+ end
+
+ it "renders the proper status for a user that is trusted" do
+ trusted_user = create(:user, :trusted)
+ status = helper.user_status(trusted_user)
+ expect(status).to eq "Trusted"
+ end
+
+ it "renders the proper status for a user that is good standing" do
+ good_standing_user = create(:user)
+ status = helper.user_status(good_standing_user)
+ expect(status).to eq "Good Standing"
+ end
+ end
end
diff --git a/spec/requests/admin/users/users_export_spec.rb b/spec/requests/admin/users/users_export_spec.rb
new file mode 100644
index 000000000..7972e1fe3
--- /dev/null
+++ b/spec/requests/admin/users/users_export_spec.rb
@@ -0,0 +1,41 @@
+require "rails_helper"
+
+RSpec.describe "/admin/users/export", type: :request do
+ let(:admin) do
+ create(:user, :super_admin,
+ name: "Admin1", username: "admin1", email: "admin1@gmail.com",
+ registered_at: "2020-05-06T13:09:47+0000")
+ end
+ let!(:user) do
+ create(:user, :org_member,
+ name: "John Doe", username: "john_doe", email: "john_doe@gmail.com",
+ registered_at: "2020-06-08T13:09:47+0000")
+ end
+
+ before do
+ sign_in(admin)
+ get "#{export_admin_users_path}.csv"
+ end
+
+ it "renders successfully" do
+ expect(response).to have_http_status :ok
+ end
+
+ it "renders the headers" do
+ expect(response.body).to include("Name,Username,Email address,Status,Joining date,Last activity,Organizations")
+ end
+
+ it "shows the correct number of total rows" do
+ # This takes into account empty lines after each row
+ expect(response.body.lines.count).to eq(3)
+ end
+
+ it "shows the correct fields", :aggregate_failures do
+ expect(response.body).to include('Admin1,admin1,admin1@gmail.com,Good Standing,"06 May, 2020","06 May, 2020",[]')
+ # rubocop:disable Style/PercentLiteralDelimiters, Layout/LineLength
+ expect(response.body).to include(
+ %{John Doe,john_doe,john_doe@gmail.com,Good Standing,"08 Jun, 2020","08 Jun, 2020","[""#{user.organizations.first.name}""]"},
+ )
+ # rubocop:enable Style/PercentLiteralDelimiters, Layout/LineLength
+ end
+end