From b155749db06468c4455b6cbf4d9a627236214dc7 Mon Sep 17 00:00:00 2001 From: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> Date: Tue, 31 May 2022 16:22:25 -0600 Subject: [PATCH] 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 --- .../admin/gdpr_delete_requests_controller.rb | 2 +- .../admin/gdpr_delete_requests_query.rb | 16 ++++++ .../admin/gdpr_delete_requests/index.html.erb | 11 ++-- .../users/gdprDeleteRequests.spec.js | 31 +++++++++++ .../admin/gdpr_delete_requests_query_spec.rb | 52 +++++++++++++++++++ 5 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 app/queries/admin/gdpr_delete_requests_query.rb create mode 100644 spec/queries/admin/gdpr_delete_requests_query_spec.rb diff --git a/app/controllers/admin/gdpr_delete_requests_controller.rb b/app/controllers/admin/gdpr_delete_requests_controller.rb index 744209272..4e20cbf3c 100644 --- a/app/controllers/admin/gdpr_delete_requests_controller.rb +++ b/app/controllers/admin/gdpr_delete_requests_controller.rb @@ -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 diff --git a/app/queries/admin/gdpr_delete_requests_query.rb b/app/queries/admin/gdpr_delete_requests_query.rb new file mode 100644 index 000000000..a3ab2335a --- /dev/null +++ b/app/queries/admin/gdpr_delete_requests_query.rb @@ -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 diff --git a/app/views/admin/gdpr_delete_requests/index.html.erb b/app/views/admin/gdpr_delete_requests/index.html.erb index ece6e6b8d..003d3a1d2 100644 --- a/app/views/admin/gdpr_delete_requests/index.html.erb +++ b/app/views/admin/gdpr_delete_requests/index.html.erb @@ -2,10 +2,15 @@

Members (GDPR Delete Requests)

+ <%= form_with url: admin_gdpr_delete_requests_path, method: :get, local: true, class: "grow-1 mb-3 m:mb-0" do |f| %> + + <% end %>
-
- <%= paginate @gdpr_delete_requests, theme: "admin", scope: @gdpr_delete_requests, label: "Paginate GDPR delete requests", context: "top" %> -
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."
diff --git a/cypress/integration/seededFlows/adminFlows/users/gdprDeleteRequests.spec.js b/cypress/integration/seededFlows/adminFlows/users/gdprDeleteRequests.spec.js index 54c1d37d6..811731f9d 100644 --- a/cypress/integration/seededFlows/adminFlows/users/gdprDeleteRequests.spec.js +++ b/cypress/integration/seededFlows/adminFlows/users/gdprDeleteRequests.spec.js @@ -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(); + }; }); diff --git a/spec/queries/admin/gdpr_delete_requests_query_spec.rb b/spec/queries/admin/gdpr_delete_requests_query_spec.rb new file mode 100644 index 000000000..1be4867b3 --- /dev/null +++ b/spec/queries/admin/gdpr_delete_requests_query_spec.rb @@ -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