Checks list: filters & sort

This commit is contained in:
Colin Darie 2018-07-03 20:11:52 +02:00
parent a8ff639257
commit 380960fa75
No known key found for this signature in database
GPG Key ID: 4FB865FDBCA4BCC4
9 changed files with 189 additions and 6 deletions

View File

@ -43,6 +43,7 @@ gem 'whenever', require: false
gem 'octicons'
gem 'kaminari'
gem 'has_scope'
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.1.0', require: false

View File

@ -131,6 +131,9 @@ GEM
guard-minitest (2.4.6)
guard-compat (~> 1.2)
minitest (>= 3.0)
has_scope (0.7.2)
actionpack (>= 4.1)
activesupport (>= 4.1)
i18n (1.0.1)
concurrent-ruby (~> 1.0)
io-like (0.3.0)
@ -350,6 +353,7 @@ DEPENDENCIES
factory_bot_rails
guard
guard-minitest
has_scope
jbuilder (~> 2.5)
kaminari
launchy

View File

@ -4,8 +4,11 @@ class ChecksController < ApplicationController
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
has_scope :kind
has_scope :by_domain
def index
@checks = policy_scope(Check).order(:domain_expires_at).page(params[:page])
@checks = apply_scopes(policy_scope(Check)).order(current_sort).page(params[:page])
end
def new
@ -79,4 +82,22 @@ class ChecksController < ApplicationController
def build_empty_notification
@check.notifications.build
end
def current_sort
@current_sort ||= clean_sort || Check.default_sort
end
helper_method :current_sort
def clean_sort
return unless params[:sort].present?
field, _, direction = params[:sort].rpartition("_").map(&:to_sym)
valid_fields = [:domain, :domain_expires_at]
valid_directions = [:asc, :desc]
return unless valid_fields.include?(field)
return unless valid_directions.include?(direction)
{ field => direction }
end
end

View File

@ -11,4 +11,22 @@ module ChecksHelper
return "table-danger" if expiry_date <= 2.weeks.from_now
return "table-warning" if expiry_date <= 30.days.from_now
end
def checks_sort_links(field)
current_sort_str = current_sort.to_a.join("_")
%i[asc desc].map { |direction|
sort = "#{field}_#{direction}"
icon = direction == :asc ? "chevron-up" : "chevron-down"
html = Octicons::Octicon.new(icon).to_svg.html_safe
filter_params = current_criterias.merge(sort: sort)
link_to_unless sort == current_sort_str, html, checks_path(filter_params)
}.join
end
def current_criterias
current_scopes.merge(sort: params[:sort])
end
end

View File

@ -58,6 +58,13 @@ class Check < ApplicationRecord
OR (last_success_at <= DATE_SUB(last_run_at, INTERVAL 5 MINUTE))")
}
scope :kind, ->(kind) { where(kind: kind) }
scope :by_domain, ->(domain) { where("domain LIKE ?", "%#{domain}%") }
def self.default_sort
{ domain_expires_at: :asc }
end
private
def domain_created_at_past

View File

@ -3,9 +3,15 @@
<thead>
<tr>
<th scope="col"></th>
<th scope="col">Domain</th>
<th scope="col">Expiry date</th>
<th scope="col">Edit</th>
<th scope="col">
<%= t(".domain") %>
<%== checks_sort_links(:domain) %>
</th>
<th scope="col">
<%= t(".expiry_date") %>
<%== checks_sort_links(:domain_expires_at) %>
</th>
<th scope="col"><%= t(".edit") %></th>
</tr>
</thead>
<tbody>

View File

@ -1,13 +1,24 @@
<div class="container">
<div class="row justify-content-center">
<div class="col-12 col-lg-10">
<% if @checks.empty? %>
<% if @checks.empty? && current_scopes.blank? %>
<div class="alert alert-info">
<%= t(".no_check_yet_html", new_domain_path: new_check_path(kind: :domain), new_ssl_path: new_check_path(kind: :ssl)) %>
</div>
<% else %>
<h1><%= t(".title") %></h1>
<%= render "table", checks: @checks %>
<%= link_to("Domains", checks_path(current_criterias.merge(kind: :domain))) %>
<%= link_to("SSL", checks_path(current_criterias.merge(kind: :ssl))) %>
<%= form_tag(checks_path(current_scopes), method: :get) do %>
<%= search_field_tag :by_domain, current_scopes[:by_domain] %>
<%= button_tag t(".filter") %>
<% end %>
<% if @checks.any? %>
<%= render "table", checks: @checks %>
<% else %>
<div class="alert alert-warning"><%= t(".no_matching_check") %></div>
<% end %>
<% end %>
</div>
</div>

View File

@ -0,0 +1,7 @@
inherit_from: ../../.rubocop.yml
Metrics/ClassLength:
Enabled: false
Metrics/BlockLength:
Enabled: false

View File

@ -31,4 +31,112 @@ class ChecksControllerTest < ActionDispatch::IntegrationTest
get new_check_path(kind: :invalid)
assert_response :not_found
end
test "checks are ordered by default by expiry date sort" do
c1 = create(:check, user: @user, domain_expires_at: 20.days.from_now)
c2 = create(:check, user: @user, domain_expires_at: 10.days.from_now)
c3 = create(:check, user: @user, domain_expires_at: 1.day.from_now)
get checks_path
assert_equal [c3, c2, c1], current_checks
end
test "checks are ordered by expiry date asc" do
c1 = create(:check, user: @user, domain_expires_at: 20.days.from_now)
c2 = create(:check, user: @user, domain_expires_at: 10.days.from_now)
c3 = create(:check, user: @user, domain_expires_at: 1.day.from_now)
get checks_path(sort: :domain_expires_at_asc)
assert_equal [c3, c2, c1], current_checks
end
test "checks are ordered by reverse expiring date" do
c1 = create(:check, user: @user, domain_expires_at: 1.day.from_now)
c2 = create(:check, user: @user, domain_expires_at: 10.days.from_now)
c3 = create(:check, user: @user, domain_expires_at: 20.days.from_now)
get checks_path(sort: :domain_expires_at_desc)
assert_equal [c3, c2, c1], current_checks
end
test "checks are ordered by domain name asc" do
c1 = create(:check, user: @user, domain: "a")
c2 = create(:check, user: @user, domain: "b")
c3 = create(:check, user: @user, domain: "c")
get checks_path(sort: :domain_asc)
assert_equal [c1, c2, c3], current_checks
end
test "checks are ordered by domain name desc" do
c1 = create(:check, user: @user, domain: "a")
c2 = create(:check, user: @user, domain: "b")
c3 = create(:check, user: @user, domain: "c")
get checks_path(sort: :domain_desc)
assert_equal [c3, c2, c1], current_checks
end
test "invalid sort fallback to default sort" do
c1 = create(:check, user: @user, domain_expires_at: 20.days.from_now)
c2 = create(:check, user: @user, domain_expires_at: 10.days.from_now)
c3 = create(:check, user: @user, domain_expires_at: 1.day.from_now)
get checks_path(sort: :invalid_sort_asc)
assert_equal [c3, c2, c1], current_checks
end
test "checks are filtered by domain kind" do
c1 = create(:check, :domain, user: @user)
c2 = create(:check, :domain, user: @user)
create(:check, :ssl, user: @user)
get checks_path(kind: :domain)
assert_equal [c1, c2], current_checks
end
test "checks are filtered by ssl kind" do
create(:check, :domain, user: @user)
create(:check, :domain, user: @user)
c3 = create(:check, :ssl, user: @user)
get checks_path(kind: :ssl)
assert_equal [c3], current_checks
end
test "checks are filtered by domain name" do
c1 = create(:check, user: @user, domain: "abc")
c2 = create(:check, user: @user, domain: "bcde")
create(:check, user: @user, domain: "hijk")
get checks_path(by_domain: "bc")
assert_equal [c1, c2], current_checks
get checks_path(by_domain: "klm")
assert_empty current_checks
end
test "checks are paginated" do
create_list(:check, 40, user: @user)
get checks_path
assert_equal 1, current_checks.current_page
first_page = current_checks
get checks_path(page: 2)
assert_equal 2, current_checks.current_page
assert_not_equal first_page, current_checks
end
test "checks are scoped to current user" do
c1 = create(:check, user: @user)
create(:check)
get checks_path
assert_equal [c1], current_checks
end
def current_checks
@controller.instance_variable_get("@checks")
end
end