diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index f26b9b6d8..6655de976 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -28,6 +28,7 @@ class UsersController < ApplicationController include SortHelper helper :custom_fields include CustomFieldsHelper + include UsersHelper helper :principal_memberships require_sudo_mode :create, :update, :destroy @@ -59,6 +60,9 @@ class UsersController < ApplicationController @groups = Group.givable.sort render :layout => !request.xhr? } + format.csv { + send_data(users_to_csv(scope.order(sort_clause)), :type => 'text/csv; header=present', :filename => 'users.csv') + } format.api end end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 97e7a8bd6..3d5a270db 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -18,12 +18,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module UsersHelper + include ApplicationHelper + def users_status_options_for_select(selected) user_count_by_status = User.group('status').count.to_hash - options_for_select([[l(:label_all), ''], - ["#{l(:status_active)} (#{user_count_by_status[1].to_i})", '1'], - ["#{l(:status_registered)} (#{user_count_by_status[2].to_i})", '2'], - ["#{l(:status_locked)} (#{user_count_by_status[3].to_i})", '3']], selected.to_s) + options_for_select([[l(:label_all), '']] + (User.valid_statuses.map {|c| ["#{l('status_' + User::STATUS_NAMES[c])} (#{user_count_by_status[c].to_i})", c]}), selected.to_s) end def user_mail_notification_options(user) @@ -61,4 +60,27 @@ module UsersHelper end tabs end + + def users_to_csv(users) + Redmine::Export::CSV.generate do |csv| + columns = [ + 'login', + 'firstname', + 'lastname', + 'mail', + 'admin', + 'created_on', + 'last_login_on', + 'status' + ] + + # csv header fields + csv << columns.map{|column| l('field_' + column)} + # csv lines + columns[columns.index('status')]= 'status_name' + users.each do |user| + csv << columns.map{|column| format_object(user.send(column), false)} + end + end + end end diff --git a/app/models/user.rb b/app/models/user.rb index 02ba81439..0349d97d5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -517,15 +517,19 @@ class User < Principal name end - CSS_CLASS_BY_STATUS = { + STATUS_NAMES = { STATUS_ANONYMOUS => 'anon', STATUS_ACTIVE => 'active', STATUS_REGISTERED => 'registered', STATUS_LOCKED => 'locked' } + def status_name + l(("status_#{STATUS_NAMES[status]}")) + end + def css_classes - "user #{CSS_CLASS_BY_STATUS[status]}" + "user #{STATUS_NAMES[status]}" end # Returns the current day according to user's time zone diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 3fc82b640..f056e5619 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -55,6 +55,9 @@ <%= pagination_links_full @user_pages, @user_count %> +<% other_formats_links do |f| %> + <%= f.link_to_with_query_parameters 'CSV' %> +<% end %> <% else %>

<%= l(:label_no_data) %>

<% end %> diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb index 49d69f84a..093f36f04 100644 --- a/test/functional/users_controller_test.rb +++ b/test/functional/users_controller_test.rb @@ -64,6 +64,47 @@ class UsersControllerTest < Redmine::ControllerTest end end + def test_index_csv + with_settings :default_language => 'en' do + get :index, :params => { :format => 'csv' } + assert_response :success + + assert_equal User.logged.status(1).count, response.body.chomp.split("\n").size - 1 + assert_include 'active', response.body + assert_not_include 'locked', response.body + assert_equal 'text/csv; header=present', @response.content_type + end + end + + def test_index_csv_with_status_filter + with_settings :default_language => 'en' do + get :index, :params => { :status => 3, :format => 'csv' } + assert_response :success + + assert_equal User.logged.status(3).count, response.body.chomp.split("\n").size - 1 + assert_include 'locked', response.body + assert_not_include 'active', response.body + assert_equal 'text/csv; header=present', @response.content_type + end + end + + def test_index_csv_with_name_filter + get :index, :params => {:name => 'John', :format => 'csv'} + assert_response :success + + assert_equal User.logged.like('John').count, response.body.chomp.split("\n").size - 1 + assert_include 'John', response.body + assert_equal 'text/csv; header=present', @response.content_type + end + + def test_index_csv_with_group_filter + get :index, :params => {:group_id => '10', :format => 'csv'} + assert_response :success + + assert_equal Group.find(10).users.count, response.body.chomp.split("\n").size - 1 + assert_equal 'text/csv; header=present', @response.content_type + end + def test_show @request.session[:user_id] = nil get :show, :params => {:id => 2} diff --git a/test/unit/helpers/users_helper_test.rb b/test/unit/helpers/users_helper_test.rb new file mode 100644 index 000000000..78f966ff7 --- /dev/null +++ b/test/unit/helpers/users_helper_test.rb @@ -0,0 +1,61 @@ +# Redmine - project management software +# Copyright (C) 2006-2016 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../../test_helper', __FILE__) + +class UserHelperTest < Redmine::HelperTest + include UsersHelper + include ERB::Util + + def test_users_status_options_for_select_should_have_selected_options + selected = 1 + with_locale 'en' do + options = users_status_options_for_select(selected) + user_count_by_status = User.group('status').count.to_hash + assert_select_in options, "option[value='']", :text => 'all' + assert_select_in options, "option[selected='selected'][value='1']", :text => "active (#{user_count_by_status[1].to_i})" + assert_select_in options, "option[value='2']", :text => "registered (#{user_count_by_status[2].to_i})" + assert_select_in options, "option[value='3']", :text => "locked (#{user_count_by_status[3].to_i})" + end + end + + def test_users_to_csv_header_should_be_column_name + with_locale 'en' do + column_names = [ + 'Login', + 'First name', + 'Last name', + 'Email', + 'Administrator', + 'Created', + 'Last connection', + 'Status' + ] + csv = users_to_csv(User.logged) + column_names.each do |column_name| + assert_include column_name, csv + end + end + end + + def test_users_to_csv_should_status_convert_to_status_name + with_locale 'en' do + csv = users_to_csv(User.status(1)) + assert_include 'active', csv + end + end +end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb index 48c4cb3d4..fc6419851 100644 --- a/test/unit/user_test.rb +++ b/test/unit/user_test.rb @@ -838,6 +838,20 @@ class UserTest < ActiveSupport::TestCase assert_equal true, User.default_admin_account_changed? end + def test_status_name_should_return_status_name + user = User.new + user.status = User::STATUS_ACTIVE + + assert_equal l("status_#{User::STATUS_NAMES[user.status]}"), user.status_name + end + + def test_css_classes_should_return_class_name + user = User.new + user.status = User::STATUS_ACTIVE + + assert_equal "user " + User::STATUS_NAMES[user.status], user.css_classes + end + def test_membership_with_project_should_return_membership project = Project.find(1)