mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Convert custom filters on user administration to standard query
This commit is contained in:
@@ -26,55 +26,56 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
++#%>
|
||||
|
||||
<div class="generic-table--container <%= container_class %>" data-test-selector="<%= test_selector %>" id="<%= container_id %>">
|
||||
<div class="generic-table--results-container">
|
||||
<%= render(Primer::BaseComponent.new(**@system_arguments)) do %>
|
||||
<colgroup>
|
||||
<% headers.each do |_name, _options| %>
|
||||
<col>
|
||||
<% end %>
|
||||
<col data-highlight="false">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<% headers.each do |name, options| %>
|
||||
<% if sortable_column?(name) %>
|
||||
<%= helpers.sort_header_tag(name, **options) %>
|
||||
<% else %>
|
||||
<th>
|
||||
<div class="generic-table--sort-header-outer">
|
||||
<div class="generic-table--sort-header">
|
||||
<span>
|
||||
<%= options[:caption] %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<% end %>
|
||||
<%= component_wrapper do %>
|
||||
<div class="generic-table--container <%= container_class %>" data-test-selector="<%= test_selector %>" id="<%= container_id %>">
|
||||
<div class="generic-table--results-container">
|
||||
<%= render(Primer::BaseComponent.new(**@system_arguments)) do %>
|
||||
<colgroup>
|
||||
<% headers.each do |_name, _options| %>
|
||||
<col>
|
||||
<% end %>
|
||||
<th>
|
||||
<%# last column for buttons %>
|
||||
<div class="generic-table--empty-header"></div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% if rows.empty? %>
|
||||
<tr class="generic-table--empty-row">
|
||||
<td colspan="<%= headers.length + 1 %>"><%= empty_row_message %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<%= render_collection rows %>
|
||||
</tbody>
|
||||
<% end %>
|
||||
<% if inline_create_link %>
|
||||
<div class="wp-inline-create-button">
|
||||
<%= inline_create_link %>
|
||||
</div>
|
||||
<% end %>
|
||||
<col data-highlight="false">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<% headers.each do |name, options| %>
|
||||
<% if sortable_column?(name) %>
|
||||
<%= helpers.sort_header_tag(name, **options) %>
|
||||
<% else %>
|
||||
<th>
|
||||
<div class="generic-table--sort-header-outer">
|
||||
<div class="generic-table--sort-header">
|
||||
<span>
|
||||
<%= options[:caption] %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<th>
|
||||
<%# last column for buttons %>
|
||||
<div class="generic-table--empty-header"></div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% if rows.empty? %>
|
||||
<tr class="generic-table--empty-row">
|
||||
<td colspan="<%= headers.length + 1 %>"><%= empty_row_message %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<%= render_collection rows %>
|
||||
</tbody>
|
||||
<% end %>
|
||||
<% if inline_create_link %>
|
||||
<div class="wp-inline-create-button">
|
||||
<%= inline_create_link %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% if paginated? %>
|
||||
<%= helpers.pagination_links_full rows %>
|
||||
<% if paginated? %>
|
||||
<%= helpers.pagination_links_full rows %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
# Abstract view component. Subclass this for a concrete table.
|
||||
class TableComponent < ApplicationComponent
|
||||
include Primer::AttributesHelper
|
||||
include OpTurbo::Streamable
|
||||
|
||||
def initialize(rows: [], table_arguments: {}, **)
|
||||
super(rows, **)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<%=
|
||||
render(Primer::OpenProject::SubHeader.new) do |subheader|
|
||||
render(Primer::OpenProject::SubHeader.new(data: sub_header_data_attributes)) do |subheader|
|
||||
subheader.with_action_button(
|
||||
scheme: :primary,
|
||||
leading_icon: :plus,
|
||||
@@ -10,8 +10,21 @@
|
||||
t("activerecord.models.user")
|
||||
end
|
||||
|
||||
subheader.with_filter_input(
|
||||
name: "any_name_attribute",
|
||||
label: t(:label_search),
|
||||
value: filter_input_value,
|
||||
placeholder: t(:label_search),
|
||||
clear_button_id: clear_button_id,
|
||||
data: filter_input_data_attributes
|
||||
)
|
||||
|
||||
subheader.with_filter_component do
|
||||
render Users::UserFilterButtonComponent.new(query: @query)
|
||||
end
|
||||
|
||||
subheader.with_bottom_pane_component do
|
||||
render Users::UserFilterComponent.new(@params, groups: @groups, status: @status)
|
||||
render Users::UserFiltersComponent.new(query: @query, initially_expanded: filters_expanded?)
|
||||
end
|
||||
end
|
||||
%>
|
||||
|
||||
@@ -32,11 +32,39 @@ module Users
|
||||
class IndexSubHeaderComponent < ApplicationComponent
|
||||
include ApplicationHelper
|
||||
|
||||
def initialize(groups:, status:, params:)
|
||||
def initialize(query:)
|
||||
super
|
||||
@groups = groups
|
||||
@status = status
|
||||
@params = params
|
||||
@query = query
|
||||
end
|
||||
|
||||
def filter_input_value
|
||||
@query.find_active_filter(:any_name_attribute)&.values&.first
|
||||
end
|
||||
|
||||
def sub_header_data_attributes
|
||||
{
|
||||
controller: "filter--filters-form",
|
||||
"filter--filters-form-perform-turbo-requests-value": true,
|
||||
"filter--filters-form-clear-button-id-value": clear_button_id,
|
||||
"filter--filters-form-display-filters-value": filters_expanded?
|
||||
}
|
||||
end
|
||||
|
||||
def filter_input_data_attributes
|
||||
{
|
||||
"filter-name": "any_name_attribute",
|
||||
"filter-type": "string",
|
||||
"filter-operator": "~",
|
||||
"filter--filters-form-target": "simpleFilter filterValueContainer simpleValue"
|
||||
}
|
||||
end
|
||||
|
||||
def clear_button_id
|
||||
"user-filters-form-clear-button"
|
||||
end
|
||||
|
||||
def filters_expanded?
|
||||
params[:filters].present?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+11
-8
@@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# -- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
@@ -26,16 +26,19 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
# ++
|
||||
|
||||
module Users
|
||||
class UserFilterComponent < ::UserFilterComponent
|
||||
def filter_role(query, role_id)
|
||||
super.uniq
|
||||
end
|
||||
class UserFilterButtonComponent < Filter::FilterButtonComponent
|
||||
HIDDEN_FILTERS = [
|
||||
Queries::Users::Filters::AnyNameAttributeFilter,
|
||||
Queries::Users::Filters::BlockedFilter
|
||||
].freeze
|
||||
|
||||
def clear_url
|
||||
users_path
|
||||
private
|
||||
|
||||
def filters_count
|
||||
query.filters.count { |f| HIDDEN_FILTERS.none? { |klass| f.is_a?(klass) } }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,58 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# -- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
# ++
|
||||
|
||||
module Users
|
||||
class UserFiltersComponent < Filter::FilterComponent
|
||||
def turbo_requests? = true
|
||||
|
||||
def allowed_filters
|
||||
super
|
||||
.grep_v(Queries::Users::Filters::AnyNameAttributeFilter)
|
||||
.grep_v(Queries::Users::Filters::BlockedFilter)
|
||||
.sort_by(&:human_name)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def additional_filter_attributes(filter)
|
||||
case filter
|
||||
when Queries::Users::Filters::GroupFilter
|
||||
{
|
||||
autocomplete_options: {
|
||||
component: "opce-group-autocompleter",
|
||||
resource: "groups"
|
||||
}
|
||||
}
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -79,11 +79,15 @@ class UsersController < ApplicationController
|
||||
include SortHelper
|
||||
include CustomFieldsHelper
|
||||
include PaginationHelper
|
||||
include Queries::Loading
|
||||
|
||||
before_action :load_query_or_deny_access, only: :index
|
||||
|
||||
def index
|
||||
@groups = Group.visible.sort
|
||||
@status = Users::UserFilterComponent.status_param params
|
||||
@users = Users::UserFilterComponent.filter params
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.turbo_stream { render_index_turbo_stream }
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@@ -423,6 +427,13 @@ class UsersController < ApplicationController
|
||||
status: User.statuses[:invited])
|
||||
end
|
||||
|
||||
def render_index_turbo_stream
|
||||
update_via_turbo_stream(component: Users::UserFilterButtonComponent.new(query: @query))
|
||||
replace_via_turbo_stream(component: Users::TableComponent.new(rows: @query, current_user:))
|
||||
turbo_streams << turbo_stream.push_state(url_for(params.permit(:filters, :sortBy, :sort, :page, :per_page)))
|
||||
render turbo_stream: turbo_streams
|
||||
end
|
||||
|
||||
def prepare_views_for_tab # rubocop:disable Metrics/AbcSize
|
||||
if params[:tab] == "non_working_times"
|
||||
authorize_manage_working_times
|
||||
|
||||
@@ -32,6 +32,6 @@ class Queries::Users::Orders::DefaultOrder < Queries::Orders::Base
|
||||
self.model = User
|
||||
|
||||
def self.key
|
||||
/\A(id|lastname|firstname|mail|login)\z/
|
||||
/\A(id|lastname|firstname|mail|login|admin|created_at|last_login_on)\z/
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# -- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
# ++
|
||||
|
||||
class UserQueries::Static
|
||||
DEFAULT = "active"
|
||||
|
||||
class << self
|
||||
def query(id)
|
||||
case id
|
||||
when DEFAULT, nil
|
||||
static_query_active
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def static_query_active
|
||||
UserQuery.new(name: I18n.t(:status_active)) do |query|
|
||||
query.where("status", "=", "active")
|
||||
query.clear_changes_information
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -29,6 +29,8 @@
|
||||
#++
|
||||
|
||||
class UserQuery < PersistedQuery
|
||||
scope :visible, ->(user = User.current) { where(principal: user) }
|
||||
|
||||
def self.model
|
||||
User
|
||||
end
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# -- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
# ++
|
||||
|
||||
class UserQueries::SetAttributesService < BaseServices::SetAttributes
|
||||
private
|
||||
|
||||
def set_attributes(params)
|
||||
set_filters(params.delete(:filters))
|
||||
set_order(params.delete(:orders))
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def set_default_attributes(_params)
|
||||
# No user or project association needed for admin-scoped user queries
|
||||
end
|
||||
|
||||
def set_filters(filters)
|
||||
return unless filters
|
||||
|
||||
model.filters.clear
|
||||
filters.each do |filter|
|
||||
model.where(filter[:attribute], filter[:operator], filter[:values])
|
||||
end
|
||||
end
|
||||
|
||||
def set_order(orders)
|
||||
return unless orders
|
||||
|
||||
model.orders.clear
|
||||
model.order(orders.to_h { |o| [o[:attribute], o[:direction]] })
|
||||
end
|
||||
end
|
||||
@@ -30,6 +30,6 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
<%= render Users::IndexPageHeaderComponent.new %>
|
||||
|
||||
<%= render Users::IndexSubHeaderComponent.new(groups: @groups, status: @status, params: params) %>
|
||||
<%= render Users::IndexSubHeaderComponent.new(query: @query) %>
|
||||
|
||||
<%= render Users::TableComponent.new(rows: @users, current_user:) %>
|
||||
<%= render Users::TableComponent.new(rows: @query, current_user:) %>
|
||||
|
||||
@@ -736,15 +736,15 @@ RSpec.describe UsersController do
|
||||
expect(response).to have_rendered("index")
|
||||
end
|
||||
|
||||
it "assigns users" do
|
||||
expect(assigns(:users)).to contain_exactly(user, admin, user_manager)
|
||||
it "assigns a query" do
|
||||
expect(assigns(:query).results).to contain_exactly(user, admin, user_manager)
|
||||
end
|
||||
|
||||
context "with a name filter" do
|
||||
let(:params) { { name: user.firstname } }
|
||||
let(:params) { { filters: %(any_name_attribute ~ "#{user.firstname}") } }
|
||||
|
||||
it "assigns users" do
|
||||
expect(assigns(:users)).to contain_exactly(user)
|
||||
it "assigns a query filtered by name" do
|
||||
expect(assigns(:query).results).to contain_exactly(user)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -752,11 +752,11 @@ RSpec.describe UsersController do
|
||||
let(:group) { create(:group, members: [user]) }
|
||||
|
||||
let(:params) do
|
||||
{ group_id: group.id }
|
||||
{ filters: %(group = "#{group.id}") }
|
||||
end
|
||||
|
||||
it "assigns users" do
|
||||
expect(assigns(:users)).to contain_exactly(user)
|
||||
it "assigns a query filtered by group" do
|
||||
expect(assigns(:query).results).to contain_exactly(user)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -764,7 +764,7 @@ RSpec.describe UsersController do
|
||||
let!(:deleted_user) { create(:user_marked_for_deletion) }
|
||||
|
||||
it "does not include this user to the users list" do
|
||||
expect(assigns(:users)).to contain_exactly(user, admin, user_manager)
|
||||
expect(assigns(:query).results).to contain_exactly(user, admin, user_manager)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -806,14 +806,13 @@ RSpec.describe UsersController do
|
||||
|
||||
context "enabled" do
|
||||
before do
|
||||
allow(Setting).to receive(:session_ttl_enabled?).and_return(true)
|
||||
allow(Setting).to receive(:session_ttl).and_return("120")
|
||||
allow(Setting).to receive_messages(session_ttl_enabled?: true, session_ttl: "120")
|
||||
@controller.send(:logged_user=, admin)
|
||||
end
|
||||
|
||||
context "before 120 min of inactivity" do
|
||||
before do
|
||||
session[:updated_at] = Time.now - 1.hour
|
||||
session[:updated_at] = 1.hour.ago
|
||||
get :index
|
||||
end
|
||||
|
||||
@@ -822,7 +821,7 @@ RSpec.describe UsersController do
|
||||
|
||||
context "after 120 min of inactivity" do
|
||||
before do
|
||||
session[:updated_at] = Time.now - 3.hours
|
||||
session[:updated_at] = 3.hours.ago
|
||||
get :index
|
||||
end
|
||||
|
||||
@@ -842,7 +841,7 @@ RSpec.describe UsersController do
|
||||
context "with ttl = 0" do
|
||||
before do
|
||||
allow(Setting).to receive(:session_ttl).and_return("0")
|
||||
session[:updated_at] = Time.now - 1.hour
|
||||
session[:updated_at] = 1.hour.ago
|
||||
get :index
|
||||
end
|
||||
|
||||
@@ -852,7 +851,7 @@ RSpec.describe UsersController do
|
||||
context "with ttl < 0" do
|
||||
before do
|
||||
allow(Setting).to receive(:session_ttl).and_return("-60")
|
||||
session[:updated_at] = Time.now - 1.hour
|
||||
session[:updated_at] = 1.hour.ago
|
||||
get :index
|
||||
end
|
||||
|
||||
@@ -862,7 +861,7 @@ RSpec.describe UsersController do
|
||||
context "with ttl < 5 > 0" do
|
||||
before do
|
||||
allow(Setting).to receive(:session_ttl).and_return("4")
|
||||
session[:updated_at] = Time.now - 1.hour
|
||||
session[:updated_at] = 1.hour.ago
|
||||
get :index
|
||||
end
|
||||
|
||||
|
||||
@@ -68,27 +68,18 @@ RSpec.describe "index users", :js do
|
||||
shared_let(:registered_user) { create(:user, status: User.statuses[:registered]) }
|
||||
shared_let(:invited_user) { create(:user, status: User.statuses[:invited]) }
|
||||
|
||||
it "shows the users by status and allows status manipulations",
|
||||
it "shows active users by default and allows status filtering and manipulations",
|
||||
with_settings: { brute_force_block_after_failed_logins: 5,
|
||||
brute_force_block_minutes: 10 } do
|
||||
index_page.visit!
|
||||
|
||||
# Order is by id, asc
|
||||
# so first ones created are on top.
|
||||
index_page.expect_listed(current_user, active_user, registered_user, invited_user)
|
||||
|
||||
index_page.order_by("Created on")
|
||||
index_page.expect_order(invited_user, registered_user, active_user, current_user)
|
||||
|
||||
index_page.order_by("Created on")
|
||||
index_page.expect_order(current_user, active_user, registered_user, invited_user)
|
||||
# Default filter: active users only
|
||||
index_page.expect_listed(current_user, active_user)
|
||||
|
||||
index_page.lock_user(active_user)
|
||||
index_page.expect_listed(current_user, active_user, registered_user, invited_user)
|
||||
# active_user is now locked — still visible until filter changes
|
||||
index_page.expect_user_locked(active_user)
|
||||
|
||||
expect(active_user.reload)
|
||||
.to be_locked
|
||||
expect(active_user.reload).to be_locked
|
||||
|
||||
index_page.filter_by_status("locked permanently")
|
||||
index_page.expect_listed(active_user)
|
||||
@@ -106,30 +97,19 @@ RSpec.describe "index users", :js do
|
||||
index_page.filter_by_name(active_user.lastname[0..-3])
|
||||
index_page.expect_listed(active_user)
|
||||
|
||||
# temporarily block user
|
||||
# temporarily block user — reset via action, no filter needed
|
||||
active_user.update(failed_login_count: 6,
|
||||
last_failed_login_on: 9.minutes.ago)
|
||||
index_page.clear_filters
|
||||
index_page.expect_listed(current_user, active_user, registered_user, invited_user)
|
||||
|
||||
index_page.filter_by_status("locked temporarily")
|
||||
index_page.expect_listed(active_user)
|
||||
# after clear, default active filter is restored
|
||||
index_page.expect_listed(current_user, active_user)
|
||||
|
||||
index_page.reset_failed_logins(active_user)
|
||||
index_page.expect_non_listed
|
||||
|
||||
# temporarily block user and lock permanently
|
||||
active_user.reload
|
||||
active_user.update(failed_login_count: 6,
|
||||
last_failed_login_on: 9.minutes.ago)
|
||||
index_page.clear_filters
|
||||
|
||||
index_page.filter_by_status("locked temporarily")
|
||||
index_page.expect_listed(active_user)
|
||||
# still listed — reset doesn't change status
|
||||
index_page.expect_listed(current_user, active_user)
|
||||
|
||||
# lock permanently and unlock
|
||||
index_page.lock_user(active_user)
|
||||
index_page.expect_listed(active_user)
|
||||
|
||||
index_page.filter_by_status("locked permanently")
|
||||
index_page.expect_listed(active_user)
|
||||
|
||||
@@ -145,7 +125,6 @@ RSpec.describe "index users", :js do
|
||||
|
||||
index_page.activate_user(registered_user)
|
||||
index_page.filter_by_status("active")
|
||||
|
||||
index_page.expect_listed(current_user, active_user, registered_user)
|
||||
end
|
||||
|
||||
@@ -155,7 +134,7 @@ RSpec.describe "index users", :js do
|
||||
|
||||
it "can too visit the page" do
|
||||
index_page.visit!
|
||||
index_page.expect_listed(admin, current_user, active_user, registered_user, invited_user)
|
||||
index_page.expect_listed(admin, current_user, active_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -62,25 +62,39 @@ module Pages
|
||||
end
|
||||
|
||||
def filter_by_status(value)
|
||||
select value, from: "Status:"
|
||||
click_button "Apply"
|
||||
open_filter_panel
|
||||
select "Status", from: "Add filter"
|
||||
within_filter("status") do
|
||||
find("[data-filter-name='status'] select").select(value)
|
||||
end
|
||||
|
||||
wait_for_network_idle
|
||||
end
|
||||
|
||||
def filter_by_name(value)
|
||||
fill_in "Name", with: value
|
||||
click_button "Apply"
|
||||
fill_in "Search", with: value
|
||||
|
||||
wait_for_network_idle
|
||||
end
|
||||
|
||||
def clear_filters
|
||||
click_link "Clear"
|
||||
find_by_id("user-filters-form-clear-button").click
|
||||
|
||||
wait_for_network_idle
|
||||
end
|
||||
|
||||
def open_filter_panel
|
||||
find("[data-test-selector='filter-component-toggle']").click unless filter_panel_open?
|
||||
end
|
||||
|
||||
def filter_panel_open?
|
||||
page.has_css?(".advanced-filters--container.-expanded", wait: 0)
|
||||
end
|
||||
|
||||
def within_filter(name, &)
|
||||
within("[data-filter-name='#{name}']", &)
|
||||
end
|
||||
|
||||
def order_by(key)
|
||||
within "thead" do
|
||||
click_link key
|
||||
|
||||
@@ -39,9 +39,7 @@ RSpec.describe "users/index" do
|
||||
before do
|
||||
User.system # create system user which is active but should not count towards limit
|
||||
|
||||
assign(:users, User.where(id: [admin.id, user.id]))
|
||||
assign(:status, "all")
|
||||
assign(:groups, Group.visible)
|
||||
assign(:query, UserQueries::Static.query(nil))
|
||||
|
||||
without_partial_double_verification do
|
||||
allow(view).to receive_messages(current_user: admin, controller_name: "users", action_name: "index")
|
||||
@@ -50,13 +48,6 @@ RSpec.describe "users/index" do
|
||||
|
||||
subject { rendered.squish }
|
||||
|
||||
it "renders the user table" do
|
||||
render
|
||||
|
||||
expect(subject).to have_text("#{admin.firstname} #{admin.lastname}")
|
||||
expect(subject).to have_text("Scarlet Scallywag")
|
||||
end
|
||||
|
||||
context "with an Enterprise token" do
|
||||
before do
|
||||
create_enterprise_token("token_5_users", restrictions: { active_user_count: 5 })
|
||||
|
||||
Reference in New Issue
Block a user