Member index: filter by organizations (#17943)

* show the organizations checkboxes

* apply clear filter button and indicator

* filter by org in users query

* add some rspec tests

* add cypress specs

* correct cypress spec

* Fixing UsersQuery to ensure singular user by organization

Co-authored-by: Jeremy Friesen <jeremy.n.friesen@gmail.com>
This commit is contained in:
Suzanne Aitchison 2022-06-21 13:32:50 +01:00 committed by GitHub
parent c1a95e4595
commit d57e4cabb7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 112 additions and 8 deletions

View file

@ -35,9 +35,11 @@ module Admin
search: params[:search],
role: params[:role],
roles: params[:roles],
organizations: params[:organizations],
).page(params[:page]).per(50)
@organization_limit = 3
@organizations = Organization.order(name: :desc)
end
def edit

View file

@ -9,7 +9,8 @@ module Admin
# @param role [String, nil]
# @param search [String, nil]
# @param roles [Array<String>, nil]
def self.call(relation: User.registered, role: nil, search: nil, roles: [])
# @param organizations [Array<String>, nil]
def self.call(relation: User.registered, role: nil, search: nil, roles: [], organizations: [])
# We are at an interstitial moment where we are exposing both the role and roles param. We
# need to favor one or the other.
if role.presence
@ -18,6 +19,10 @@ module Admin
relation = filter_roles(relation: relation, roles: roles)
end
if organizations.presence
relation = filter_organization_memberships(relation: relation, organizations: organizations)
end
relation = search_relation(relation, search) if search.presence
relation.distinct.order(created_at: :desc)
end
@ -26,6 +31,11 @@ module Admin
relation.where(QUERY_CLAUSE, search: "%#{search.strip}%")
end
def self.filter_organization_memberships(relation:, organizations:)
sub_query = OrganizationMembership.select(:user_id).where(organization_id: organizations)
relation.where(id: sub_query)
end
# Apply the "where" scope to the given relation for the given roles.
#
# @param relation [ActiveRecord::Relation<User>]

View file

@ -50,8 +50,26 @@
Last activity options
</details>
<details data-section="organizations" class="admin-details">
<summary class="py-4 flex justify-between text-uppercase color-base-60 fs-s">Organizations<%= crayons_icon_tag("chevron-down", aria_hidden: true, class: "summary-icon") %></summary>
Organizations options
<summary class="py-4 flex justify-between text-uppercase color-base-60 fs-s">
<span class="flex">
Organizations
<span class="js-filtered-indicator-organizations relative ml-2 <%= params[:organizations].blank? ? "hidden" : "" %>">
<span class="block absolute c-indicator c-indicator--info"></span>
</span>
</span>
<%= crayons_icon_tag("chevron-down", aria_hidden: true, class: "summary-icon") %>
</summary>
<fieldset class="js-organizations-fieldset pb-4 flex flex-col items-start">
<legend class="screen-reader-only">Organizations</legend>
<% @organizations.each_with_index do |org, index| %>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :organizations, { multiple: true, class: "crayons-checkbox", id: "filter-org-#{index}", checked: params[:organizations]&.include?(org.id.to_s) }, org.id, false %>
<%= f.label :organizations, for: "filter-org-#{index}", class: "crayons-field__label" do %><%= org.name %><% end %>
</div>
<% end %>
</fieldset>
<button type="button" class="js-clear-filter-btn c-btn <%= params[:organizations].blank? ? "hidden" : "" %>" data-checkbox-fieldset-selector=".js-organizations-fieldset" data-filter-indicator-selector=".js-filtered-indicator-organizations" aria-label="Clear organizations filter">Clear filter</button>
</details>
</div>
<div class="flex gap-2 mt-auto">

View file

@ -29,20 +29,31 @@ describe('Filter user index', () => {
cy.findAllByRole('button', { name: 'Filter' }).last().click();
cy.getModal().within(() => {
cy.findAllByText('Member roles').first().click();
cy.findByRole('group', { name: 'Member roles' }).should('be.visible');
cy.findByRole('group', { name: 'Member roles' })
.as('memberRoles')
.should('be.visible');
cy.get('@memberRoles')
.findAllByRole('checkbox')
.should('have.length', 6);
cy.findAllByRole('checkbox').should('have.length', 6);
cy.findByRole('button', { name: 'See more roles' })
.as('seeMoreButton')
.should('have.attr', 'aria-pressed', 'false')
.click()
.should('have.attr', 'aria-pressed', 'true');
cy.findAllByRole('checkbox').should('have.length', 16);
cy.get('@memberRoles')
.findAllByRole('checkbox')
.should('have.length', 16);
cy.get('@seeMoreButton')
.click()
.should('have.attr', 'aria-pressed', 'false');
cy.findAllByRole('checkbox').should('have.length', 6);
cy.get('@memberRoles')
.findAllByRole('checkbox')
.should('have.length', 6);
});
});
@ -100,4 +111,40 @@ describe('Filter user index', () => {
});
});
});
describe('Organizations', () => {
it('filters by organizations', () => {
cy.findAllByRole('button', { name: 'Filter' }).last().click();
cy.getModal().within(() => {
cy.findAllByText('Organizations').first().click();
cy.findByRole('group', { name: 'Organizations' }).should('be.visible');
cy.findByRole('checkbox', { name: 'Bachmanity' }).check();
cy.findByRole('checkbox', { name: 'Awesome Org' }).check();
cy.findByRole('button', { name: 'Apply filters' }).click();
});
cy.findAllByRole('row').should('have.length', 4);
});
});
describe('Multiple filters', () => {
it('filters by multiple criteria', () => {
cy.findAllByRole('button', { name: 'Filter' }).last().click();
cy.getModal().within(() => {
cy.findAllByText('Member roles').first().click();
cy.findByRole('group', { name: 'Member roles' }).should('be.visible');
cy.findByRole('checkbox', { name: 'Super Admin' }).check();
cy.findAllByText('Organizations').first().click();
cy.findByRole('group', { name: 'Organizations' }).should('be.visible');
cy.findByRole('checkbox', { name: 'Bachmanity' }).check();
cy.findByRole('checkbox', { name: 'Awesome Org' }).check();
cy.findByRole('button', { name: 'Apply filters' }).click();
});
cy.findAllByRole('row').should('have.length', 2);
});
});
});

View file

@ -1,12 +1,16 @@
require "rails_helper"
RSpec.describe Admin::UsersQuery, type: :query do
subject { described_class.call(search: search, role: role, roles: roles) }
subject { described_class.call(search: search, role: role, roles: roles, organizations: organizations) }
let(:role) { nil }
let(:roles) { [] }
let(:organizations) { [] }
let(:search) { [] }
let!(:org1) { create(:organization, name: "Org1") }
let!(:org2) { create(:organization, name: "Org2") }
let!(:user) { create(:user, :trusted, name: "Greg") }
let!(:user2) { create(:user, :trusted, name: "Gregory") }
let!(:user3) { create(:user, :tag_moderator, name: "Paul") }
@ -64,5 +68,28 @@ RSpec.describe Admin::UsersQuery, type: :query do
it { is_expected.to eq([user8, user7, user6, user5, user4]) }
end
context "when given organizations" do
before do
create(:organization_membership, user: user, organization: org1, type_of_user: "member")
create(:organization_membership, user: user2, organization: org2, type_of_user: "member")
end
let(:organizations) { [org1.id, org2.id] }
it { is_expected.to eq([user2, user]) }
end
context "when given organizations and roles" do
before do
create(:organization_membership, user: user, organization: org1, type_of_user: "member")
create(:organization_membership, user: user4, organization: org2, type_of_user: "member")
end
let(:organizations) { [org1.id, org2.id] }
let(:roles) { ["Admin"] }
it { is_expected.to eq([user4]) }
end
end
end