Searchable GDPR Delete Requests Table (#17633)

* Adds search functionality to GDPR delete requests table

* Adds e2e tests for searching GDPR delete requests

* WIP: Address PR review feedback and add a RSpec test

* Updates spec name to include _query

* Updates Adnub::GDPRDeleteRequests#index

* Adjusts expectations within gdpr_delete_requests_query_spec.rb

* Adjusts GDPR Delete Requests Query search default

* Updates gdpr_delete_requests_query_spec.rb
This commit is contained in:
Julianna Tetreault 2022-05-31 16:22:25 -06:00 committed by GitHub
parent 127fe37e19
commit b155749db0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 108 additions and 4 deletions

View file

@ -3,7 +3,7 @@ module Admin
layout "admin"
def index
@gdpr_delete_requests = ::GDPRDeleteRequest.order(created_at: :desc).page(params[:page]).per(50)
@gdpr_delete_requests = Admin::GDPRDeleteRequestsQuery.call(search: params[:search]).page(params[:page]).per(50)
end
def destroy

View file

@ -0,0 +1,16 @@
module Admin
module GDPRDeleteRequestsQuery
QUERY_CLAUSE = "#{GDPRDeleteRequest.table_name}.email ILIKE :search OR " \
"#{GDPRDeleteRequest.table_name}.username ILIKE :search".freeze
def self.call(relation: ::GDPRDeleteRequest.all, search: {})
relation = search_relation(relation, search) if search.presence
relation.order(created_at: :desc)
end
def self.search_relation(relation, search)
relation.where(QUERY_CLAUSE, search: "%#{search}%")
end
end
end

View file

@ -2,10 +2,15 @@
<div id="gdpr-delete-requests-content" class="crayons-card overflow-admin-main-layout-padding p-4 m:p-0">
<header class="flex flex-col l:flex-row justify-content-between l:items-center p-0 m:p-7 pb-4">
<h1 class="crayons-title ml-3 l:ml-0">Members<span class="screen-reader-only"> (GDPR Delete Requests)</span></h1>
<%= form_with url: admin_gdpr_delete_requests_path, method: :get, local: true, class: "grow-1 mb-3 m:mb-0" do |f| %>
<div class="hidden m:flex flex-col m:flex-row justify-content-between p-3 xl:px-0">
<div class="flex grow-1 justify-content-end">
<%= paginate @gdpr_delete_requests, theme: "admin", scope: @gdpr_delete_requests, label: "Paginate GDPR delete requests", context: "top" %>
</div>
<%= render "admin/users/controls/search_field", f: f, placeholder: "Search members...", aria_label: "Search members by email or username", context: "large" %>
</div>
<% end %>
</header>
<div class="flex justify-end px-2 m:px-7">
<%= paginate @gdpr_delete_requests, theme: "admin", scope: @gdpr_delete_requests, label: "Paginate GDPR delete requests", context: "top" %>
</div>
<div id="table-description" class="my-4 max-w-75 color-base-60 px-2 m:px-7">
These accounts have been deleted from the community. Ensure that any data held in external databases or mailing lists is deleted before marking as "Deleted."
</div>

View file

@ -62,4 +62,35 @@ describe('GDPR Delete Requests', () => {
}).should('not.exist');
cy.get('@confirmButton').should('exist').should('have.focus');
});
it('Searches for a user', () => {
// The user should be visible on the page
cy.findByRole('table')
.findByText('gdpr-delete-user@forem.local')
.should('exist');
// Search for a term that should match the entry
searchForMember('delete');
cy.url().should('contain', 'search=delete');
cy.findByRole('table')
.findByText('gdpr-delete-user@forem.local')
.should('exist');
// Search for a term that shouldn't match the entry
searchForMember('something');
cy.url().should('contain', 'search=something');
cy.findByRole('table')
.findByText('gdpr-delete-user@forem.local')
.should('not.exist');
});
const searchForMember = (searchTerm) => {
cy.findByRole('textbox', {
name: 'Search members by email or username',
})
.clear()
.type(searchTerm);
cy.findByRole('button', { name: 'Search' }).click();
};
});

View file

@ -0,0 +1,52 @@
require "rails_helper"
RSpec.describe Admin::GDPRDeleteRequestsQuery, type: :query do
subject { described_class.call(search: search) }
let!(:gdpr_delete_request_1) { create(:gdpr_delete_request, username: "delete_1", email: "delete_1@test.com") }
let!(:gdpr_delete_request_2) { create(:gdpr_delete_request, username: "delete_2", email: "delete_2@test.com") }
let!(:gdpr_delete_request_3) { create(:gdpr_delete_request, username: "delete_3", email: "delete_3@test.com") }
let!(:gdpr_delete_request_11) { create(:gdpr_delete_request, username: "delete_11", email: "delete_11@test.com") }
describe ".call" do
context "when no arguments are given" do
it "returns all users" do
# rubocop:disable Layout/LineLength
expect(described_class.call).to match_array([gdpr_delete_request_1, gdpr_delete_request_2, gdpr_delete_request_3, gdpr_delete_request_11])
# rubocop:enable Layout/LineLength
end
end
context "when searching for a user by username" do
let(:search) { "delete_2" }
it { is_expected.to match_array([gdpr_delete_request_2]) }
end
context "when searching for a user by email" do
let(:search) { "delete_1@test.com" }
it { is_expected.to match_array([gdpr_delete_request_1]) }
end
context "when searching for ambiguous terms that matches multiple users" do
let(:search) { "delete_1" }
it { is_expected.to match_array([gdpr_delete_request_1, gdpr_delete_request_11]) }
end
context "when searching for ambiguous terms that matches both emails and usernames" do
let(:search) { "delete" }
# rubocop:disable Layout/LineLength
it { is_expected.to match_array([gdpr_delete_request_1, gdpr_delete_request_2, gdpr_delete_request_3, gdpr_delete_request_11]) }
# rubocop:enable Layout/LineLength
end
context "when passed a non-existent email or username" do
let(:search) { "non_existent_email" }
it { is_expected.to match_array([]) }
end
end
end