Project

General

Profile

Feature #37674 » 0001-introduces-a-UserQuery-model-for-admin-users.patch

Jens Krämer, 2022-09-14 04:37

View differences:

app/controllers/context_menus_controller.rb
108 108
    end
109 109
    render layout: false
110 110
  end
111

  
112
  def users
113
    @users = User.where(id: params[:ids]).to_a
114

  
115
    (render_404; return) unless @users.present?
116
    if (@users.size == 1)
117
      @user = @users.first
118
    end
119
    render layout: false
120
  end
111 121
end
app/controllers/queries_controller.rb
177 177
    end
178 178
  end
179 179

  
180
  def redirect_to_user_query(options)
181
    redirect_to users_path(options)
182
  end
183

  
180 184
  # Returns the Query subclass, IssueQuery by default
181 185
  # for compatibility with previous behaviour
182 186
  def query_class
app/controllers/users_controller.rb
34 34
  helper :principal_memberships
35 35
  helper :activities
36 36
  include ActivitiesHelper
37
  helper :queries
38
  include QueriesHelper
39
  helper :user_queries
40
  include UserQueriesHelper
37 41

  
38 42
  require_sudo_mode :create, :update, :destroy
39 43

  
40 44
  def index
41
    sort_init 'login', 'asc'
42
    sort_update %w(login firstname lastname admin created_on last_login_on)
45
    use_session = !request.format.csv?
46
    retrieve_query(UserQuery, use_session)
43 47

  
44
    case params[:format]
45
    when 'xml', 'json'
46
      @offset, @limit = api_offset_and_limit
47
    else
48
      @limit = per_page_option
49
    end
50

  
51
    @status = params[:status] || 1
52

  
53
    scope = User.logged.status(@status).preload(:email_address)
54
    scope = scope.like(params[:name]) if params[:name].present?
55
    scope = scope.in_group(params[:group_id]) if params[:group_id].present?
48
    if @query.valid?
49
      scope = @query.results_scope
56 50

  
57
    if params[:twofa].present?
58
      case params[:twofa].to_i
59
      when 1
60
        scope = scope.where.not(twofa_scheme: nil)
61
      when 0
62
        scope = scope.where(twofa_scheme: nil)
63
      end
64
    end
51
#      sort_init 'login', 'asc'
52
#      sort_update %w(login firstname lastname admin created_on last_login_on)
65 53

  
66
    @user_count = scope.count
67
    @user_pages = Paginator.new @user_count, @limit, params['page']
68
    @offset ||= @user_pages.offset
69
    @users =  scope.order(sort_clause).limit(@limit).offset(@offset).to_a
54
      @user_count = scope.count
70 55

  
71
    respond_to do |format|
72
      format.html do
73
        @groups = Group.givable.sort
74
        render :layout => !request.xhr?
56
      respond_to do |format|
57
        format.html do
58
          @limit = per_page_option
59
          @user_pages = Paginator.new @user_count, @limit, params['page']
60
          @offset ||= @user_pages.offset
61
          @users = scope.limit(@limit).offset(@offset).to_a
62
          render :layout => !request.xhr?
63
        end
64
        format.csv do
65
          # Export all entries
66
          @entries = scope.to_a
67
          send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'users.csv')
68
        end
69
        format.api do
70
          @offset, @limit = api_offset_and_limit
71
          @users = scope.limit(@limit).offset(@offset).to_a
72
        end
75 73
      end
76
      format.csv do
77
        send_data(users_to_csv(scope.order(sort_clause)), :type => 'text/csv; header=present', :filename => 'users.csv')
74
    else
75
      respond_to do |format|
76
        format.html {render :layout => !request.xhr?}
77
        format.csv {head 422}
78
        format.api {render_validation_errors(@query)}
78 79
      end
79
      format.api
80 80
    end
81 81
  end
82 82

  
app/helpers/user_queries_helper.rb
1
module UserQueriesHelper
2

  
3
  def column_value(column, object, value)
4
    if object.is_a?(User) && column.name == :status
5
      user_status_label(column.value_object(object))
6
    else
7
      super
8
    end
9
  end
10

  
11
  def csv_value(column, object, value)
12
    if object.is_a?(User)
13
      case column.name
14
      when :status
15
        user_status_label(column.value_object(object))
16
      when :twofa_scheme
17
        twofa_scheme_label value
18
      else
19
        super
20
      end
21
    else
22
      super
23
    end
24
  end
25

  
26
  def user_status_label(value)
27
    case value.to_i
28
    when User::STATUS_ACTIVE
29
      l(:status_active)
30
    when User::STATUS_REGISTERED
31
      l(:status_registered)
32
    when User::STATUS_LOCKED
33
      l(:status_locked)
34
    end
35
  end
36

  
37
  def twofa_scheme_label(value)
38
    if value
39
      ::I18n.t :"twofa__#{value}__name"
40
    else
41
      ::I18n.t :label_disabled
42
    end
43
  end
44
end
app/models/query.rb
150 150
  end
151 151

  
152 152
  def value_object(object)
153
    if custom_field.visible_by?(object.project, User.current)
153
    project = object.project if object.respond_to?(:project)
154
    if custom_field.visible_by?(project, User.current)
154 155
      cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}
155 156
      cv.size > 1 ? cv.sort_by {|e| e.value.to_s} : cv.first
156 157
    else
app/models/user_query.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-2022  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18

  
19
class UserQuery < Query
20
  self.queried_class = Principal # must be Principal (not User) for custom field filters to work
21

  
22
  self.available_columns = [
23
    QueryColumn.new(:login, sortable: "#{User.table_name}.login"),
24
    QueryColumn.new(:firstname, sortable: "#{User.table_name}.firstname"),
25
    QueryColumn.new(:lastname, sortable: "#{User.table_name}.lastname"),
26
    QueryColumn.new(:mail, sortable: "#{EmailAddress.table_name}.address"),
27
    QueryColumn.new(:admin, sortable: "#{User.table_name}.admin"),
28
    QueryColumn.new(:created_on, :sortable => "#{User.table_name}.created_on"),
29
    QueryColumn.new(:updated_on, :sortable => "#{User.table_name}.updated_on"),
30
    QueryColumn.new(:last_login_on, :sortable => "#{User.table_name}.last_login_on"),
31
    QueryColumn.new(:passwd_changed_on, :sortable => "#{User.table_name}.passwd_changed_on"),
32
    QueryColumn.new(:status, sortable: "#{User.table_name}.status")
33
  ]
34

  
35
  def initialize(attributes=nil, *args)
36
    super attributes
37
    self.filters ||= { 'status' => {operator: "=", values: [User::STATUS_ACTIVE]} }
38
  end
39

  
40
  def initialize_available_filters
41
    add_available_filter "status",
42
      type: :list, values: ->{ user_statuses_values }
43
    add_available_filter "is_member_of_group",
44
      type: :list_optional,
45
      values: ->{ Group.givable.visible.map {|g| [g.name, g.id.to_s] } }
46
    if Setting.twofa?
47
      add_available_filter "twofa_scheme",
48
        type: :list_optional,
49
        values: ->{ Redmine::Twofa.available_schemes.map {|s| [I18n.t("twofa__#{s}__name"), s] } }
50
    end
51
    add_available_filter "name", type: :text
52
    add_available_filter "created_on", type: :date_past
53
    add_available_filter "last_login_on", type: :date_past
54
    add_available_filter "admin",
55
      type: :list,
56
      values: [ [l(:general_text_yes), '1'], [l(:general_text_no), '0'] ]
57
    add_custom_fields_filters(user_custom_fields)
58
  end
59

  
60
  def user_statuses_values
61
    [
62
      [l(:status_active), User::STATUS_ACTIVE.to_s],
63
      [l(:status_registered), User::STATUS_REGISTERED.to_s],
64
      [l(:status_locked), User::STATUS_LOCKED.to_s]
65
    ]
66
  end
67

  
68
  def available_columns
69
    return @available_columns if @available_columns
70

  
71
    @available_columns = self.class.available_columns.dup
72
    if Setting.twofa?
73
      @available_columns << QueryColumn.new(:twofa_scheme, sortable: "#{User.table_name}.twofa_scheme")
74
    end
75
    @available_columns += user_custom_fields.visible.
76
                            map {|cf| QueryCustomFieldColumn.new(cf)}
77

  
78
    @available_columns
79
  end
80

  
81
  # Returns a scope of user custom fields that are available as columns or filters
82
  def user_custom_fields
83
    UserCustomField.sorted
84
  end
85

  
86

  
87
  def default_columns_names
88
    @default_columns_names ||= %i[ login firstname lastname mail admin created_on last_login_on ]
89
  end
90

  
91
  def default_sort_criteria
92
    [['login', 'asc']]
93
  end
94

  
95
  def base_scope
96
    User.logged.where(statement).includes(:email_address)
97
  end
98

  
99
  def results_scope(options={})
100
    order_option = [group_by_sort_order, (options[:order] || sort_clause)].flatten.reject(&:blank?)
101

  
102
    base_scope.
103
      order(order_option).
104
      joins(joins_for_order_statement(order_option.join(',')))
105
  end
106

  
107
  def sql_for_admin_field(field, operator, value)
108
    return unless value = value.first
109
    true_value = operator == '=' ? '1' : '0'
110
    val = (value.to_s == true_value) ?
111
      self.class.connection.quoted_true :
112
      self.class.connection.quoted_false
113
    "(#{User.table_name}.admin = #{val})"
114
  end
115

  
116
  def sql_for_is_member_of_group_field(field, operator, value)
117
    if ["*", "!*"].include? operator
118
      value = Group.givable.map(&:id)
119
    end
120

  
121
    e = operator.start_with?("!") ? "NOT EXISTS" : "EXISTS"
122

  
123
    "(#{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")}))"
124
  end
125

  
126
  def sql_for_name_field(field, operator, value)
127
    match = operator == '~'
128
    name_sql = sql_contains("CONCAT(login, ' ', firstname, ' ', lastname)",
129
                            value.first, match: match)
130

  
131
    emails = EmailAddress.table_name
132
    email_sql = <<-SQL
133
      #{match ? "EXISTS" : "NOT EXISTS"}
134
      (SELECT 1 FROM #{emails} WHERE
135
        #{emails}.user_id = #{User.table_name}.id AND
136
        #{sql_contains("#{emails}.address", value.first, match: true)})
137
    SQL
138

  
139
    if match
140
      "(#{name_sql}) OR (#{email_sql})"
141
    else
142
      "(#{name_sql}) AND (#{email_sql})"
143
    end
144
  end
145

  
146
end
app/views/context_menus/users.html.erb
1
<ul>
2
  <% if @user %>
3
    <% if @user.locked? %>
4
      <li>
5
        <%= context_menu_link l(:button_unlock), user_path(@user, user: { status: User::STATUS_ACTIVE }, back_url: @back), method: :put, class: 'icon icon-unlock' %>
6
      </li>
7
    <% elsif User.current != @user %>
8
      <li>
9
        <%= context_menu_link l(:button_lock), user_path(@user, user: { status: User::STATUS_LOCKED }, back_url: @back), method: :put, class: 'icon icon-lock' %>
10
      </li>
11
    <% end %>
12

  
13
    <li>
14
      <%= context_menu_link l(:button_edit), edit_user_path(@user, back_url: @back), class: 'icon icon-edit' %>
15
    </li>
16

  
17
    <% unless User.current == @user %>
18
      <li>
19
        <%= context_menu_link l(:button_delete), user_path(@user, back_url: @back),
20
          method: :delete, class: 'icon icon-del' %>
21
      </li>
22
    <% end %>
23
  <% end %>
24
</ul>
app/views/users/_list.html.erb
1
<%= form_tag({}, data: {cm_url: users_context_menu_path}) do -%>
2
<%= hidden_field_tag 'back_url', url_for(params: request.query_parameters), id: nil %>
3
<div class="autoscroll">
4
<table class="list odd-even users">
5
<thead>
6
  <tr>
7
    <th class="checkbox hide-when-print">
8
      <%= check_box_tag 'check_all', '', false, :class => 'toggle-selection',
9
        :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
10
    </th>
11
    <% @query.inline_columns.each do |column| %>
12
      <%= column_header(@query, column) %>
13
    <% end %>
14
    <th></th>
15
  </tr>
16
</thead>
17
<tbody>
18
<% grouped_query_results(users, @query) do |user, group_name, group_count, group_totals| -%>
19
  <% if group_name %>
20
    <% reset_cycle %>
21
    <tr class="group open">
22
      <td colspan="<%= @query.inline_columns.size + 2 %>">
23
        <span class="expander" onclick="toggleRowGroup(this);">&nbsp;</span>
24
        <span class="name"><%= group_name %></span>
25
        <% if group_count %>
26
        <span class="count"><%= group_count %></span>
27
        <% end %>
28
        <span class="totals"><%= group_totals %></span>
29
        <%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}",
30
                             "toggleAllRowGroups(this)", :class => 'toggle-all') %>
31
      </td>
32
    </tr>
33
  <% end %>
34
  <tr id="user-<%= user.id %>" class="user <%= cycle("odd", "even") %> hascontextmenu">
35
    <td class="checkbox hide-when-print"><%= check_box_tag("ids[]", user.id, false, id: nil) %></td>
36
    <% @query.inline_columns.each do |column| %>
37
      <% if column.name == :login %>
38
        <%= content_tag('td', link_to(user.login, edit_user_path(user)), class: column.css_classes) %>
39
      <% else %>
40
        <%= content_tag('td', column_content(column, user), class: column.css_classes) %>
41
      <% end %>
42
    <% end %>
43
    <td class="buttons">
44
      <%= link_to_context_menu %>
45
    </td>
46
  </tr>
47
  <% @query.block_columns.each do |column|
48
       if (text = column_content(column, issue)) && text.present? -%>
49
  <tr class="<%= current_cycle %>">
50
    <td colspan="<%= @query.inline_columns.size + 1 %>" class="<%= column.css_classes %>">
51
    <% if query.block_columns.count > 1 %>
52
      <span><%= column.caption %></span>
53
    <% end %>
54
    <%= text %>
55
    </td>
56
  </tr>
57
  <% end -%>
58
  <% end -%>
59
<% end -%>
60
</tbody>
61
</table>
62
</div>
63
<% end -%>
64

  
65
<%= context_menu %>
66

  
app/views/users/index.html.erb
7 7
  <% end %>
8 8
</div>
9 9

  
10
<h2><%=l(:label_user_plural)%></h2>
10
<h2><%= @query.new_record? ? l(:label_user_plural) : @query.name %></h2>
11 11

  
12
<%= form_tag(users_path, { :method => :get, :id => 'users_form' }) do %>
13
<fieldset><legend><%= l(:label_filter_plural) %></legend>
14
<label for='status'><%= l(:field_status) %>:</label>
15
<%= select_tag 'status', users_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;"  %>
16

  
17
<% if @groups.present? %>
18
<label for='group_id'><%= l(:label_group) %>:</label>
19
<%= 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;"  %>
20
<% end %>
21

  
22
<% if Setting.twofa_required? || Setting.twofa_optional? %>
23
  <label for='twofa'><%= l(:setting_twofa) %>:</label>
24
  <%= 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 %>
12
<%= form_tag(users_path, method: :get, id: 'query_form') do %>
13
  <%= render partial: 'queries/query_form' %>
25 14
<% end %>
26 15

  
27
<label for='name'><%= l(:label_user) %>:</label>
28
<%= text_field_tag 'name', params[:name], :size => 30 %>
29
<%= submit_tag l(:button_apply), :class => "small", :name => nil %>
30
<%= link_to l(:button_clear), users_path, :class => 'icon icon-reload' %>
31
</fieldset>
32
<%= hidden_field_tag 'encoding', l(:general_csv_encoding) unless l(:general_csv_encoding).casecmp('UTF-8') == 0 %>
33
<% end %>
34
&nbsp;
35

  
36
<% if @users.any? %>
37
<div class="autoscroll">
38
<table class="list users">
39
  <thead><tr>
40
  <%= sort_header_tag('login', :caption => l(:field_login)) %>
41
  <%= sort_header_tag('firstname', :caption => l(:field_firstname)) %>
42
  <%= sort_header_tag('lastname', :caption => l(:field_lastname)) %>
43
  <th><%= l(:field_mail) %></th>
44
  <%= sort_header_tag('admin', :caption => l(:field_admin), :default_order => 'desc') %>
45
  <% if Setting.twofa_required? || Setting.twofa_optional? %>
46
    <th class="whitespace-normal"><%= l(:setting_twofa) %></th>
16
<% if @query.valid? %>
17
  <% if @users.empty? %>
18
    <p class="nodata"><%= l(:label_no_data) %></p>
19
  <% else %>
20
    <%= render_query_totals(@query) %>
21
    <%= render partial: 'list', :locals => { :users => @users }%>
22
    <span class="pagination"><%= pagination_links_full @user_pages, @user_count %></span>
47 23
  <% end %>
48
  <%= sort_header_tag('created_on', :caption => l(:field_created_on), :default_order => 'desc') %>
49
  <%= sort_header_tag('last_login_on', :caption => l(:field_last_login_on), :default_order => 'desc') %>
50
    <th></th>
51
  </tr></thead>
52
  <tbody>
53
<% for user in @users -%>
54
  <tr class="<%= user.css_classes %>">
55
  <td class="username"><%= avatar(user, :size => "14") %><%= link_to user.login, edit_user_path(user) %></td>
56
  <td class="firstname"><%= user.firstname %></td>
57
  <td class="lastname"><%= user.lastname %></td>
58
  <td class="email"><%= mail_to(user.mail) %></td>
59
  <td class="tick"><%= checked_image user.admin? %></td>
60
  <% if Setting.twofa_required? || Setting.twofa_optional? %>
61
    <td class="twofa tick"><%= checked_image user.twofa_active? %></td>
24
  <% other_formats_links do |f| %>
25
    <%= f.link_to_with_query_parameters 'CSV', {}, :onclick => "showModal('csv-export-options', '350px'); return false;" %>
62 26
  <% end %>
63
  <td class="created_on"><%= format_time(user.created_on) %></td>
64
  <td class="last_login_on"><%= format_time(user.last_login_on) unless user.last_login_on.nil? %></td>
65
    <td class="buttons">
66
      <%= change_status_link(user) %>
67
      <%= delete_link user_path(user, :back_url => request.original_fullpath), :data => {} unless User.current == user %>
68
    </td>
69
  </tr>
70
<% end -%>
71
  </tbody>
72
</table>
73
</div>
74
<span class="pagination"><%= pagination_links_full @user_pages, @user_count %></span>
75
<% other_formats_links do |f| %>
76
  <%= f.link_to_with_query_parameters 'CSV', {}, :onclick => "showModal('csv-export-options', '330px'); return false;" %>
77
<% end %>
78
<div id="csv-export-options" style="display: none;">
79
  <h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
80
  <%= export_csv_encoding_select_tag %>
81
  <p class="buttons">
82
    <%= submit_tag l(:button_export), :name => nil, :id => 'csv-export-button' %>
83
    <%= submit_tag l(:button_cancel), :name => nil, :onclick => 'hideModal(this);', :type => 'button' %>
84
  </p>
85
</div>
86
<%= javascript_tag do %>
87
$(document).ready(function(){
88
  $('input#csv-export-button').click(function(){
89
    $('form input#encoding').val($('select#encoding option:selected').val());
90
    $('form#users_form').attr('action', "<%= users_path(:format => 'csv') %>").submit();
91
    $('form#users_form').attr('action', '<%= users_path %>');
92
    hideModal(this);
93
  });
94
});
95
<% end %>
96
<% else %>
97
<p class="nodata"><%= l(:label_no_data) %></p>
27

  
28
  <div id="csv-export-options" style="display:none;">
29
    <h3 class="title"><%= l(:label_export_options, :export_format => 'CSV') %></h3>
30
    <%= form_tag(users_path(format: 'csv'), method: :get, id: 'csv-export-form') do %>
31
    <%= query_as_hidden_field_tags(@query) %>
32
    <%= hidden_field_tag('query_name', @query.name) %>
33
    <p>
34
      <label><%= radio_button_tag 'c[]', '', true %> <%= l(:description_selected_columns) %></label><br />
35
      <label><%= radio_button_tag 'c[]', 'all_inline' %> <%= l(:description_all_columns) %></label>
36
    </p>
37
    <% if @query.available_block_columns.any? %>
38
      <fieldset id="csv-export-block-columns">
39
        <legend>
40
          <%= toggle_checkboxes_link('#csv-export-block-columns input[type=checkbox]') %>
41
        </legend>
42
        <% @query.available_block_columns.each do |column| %>
43
          <label><%= check_box_tag 'c[]', column.name, @query.has_column?(column), :id => nil %> <%= column.caption %></label>
44
        <% end %>
45
      </fieldset>
46
    <% end %>
47
    <%= export_csv_encoding_select_tag %>
48
    <p class="buttons">
49
      <%= submit_tag l(:button_export), :name => nil, :onclick => "hideModal(this);", :data => { :disable_with => false } %>
50
      <%= link_to_function l(:button_cancel), "hideModal(this);" %>
51
    </p>
52
    <% end %>
53
  </div>
98 54
<% end %>
99 55

  
56
<% content_for :sidebar do %>
57
  <%= render_sidebar_queries(UserQuery, nil) %>
58
  <%= call_hook(:view_users_sidebar_queries_bottom) %>
59
<% end %>
100 60
<% html_title(l(:label_user_plural)) -%>
config/locales/de.yml
331 331
  field_is_filter: Als Filter benutzen
332 332
  field_is_for_all: Für alle Projekte
333 333
  field_is_in_roadmap: In der Roadmap anzeigen
334
  field_is_member_of_group: Mitglied in Gruppe
334 335
  field_is_private: Privat
335 336
  field_is_public: Öffentlich
336 337
  field_is_required: Erforderlich
config/locales/en.yml
302 302
  field_title: Title
303 303
  field_project: Project
304 304
  field_issue: Issue
305
  field_is_member_of_group: Member of group
305 306
  field_status: Status
306 307
  field_notes: Notes
307 308
  field_is_closed: Issue closed
config/routes.rb
108 108
  match 'my/twofa/backup_codes', :controller => 'twofa_backup_codes', :action => 'show', :via => [:get]
109 109
  match 'users/:user_id/twofa/deactivate', :controller => 'twofa', :action => 'admin_deactivate', :via => :post
110 110

  
111
  match '/users/context_menu', to: 'context_menus#users', as: :users_context_menu, via: [:get, :post]
111 112
  resources :users do
112 113
    resources :memberships, :controller => 'principal_memberships'
113 114
    resources :email_addresses, :only => [:index, :create, :update, :destroy]
test/functional/users_controller_test.rb
37 37
  def test_index
38 38
    get :index
39 39
    assert_response :success
40
    active = User.active.first
41
    locked = User.where(status: User::STATUS_LOCKED).first
40 42
    assert_select 'table.users'
41
    assert_select 'tr.user.active'
42
    assert_select 'tr.user.locked', 0
43
    assert_select "tr#user-#{active.id}"
44
    assert_select "tr#user-#{locked.id}", 0
43 45
  end
44 46

  
45 47
  def test_index_with_status_filter
46
    get :index, :params => {:status => 3}
48
    get :index, params: { set_filter: 1, f: ['status'], op: {status: '='}, v: {status: [3]} }
47 49
    assert_response :success
48
    assert_select 'tr.user.active', 0
49
    assert_select 'tr.user.locked'
50
    assert_select "tr.user", User.where(status: 3).count
50 51
  end
51 52

  
52 53
  def test_index_with_name_filter
53
    get :index, :params => {:name => 'john'}
54
    get :index, params: { set_filter: 1, f: ['name'], op: {name: '~'}, v: {name: ['john']} }
54 55
    assert_response :success
55
    assert_select 'tr.user td.username', :text => 'jsmith'
56
    assert_select 'tr.user td.login', text: 'jsmith'
56 57
    assert_select 'tr.user', 1
57 58
  end
58 59

  
59 60
  def test_index_with_group_filter
60
    get :index, :params => {:group_id => '10'}
61
    get :index, params: {
62
      set_filter: 1,
63
      f: ['is_member_of_group'], op: {is_member_of_group: '='}, v: {is_member_of_group: ['10']}
64
    }
61 65
    assert_response :success
62

  
63 66
    assert_select 'tr.user', Group.find(10).users.count
64
    assert_select 'select[name=group_id]' do
65
      assert_select 'option[value="10"][selected=selected]'
66
    end
67 67
  end
68 68

  
69 69
  def test_index_should_not_show_2fa_filter_and_column_if_disabled
......
71 71
      get :index
72 72
      assert_response :success
73 73

  
74
      assert_select "select#twofa", 0
75
      assert_select 'td.twofa', 0
74
      assert_select "select#add_filter_select" do
75
        assert_select "option[value=twofa_scheme]", 0
76
      end
77
      assert_select "select#available_c" do
78
        assert_select "option[value=twofa_scheme]", 0
79
      end
76 80
    end
77 81
  end
78 82

  
......
83 87
      user.twofa_scheme = "totp"
84 88
      user.save
85 89

  
86
      get :index, :params => {:twofa => '1'}
90
      get :index, params: { set_filter: 1, f: ['twofa_scheme'], op: {twofa_scheme: '*'} }
87 91
      assert_response :success
88 92

  
89
      assert_select "select#twofa", 1
90

  
93
      assert_select 'tr#user-1', 1
91 94
      assert_select 'tr.user', 1
92
      assert_select 'td.twofa.tick .icon-checked'
95

  
96
      assert_select "select#add_filter_select" do
97
        assert_select "option[value=twofa_scheme]"
98
      end
99
      assert_select "select#available_c" do
100
        assert_select "option[value=twofa_scheme]"
101
      end
102
    end
103

  
104
  def test_index_filter_by_twofa_scheme
105
      get :index, params: {
106
        set_filter: 1,
107
        f: ['twofa_scheme'], op: {twofa_scheme: '='}, v: {twofa_scheme: ['totp']}
108
      }
109
      assert_response :success
110

  
111
      assert_select 'tr#user-1', 1
112

  
113
      assert_select "select#add_filter_select" do
114
        assert_select "option[value=twofa_scheme]"
115
      end
116
      assert_select "select#available_c" do
117
        assert_select "option[value=twofa_scheme]"
118
      end
93 119
    end
94 120
  end
95 121

  
......
100 126
      user.twofa_scheme = "totp"
101 127
      user.save
102 128

  
103
      get :index, :params => {:twofa => '0'}
129
      get :index, params: { set_filter: 1, f: ['twofa_scheme'], op: {twofa_scheme: '!*'} }
104 130
      assert_response :success
105 131

  
106
      assert_select "select#twofa", 1
107
      assert_select "td.twofa.tick" do
108
        assert_select "span.icon-checked", 0
109
      end
132
      assert_select 'tr#user-1', 0
133
      assert_select 'tr.user'
110 134
    end
111 135
  end
112 136

  
......
114 138
    with_settings :default_language => 'en' do
115 139
      user = User.logged.status(1).first
116 140
      user.update(passwd_changed_on: Time.current.last_month, twofa_scheme: 'totp')
117
      get :index, params: {format: 'csv'}
141
      get :index, params: {format: 'csv', c: ['updated_on', 'status', 'passwd_changed_on', 'twofa_scheme']}
118 142
      assert_response :success
119 143

  
120 144
      assert_equal User.logged.status(1).count, response.body.chomp.split("\n").size - 1
......
142 166

  
143 167
    User.find(@request.session[:user_id]).update(:language => nil)
144 168
    with_settings :default_language => 'fr' do
145
      get :index, :params => {:name => user.lastname, :format => 'csv'}
169
      get :index, params: {
170
        c: [ "cf_#{float_custom_field.id}", "cf_#{date_custom_field.id}" ],
171
        f: ["name"],
172
        op: { name: "~" },
173
        v: { name: [user.lastname] },
174
        format: 'csv'
175
      }
146 176
      assert_response :success
147 177

  
148 178
      assert_include 'float field;date field', response.body
......
153 183

  
154 184
  def test_index_csv_with_status_filter
155 185
    with_settings :default_language => 'en' do
156
      get :index, :params => {:status => 3, :format => 'csv'}
186
      get :index, :params => {
187
        :set_filter => '1',
188
        f: [:status], :op => { :status => '=' }, :v => { :status => [3] },
189
        c: [:login, :status],
190
        :format => 'csv'
191
      }
157 192
      assert_response :success
158 193

  
159 194
      assert_equal User.logged.status(3).count, response.body.chomp.split("\n").size - 1
......
164 199
  end
165 200

  
166 201
  def test_index_csv_with_name_filter
167
    get :index, :params => {:name => 'John', :format => 'csv'}
202
    get :index, :params => {
203
      :set_filter => '1',
204
      f: [:name], :op => { :name => '~' }, :v => { :name => ['John'] },
205
      c: [:login, :firstname, :status],
206
      :format => 'csv'
207
    }
168 208
    assert_response :success
169 209

  
170 210
    assert_equal User.logged.like('John').count, response.body.chomp.split("\n").size - 1
......
173 213
  end
174 214

  
175 215
  def test_index_csv_with_group_filter
176
    get :index, :params => {:group_id => '10', :format => 'csv'}
216
    get :index, :params => {
217
      :set_filter => '1',
218
      f: [:is_member_of_group], :op => { :is_member_of_group => '=' }, :v => { :is_member_of_group => [10] },
219
      c: [:login, :status],
220
      :format => 'csv'
221
    }
177 222
    assert_response :success
178 223

  
179 224
    assert_equal Group.find(10).users.count, response.body.chomp.split("\n").size - 1
(2-2/11)