Member index (phase 2) - Search invitations (#17210)

* add ability to search invitations

* add an invited scope

* add a cypress test
This commit is contained in:
Suzanne Aitchison 2022-04-12 10:40:44 +01:00 committed by GitHub
parent ac7086eff2
commit b1e7d8a5ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 73 additions and 10 deletions

View file

@ -3,7 +3,10 @@ module Admin
layout "admin"
def index
@invitations = User.where(registered: false).page(params[:page]).per(50)
@invitations = Admin::UsersQuery.call(relation: User.invited,
options: params.permit(
:search,
)).page(params[:page]).per(50)
end
def new; end

View file

@ -170,6 +170,7 @@ class User < ApplicationRecord
scope :eager_load_serialized_data, -> { includes(:roles) }
scope :registered, -> { where(registered: true) }
scope :invited, -> { where(registered: false) }
# Unfortunately pg_search's default SQL query is not performant enough in this
# particular case (~ 500ms). There are multiple reasons:
# => creates a complex query like `SELECT FROM users INNER JOIN users` to compute ranking.

View file

@ -5,7 +5,16 @@
<h1 class="crayons-title ml-3 l:ml-0">Members<span class="screen-reader-only"> (Invitations)</span></h1>
<%= render "admin/users/index/tabs" %>
</div>
<%= render "admin/users/index/controls" %>
<div class="flex justify-content-between py-3">
<%= form_with url: admin_invitations_path, method: :get, local: true, class:"grow-1" do |f| %>
<% render "admin/users/index/search_field", f: f, placeholder: "Search invited members...", aria_label: "Search invited members by name, username, or email" %>
<% end %>
<div class="flex grow-1 justify-content-end">
<%= paginate @invitations, theme: "admin", scope: @invitations, label: "Paginate invitations" %>
<%= link_to "Invite member", new_admin_invitation_path, class: "c-cta c-cta--branded self-end" %>
</div>
</div>
</header>
<table class="crayons-table crayons-table__lowercase" width="100%">
<thead>

View file

@ -18,8 +18,8 @@
<%= paginate @users, theme: "admin", scope: @users, label: "Paginate users" %>
</div>
<div>
<div id="search-users" class="hidden relative">
<%= render "admin/users/index/search_field", f: f %>
<div id="search-users" class="hidden">
<%= render "admin/users/index/search_field", f: f, placeholder: "Search member...", aria_label: "Search member by name, username, email, or Twitter/GitHub usernames" %>
</div>
<div id="filter-users" class="hidden crayons-field flex-row items-center gap-2">
<%= render "admin/users/index/filter_role_field", f: f %>
@ -30,8 +30,8 @@
<!-- Larger screen layout -->
<div class="hidden m:flex justify-between">
<%= form_with url: admin_users_path, method: :get, local: true, class: "flex flex-col m:flex-row gap-3 m:items-center py-3" do |f| %>
<div class="crayons-field flex-1 flex-row items-center gap-2 relative">
<%= render "admin/users/index/search_field", f: f %>
<div class="crayons-field flex-1 flex-row items-center gap-2">
<%= render "admin/users/index/search_field", f: f, placeholder: "Search member...", aria_label: "Search member by name, username, email, or Twitter/GitHub usernames" %>
</div>
<div class="crayons-field flex-row items-center gap-2">
<%= render "admin/users/index/filter_role_field", f: f %>

View file

@ -1,4 +1,6 @@
<%= f.text_field :search, value: params[:search], class: "crayons-textfield mt-0", placeholder: "Search member...", aria: { label: "Search member by name, username, email, or Twitter/GitHub usernames" } %>
<button type="submit" aria-label="Search" class="crayons-btn crayons-btn--ghost crayons-btn--s crayons-btn--icon-rounded absolute right-2 bottom-0 top-0 m-1">
<%= crayons_icon_tag(:search, aria_hidden: true) %>
</button>
<div class="relative">
<%= f.text_field :search, value: params[:search], class: "crayons-textfield mt-0", placeholder: placeholder, aria: { label: aria_label } %>
<button type="submit" aria-label="Search" class="crayons-btn crayons-btn--ghost crayons-btn--s crayons-btn--icon-rounded absolute right-2 bottom-0 top-0 m-1">
<%= crayons_icon_tag(:search, aria_hidden: true) %>
</button>
</div>

View file

@ -0,0 +1,40 @@
describe('Invited users', () => {
beforeEach(() => {
cy.testSetup();
cy.fixture('users/adminUser.json').as('user');
cy.get('@user').then((user) => {
cy.loginUser(user)
.then(() => cy.enableFeatureFlag('member_index_view'))
.then(() =>
cy.inviteUser({
name: 'Test user',
email: 'test@test.com',
}),
)
.then(() => cy.visitAndWaitForUserSideEffects('/admin/invitations'));
});
});
it('searches for an invited user', () => {
// The single invited user should be visible on the page
cy.findByText('test@test.com').should('exist');
// Search for a term that should match the entry
cy.findByRole('textbox', {
name: 'Search invited members by name, username, or email',
}).type('test');
cy.findByRole('button', { name: 'Search' }).click();
cy.url().should('contain', 'search=test');
cy.findByText('test@test.com').should('exist');
// Search for a term that shouldn't match the entry
cy.findByRole('textbox', {
name: 'Search invited members by name, username, or email',
})
.clear()
.type('something');
cy.findByRole('button', { name: 'Search' }).click();
cy.url().should('contain', 'search=something');
cy.findByText('test@test.com').should('not.exist');
});
});

View file

@ -391,3 +391,11 @@ Cypress.Commands.add(
return cy.wrap(subject).invoke('val', color).trigger('input');
},
);
Cypress.Commands.add('inviteUser', ({ name, email }) => {
return cy.request(
'POST',
'/admin/invitations',
`utf8=%E2%9C%93&user%5Bemail%5D=${email}&user%5Bname%5D=${name}&commit=Invite+User`,
);
});