From d57e4cabb764068d08252d83c54f362c8cfefab3 Mon Sep 17 00:00:00 2001 From: Suzanne Aitchison Date: Tue, 21 Jun 2022 13:32:50 +0100 Subject: [PATCH] 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 --- app/controllers/admin/users_controller.rb | 2 + app/queries/admin/users_query.rb | 12 +++- .../admin/users/index/_filters_modal.html.erb | 22 +++++++- .../adminFlows/users/filterUserIndex.spec.js | 55 +++++++++++++++++-- spec/queries/admin/users_query_spec.rb | 29 +++++++++- 5 files changed, 112 insertions(+), 8 deletions(-) diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index c95dccaf3..0821a4eae 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -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 diff --git a/app/queries/admin/users_query.rb b/app/queries/admin/users_query.rb index 140b1b001..c8aea9065 100644 --- a/app/queries/admin/users_query.rb +++ b/app/queries/admin/users_query.rb @@ -9,7 +9,8 @@ module Admin # @param role [String, nil] # @param search [String, nil] # @param roles [Array, nil] - def self.call(relation: User.registered, role: nil, search: nil, roles: []) + # @param organizations [Array, 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] diff --git a/app/views/admin/users/index/_filters_modal.html.erb b/app/views/admin/users/index/_filters_modal.html.erb index b3a0ae0f7..992c68229 100644 --- a/app/views/admin/users/index/_filters_modal.html.erb +++ b/app/views/admin/users/index/_filters_modal.html.erb @@ -50,8 +50,26 @@ Last activity options
- Organizations<%= crayons_icon_tag("chevron-down", aria_hidden: true, class: "summary-icon") %> - Organizations options + + + Organizations + "> + + + + <%= crayons_icon_tag("chevron-down", aria_hidden: true, class: "summary-icon") %> + +
+ Organizations + <% @organizations.each_with_index do |org, index| %> +
+ <%= 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 %> +
+ <% end %> +
+ +
diff --git a/cypress/integration/seededFlows/adminFlows/users/filterUserIndex.spec.js b/cypress/integration/seededFlows/adminFlows/users/filterUserIndex.spec.js index 388405740..3fcd9999e 100644 --- a/cypress/integration/seededFlows/adminFlows/users/filterUserIndex.spec.js +++ b/cypress/integration/seededFlows/adminFlows/users/filterUserIndex.spec.js @@ -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); + }); + }); }); diff --git a/spec/queries/admin/users_query_spec.rb b/spec/queries/admin/users_query_spec.rb index 6b7a43a4d..860b6847d 100644 --- a/spec/queries/admin/users_query_spec.rb +++ b/spec/queries/admin/users_query_spec.rb @@ -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