Patch by Jens Krämer. git-svn-id: https://svn.redmine.org/redmine/trunk@21823 e93f8b46-1217-0410-a6f0-8f06a7374b81tags/5.1.0
@@ -108,4 +108,14 @@ class ContextMenusController < ApplicationController | |||
end | |||
render layout: false | |||
end | |||
def users | |||
@users = User.where(id: params[:ids]).to_a | |||
(render_404; return) unless @users.present? | |||
if @users.size == 1 | |||
@user = @users.first | |||
end | |||
render layout: false | |||
end | |||
end |
@@ -177,6 +177,10 @@ class QueriesController < ApplicationController | |||
end | |||
end | |||
def redirect_to_user_query(options) | |||
redirect_to users_path(options) | |||
end | |||
# Returns the Query subclass, IssueQuery by default | |||
# for compatibility with previous behaviour | |||
def query_class |
@@ -34,49 +34,46 @@ class UsersController < ApplicationController | |||
helper :principal_memberships | |||
helper :activities | |||
include ActivitiesHelper | |||
helper :queries | |||
include QueriesHelper | |||
helper :user_queries | |||
include UserQueriesHelper | |||
require_sudo_mode :create, :update, :destroy | |||
def index | |||
sort_init 'login', 'asc' | |||
sort_update %w(login firstname lastname admin created_on last_login_on) | |||
use_session = !request.format.csv? | |||
retrieve_query(UserQuery, use_session) | |||
case params[:format] | |||
when 'xml', 'json' | |||
@offset, @limit = api_offset_and_limit | |||
else | |||
@limit = per_page_option | |||
end | |||
@status = params[:status] || 1 | |||
if @query.valid? | |||
scope = @query.results_scope | |||
scope = User.logged.status(@status).preload(:email_address) | |||
scope = scope.like(params[:name]) if params[:name].present? | |||
scope = scope.in_group(params[:group_id]) if params[:group_id].present? | |||
@user_count = scope.count | |||
if params[:twofa].present? | |||
case params[:twofa].to_i | |||
when 1 | |||
scope = scope.where.not(twofa_scheme: nil) | |||
when 0 | |||
scope = scope.where(twofa_scheme: nil) | |||
end | |||
end | |||
@user_count = scope.count | |||
@user_pages = Paginator.new @user_count, @limit, params['page'] | |||
@offset ||= @user_pages.offset | |||
@users = scope.order(sort_clause).limit(@limit).offset(@offset).to_a | |||
respond_to do |format| | |||
format.html do | |||
@groups = Group.givable.sort | |||
render :layout => !request.xhr? | |||
respond_to do |format| | |||
format.html do | |||
@limit = per_page_option | |||
@user_pages = Paginator.new @user_count, @limit, params['page'] | |||
@offset ||= @user_pages.offset | |||
@users = scope.limit(@limit).offset(@offset).to_a | |||
render :layout => !request.xhr? | |||
end | |||
format.csv do | |||
# Export all entries | |||
@entries = scope.to_a | |||
send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'users.csv') | |||
end | |||
format.api do | |||
@offset, @limit = api_offset_and_limit | |||
@users = scope.limit(@limit).offset(@offset).to_a | |||
end | |||
end | |||
format.csv do | |||
send_data(users_to_csv(scope.order(sort_clause)), :type => 'text/csv; header=present', :filename => 'users.csv') | |||
else | |||
respond_to do |format| | |||
format.html {render :layout => !request.xhr?} | |||
format.csv {head :unprocessable_entity} | |||
format.api {render_validation_errors(@query)} | |||
end | |||
format.api | |||
end | |||
end | |||
@@ -0,0 +1,62 @@ | |||
# frozen_string_literal: true | |||
# Redmine - project management software | |||
# Copyright (C) 2006-2022 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. | |||
module UserQueriesHelper | |||
def column_value(column, object, value) | |||
if object.is_a?(User) && column.name == :status | |||
user_status_label(column.value_object(object)) | |||
else | |||
super | |||
end | |||
end | |||
def csv_value(column, object, value) | |||
if object.is_a?(User) | |||
case column.name | |||
when :status | |||
user_status_label(column.value_object(object)) | |||
when :twofa_scheme | |||
twofa_scheme_label value | |||
else | |||
super | |||
end | |||
else | |||
super | |||
end | |||
end | |||
def user_status_label(value) | |||
case value.to_i | |||
when User::STATUS_ACTIVE | |||
l(:status_active) | |||
when User::STATUS_REGISTERED | |||
l(:status_registered) | |||
when User::STATUS_LOCKED | |||
l(:status_locked) | |||
end | |||
end | |||
def twofa_scheme_label(value) | |||
if value | |||
::I18n.t :"twofa__#{value}__name" | |||
else | |||
::I18n.t :label_disabled | |||
end | |||
end | |||
end |
@@ -150,7 +150,8 @@ class QueryCustomFieldColumn < QueryColumn | |||
end | |||
def value_object(object) | |||
if custom_field.visible_by?(object.project, User.current) | |||
project = object.project if object.respond_to?(:project) | |||
if custom_field.visible_by?(project, User.current) | |||
cv = object.custom_values.select {|v| v.custom_field_id == @cf.id} | |||
cv.size > 1 ? cv.sort_by {|e| e.value.to_s} : cv.first | |||
else |
@@ -0,0 +1,156 @@ | |||
# frozen_string_literal: true | |||
# Redmine - project management software | |||
# Copyright (C) 2006-2022 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 | |||
class UserQuery < Query | |||
self.queried_class = Principal # must be Principal (not User) for custom field filters to work | |||
self.available_columns = [ | |||
QueryColumn.new(:login, sortable: "#{User.table_name}.login"), | |||
QueryColumn.new(:firstname, sortable: "#{User.table_name}.firstname"), | |||
QueryColumn.new(:lastname, sortable: "#{User.table_name}.lastname"), | |||
QueryColumn.new(:mail, sortable: "#{EmailAddress.table_name}.address"), | |||
QueryColumn.new(:admin, sortable: "#{User.table_name}.admin"), | |||
QueryColumn.new(:created_on, :sortable => "#{User.table_name}.created_on"), | |||
QueryColumn.new(:updated_on, :sortable => "#{User.table_name}.updated_on"), | |||
QueryColumn.new(:last_login_on, :sortable => "#{User.table_name}.last_login_on"), | |||
QueryColumn.new(:passwd_changed_on, :sortable => "#{User.table_name}.passwd_changed_on"), | |||
QueryColumn.new(:status, sortable: "#{User.table_name}.status"), | |||
QueryAssociationColumn.new(:auth_source, :name, caption: :field_auth_source, sortable: "#{AuthSource.table_name}.name") | |||
] | |||
def initialize(attributes=nil, *args) | |||
super attributes | |||
self.filters ||= { 'status' => {operator: "=", values: [User::STATUS_ACTIVE]} } | |||
end | |||
def initialize_available_filters | |||
add_available_filter "status", | |||
type: :list, values: ->{ user_statuses_values } | |||
add_available_filter "auth_source_id", | |||
type: :list_optional, values: ->{ auth_sources_values } | |||
add_available_filter "is_member_of_group", | |||
type: :list_optional, | |||
values: ->{ Group.givable.visible.map {|g| [g.name, g.id.to_s] } } | |||
if Setting.twofa? | |||
add_available_filter "twofa_scheme", | |||
type: :list_optional, | |||
values: ->{ Redmine::Twofa.available_schemes.map {|s| [I18n.t("twofa__#{s}__name"), s] } } | |||
end | |||
add_available_filter "login", type: :string | |||
add_available_filter "firstname", type: :string | |||
add_available_filter "lastname", type: :string | |||
add_available_filter "mail", type: :string | |||
add_available_filter "created_on", type: :date_past | |||
add_available_filter "last_login_on", type: :date_past | |||
add_available_filter "admin", | |||
type: :list, | |||
values: [[l(:general_text_yes), '1'], [l(:general_text_no), '0']] | |||
add_custom_fields_filters(user_custom_fields) | |||
end | |||
def auth_sources_values | |||
AuthSource.order(name: :asc).to_a.map do |auth_source| | |||
[auth_source.name, auth_source.id] | |||
end | |||
end | |||
def user_statuses_values | |||
[ | |||
[l(:status_active), User::STATUS_ACTIVE.to_s], | |||
[l(:status_registered), User::STATUS_REGISTERED.to_s], | |||
[l(:status_locked), User::STATUS_LOCKED.to_s] | |||
] | |||
end | |||
def available_columns | |||
return @available_columns if @available_columns | |||
@available_columns = self.class.available_columns.dup | |||
if Setting.twofa? | |||
@available_columns << QueryColumn.new(:twofa_scheme, sortable: "#{User.table_name}.twofa_scheme") | |||
end | |||
@available_columns += user_custom_fields.visible. | |||
map {|cf| QueryCustomFieldColumn.new(cf)} | |||
@available_columns | |||
end | |||
# Returns a scope of user custom fields that are available as columns or filters | |||
def user_custom_fields | |||
UserCustomField.sorted | |||
end | |||
def default_columns_names | |||
@default_columns_names ||= [:login, :firstname, :lastname, :mail, :admin, :created_on, :last_login_on] | |||
end | |||
def default_sort_criteria | |||
[['login', 'asc']] | |||
end | |||
def base_scope | |||
User.logged.where(statement).includes(:email_address) | |||
end | |||
def results_scope(options={}) | |||
order_option = [group_by_sort_order, (options[:order] || sort_clause)].flatten.reject(&:blank?) | |||
base_scope. | |||
order(order_option). | |||
joins(joins_for_order_statement(order_option.join(','))) | |||
end | |||
def sql_for_admin_field(field, operator, value) | |||
return unless value = value.first | |||
true_value = operator == '=' ? '1' : '0' | |||
val = | |||
if value.to_s == true_value | |||
self.class.connection.quoted_true | |||
else | |||
self.class.connection.quoted_false | |||
end | |||
"(#{User.table_name}.admin = #{val})" | |||
end | |||
def sql_for_is_member_of_group_field(field, operator, value) | |||
if ["*", "!*"].include? operator | |||
value = Group.givable.map(&:id) | |||
end | |||
e = operator.start_with?("!") ? "NOT EXISTS" : "EXISTS" | |||
"(#{e} (SELECT 1 FROM groups_users WHERE #{User.table_name}.id = groups_users.user_id AND #{sql_for_field(field, '=', value, 'groups_users', 'group_id')}))" | |||
end | |||
def sql_for_mail_field(field, operator, value) | |||
if operator == '!*' | |||
match = false | |||
operator = '*' | |||
else | |||
match = true | |||
end | |||
emails = EmailAddress.table_name | |||
<<-SQL | |||
#{match ? 'EXISTS' : 'NOT EXISTS'} | |||
(SELECT 1 FROM #{emails} WHERE | |||
#{emails}.user_id = #{User.table_name}.id AND | |||
#{sql_for_field(:mail, operator, value, emails, 'address')}) | |||
SQL | |||
end | |||
end |
@@ -0,0 +1,24 @@ | |||
<ul> | |||
<% if @user %> | |||
<% if @user.locked? %> | |||
<li> | |||
<%= context_menu_link l(:button_unlock), user_path(@user, user: { status: User::STATUS_ACTIVE }, back_url: @back), method: :put, class: 'icon icon-unlock' %> | |||
</li> | |||
<% elsif User.current != @user %> | |||
<li> | |||
<%= context_menu_link l(:button_lock), user_path(@user, user: { status: User::STATUS_LOCKED }, back_url: @back), method: :put, class: 'icon icon-lock' %> | |||
</li> | |||
<% end %> | |||
<li> | |||
<%= context_menu_link l(:button_edit), edit_user_path(@user, back_url: @back), class: 'icon icon-edit' %> | |||
</li> | |||
<% unless User.current == @user %> | |||
<li> | |||
<%= context_menu_link l(:button_delete), user_path(@user, back_url: @back), | |||
method: :delete, class: 'icon icon-del' %> | |||
</li> | |||
<% end %> | |||
<% end %> | |||
</ul> |
@@ -0,0 +1,65 @@ | |||
<%= form_tag({}, data: {cm_url: users_context_menu_path}) do -%> | |||
<%= hidden_field_tag 'back_url', url_for(params: request.query_parameters), id: nil %> | |||
<div class="autoscroll"> | |||
<table class="list odd-even users"> | |||
<thead> | |||
<tr> | |||
<th class="checkbox hide-when-print"> | |||
<%= check_box_tag 'check_all', '', false, :class => 'toggle-selection', | |||
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %> | |||
</th> | |||
<% @query.inline_columns.each do |column| %> | |||
<%= column_header(@query, column) %> | |||
<% end %> | |||
<th></th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<% grouped_query_results(users, @query) do |user, group_name, group_count, group_totals| -%> | |||
<% if group_name %> | |||
<% reset_cycle %> | |||
<tr class="group open"> | |||
<td colspan="<%= @query.inline_columns.size + 2 %>"> | |||
<span class="expander" onclick="toggleRowGroup(this);"> </span> | |||
<span class="name"><%= group_name %></span> | |||
<% if group_count %> | |||
<span class="count"><%= group_count %></span> | |||
<% end %> | |||
<span class="totals"><%= group_totals %></span> | |||
<%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}", | |||
"toggleAllRowGroups(this)", :class => 'toggle-all') %> | |||
</td> | |||
</tr> | |||
<% end %> | |||
<tr id="user-<%= user.id %>" class="user <%= cycle("odd", "even") %> hascontextmenu"> | |||
<td class="checkbox hide-when-print"><%= check_box_tag("ids[]", user.id, false, id: nil) %></td> | |||
<% @query.inline_columns.each do |column| %> | |||
<% if column.name == :login %> | |||
<%= content_tag('td', link_to(user.login, edit_user_path(user)), class: column.css_classes) %> | |||
<% else %> | |||
<%= content_tag('td', column_content(column, user), class: column.css_classes) %> | |||
<% end %> | |||
<% end %> | |||
<td class="buttons"> | |||
<%= link_to_context_menu %> | |||
</td> | |||
</tr> | |||
<% @query.block_columns.each do |column| | |||
if (text = column_content(column, issue)) && text.present? -%> | |||
<tr class="<%= current_cycle %>"> | |||
<td colspan="<%= @query.inline_columns.size + 1 %>" class="<%= column.css_classes %>"> | |||
<% if query.block_columns.count > 1 %> | |||
<span><%= column.caption %></span> | |||
<% end %> | |||
<%= text %> | |||
</td> | |||
</tr> | |||
<% end -%> | |||
<% end -%> | |||
<% end -%> | |||
</tbody> | |||
</table> | |||
</div> | |||
<% end -%> | |||
<%= context_menu %> |
@@ -7,94 +7,54 @@ | |||
<% end %> | |||
</div> | |||
<h2><%=l(:label_user_plural)%></h2> | |||
<h2><%= @query.new_record? ? l(:label_user_plural) : @query.name %></h2> | |||
<%= form_tag(users_path, { :method => :get, :id => 'users_form' }) do %> | |||
<fieldset><legend><%= l(:label_filter_plural) %></legend> | |||
<label for='status'><%= l(:field_status) %>:</label> | |||
<%= select_tag 'status', users_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %> | |||
<% if @groups.present? %> | |||
<label for='group_id'><%= l(:label_group) %>:</label> | |||
<%= select_tag 'group_id', content_tag('option') + options_from_collection_for_select(@groups, :id, :name, params[:group_id].to_i), :onchange => "this.form.submit(); return false;" %> | |||
<% end %> | |||
<% if Setting.twofa_required? || Setting.twofa_optional? %> | |||
<label for='twofa'><%= l(:setting_twofa) %>:</label> | |||
<%= select_tag 'twofa', options_for_select([[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], params[:twofa]), :onchange => "this.form.submit(); return false;", :include_blank => true %> | |||
<%= form_tag(users_path, method: :get, id: 'query_form') do %> | |||
<%= render partial: 'queries/query_form' %> | |||
<% end %> | |||
<label for='name'><%= l(:label_user) %>:</label> | |||
<%= text_field_tag 'name', params[:name], :size => 30 %> | |||
<%= submit_tag l(:button_apply), :class => "small", :name => nil %> | |||
<%= link_to l(:button_clear), users_path, :class => 'icon icon-reload' %> | |||
</fieldset> | |||
<%= hidden_field_tag 'encoding', l(:general_csv_encoding) unless l(:general_csv_encoding).casecmp('UTF-8') == 0 %> | |||
<% end %> | |||
| |||
<% if @users.any? %> | |||
<div class="autoscroll"> | |||
<table class="list users"> | |||
<thead><tr> | |||
<%= sort_header_tag('login', :caption => l(:field_login)) %> | |||
<%= sort_header_tag('firstname', :caption => l(:field_firstname)) %> | |||
<%= sort_header_tag('lastname', :caption => l(:field_lastname)) %> | |||
<th><%= l(:field_mail) %></th> | |||
<%= sort_header_tag('admin', :caption => l(:field_admin), :default_order => 'desc') %> | |||
<% if Setting.twofa_required? || Setting.twofa_optional? %> | |||
<th class="whitespace-normal"><%= l(:setting_twofa) %></th> | |||
<% if @query.valid? %> | |||
<% if @users.empty? %> | |||
<p class="nodata"><%= l(:label_no_data) %></p> | |||
<% else %> | |||
<%= render_query_totals(@query) %> | |||
<%= render partial: 'list', :locals => { :users => @users }%> | |||
<span class="pagination"><%= pagination_links_full @user_pages, @user_count %></span> | |||
<% end %> | |||
<%= sort_header_tag('created_on', :caption => l(:field_created_on), :default_order => 'desc') %> | |||
<%= sort_header_tag('last_login_on', :caption => l(:field_last_login_on), :default_order => 'desc') %> | |||
<th></th> | |||
</tr></thead> | |||
<tbody> | |||
<% for user in @users -%> | |||
<tr class="<%= user.css_classes %>"> | |||
<td class="username"><%= avatar(user, :size => "14") %><%= link_to user.login, edit_user_path(user) %></td> | |||
<td class="firstname"><%= user.firstname %></td> | |||
<td class="lastname"><%= user.lastname %></td> | |||
<td class="email"><%= mail_to(user.mail) %></td> | |||
<td class="tick"><%= checked_image user.admin? %></td> | |||
<% if Setting.twofa_required? || Setting.twofa_optional? %> | |||
<td class="twofa tick"><%= checked_image user.twofa_active? %></td> | |||
<% other_formats_links do |f| %> | |||
<%= f.link_to_with_query_parameters 'CSV', {}, :onclick => "showModal('csv-export-options', '350px'); return false;" %> | |||
<% end %> | |||
<td class="created_on"><%= format_time(user.created_on) %></td> | |||
<td class="last_login_on"><%= format_time(user.last_login_on) unless user.last_login_on.nil? %></td> | |||
<td class="buttons"> | |||
<%= change_status_link(user) %> | |||
<%= delete_link user_path(user, :back_url => request.original_fullpath), :data => {} unless User.current == user %> | |||
</td> | |||
</tr> | |||
<% end -%> | |||
</tbody> | |||
</table> | |||
</div> | |||
<span class="pagination"><%= pagination_links_full @user_pages, @user_count %></span> | |||
<% other_formats_links do |f| %> | |||
<%= f.link_to_with_query_parameters 'CSV', {}, :onclick => "showModal('csv-export-options', '330px'); return false;" %> | |||
<% end %> | |||
<div id="csv-export-options" style="display: none;"> | |||
<h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3> | |||
<%= export_csv_encoding_select_tag %> | |||
<p class="buttons"> | |||
<%= submit_tag l(:button_export), :name => nil, :id => 'csv-export-button' %> | |||
<%= submit_tag l(:button_cancel), :name => nil, :onclick => 'hideModal(this);', :type => 'button' %> | |||
</p> | |||
</div> | |||
<%= javascript_tag do %> | |||
$(document).ready(function(){ | |||
$('input#csv-export-button').click(function(){ | |||
$('form input#encoding').val($('select#encoding option:selected').val()); | |||
$('form#users_form').attr('action', "<%= users_path(:format => 'csv') %>").submit(); | |||
$('form#users_form').attr('action', '<%= users_path %>'); | |||
hideModal(this); | |||
}); | |||
}); | |||
<% end %> | |||
<% else %> | |||
<p class="nodata"><%= l(:label_no_data) %></p> | |||
<div id="csv-export-options" style="display:none;"> | |||
<h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3> | |||
<%= form_tag(users_path(format: 'csv'), method: :get, id: 'csv-export-form') do %> | |||
<%= query_as_hidden_field_tags(@query) %> | |||
<%= hidden_field_tag('query_name', @query.name) %> | |||
<p> | |||
<label><%= radio_button_tag 'c[]', '', true %> <%= l(:description_selected_columns) %></label><br /> | |||
<label><%= radio_button_tag 'c[]', 'all_inline' %> <%= l(:description_all_columns) %></label> | |||
</p> | |||
<% if @query.available_block_columns.any? %> | |||
<fieldset id="csv-export-block-columns"> | |||
<legend> | |||
<%= toggle_checkboxes_link('#csv-export-block-columns input[type=checkbox]') %> | |||
</legend> | |||
<% @query.available_block_columns.each do |column| %> | |||
<label><%= check_box_tag 'c[]', column.name, @query.has_column?(column), :id => nil %> <%= column.caption %></label> | |||
<% end %> | |||
</fieldset> | |||
<% end %> | |||
<%= export_csv_encoding_select_tag %> | |||
<p class="buttons"> | |||
<%= submit_tag l(:button_export), :name => nil, :onclick => "hideModal(this);", :data => { :disable_with => false } %> | |||
<%= link_to_function l(:button_cancel), "hideModal(this);" %> | |||
</p> | |||
<% end %> | |||
</div> | |||
<% end %> | |||
<% content_for :sidebar do %> | |||
<%= render_sidebar_queries(UserQuery, nil) %> | |||
<%= call_hook(:view_users_sidebar_queries_bottom) %> | |||
<% end %> | |||
<% html_title(l(:label_user_plural)) -%> |
@@ -331,6 +331,7 @@ de: | |||
field_is_filter: Als Filter benutzen | |||
field_is_for_all: Für alle Projekte | |||
field_is_in_roadmap: In der Roadmap anzeigen | |||
field_is_member_of_group: Mitglied in Gruppe | |||
field_is_private: Privat | |||
field_is_public: Öffentlich | |||
field_is_required: Erforderlich |
@@ -302,6 +302,7 @@ en: | |||
field_title: Title | |||
field_project: Project | |||
field_issue: Issue | |||
field_is_member_of_group: Member of group | |||
field_status: Status | |||
field_notes: Notes | |||
field_is_closed: Issue closed |
@@ -108,6 +108,7 @@ Rails.application.routes.draw do | |||
match 'my/twofa/backup_codes', :controller => 'twofa_backup_codes', :action => 'show', :via => [:get] | |||
match 'users/:user_id/twofa/deactivate', :controller => 'twofa', :action => 'admin_deactivate', :via => :post | |||
match '/users/context_menu', to: 'context_menus#users', as: :users_context_menu, via: [:get, :post] | |||
resources :users do | |||
resources :memberships, :controller => 'principal_memberships' | |||
resources :email_addresses, :only => [:index, :create, :update, :destroy] |
@@ -37,33 +37,33 @@ class UsersControllerTest < Redmine::ControllerTest | |||
def test_index | |||
get :index | |||
assert_response :success | |||
active = User.active.first | |||
locked = User.where(status: User::STATUS_LOCKED).first | |||
assert_select 'table.users' | |||
assert_select 'tr.user.active' | |||
assert_select 'tr.user.locked', 0 | |||
assert_select "tr#user-#{active.id}" | |||
assert_select "tr#user-#{locked.id}", 0 | |||
end | |||
def test_index_with_status_filter | |||
get :index, :params => {:status => 3} | |||
get :index, params: { set_filter: 1, f: ['status'], op: {status: '='}, v: {status: [3]} } | |||
assert_response :success | |||
assert_select 'tr.user.active', 0 | |||
assert_select 'tr.user.locked' | |||
assert_select "tr.user", User.where(status: 3).count | |||
end | |||
def test_index_with_name_filter | |||
get :index, :params => {:name => 'john'} | |||
def test_index_with_firstname_filter | |||
get :index, params: { set_filter: 1, f: ['firstname'], op: {firstname: '~'}, v: {firstname: ['john']} } | |||
assert_response :success | |||
assert_select 'tr.user td.username', :text => 'jsmith' | |||
assert_select 'tr.user td.login', text: 'jsmith' | |||
assert_select 'tr.user', 1 | |||
end | |||
def test_index_with_group_filter | |||
get :index, :params => {:group_id => '10'} | |||
get :index, params: { | |||
set_filter: 1, | |||
f: ['is_member_of_group'], op: {is_member_of_group: '='}, v: {is_member_of_group: ['10']} | |||
} | |||
assert_response :success | |||
assert_select 'tr.user', Group.find(10).users.count | |||
assert_select 'select[name=group_id]' do | |||
assert_select 'option[value="10"][selected=selected]' | |||
end | |||
end | |||
def test_index_should_not_show_2fa_filter_and_column_if_disabled | |||
@@ -71,8 +71,12 @@ class UsersControllerTest < Redmine::ControllerTest | |||
get :index | |||
assert_response :success | |||
assert_select "select#twofa", 0 | |||
assert_select 'td.twofa', 0 | |||
assert_select "select#add_filter_select" do | |||
assert_select "option[value=twofa_scheme]", 0 | |||
end | |||
assert_select "select#available_c" do | |||
assert_select "option[value=twofa_scheme]", 0 | |||
end | |||
end | |||
end | |||
@@ -83,30 +87,91 @@ class UsersControllerTest < Redmine::ControllerTest | |||
user.twofa_scheme = "totp" | |||
user.save | |||
get :index, :params => {:twofa => '1'} | |||
get :index, params: { set_filter: 1, f: ['twofa_scheme'], op: {twofa_scheme: '*'} } | |||
assert_response :success | |||
assert_select "select#twofa", 1 | |||
assert_select 'tr#user-1', 1 | |||
assert_select 'tr.user', 1 | |||
assert_select 'td.twofa.tick .icon-checked' | |||
assert_select "select#add_filter_select" do | |||
assert_select "option[value=twofa_scheme]" | |||
end | |||
assert_select "select#available_c" do | |||
assert_select "option[value=twofa_scheme]" | |||
end | |||
end | |||
end | |||
def test_index_filter_by_twofa_no | |||
def test_index_filter_by_twofa_scheme | |||
with_settings twofa: "1" do | |||
user = User.find(1) | |||
user.twofa_totp_key = "AVYA3RARZ3GY3VWT7MIEJ72I5TTJRO3X" | |||
user.twofa_scheme = "totp" | |||
user.save | |||
get :index, :params => {:twofa => '0'} | |||
get :index, params: { | |||
set_filter: 1, | |||
f: ['twofa_scheme'], op: {twofa_scheme: '='}, v: {twofa_scheme: ['totp']} | |||
} | |||
assert_response :success | |||
assert_select "select#twofa", 1 | |||
assert_select "td.twofa.tick" do | |||
assert_select "span.icon-checked", 0 | |||
assert_select 'tr#user-1', 1 | |||
assert_select "select#add_filter_select" do | |||
assert_select "option[value=twofa_scheme]" | |||
end | |||
assert_select "select#available_c" do | |||
assert_select "option[value=twofa_scheme]" | |||
end | |||
end | |||
end | |||
def test_index_filter_by_twofa_no | |||
with_settings twofa: "1" do | |||
user = User.find(1) | |||
user.twofa_totp_key = "AVYA3RARZ3GY3VWT7MIEJ72I5TTJRO3X" | |||
user.twofa_scheme = "totp" | |||
user.save | |||
get :index, params: { set_filter: 1, f: ['twofa_scheme'], op: {twofa_scheme: '!*'} } | |||
assert_response :success | |||
assert_select 'tr#user-1', 0 | |||
assert_select 'tr.user' | |||
end | |||
end | |||
def test_index_filter_by_auth_source_none | |||
user = User.find(1) | |||
user.update_column :auth_source_id, 1 | |||
get :index, params: { | |||
set_filter: 1, | |||
f: ['auth_source_id'], op: {auth_source_id: '!*'} | |||
} | |||
assert_response :success | |||
assert_select 'tr.user' | |||
assert_select 'tr#user-1', 0 | |||
end | |||
def test_index_filter_by_auth_source | |||
user = User.find(1) | |||
user.update_column :auth_source_id, 1 | |||
get :index, params: { | |||
set_filter: 1, | |||
f: ['auth_source_id'], op: {auth_source_id: '='}, v: {auth_source_id: ['1']} | |||
} | |||
assert_response :success | |||
assert_select 'tr#user-1', 1 | |||
assert_select "select#add_filter_select" do | |||
assert_select "option[value=auth_source_id]" | |||
end | |||
assert_select "select#available_c" do | |||
assert_select "option[value='auth_source.name']" | |||
end | |||
end | |||
@@ -114,7 +179,7 @@ class UsersControllerTest < Redmine::ControllerTest | |||
with_settings :default_language => 'en' do | |||
user = User.logged.status(1).first | |||
user.update(passwd_changed_on: Time.current.last_month, twofa_scheme: 'totp') | |||
get :index, params: {format: 'csv'} | |||
get :index, params: {format: 'csv', c: ['updated_on', 'status', 'passwd_changed_on', 'twofa_scheme']} | |||
assert_response :success | |||
assert_equal User.logged.status(1).count, response.body.chomp.split("\n").size - 1 | |||
@@ -142,7 +207,13 @@ class UsersControllerTest < Redmine::ControllerTest | |||
User.find(@request.session[:user_id]).update(:language => nil) | |||
with_settings :default_language => 'fr' do | |||
get :index, :params => {:name => user.lastname, :format => 'csv'} | |||
get :index, params: { | |||
c: ["cf_#{float_custom_field.id}", "cf_#{date_custom_field.id}"], | |||
f: ["name"], | |||
op: { name: "~" }, | |||
v: { name: [user.lastname] }, | |||
format: 'csv' | |||
} | |||
assert_response :success | |||
assert_include 'float field;date field', response.body | |||
@@ -153,7 +224,12 @@ class UsersControllerTest < Redmine::ControllerTest | |||
def test_index_csv_with_status_filter | |||
with_settings :default_language => 'en' do | |||
get :index, :params => {:status => 3, :format => 'csv'} | |||
get :index, :params => { | |||
:set_filter => '1', | |||
:f => [:status], :op => { :status => '=' }, :v => { :status => [3] }, | |||
:c => [:login, :status], | |||
:format => 'csv' | |||
} | |||
assert_response :success | |||
assert_equal User.logged.status(3).count, response.body.chomp.split("\n").size - 1 | |||
@@ -164,7 +240,12 @@ class UsersControllerTest < Redmine::ControllerTest | |||
end | |||
def test_index_csv_with_name_filter | |||
get :index, :params => {:name => 'John', :format => 'csv'} | |||
get :index, :params => { | |||
:set_filter => '1', | |||
:f => [:firstname], :op => { :firstname => '~' }, :v => { :firstname => ['John'] }, | |||
:c => [:login, :firstname, :status], | |||
:format => 'csv' | |||
} | |||
assert_response :success | |||
assert_equal User.logged.like('John').count, response.body.chomp.split("\n").size - 1 | |||
@@ -173,7 +254,12 @@ class UsersControllerTest < Redmine::ControllerTest | |||
end | |||
def test_index_csv_with_group_filter | |||
get :index, :params => {:group_id => '10', :format => 'csv'} | |||
get :index, :params => { | |||
:set_filter => '1', | |||
:f => [:is_member_of_group], :op => { :is_member_of_group => '=' }, :v => { :is_member_of_group => [10] }, | |||
:c => [:login, :status], | |||
:format => 'csv' | |||
} | |||
assert_response :success | |||
assert_equal Group.find(10).users.count, response.body.chomp.split("\n").size - 1 |