Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class UsersController < ApplicationController
before_action :set_user, only: %i[ edit update destroy ]

def index
@users = User.all
@users = User.all.search_with_params(user_search_params)
end

def new
Expand Down Expand Up @@ -52,4 +52,9 @@ def set_user
def user_params
params.expect(user: [ :email, :password, :is_admin ])
end

def user_search_params
return {} unless params[:search].present?
params.expect(search: [ :email, :is_admin, :order ])
end
end
15 changes: 15 additions & 0 deletions app/javascript/controllers/search_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Controller } from "@hotwired/stimulus";
import { useDebounce } from "stimulus-use";

export default class extends Controller {
static targets = ["search"];
static debounces = ["submit"];

connect() {
useDebounce(this, { wait: 300 });
}

submit() {
this.searchTarget.requestSubmit();
}
}
6 changes: 4 additions & 2 deletions app/models/concerns/searcheable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,18 @@ def sort_order(order_from_params)

order_from_params
end
end

def search_with_params(params)
included do
scope :search_with_params, ->(params) do
self
.then { |scope| params[:state].present? ? scope.by_state(params[:state]) : scope }
.then { |scope| params[:provider_id].present? ? scope.by_provider(params[:provider_id]) : scope }
.then { |scope| params[:language_id].present? ? scope.by_language(params[:language_id]): scope }
.then { |scope| params[:year].present? ? scope.by_year(params[:year]) : scope }
.then { |scope| params[:month].present? ? scope.by_month(params[:month]) : scope }
.then { |scope| params[:query].present? ? scope.search(params[:query]) : scope }
.then { |scope| scope.order(created_at: sort_order(params[:order])) }
.then { |scope| params[:order].present? ? scope.order(created_at: sort_order(params[:order].to_sym)) : scope }
end
end
end
7 changes: 7 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ class User < ApplicationRecord
validates :email, presence: true, uniqueness: true, format: URI::MailTo::EMAIL_REGEXP
validates :password_digest, presence: true

scope :search_with_params, ->(params) do
self
.then { |scope| params[:email].present? ? scope.where("email ILIKE ?", "%#{params[:email]}%") : scope }
.then { |scope| params[:is_admin].present? ? scope.where(is_admin: params[:is_admin]) : scope }
.then { |scope| scope.order(created_at: params[:order]&.to_sym || :desc) }
end

def topics
Topic.where(provider_id: providers.pluck(:id))
end
Expand Down
39 changes: 38 additions & 1 deletion app/views/users/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
<div class="page-heading">
<section class="section">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h2 class="card-title">Search</h2>
</div>
<div class="card-content">
<div class="card-body">
<%= form_for :search, url: users_path, method: :get, data: { controller: "search", search_target: "search", turbo_frame: "user-list", turbo_action: "advance" } do |f| %>
<div class="form-body">
<div class="row">
<div class="col-md-6 col-12">
<div class="form-group">
<%= f.label :email %>
<%= f.text_field :email, value: params.dig(:search, :email), class: "form-control", data: { action: "input->search#submit" } %>
</div>
</div>
<div class="col-md-6 col-12">
<div class="form-group">
<%= f.label "Role" %>
<%= f.select :is_admin, options_for_select([["Admin", "true"], ["Contributor", "false"]], params.dig(:search, :is_admin)), { prompt: "Select user role" }, class: "form-select", data: { action: "change->search#submit" } %>
</div>
</div>
<div class="col-md-6 col-12">
<div class="form-group">
<%= f.label :order %>
<%= f.select :order, options_for_select([["By most recently added", :desc], ["By least recently added", :asc]], params.dig(:search, :order)), {}, class: "form-select", data: { action: "change->search#submit" } %>
</div>
</div>
<div class="col-12 d-flex justify-content-end">
<%= link_to "Clear", users_path, class: "btn btn-light-secondary me-1 mb-1" %>
</div>
</div>
</div>
<% end %>
</div>
</div>
</div>

<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h2>User List</h2>
Expand All @@ -20,7 +57,7 @@
<% @users.each do |user| %>
<tr>
<td><%= user.email %></td>
<td><%= user.is_admin %></td>
<td><%= user.is_admin ? "✅" : "✖️" %></td>
<td class="text-end">
<%= link_to edit_user_path(user), class: "btn btn-secondary btn-sm pr-2" do %>
<i class="bi bi-pencil"></i> Edit
Expand Down
2 changes: 2 additions & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,6 @@
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")

config.include SystemHelpers, type: :system
end
8 changes: 7 additions & 1 deletion spec/support/capybara.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
Capybara::Selenium::Driver.new(app, browser: :chrome)
end

Capybara.register_driver :selenium_chrome_headless do |app|
options = Selenium::WebDriver::Chrome::Options.new(args: %w[headless disable-gpu window-size=1400,1400])

Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end

module CapybaraPage
def page
Capybara.string(response.body)
Expand All @@ -14,7 +20,7 @@ def page

RSpec.configure do |config|
config.before(:each, type: :system) do
driven_by :rack_test
driven_by :selenium_chrome_headless
end

config.before(:each, :debug, type: :system) do
Expand Down
9 changes: 9 additions & 0 deletions spec/support/system_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module SystemHelpers
def login_as(user)
visit root_path
click_link("Sign In")
fill_in "email", with: user.email
fill_in "password", with: user.password
click_button("Sign in")
end
end
112 changes: 112 additions & 0 deletions spec/system/topic_search_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
require "rails_helper"

RSpec.describe "Topics search", type: :system do
let(:admin) { create(:user, :admin, email: "[email protected]") }
let(:english) { create(:language, name: "English") }
let(:spanish) { create(:language, name: "Spanish") }
let!(:spanish_active_topic) { create(:topic, language: spanish, title: "Tratamiento del resfriado", created_at: Date.new(2025, 02, 03)) }
let!(:english_active_topic) { create(:topic, language: english, title: "How to treat colds", description: "All the latest information about nasopharyngitis", created_at: Date.new(2025, 03, 04)) }
let!(:english_archived_topic) { create(:topic, :archived, language: english, title: "Obsolete", created_at: Date.new(2023, 02, 01)) }

before do
login_as(admin)
click_link("Topics")
end

it "shows all topics" do
expect(page).to have_text(english_active_topic.title)
expect(page).to have_text(spanish_active_topic.title)
expect(page).to have_text(english_archived_topic.title)
end

context "when searching by title" do
it "only displays topics matching the search" do
fill_in "search_query", with: "tratamiento"

expect(page).to have_text(spanish_active_topic.title)
expect(page).not_to have_text(english_active_topic.title)
expect(page).not_to have_text(english_archived_topic.title)
end
end

context "when searching by description" do
it "only displays topics matching the search" do
fill_in "search_query", with: "pharyn"

expect(page).to have_text(english_active_topic.title)
expect(page).not_to have_text(spanish_active_topic.title)
expect(page).not_to have_text(english_archived_topic.title)
end
end

context "when searching by language" do
it "only displays topics matching the search" do
select "Spanish", from: "search_language_id"

expect(page).to have_text(spanish_active_topic.title)
expect(page).not_to have_text(english_active_topic.title)
expect(page).not_to have_text(english_archived_topic.title)

select "English", from: "search_language_id"

expect(page).to have_text(english_active_topic.title)
expect(page).to have_text(english_archived_topic.title)
expect(page).not_to have_text(spanish_active_topic.title)
end
end

context "when searching by year" do
it "only displays topics matching the search" do
select "2025", from: "search_year"

expect(page).to have_text(spanish_active_topic.title)
expect(page).to have_text(english_active_topic.title)
expect(page).not_to have_text(english_archived_topic.title)

select "2023", from: "search_year"

expect(page).to have_text(english_archived_topic.title)
expect(page).not_to have_text(spanish_active_topic.title)
expect(page).not_to have_text(english_active_topic.title)
end
end

context "when searching by month" do
it "only displays topics matching the search" do
select "2", from: "search_month"

expect(page).to have_text(spanish_active_topic.title)
expect(page).to have_text(english_archived_topic.title)
expect(page).not_to have_text(english_active_topic.title)

select "3", from: "search_month"

expect(page).to have_text(english_active_topic.title)
expect(page).not_to have_text(english_archived_topic.title)
expect(page).not_to have_text(spanish_active_topic.title)
end
end

context "when searching by state" do
it "only displays topics matching the search" do
select "active", from: "search_state"

expect(page).to have_text(spanish_active_topic.title)
expect(page).to have_text(english_active_topic.title)
expect(page).not_to have_text(english_archived_topic.title)

select "archived", from: "search_state"

expect(page).to have_text(english_archived_topic.title)
expect(page).not_to have_text(spanish_active_topic.title)
expect(page).not_to have_text(english_active_topic.title)
end
end

context "when sorting" do
it "displays users in the selected order" do
select "asc", from: "search_order"
expect(page).to have_text(/#{english_archived_topic.title}.+#{spanish_active_topic.title}.+#{english_active_topic.title}/m)
end
end
end
49 changes: 49 additions & 0 deletions spec/system/user_search_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require "rails_helper"

RSpec.describe "User search", type: :system do
let(:admin) { create(:user, :admin, email: "[email protected]", created_at: 3.days.ago) }
let!(:martin) { create(:user, email: "[email protected]", created_at: 2.days.ago) }
let!(:rosemary) { create(:user, email: "[email protected]", created_at: 1.day.ago) }

before do
login_as(admin)
click_link("Users")
end

it "shows by default the users from most to least recently added" do
expect(page).to have_text(/#{rosemary.email}.+#{martin.email}.+#{admin.email}/m)
end

context "when searching by email" do
it "only displays users matching the search" do
fill_in "search[email]", with: "mar"

expect(page).to have_text(rosemary.email)
expect(page).to have_text(martin.email)
expect(page).not_to have_text(admin.email)
end
end

context "when searching by role" do
it "only displays users matching the search" do
select "Admin", from: "search_is_admin"

expect(page).to have_text(admin.email)
expect(page).not_to have_text(rosemary.email)
expect(page).not_to have_text(martin.email)

select "Contributor", from: "search_is_admin"

expect(page).to have_text(rosemary.email)
expect(page).to have_text(martin.email)
expect(page).not_to have_text(admin.email)
end
end

context "when sorting" do
it "displays users in the selected order" do
select "By least recently added", from: "search_order"
expect(page).to have_text(/#{admin.email}.+#{martin.email}.+#{rosemary.email}/m)
end
end
end