From a384585256b36f949b1b346d4fb9a61d954be2ed Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Tue, 3 Jul 2018 17:37:32 +0200 Subject: [PATCH 01/16] Rails inflection: SSL --- config/initializers/inflections.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index ac033bf..af698fc 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -11,6 +11,6 @@ # end # These inflection rules are supported but not enabled by default: -# ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.acronym 'RESTful' -# end +ActiveSupport::Inflector.inflections(:en) do |inflect| + inflect.acronym 'SSL' +end From c619d39bde95ee8ea58fa19983f0ccfe4b99cc74 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Tue, 3 Jul 2018 17:38:06 +0200 Subject: [PATCH 02/16] Adapt new check form to predefined kind, more i18n --- app/controllers/application_controller.rb | 10 ++++ app/controllers/checks_controller.rb | 12 +++-- app/views/checks/_form.html.erb | 7 ++- app/views/checks/index.html.erb | 4 +- app/views/checks/new.html.erb | 2 +- app/views/shared/_navbar.html.erb | 7 ++- config/locales/en.yml | 22 +++++++++ config/locales/fr.yml | 36 +++++++++++++- test/controllers/checks_controller_test.rb | 33 +++++++++++-- test/system/checks_test.rb | 57 ++++++++++++++++------ 10 files changed, 161 insertions(+), 29 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5127212..86a4a95 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -20,4 +20,14 @@ class ApplicationController < ActionController::Base def set_locale I18n.locale = current_user.try(:locale) || I18n.default_locale end + + def not_found + fail ActionController::RoutingError, "Not Found" + rescue StandardError + render_404 + end + + def render_404 + render file: "#{Rails.root}/public/404", status: :not_found + end end diff --git a/app/controllers/checks_controller.rb b/app/controllers/checks_controller.rb index b2566a6..d9fc444 100644 --- a/app/controllers/checks_controller.rb +++ b/app/controllers/checks_controller.rb @@ -10,8 +10,14 @@ class ChecksController < ApplicationController def new @check = Check.new - build_empty_notification authorize @check + + if params[:kind].present? + return not_found unless Check.kinds.key?(params[:kind]) + @check.kind = params[:kind] + end + + build_empty_notification end def create @@ -20,10 +26,10 @@ class ChecksController < ApplicationController authorize @check if @check.save - flash[:notice] = "Your check has been saved." + flash[:notice] = t(".saved") redirect_to checks_path else - flash.now[:alert] = "An error occured." + flash.now[:alert] = t(".invalid") render :new end end diff --git a/app/views/checks/_form.html.erb b/app/views/checks/_form.html.erb index e4dd82f..959d5b0 100644 --- a/app/views/checks/_form.html.erb +++ b/app/views/checks/_form.html.erb @@ -1,8 +1,11 @@ <%= simple_form_for(check) do |f| %> - <%= f.input :domain, autofocus: true, input_html: { autocapitalize: :none, autocorrect: :off } %> + <%= f.input :domain, + autofocus: true, + input_html: { autocapitalize: :none, autocorrect: :off }, + label: t(".#{check.kind || "generic" }.domain") %> <% if check.new_record? %> - <%= f.input :kind, as: :radio_buttons, collection: Check.kinds.keys if check.new_record? %> + <%= f.input :kind, as: check.kind.present? ? :hidden : :radio_buttons, collection: Check.kinds.keys %> <% end %> <%= f.input :comment %> diff --git a/app/views/checks/index.html.erb b/app/views/checks/index.html.erb index d425008..f823b6f 100644 --- a/app/views/checks/index.html.erb +++ b/app/views/checks/index.html.erb @@ -3,10 +3,10 @@
<% if @checks.empty? %>
- <%= t(".no_check_yet_html", new_domain_path: new_check_path, new_ssl_path: new_check_path) %> + <%= t(".no_check_yet_html", new_domain_path: new_check_path(kind: :domain), new_ssl_path: new_check_path(kind: :ssl)) %>
<% else %> -

List of your checks

+

<%= t(".title") %>

<%= render "table", checks: @checks %> <% end %>
diff --git a/app/views/checks/new.html.erb b/app/views/checks/new.html.erb index 97b34b8..3a662a7 100644 --- a/app/views/checks/new.html.erb +++ b/app/views/checks/new.html.erb @@ -1,7 +1,7 @@
-

Create a new check

+

<%= t(".#{@check.kind}.title") %>

<%= render "form", check: @check %>
diff --git a/app/views/shared/_navbar.html.erb b/app/views/shared/_navbar.html.erb index 02f3c53..fccb7eb 100644 --- a/app/views/shared/_navbar.html.erb +++ b/app/views/shared/_navbar.html.erb @@ -8,10 +8,13 @@ diff --git a/config/locales/en.yml b/config/locales/en.yml index 9a34ad9..5bde33f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -53,6 +53,9 @@ en: en: English fr: French navbar: + my_checks: "My checks" + new_domain_check: "New domain check" + new_ssl_check: "New SSL check" sign_up: "Sign up" sign_in: "Sign in" sign_out: "Sign out" @@ -60,11 +63,30 @@ en: checks: index: + title: List of your checks no_check_yet_html: | You have not set up a check yet. Please add a domain or a ssl ! + + new: + title: New check + domain: + title: New domain check + ssl: + title: New SSL check + + create: + saved: "Your check has been saved." + invalid: "Please check the form." + form: + generic: + domain: Domain + domain: + domain: Domain name + ssl: + domain: Hostname notifications_hint: | Receive notifications to warn you when our system detects that the expiration date is coming. The time is set in number of days. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index ed5926f..2477a7f 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -2,8 +2,15 @@ fr: activerecord: attributes: check: + domain: "Domaine" + comment: "Commentaire" + vendor: "Fournisseur" + kind: Type domain_created_at: "Date de création" domain_updated_at: "Date de modification" + notification: + interval: Délai + recipient: Destinataire user: tos_accepted: "Conditions d'utilisation" notifications_enabled: "Notifications activées" @@ -11,6 +18,11 @@ fr: models: check: past: "ne peut être dans le futur" + helpers: + submit: + check: + create: "Créer" + update: "Valider" time: am: am @@ -61,6 +73,9 @@ fr: en: Anglais fr: Français navbar: + my_checks: "Mes vérifications" + new_domain_check: "Nouveau nom de domaine" + new_ssl_check: "Nouveau certificat SSL" sign_up: "Enregistrement" sign_in: "Connexion" sign_out: "Déconnexion" @@ -68,11 +83,30 @@ fr: checks: index: + title: "Liste de vos vérifications" no_check_yet_html: | Vous n'avez pas encore créé de vérification. Vous pouvez en ajouter pour un domaine ou un SSL ! + + new: + title: Nouvelle vérification + domain: + title: Nouvelle vérification d'un nom de domaine + ssl: + title: Nouvelle vérification d'un certificat SSL + + create: + saved: La vérification est enregistrée. + invalid: Veuillez vérifier le formulaire. + form: + generic: + domain: Domaine + domain: + domain: Nom de domaine + ssl: + domain: Nom d'hôte notifications_hint: | Recevez des notifications pour vous avertir lorsque notre système détecte - que la date d'expiration approche. Le délais est indiqué ennombre de jours. + que la date d'expiration approche. Le délai est indiqué ennombre de jours. diff --git a/test/controllers/checks_controller_test.rb b/test/controllers/checks_controller_test.rb index 77435b7..98035cf 100644 --- a/test/controllers/checks_controller_test.rb +++ b/test/controllers/checks_controller_test.rb @@ -1,7 +1,34 @@ require "test_helper" class ChecksControllerTest < ActionDispatch::IntegrationTest - # test "the truth" do - # assert true - # end + setup do + @user = create(:user) + login_as(@user) + end + + test "no logged users are redirected to signin form" do + logout + get new_check_path + assert_redirected_to new_user_session_path + end + + test "new without kind does not trigger an error" do + get new_check_path + assert_response :success + end + + test "new with kind domain does not trigger an error" do + get new_check_path(kind: :domain) + assert_response :success + end + + test "new with kind ssl does not trigger an error" do + get new_check_path(kind: :ssl) + assert_response :success + end + + test "new with an invalid kind returns an error" do + get new_check_path(kind: :invalid) + assert_response :not_found + end end diff --git a/test/system/checks_test.rb b/test/system/checks_test.rb index 1fefa51..e7be944 100644 --- a/test/system/checks_test.rb +++ b/test/system/checks_test.rb @@ -8,29 +8,28 @@ class ChecksTest < ApplicationSystemTestCase @check = create(:check, :with_notifications, user: @user) end - test "create a check and a notification" do + test "create a check and a notification without kind" do visit new_check_path - domain = "domain-test.fr" - fill_in("check[domain]", with: domain) choose "domain" - recipient = "recipient@example.org" - fill_in("check[notifications_attributes][0][recipient]", with: recipient) - fill_in("check[notifications_attributes][0][interval]", with: 30) + fill_and_valid_new_check + end - click_button + test "create a predefined domain check" do + visit new_check_path(kind: :domain) - assert_equal checks_path, page.current_path + refute page.has_css? "domain[kind]" - assert page.has_css?(".alert-success") - assert page.has_content?(domain) + fill_and_valid_new_check + end - notification = Notification.last - assert_equal recipient, notification.recipient - assert_equal 30, notification.interval - assert notification.email? - assert notification.pending? + test "create a predefined ssl check" do + visit new_check_path(kind: :ssl) + + refute page.has_css? "domain[kind]" + + fill_and_valid_new_check end test "remove a notification" do @@ -83,4 +82,32 @@ class ChecksTest < ApplicationSystemTestCase assert notification.email? assert notification.pending? end + + private + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def fill_and_valid_new_check + domain = "domain-test.fr" + fill_in("check[domain]", with: domain) + + recipient = "recipient@example.org" + fill_in("check[notifications_attributes][0][recipient]", with: recipient) + fill_in("check[notifications_attributes][0][interval]", with: 30) + + click_button + + assert_equal checks_path, page.current_path + + assert page.has_css?(".alert-success") + assert page.has_content?(domain) + + notification = Notification.last + assert_equal recipient, notification.recipient + assert_equal 30, notification.interval + assert notification.email? + assert notification.pending? + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength end From 3ef8e6322a282ad5197c378ef6cbb9b95b08512a Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Tue, 3 Jul 2018 19:45:24 +0200 Subject: [PATCH 03/16] [GEM] +kaminari for pagination. --- Gemfile | 1 + Gemfile.lock | 13 ++++++++++++ app/views/kaminari/_first_page.html.erb | 11 ++++++++++ app/views/kaminari/_gap.html.erb | 10 +++++++++ app/views/kaminari/_last_page.html.erb | 11 ++++++++++ app/views/kaminari/_next_page.html.erb | 11 ++++++++++ app/views/kaminari/_page.html.erb | 15 ++++++++++++++ app/views/kaminari/_paginator.html.erb | 27 +++++++++++++++++++++++++ app/views/kaminari/_prev_page.html.erb | 11 ++++++++++ config/initializers/kaminari_config.rb | 12 +++++++++++ config/locales/fr.yml | 15 ++++++++++++++ 11 files changed, 137 insertions(+) create mode 100644 app/views/kaminari/_first_page.html.erb create mode 100644 app/views/kaminari/_gap.html.erb create mode 100644 app/views/kaminari/_last_page.html.erb create mode 100644 app/views/kaminari/_next_page.html.erb create mode 100644 app/views/kaminari/_page.html.erb create mode 100644 app/views/kaminari/_paginator.html.erb create mode 100644 app/views/kaminari/_prev_page.html.erb create mode 100644 config/initializers/kaminari_config.rb diff --git a/Gemfile b/Gemfile index aacc982..c050aba 100644 --- a/Gemfile +++ b/Gemfile @@ -42,6 +42,7 @@ gem 'whenever', require: false gem 'octicons' +gem 'kaminari' # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', '>= 1.1.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 61f2f33..7468329 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -138,6 +138,18 @@ GEM activesupport (>= 4.2.0) multi_json (>= 1.2) json (2.1.0) + kaminari (1.1.1) + activesupport (>= 4.1.0) + kaminari-actionview (= 1.1.1) + kaminari-activerecord (= 1.1.1) + kaminari-core (= 1.1.1) + kaminari-actionview (1.1.1) + actionview + kaminari-core (= 1.1.1) + kaminari-activerecord (1.1.1) + activerecord + kaminari-core (= 1.1.1) + kaminari-core (1.1.1) launchy (2.4.3) addressable (~> 2.3) letter_opener (1.6.0) @@ -339,6 +351,7 @@ DEPENDENCIES guard guard-minitest jbuilder (~> 2.5) + kaminari launchy letter_opener_web listen (>= 3.0.5, < 3.2) diff --git a/app/views/kaminari/_first_page.html.erb b/app/views/kaminari/_first_page.html.erb new file mode 100644 index 0000000..be84d7b --- /dev/null +++ b/app/views/kaminari/_first_page.html.erb @@ -0,0 +1,11 @@ +<%# Link to the "First" page + - available local variables + url: url to the first page + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +
  • + <%= link_to t('views.pagination.first').html_safe, url, remote: remote, class: "page-link" %> +
  • diff --git a/app/views/kaminari/_gap.html.erb b/app/views/kaminari/_gap.html.erb new file mode 100644 index 0000000..cd730b7 --- /dev/null +++ b/app/views/kaminari/_gap.html.erb @@ -0,0 +1,10 @@ +<%# Non-link tag that stands for skipped pages... + - available local variables + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +
  • + <%= t('views.pagination.truncate').html_safe %> +
  • diff --git a/app/views/kaminari/_last_page.html.erb b/app/views/kaminari/_last_page.html.erb new file mode 100644 index 0000000..4ef40e0 --- /dev/null +++ b/app/views/kaminari/_last_page.html.erb @@ -0,0 +1,11 @@ +<%# Link to the "Last" page + - available local variables + url: url to the last page + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +
  • + <%= link_to t('views.pagination.last').html_safe, url, remote: remote, class: "page-link" %> +
  • diff --git a/app/views/kaminari/_next_page.html.erb b/app/views/kaminari/_next_page.html.erb new file mode 100644 index 0000000..c967dd8 --- /dev/null +++ b/app/views/kaminari/_next_page.html.erb @@ -0,0 +1,11 @@ +<%# Link to the "Next" page + - available local variables + url: url to the next page + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +
  • + <%= link_to t('views.pagination.next').html_safe, url, rel: 'next', remote: remote, class: "page-link" %> +
  • diff --git a/app/views/kaminari/_page.html.erb b/app/views/kaminari/_page.html.erb new file mode 100644 index 0000000..efcbe03 --- /dev/null +++ b/app/views/kaminari/_page.html.erb @@ -0,0 +1,15 @@ +<%# Link showing page number + - available local variables + page: a page object for "this" page + url: url to this page + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +
  • + <%= link_to page, url, remote: remote, rel: page.rel, class: "page-link" %> + <% if page.current? %> + (current) + <% end %> +
  • diff --git a/app/views/kaminari/_paginator.html.erb b/app/views/kaminari/_paginator.html.erb new file mode 100644 index 0000000..7a26612 --- /dev/null +++ b/app/views/kaminari/_paginator.html.erb @@ -0,0 +1,27 @@ +<%# The container tag + - available local variables + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote + paginator: the paginator that renders the pagination tags inside +-%> +<%= paginator.render do -%> + +<% end -%> diff --git a/app/views/kaminari/_prev_page.html.erb b/app/views/kaminari/_prev_page.html.erb new file mode 100644 index 0000000..d272a5a --- /dev/null +++ b/app/views/kaminari/_prev_page.html.erb @@ -0,0 +1,11 @@ +<%# Link to the "Previous" page + - available local variables + url: url to the previous page + current_page: a page object for the currently displayed page + total_pages: total number of pages + per_page: number of items to fetch per page + remote: data-remote +-%> +
  • + <%= link_to t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote, class: "page-link" %> +
  • diff --git a/config/initializers/kaminari_config.rb b/config/initializers/kaminari_config.rb new file mode 100644 index 0000000..aed7d31 --- /dev/null +++ b/config/initializers/kaminari_config.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true +Kaminari.configure do |config| + config.default_per_page = 20 + config.max_per_page = 200 + # config.window = 4 + # config.outer_window = 0 + # config.left = 0 + # config.right = 0 + # config.page_method_name = :page + # config.param_name = :page + # config.params_on_first_page = false +end diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 2477a7f..c2a85e4 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -23,6 +23,21 @@ fr: check: create: "Créer" update: "Valider" + page_entries_info: + one_page: + display_entries: + zero: "Pas de %{entry_name} trouvé." + one: "Affiche 1 %{entry_name}" + other: "Affiche les %{count} %{entry_name}" + more_pages: + display_entries: "Affiche %{entry_name} %{first} - %{last} de %{total} au total" + views: + pagination: + first: "« Début" + last: "Fin »" + previous: "‹ Préc" + next: "Suiv ›" + truncate: "…" time: am: am From 6687c3d341f65ced734fd9a1d9fdd41b14c769cf Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Tue, 3 Jul 2018 19:46:15 +0200 Subject: [PATCH 04/16] Added pagination to checks. --- app/controllers/checks_controller.rb | 2 +- app/views/checks/_table.html.erb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/checks_controller.rb b/app/controllers/checks_controller.rb index d9fc444..b592408 100644 --- a/app/controllers/checks_controller.rb +++ b/app/controllers/checks_controller.rb @@ -5,7 +5,7 @@ class ChecksController < ApplicationController after_action :verify_policy_scoped, only: :index def index - @checks = policy_scope(Check).order(:domain_expires_at) + @checks = policy_scope(Check).order(:domain_expires_at).page(params[:page]) end def new diff --git a/app/views/checks/_table.html.erb b/app/views/checks/_table.html.erb index d177256..a0f05b0 100644 --- a/app/views/checks/_table.html.erb +++ b/app/views/checks/_table.html.erb @@ -30,3 +30,5 @@
    + +<%= paginate @checks %> From c172b52b499a6d326cdbcbc7336ee7dabf7b23bf Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Tue, 3 Jul 2018 19:46:39 +0200 Subject: [PATCH 05/16] Large random seeds checks --- db/seeds.rb | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index 2bf199b..560b288 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -7,7 +7,16 @@ user1 = User.create!( email: "colin@example.org", password: "password", tos_accepted: true, - confirmed_at: Time.now + confirmed_at: Time.now, + locale: :fr, +) + +user2 = User.create!( + email: "colin+en@example.org", + password: "password", + tos_accepted: true, + confirmed_at: Time.now, + locale: :en, ) check_chexpire_org = Check.create!( @@ -24,7 +33,7 @@ check_chexpire_org = Check.create!( check_chexpire_org_error = Check.create!( user: user1, kind: :domain, - domain: "chexpire.org", + domain: "chexpire-error.org", domain_expires_at: 1.week.from_now, domain_updated_at: 6.months.ago, domain_created_at: Time.new(2016, 8, 4, 12, 15, 1), @@ -48,7 +57,7 @@ ssl_check_chexpire_org = Check.create!( ssl_check_chexpire_org_error = Check.create!( user: user1, kind: :ssl, - domain: "chexpire.org", + domain: "chexpire-error.org", domain_expires_at: 1.week.from_now, domain_updated_at: 6.months.ago, domain_created_at: Time.new(2016, 8, 4, 12, 15, 1), @@ -59,6 +68,20 @@ ssl_check_chexpire_org_error = Check.create!( ) +100.times do |i| + ext = %w[com net org fr].sample + word = (0...rand(4..12)).map { (97 + rand(26)).chr }.join + + Check.create!( + user: [user1, user2].sample, + kind: Check.kinds.keys.sample, + domain: "#{word}.#{ext}", + domain_expires_at: rand(1..300).days.from_now, + domain_updated_at: rand(1..300).days.ago, + domain_created_at: rand(301..3000).days.ago, + ) +end + Notification.create!( check: check_chexpire_org, interval: 15, From a8ff639257e8af24fbba46e5c4d592b2b926dd2f Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Tue, 3 Jul 2018 19:46:51 +0200 Subject: [PATCH 06/16] [yarn] +turbolinks --- app/frontend/packs/application.js | 2 ++ package.json | 3 ++- yarn.lock | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/frontend/packs/application.js b/app/frontend/packs/application.js index 30be22b..6a32480 100644 --- a/app/frontend/packs/application.js +++ b/app/frontend/packs/application.js @@ -8,6 +8,7 @@ // layout file, like app/views/layouts/application.html.erb import Rails from 'rails-ujs'; +import Turbolinks from 'turbolinks'; import 'bootstrap/js/dist/collapse'; import 'bootstrap/js/dist/dropdown'; @@ -15,3 +16,4 @@ import 'bootstrap/js/dist/dropdown'; import '../scss'; Rails.start() +Turbolinks.start() diff --git a/package.json b/package.json index 93b6085..e00de19 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "exports-loader": "^0.7.0", "jquery": "^3.3.1", "popper.js": "^1.14.3", - "rails-ujs": "^5.2.0" + "rails-ujs": "^5.2.0", + "turbolinks": "^5.1.1" }, "devDependencies": { "webpack-dev-server": "2.11.2" diff --git a/yarn.lock b/yarn.lock index 83f3e0f..010038d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5598,6 +5598,10 @@ tunnel-agent@~0.4.1: version "0.4.3" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" +turbolinks@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/turbolinks/-/turbolinks-5.1.1.tgz#3d418a2d8172edbde5e787bf74cb7bef151ae43f" + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" From 380960fa75fe8985a73e5889bd233d917b843972 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Tue, 3 Jul 2018 20:11:52 +0200 Subject: [PATCH 07/16] Checks list: filters & sort --- Gemfile | 1 + Gemfile.lock | 4 + app/controllers/checks_controller.rb | 23 ++++- app/helpers/checks_helper.rb | 18 ++++ app/models/check.rb | 7 ++ app/views/checks/_table.html.erb | 12 ++- app/views/checks/index.html.erb | 15 ++- test/controllers/.rubocop.yml | 7 ++ test/controllers/checks_controller_test.rb | 108 +++++++++++++++++++++ 9 files changed, 189 insertions(+), 6 deletions(-) create mode 100644 test/controllers/.rubocop.yml diff --git a/Gemfile b/Gemfile index c050aba..e3665cb 100644 --- a/Gemfile +++ b/Gemfile @@ -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 diff --git a/Gemfile.lock b/Gemfile.lock index 7468329..5e091d7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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 diff --git a/app/controllers/checks_controller.rb b/app/controllers/checks_controller.rb index b592408..fff3482 100644 --- a/app/controllers/checks_controller.rb +++ b/app/controllers/checks_controller.rb @@ -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 diff --git a/app/helpers/checks_helper.rb b/app/helpers/checks_helper.rb index ae82a4c..84bf8e0 100644 --- a/app/helpers/checks_helper.rb +++ b/app/helpers/checks_helper.rb @@ -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 diff --git a/app/models/check.rb b/app/models/check.rb index 115119b..6d214a6 100644 --- a/app/models/check.rb +++ b/app/models/check.rb @@ -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 diff --git a/app/views/checks/_table.html.erb b/app/views/checks/_table.html.erb index a0f05b0..94531a1 100644 --- a/app/views/checks/_table.html.erb +++ b/app/views/checks/_table.html.erb @@ -3,9 +3,15 @@ - Domain - Expiry date - Edit + + <%= t(".domain") %> + <%== checks_sort_links(:domain) %> + + + <%= t(".expiry_date") %> + <%== checks_sort_links(:domain_expires_at) %> + + <%= t(".edit") %> diff --git a/app/views/checks/index.html.erb b/app/views/checks/index.html.erb index f823b6f..606ee1f 100644 --- a/app/views/checks/index.html.erb +++ b/app/views/checks/index.html.erb @@ -1,13 +1,24 @@
    - <% if @checks.empty? %> + <% if @checks.empty? && current_scopes.blank? %>
    <%= t(".no_check_yet_html", new_domain_path: new_check_path(kind: :domain), new_ssl_path: new_check_path(kind: :ssl)) %>
    <% else %>

    <%= t(".title") %>

    - <%= 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 %> +
    <%= t(".no_matching_check") %>
    + <% end %> <% end %>
    diff --git a/test/controllers/.rubocop.yml b/test/controllers/.rubocop.yml new file mode 100644 index 0000000..9df3ff0 --- /dev/null +++ b/test/controllers/.rubocop.yml @@ -0,0 +1,7 @@ +inherit_from: ../../.rubocop.yml + +Metrics/ClassLength: + Enabled: false + +Metrics/BlockLength: + Enabled: false diff --git a/test/controllers/checks_controller_test.rb b/test/controllers/checks_controller_test.rb index 98035cf..54650cc 100644 --- a/test/controllers/checks_controller_test.rb +++ b/test/controllers/checks_controller_test.rb @@ -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 From cac52c100738c392e134d6c4c84ee41fdb5929e5 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Wed, 4 Jul 2018 11:55:44 +0200 Subject: [PATCH 08/16] Checks in error filterable --- app/controllers/checks_controller.rb | 1 + app/models/check.rb | 4 ++++ app/views/checks/index.html.erb | 1 + test/controllers/checks_controller_test.rb | 8 ++++++++ 4 files changed, 14 insertions(+) diff --git a/app/controllers/checks_controller.rb b/app/controllers/checks_controller.rb index fff3482..7610ab3 100644 --- a/app/controllers/checks_controller.rb +++ b/app/controllers/checks_controller.rb @@ -6,6 +6,7 @@ class ChecksController < ApplicationController has_scope :kind has_scope :by_domain + has_scope :recurrent_failures, type: :boolean def index @checks = apply_scopes(policy_scope(Check)).order(current_sort).page(params[:page]) diff --git a/app/models/check.rb b/app/models/check.rb index 6d214a6..a54def2 100644 --- a/app/models/check.rb +++ b/app/models/check.rb @@ -60,6 +60,10 @@ class Check < ApplicationRecord scope :kind, ->(kind) { where(kind: kind) } scope :by_domain, ->(domain) { where("domain LIKE ?", "%#{domain}%") } + scope :recurrent_failures, -> { + where("last_run_at IS NOT NULL"). + where("last_success_at IS NULL OR last_success_at <= DATE_SUB(last_run_at, INTERVAL 72 HOUR)") + } def self.default_sort { domain_expires_at: :asc } diff --git a/app/views/checks/index.html.erb b/app/views/checks/index.html.erb index 606ee1f..b6ce6b3 100644 --- a/app/views/checks/index.html.erb +++ b/app/views/checks/index.html.erb @@ -9,6 +9,7 @@

    <%= t(".title") %>

    <%= link_to("Domains", checks_path(current_criterias.merge(kind: :domain))) %> <%= link_to("SSL", checks_path(current_criterias.merge(kind: :ssl))) %> + <%= link_to("In error", checks_path(current_criterias.merge(recurrent_failures: true))) %> <%= form_tag(checks_path(current_scopes), method: :get) do %> <%= search_field_tag :by_domain, current_scopes[:by_domain] %> <%= button_tag t(".filter") %> diff --git a/test/controllers/checks_controller_test.rb b/test/controllers/checks_controller_test.rb index 54650cc..92e22d9 100644 --- a/test/controllers/checks_controller_test.rb +++ b/test/controllers/checks_controller_test.rb @@ -116,6 +116,14 @@ class ChecksControllerTest < ActionDispatch::IntegrationTest assert_empty current_checks end + test "checks in error are filtered" do + c1 = create(:check, :last_runs_failed, user: @user) + create(:check, user: @user) + + get checks_path(recurrent_failures: true) + assert_equal [c1], current_checks + end + test "checks are paginated" do create_list(:check, 40, user: @user) From 9279d8eed47e3a18b804172e4c1d1c4b46879034 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Wed, 4 Jul 2018 12:33:50 +0200 Subject: [PATCH 09/16] Checks listing design & responsivity --- app/frontend/packs/application.js | 1 + app/frontend/scss/_variables.scss | 10 ++++++++ app/frontend/scss/index.scss | 1 + app/helpers/application_helper.rb | 4 +++ app/helpers/checks_helper.rb | 34 +++++++++++++++++++------ app/models/check.rb | 4 +-- app/views/checks/_filters.html.erb | 40 ++++++++++++++++++++++++++++++ app/views/checks/_table.html.erb | 21 ++++++++++------ app/views/checks/index.html.erb | 15 ++++------- config/locales/en.yml | 19 ++++++++++++++ config/locales/fr.yml | 19 ++++++++++++++ db/seeds.rb | 2 +- 12 files changed, 143 insertions(+), 27 deletions(-) create mode 100644 app/frontend/scss/_variables.scss create mode 100644 app/views/checks/_filters.html.erb diff --git a/app/frontend/packs/application.js b/app/frontend/packs/application.js index 6a32480..4ae8b7a 100644 --- a/app/frontend/packs/application.js +++ b/app/frontend/packs/application.js @@ -12,6 +12,7 @@ import Turbolinks from 'turbolinks'; import 'bootstrap/js/dist/collapse'; import 'bootstrap/js/dist/dropdown'; +import 'bootstrap/js/dist/button'; import '../scss'; diff --git a/app/frontend/scss/_variables.scss b/app/frontend/scss/_variables.scss new file mode 100644 index 0000000..822dfbf --- /dev/null +++ b/app/frontend/scss/_variables.scss @@ -0,0 +1,10 @@ +$input-placeholder-color: #adb5bd; +$enable-rounded: false; +$theme-colors: ( + "primary": #259EDB, + "secondary": #565554, + "success": #42935C, + "warning": #F6AE2D, + "danger": #E94F37, + "info": #2E86AB, +); diff --git a/app/frontend/scss/index.scss b/app/frontend/scss/index.scss index 2577ff8..4b15d76 100644 --- a/app/frontend/scss/index.scss +++ b/app/frontend/scss/index.scss @@ -1,3 +1,4 @@ +@import '_variables'; @import '~bootstrap/scss/bootstrap'; @import 'layout'; @import 'icons'; diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 53fa2d6..3d6fea9 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,4 +1,8 @@ module ApplicationHelper + def format_date(time, format: :long) + l(time.utc.to_date, format: format) + end + def format_utc(time, format: :default) l(time.utc, format: format) end diff --git a/app/helpers/checks_helper.rb b/app/helpers/checks_helper.rb index 84bf8e0..654671d 100644 --- a/app/helpers/checks_helper.rb +++ b/app/helpers/checks_helper.rb @@ -1,15 +1,11 @@ module ChecksHelper - def check_kind_label(check) - check.kind.upcase - end - def check_row_class(check) expiry_date = check.domain_expires_at return unless expiry_date.present? - return "table-danger" if expiry_date <= 2.weeks.from_now - return "table-warning" if expiry_date <= 30.days.from_now + return "table-danger" if expiry_date <= 3.days.from_now + return "table-warning" if expiry_date < 1.month.from_now end def checks_sort_links(field) @@ -19,7 +15,7 @@ module ChecksHelper sort = "#{field}_#{direction}" icon = direction == :asc ? "chevron-up" : "chevron-down" - html = Octicons::Octicon.new(icon).to_svg.html_safe + html = Octicons::Octicon.new(icon, class: "mx-1").to_svg.html_safe filter_params = current_criterias.merge(sort: sort) link_to_unless sort == current_sort_str, html, checks_path(filter_params) @@ -29,4 +25,28 @@ module ChecksHelper def current_criterias current_scopes.merge(sort: params[:sort]) end + + def scoped_with?(scope) + name, value = scope.first + scope_value = current_scopes[name] + scope_value = scope_value.to_sym if scope_value.respond_to?(:to_sym) + + scope_value == value + end + + def check_button_criterias(scope) + if scoped_with?(scope) + current_criterias.except(scope.keys.first) + else + current_criterias.merge(scope) + end + end + + def check_button_scope_class(scope = nil) + "btn btn-sm " << if scope && scoped_with?(scope) + "btn-info active" + else + "btn-outline-info" + end + end end diff --git a/app/models/check.rb b/app/models/check.rb index a54def2..5074339 100644 --- a/app/models/check.rb +++ b/app/models/check.rb @@ -61,8 +61,8 @@ class Check < ApplicationRecord scope :kind, ->(kind) { where(kind: kind) } scope :by_domain, ->(domain) { where("domain LIKE ?", "%#{domain}%") } scope :recurrent_failures, -> { - where("last_run_at IS NOT NULL"). - where("last_success_at IS NULL OR last_success_at <= DATE_SUB(last_run_at, INTERVAL 72 HOUR)") + where("last_run_at IS NOT NULL") + .where("last_success_at IS NULL OR last_success_at <= DATE_SUB(last_run_at, INTERVAL 3 DAY)") } def self.default_sort diff --git a/app/views/checks/_filters.html.erb b/app/views/checks/_filters.html.erb new file mode 100644 index 0000000..1c84746 --- /dev/null +++ b/app/views/checks/_filters.html.erb @@ -0,0 +1,40 @@ +
    + +
    +
    + <% Check.kinds.keys.map(&:to_sym).each do |kind_name| %> + <%= link_to t(".kind_#{kind_name}"), + checks_path(check_button_criterias(kind: kind_name)), + class: check_button_scope_class(kind: kind_name) %> + <% end %> +
    + + <%= link_to t(".with_error"), + checks_path(check_button_criterias(recurrent_failures: true)), + class: check_button_scope_class(recurrent_failures: true) %> +
    + +
    + <%= form_tag(checks_path, method: :get) do %> +
    +
    +
    + <%= search_field_tag :by_domain, current_scopes[:by_domain], class: "form-control form-control-sm", placeholder: ".com, example.org, …" %> +
    + <%= button_tag Octicons::Octicon.new("search").to_svg.html_safe, class: "btn btn-sm btn-outline-secondary" %> +
    +
    + + <%- current_criterias.except(:by_domain).compact.each_pair do |name, value| %> + <%= hidden_field_tag name, value%> + <% end %> +
    + +
    + <%= link_to Octicons::Octicon.new("x").to_svg.html_safe, checks_path, class: "btn btn-danger btn-sm btn-outline-danger" %> +
    +
    + + <% end %> +
    +
    diff --git a/app/views/checks/_table.html.erb b/app/views/checks/_table.html.erb index 94531a1..074cd57 100644 --- a/app/views/checks/_table.html.erb +++ b/app/views/checks/_table.html.erb @@ -1,32 +1,39 @@ -
    +
    - + <% checks.each do |check| %> - <% checks.each do |check| %> - - + @@ -23,19 +24,19 @@ + - -
    <%= t(".domain") %> - <%== checks_sort_links(:domain) %> + + <%== checks_sort_links(:domain) %> + <%= t(".expiry_date") %> - <%== checks_sort_links(:domain_expires_at) %> + + <%== checks_sort_links(:domain_expires_at) %> + <%= t(".edit") %><%= t(".edit") %>
    - <%= check_kind_label(check) %> + <%= t(".kind_labels.#{check.kind}") %> <%= check.domain %> - <%= format_utc(check.domain_expires_at) if check.domain_expires_at.present? %> + <% if check.domain_expires_at.present? %> + <%= content_tag :span, format_date(check.domain_expires_at), class: "d-none d-md-inline" %> + <%= content_tag :span, format_date(check.domain_expires_at, format: :short), class: "d-inline d-md-none" %> + <% end %> + <%= link_to edit_check_path(check) do %> <%== Octicons::Octicon.new("pencil").to_svg %> <% end %> diff --git a/app/views/checks/index.html.erb b/app/views/checks/index.html.erb index b6ce6b3..642c41d 100644 --- a/app/views/checks/index.html.erb +++ b/app/views/checks/index.html.erb @@ -1,19 +1,14 @@ -
    +
    -
    +
    <% if @checks.empty? && current_scopes.blank? %>
    <%= t(".no_check_yet_html", new_domain_path: new_check_path(kind: :domain), new_ssl_path: new_check_path(kind: :ssl)) %>
    <% else %> -

    <%= t(".title") %>

    - <%= link_to("Domains", checks_path(current_criterias.merge(kind: :domain))) %> - <%= link_to("SSL", checks_path(current_criterias.merge(kind: :ssl))) %> - <%= link_to("In error", checks_path(current_criterias.merge(recurrent_failures: true))) %> - <%= form_tag(checks_path(current_scopes), method: :get) do %> - <%= search_field_tag :by_domain, current_scopes[:by_domain] %> - <%= button_tag t(".filter") %> - <% end %> +

    <%= t(".title") %>

    + + <%= render "filters" %> <% if @checks.any? %> <%= render "table", checks: @checks %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 5bde33f..a17b4ec 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -12,6 +12,11 @@ en: check: past: "can't be in the future" + date: + formats: + short: "%-d, %b %Y" + long: "%A, %B %d, %Y" + devise: registrations: new: @@ -64,6 +69,7 @@ en: checks: index: title: List of your checks + no_matching_check: "No checks match your filters." no_check_yet_html: | You have not set up a check yet. Please add a domain @@ -80,6 +86,11 @@ en: saved: "Your check has been saved." invalid: "Please check the form." + filters: + kind_domain: Domain + kind_ssl: SSL + with_error: Error + form: generic: domain: Domain @@ -90,3 +101,11 @@ en: notifications_hint: | Receive notifications to warn you when our system detects that the expiration date is coming. The time is set in number of days. + + table: + domain: Name + expiry_date: Expiration + edit: Edit + kind_labels: + domain: Domain + ssl: SSL diff --git a/config/locales/fr.yml b/config/locales/fr.yml index c2a85e4..aa2c3b7 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -47,6 +47,11 @@ fr: short: "%d %b %H:%M" pm: pm + date: + formats: + short: "%d/%m/%Y" + long: "%A %d %B %Y" + devise: registrations: new: @@ -99,6 +104,7 @@ fr: checks: index: title: "Liste de vos vérifications" + no_matching_check: "Aucune vérification ne correspond à vos critères." no_check_yet_html: | Vous n'avez pas encore créé de vérification. Vous pouvez en ajouter pour un domaine @@ -115,6 +121,11 @@ fr: saved: La vérification est enregistrée. invalid: Veuillez vérifier le formulaire. + filters: + kind_domain: Domaine + kind_ssl: SSL + with_error: En erreur + form: generic: domain: Domaine @@ -125,3 +136,11 @@ fr: notifications_hint: | Recevez des notifications pour vous avertir lorsque notre système détecte que la date d'expiration approche. Le délai est indiqué ennombre de jours. + + table: + domain: Nom + expiry_date: "Expiration" + edit: Modifier + kind_labels: + domain: Domaine + ssl: SSL diff --git a/db/seeds.rb b/db/seeds.rb index 560b288..e81814b 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -76,7 +76,7 @@ ssl_check_chexpire_org_error = Check.create!( user: [user1, user2].sample, kind: Check.kinds.keys.sample, domain: "#{word}.#{ext}", - domain_expires_at: rand(1..300).days.from_now, + domain_expires_at: rand(8..300).days.from_now, domain_updated_at: rand(1..300).days.ago, domain_created_at: rand(301..3000).days.ago, ) From 3bfca3b81fe198fa522be08bedc56348ca9c800f Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Wed, 4 Jul 2018 19:05:52 +0200 Subject: [PATCH 10/16] Display an icon for check in error --- app/frontend/packs/application.js | 5 +++ app/frontend/scss/components/checks.scss | 4 ++ app/helpers/checks_helper.rb | 6 +++ app/models/check.rb | 21 ++++++++- app/views/checks/_table.html.erb | 13 +++++- config/locales/en.yml | 5 +++ config/locales/fr.yml | 5 +++ db/seeds.rb | 23 ++++++++-- test/controllers/checks_controller_test.rb | 2 +- test/models/check_test.rb | 52 ++++++++++++++++++++++ 10 files changed, 128 insertions(+), 8 deletions(-) diff --git a/app/frontend/packs/application.js b/app/frontend/packs/application.js index 4ae8b7a..d2d55db 100644 --- a/app/frontend/packs/application.js +++ b/app/frontend/packs/application.js @@ -13,8 +13,13 @@ import Turbolinks from 'turbolinks'; import 'bootstrap/js/dist/collapse'; import 'bootstrap/js/dist/dropdown'; import 'bootstrap/js/dist/button'; +import 'bootstrap/js/dist/tooltip'; import '../scss'; Rails.start() Turbolinks.start() + +document.addEventListener("turbolinks:load", () => { + $('[data-toggle="tooltip"]').tooltip(); +}); diff --git a/app/frontend/scss/components/checks.scss b/app/frontend/scss/components/checks.scss index 26f3f5d..6c24b24 100644 --- a/app/frontend/scss/components/checks.scss +++ b/app/frontend/scss/components/checks.scss @@ -2,4 +2,8 @@ .action a { color: black; } + + .kind .octicon { + vertical-align: middle; + } } diff --git a/app/helpers/checks_helper.rb b/app/helpers/checks_helper.rb index 654671d..5976d8e 100644 --- a/app/helpers/checks_helper.rb +++ b/app/helpers/checks_helper.rb @@ -49,4 +49,10 @@ module ChecksHelper "btn-outline-info" end end + + def check_last_success_title(check) + return t(".never_succeeded") if check.last_success_at.nil? + + t(".days_from_last_success", count: check.days_from_last_success) + end end diff --git a/app/models/check.rb b/app/models/check.rb index 5074339..958f5ca 100644 --- a/app/models/check.rb +++ b/app/models/check.rb @@ -27,6 +27,8 @@ # class Check < ApplicationRecord + ERROR_DELAY_DAYS = 3 + belongs_to :user has_many :logs, class_name: "CheckLog" has_many :notifications, validate: true, dependent: :destroy @@ -61,14 +63,29 @@ class Check < ApplicationRecord scope :kind, ->(kind) { where(kind: kind) } scope :by_domain, ->(domain) { where("domain LIKE ?", "%#{domain}%") } scope :recurrent_failures, -> { - where("last_run_at IS NOT NULL") - .where("last_success_at IS NULL OR last_success_at <= DATE_SUB(last_run_at, INTERVAL 3 DAY)") + interval = "INTERVAL #{ERROR_DELAY_DAYS} DAY" + where("last_run_at IS NOT NULL AND created_at <= DATE_SUB(NOW(), #{interval})") + .where("last_success_at IS NULL OR last_success_at <= DATE_SUB(last_run_at, #{interval})") } def self.default_sort { domain_expires_at: :asc } end + def in_error? + return false if created_at > ERROR_DELAY_DAYS.days.ago + return false if last_run_at.nil? + return true if last_success_at.nil? + + last_success_at < ERROR_DELAY_DAYS.days.ago + end + + def days_from_last_success + return unless last_success_at.present? + + (Date.today - last_success_at.to_date).to_i + end + private def domain_created_at_past diff --git a/app/views/checks/_table.html.erb b/app/views/checks/_table.html.erb index 074cd57..e164399 100644 --- a/app/views/checks/_table.html.erb +++ b/app/views/checks/_table.html.erb @@ -21,8 +21,19 @@
    + <%= t(".kind_labels.#{check.kind}") %> + <%- if check.in_error? %> + <%== content_tag( + :span, + Octicons::Octicon.new("alert", class: "ml-1").to_svg.html_safe, + class: "text-danger", + data: { + toggle: "tooltip", + placement: "right", + title: check_last_success_title(check) + }) %> + <% end %> <%= check.domain %> diff --git a/config/locales/en.yml b/config/locales/en.yml index a17b4ec..bd4ab70 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -109,3 +109,8 @@ en: kind_labels: domain: Domain ssl: SSL + never_succeeded: "Chexpire has never been able to perform a check." + days_from_last_success: + zero: "Last check successful: today" + one: "Last check successful: yesterday" + other: "Last check successful %{count} days ago" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index aa2c3b7..5c76b55 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -144,3 +144,8 @@ fr: kind_labels: domain: Domaine ssl: SSL + never_succeeded: "Chexpire n'a jamais pu effectuer de vérification." + days_from_last_success: + zero: "Dernière vérification réussie : aujourd'hui" + one: "Dernière vérification réussie : hier" + other: "Dernière vérification réussie il y a %{count} jours" diff --git a/db/seeds.rb b/db/seeds.rb index e81814b..996d657 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -19,6 +19,8 @@ user2 = User.create!( locale: :en, ) +users = [user1, user2] + check_chexpire_org = Check.create!( user: user1, kind: :domain, @@ -40,7 +42,7 @@ check_chexpire_org_error = Check.create!( comment: "The date are fake, this is a seed !", vendor: "Some random registrar", last_run_at: 20.minutes.ago, - last_success_at: 4.days.ago, + created_at: 3.weeks.ago, ) ssl_check_chexpire_org = Check.create!( @@ -68,12 +70,12 @@ ssl_check_chexpire_org_error = Check.create!( ) -100.times do |i| +def check_factory(users) ext = %w[com net org fr].sample word = (0...rand(4..12)).map { (97 + rand(26)).chr }.join - Check.create!( - user: [user1, user2].sample, + Check.new( + user: users.sample, kind: Check.kinds.keys.sample, domain: "#{word}.#{ext}", domain_expires_at: rand(8..300).days.from_now, @@ -82,6 +84,19 @@ ssl_check_chexpire_org_error = Check.create!( ) end +100.times do |i| + check_factory(users).save! +end + +# checks with error +10.times do |i| + check_factory(users).update_attributes( + created_at: rand(1..300).days.ago, + last_run_at: 4.hours.ago, + last_success_at: rand(10...100).days.ago, + ) +end + Notification.create!( check: check_chexpire_org, interval: 15, diff --git a/test/controllers/checks_controller_test.rb b/test/controllers/checks_controller_test.rb index 92e22d9..a430258 100644 --- a/test/controllers/checks_controller_test.rb +++ b/test/controllers/checks_controller_test.rb @@ -117,7 +117,7 @@ class ChecksControllerTest < ActionDispatch::IntegrationTest end test "checks in error are filtered" do - c1 = create(:check, :last_runs_failed, user: @user) + c1 = create(:check, :last_runs_failed, created_at: 1.week.ago, user: @user) create(:check, user: @user) get checks_path(recurrent_failures: true) diff --git a/test/models/check_test.rb b/test/models/check_test.rb index 1dde49b..9606150 100644 --- a/test/models/check_test.rb +++ b/test/models/check_test.rb @@ -49,4 +49,56 @@ class CheckTest < ActiveSupport::TestCase assert notification.pending? assert_nil notification.sent_at end + + test "in_error? for recently added" do + check = build(:check, created_at: 1.day.ago) + refute check.in_error? + + check = build(:check, created_at: 1.day.ago, last_run_at: 3.minutes.ago) + refute check.in_error? + + check = build(:check, created_at: 1.day.ago, last_success_at: 1.hour.ago) + refute check.in_error? + end + + test "in_error? for never success check, with at least 1 run" do + check = build(:check, created_at: 3.weeks.ago, last_run_at: 1.day.ago) + assert check.in_error? + + check = build(:check, created_at: 3.weeks.ago, last_run_at: 4.days.ago) + assert check.in_error? + end + + test "in_error? ignore check without run" do + check = build(:check, created_at: 3.weeks.ago) + refute check.in_error? + end + + test "in_error? for last success a few days ago" do + check = build(:check, created_at: 3.weeks.ago, + last_success_at: 10.days.ago, last_run_at: 1.day.ago) + assert check.in_error? + + check = build(:check, created_at: 3.weeks.ago, + last_success_at: 1.days.ago, last_run_at: 1.day.ago) + refute check.in_error? + end + + test "days_from_last_success without any success" do + check = build(:check) + assert_nil check.days_from_last_success + + check = build(:check, last_run_at: 1.day.ago) + assert_nil check.days_from_last_success + end + + test "days_from_last_success" do + check = build(:check, last_success_at: 10.days.ago - 1.hour) + assert_equal 10, check.days_from_last_success + end + + test "days_from_last_success with a time" do + check = build(:check, last_success_at: (10.1 * 24).hours.ago) + assert_equal 10, check.days_from_last_success + end end From e5db8f1b9672a08e95c6f9d154abd66ce7add406 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 5 Jul 2018 09:54:29 +0200 Subject: [PATCH 11/16] More dashboard UI improvements --- app/frontend/scss/components/checks.scss | 4 ---- app/helpers/checks_helper.rb | 21 ++++++++++++++------- app/views/checks/_table.html.erb | 21 +++++++++++---------- app/views/checks/index.html.erb | 2 +- config/locales/en.yml | 8 +++++--- config/locales/fr.yml | 8 +++++--- 6 files changed, 36 insertions(+), 28 deletions(-) diff --git a/app/frontend/scss/components/checks.scss b/app/frontend/scss/components/checks.scss index 6c24b24..26f3f5d 100644 --- a/app/frontend/scss/components/checks.scss +++ b/app/frontend/scss/components/checks.scss @@ -2,8 +2,4 @@ .action a { color: black; } - - .kind .octicon { - vertical-align: middle; - } } diff --git a/app/helpers/checks_helper.rb b/app/helpers/checks_helper.rb index 5976d8e..0eec22c 100644 --- a/app/helpers/checks_helper.rb +++ b/app/helpers/checks_helper.rb @@ -9,17 +9,24 @@ module ChecksHelper end def checks_sort_links(field) + %i[asc desc].map { |direction| + checks_sort_link(field, direction) + }.join + end + + def checks_sort_link(field, direction) + classes = "btn btn-light btn-sm mx-1 mx-1 px-1 py-0" current_sort_str = current_sort.to_a.join("_") - %i[asc desc].map { |direction| - sort = "#{field}_#{direction}" + sort = "#{field}_#{direction}" - icon = direction == :asc ? "chevron-up" : "chevron-down" - html = Octicons::Octicon.new(icon, class: "mx-1").to_svg.html_safe + 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 + filter_params = current_criterias.merge(sort: sort) + link_to_unless sort == current_sort_str, html, checks_path(filter_params), class: classes do + content_tag(:span, html, class: classes + " active") + end end def current_criterias diff --git a/app/views/checks/_table.html.erb b/app/views/checks/_table.html.erb index e164399..776d1de 100644 --- a/app/views/checks/_table.html.erb +++ b/app/views/checks/_table.html.erb @@ -4,18 +4,19 @@
    - <%= t(".domain") %> - + <%= t(".th.domain") %> + <%== checks_sort_links(:domain) %> - <%= t(".expiry_date") %> - + <%= t(".th.expiry_date") %> + <%= t(".th.expiry_date_short") %> + <%== checks_sort_links(:domain_expires_at) %> <%= t(".edit") %><%= t(".th.edit") %>
    <%= t(".kind_labels.#{check.kind}") %> + <%- if check.in_error? %> <%== content_tag( :span, Octicons::Octicon.new("alert", class: "ml-1").to_svg.html_safe, - class: "text-danger", + class: "in-error text-danger", data: { toggle: "tooltip", - placement: "right", + placement: "bottom", title: check_last_success_title(check) }) %> <% end %> - <%= check.domain %> @@ -44,7 +45,7 @@ <%= content_tag :span, format_date(check.domain_expires_at, format: :short), class: "d-inline d-md-none" %> <% end %> + <%= link_to edit_check_path(check) do %> <%== Octicons::Octicon.new("pencil").to_svg %> <% end %> diff --git a/app/views/checks/index.html.erb b/app/views/checks/index.html.erb index 642c41d..ae8d626 100644 --- a/app/views/checks/index.html.erb +++ b/app/views/checks/index.html.erb @@ -1,6 +1,6 @@
    -
    +
    <% if @checks.empty? && current_scopes.blank? %>
    <%= t(".no_check_yet_html", new_domain_path: new_check_path(kind: :domain), new_ssl_path: new_check_path(kind: :ssl)) %> diff --git a/config/locales/en.yml b/config/locales/en.yml index bd4ab70..b146f5f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -103,9 +103,11 @@ en: expiration date is coming. The time is set in number of days. table: - domain: Name - expiry_date: Expiration - edit: Edit + th: + domain: Name + expiry_date: "Expiration date" + expiry_date_short: "Exp." + edit: Edit kind_labels: domain: Domain ssl: SSL diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 5c76b55..799126a 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -138,9 +138,11 @@ fr: que la date d'expiration approche. Le délai est indiqué ennombre de jours. table: - domain: Nom - expiry_date: "Expiration" - edit: Modifier + th: + domain: Nom + expiry_date: "Date d'expiration" + expiry_date_short: "Exp." + edit: Modifier kind_labels: domain: Domaine ssl: SSL From 900bdb8b1efa3c9dc0bfe23bd8ed2f08110785ac Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 5 Jul 2018 10:09:05 +0200 Subject: [PATCH 12/16] ChecksHelper frozen_string_literal: true --- app/helpers/checks_helper.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/helpers/checks_helper.rb b/app/helpers/checks_helper.rb index 0eec22c..af4ae58 100644 --- a/app/helpers/checks_helper.rb +++ b/app/helpers/checks_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ChecksHelper def check_row_class(check) expiry_date = check.domain_expires_at @@ -50,7 +52,7 @@ module ChecksHelper end def check_button_scope_class(scope = nil) - "btn btn-sm " << if scope && scoped_with?(scope) + "btn btn-sm " + if scope && scoped_with?(scope) "btn-info active" else "btn-outline-info" From 837f48ff8196f79e7b52186be8d4978c8c60001d Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 5 Jul 2018 10:24:26 +0200 Subject: [PATCH 13/16] Reduce checks helper method complexity --- app/controllers/checks_controller.rb | 4 ++-- app/helpers/checks_helper.rb | 28 +++++++++++++++++++--------- app/models/check.rb | 2 +- app/views/checks/_table.html.erb | 12 +----------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/app/controllers/checks_controller.rb b/app/controllers/checks_controller.rb index 7610ab3..d252411 100644 --- a/app/controllers/checks_controller.rb +++ b/app/controllers/checks_controller.rb @@ -9,7 +9,7 @@ class ChecksController < ApplicationController has_scope :recurrent_failures, type: :boolean def index - @checks = apply_scopes(policy_scope(Check)).order(current_sort).page(params[:page]) + @checks = apply_scopes(policy_scope(Check)).order(Hash[*current_sort]).page(params[:page]) end def new @@ -99,6 +99,6 @@ class ChecksController < ApplicationController return unless valid_fields.include?(field) return unless valid_directions.include?(direction) - { field => direction } + [field, direction] end end diff --git a/app/helpers/checks_helper.rb b/app/helpers/checks_helper.rb index af4ae58..81dfd3d 100644 --- a/app/helpers/checks_helper.rb +++ b/app/helpers/checks_helper.rb @@ -18,19 +18,29 @@ module ChecksHelper def checks_sort_link(field, direction) classes = "btn btn-light btn-sm mx-1 mx-1 px-1 py-0" - current_sort_str = current_sort.to_a.join("_") - - sort = "#{field}_#{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), class: classes do + sort_path = checks_path(current_criterias.merge(sort: sort.join("_"))) + link_to_unless sort == current_sort, html, sort_path, class: classes do content_tag(:span, html, class: classes + " active") end end + def check_in_error(check) + content_tag( + :span, + Octicons::Octicon.new("alert", class: "ml-1").to_svg.html_safe, + class: "in-error text-danger", + data: { + toggle: "tooltip", + placement: "bottom", + title: check_last_success_title(check) + }) + end + def current_criterias current_scopes.merge(sort: params[:sort]) end @@ -53,10 +63,10 @@ module ChecksHelper def check_button_scope_class(scope = nil) "btn btn-sm " + if scope && scoped_with?(scope) - "btn-info active" - else - "btn-outline-info" - end + "btn-info active" + else + "btn-outline-info" + end end def check_last_success_title(check) diff --git a/app/models/check.rb b/app/models/check.rb index 958f5ca..de2d32d 100644 --- a/app/models/check.rb +++ b/app/models/check.rb @@ -69,7 +69,7 @@ class Check < ApplicationRecord } def self.default_sort - { domain_expires_at: :asc } + [:domain_expires_at, :asc] end def in_error? diff --git a/app/views/checks/_table.html.erb b/app/views/checks/_table.html.erb index 776d1de..a9d8cfc 100644 --- a/app/views/checks/_table.html.erb +++ b/app/views/checks/_table.html.erb @@ -26,17 +26,7 @@ <%= t(".kind_labels.#{check.kind}") %>
    - <%- if check.in_error? %> - <%== content_tag( - :span, - Octicons::Octicon.new("alert", class: "ml-1").to_svg.html_safe, - class: "in-error text-danger", - data: { - toggle: "tooltip", - placement: "bottom", - title: check_last_success_title(check) - }) %> - <% end %> + <%= check_in_error(check) if check.in_error? %> <%= check.domain %> From fac8d88d3857d30bea6a6cfe19090a74222a4d1c Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 5 Jul 2018 12:26:00 +0200 Subject: [PATCH 14/16] Capybara: fixed screenshot in system cases --- test/application_system_test_case.rb | 2 +- test/test_helper.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb index 89fe8b5..e62a52a 100644 --- a/test/application_system_test_case.rb +++ b/test/application_system_test_case.rb @@ -4,7 +4,7 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :headless_chrome def teardown - Capybara.reset_sessions! Warden.test_reset! + super end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 91ae325..afc4077 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -42,6 +42,7 @@ Capybara.register_driver :headless_chrome do |app| end Capybara.save_path = Rails.root.join("tmp/capybara") Capybara.javascript_driver = :headless_chrome +Capybara.default_driver = :headless_chrome # Disable Open4 real system calls require "open4" From d7c0647f4023255e2c0b1aea24f4e1b8d25a003c Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 5 Jul 2018 12:26:31 +0200 Subject: [PATCH 15/16] Dashboard: minor english improvements --- config/locales/en.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index b146f5f..40adaf0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -15,7 +15,7 @@ en: date: formats: short: "%-d, %b %Y" - long: "%A, %B %d, %Y" + long: "%A, %B %-d, %Y" devise: registrations: @@ -89,7 +89,7 @@ en: filters: kind_domain: Domain kind_ssl: SSL - with_error: Error + with_error: With error form: generic: From cbb0b98898bfae8ba447a5cb2cef54a46c994677 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Thu, 5 Jul 2018 12:26:57 +0200 Subject: [PATCH 16/16] System tests for dashboard --- app/frontend/scss/components/checks.scss | 2 +- app/helpers/checks_helper.rb | 5 +- app/views/checks/_filters.html.erb | 2 +- app/views/checks/_table.html.erb | 2 +- test/system/.rubocop.yml | 4 + test/system/checks_test.rb | 191 +++++++++++++++++++++-- 6 files changed, 191 insertions(+), 15 deletions(-) create mode 100644 test/system/.rubocop.yml diff --git a/app/frontend/scss/components/checks.scss b/app/frontend/scss/components/checks.scss index 26f3f5d..f0e20e6 100644 --- a/app/frontend/scss/components/checks.scss +++ b/app/frontend/scss/components/checks.scss @@ -1,4 +1,4 @@ -.table-checks { +.checks-table { .action a { color: black; } diff --git a/app/helpers/checks_helper.rb b/app/helpers/checks_helper.rb index 81dfd3d..f779644 100644 --- a/app/helpers/checks_helper.rb +++ b/app/helpers/checks_helper.rb @@ -37,8 +37,9 @@ module ChecksHelper data: { toggle: "tooltip", placement: "bottom", - title: check_last_success_title(check) - }) + title: check_last_success_title(check), + }, + ) end def current_criterias diff --git a/app/views/checks/_filters.html.erb b/app/views/checks/_filters.html.erb index 1c84746..80e6b17 100644 --- a/app/views/checks/_filters.html.erb +++ b/app/views/checks/_filters.html.erb @@ -1,4 +1,4 @@ -
    +
    diff --git a/app/views/checks/_table.html.erb b/app/views/checks/_table.html.erb index a9d8cfc..fcda912 100644 --- a/app/views/checks/_table.html.erb +++ b/app/views/checks/_table.html.erb @@ -1,5 +1,5 @@
    - +
    diff --git a/test/system/.rubocop.yml b/test/system/.rubocop.yml new file mode 100644 index 0000000..229dd53 --- /dev/null +++ b/test/system/.rubocop.yml @@ -0,0 +1,4 @@ +inherit_from: ../../.rubocop.yml + +Metrics/ClassLength: + Enabled: false diff --git a/test/system/checks_test.rb b/test/system/checks_test.rb index e7be944..994346c 100644 --- a/test/system/checks_test.rb +++ b/test/system/checks_test.rb @@ -4,8 +4,6 @@ class ChecksTest < ApplicationSystemTestCase setup do @user = create(:user) login_as(@user) - - @check = create(:check, :with_notifications, user: @user) end test "create a check and a notification without kind" do @@ -33,12 +31,13 @@ class ChecksTest < ApplicationSystemTestCase end test "remove a notification" do - visit edit_check_path(@check) - notification = @check.notifications.first + check = create(:check, :with_notifications, domain: "dom-with-notif.net", user: @user) + visit edit_check_path(check) + notification = check.notifications.first selector = "[data-notification-id=\"#{notification.id}\"]" - assert_difference "Notification.where(check_id: #{@check.id}).count", -1 do + assert_difference "Notification.where(check_id: #{check.id}).count", -1 do within selector do find(".btn-danger").click end @@ -48,7 +47,8 @@ class ChecksTest < ApplicationSystemTestCase end test "update a check" do - visit edit_check_path(@check) + check = create(:check, :with_notifications, domain: "dom-with-notif.net", user: @user) + visit edit_check_path(check) fill_in "check[comment]", with: "My comment" @@ -57,18 +57,19 @@ class ChecksTest < ApplicationSystemTestCase assert_equal checks_path, page.current_path assert page.has_css?(".alert-success") - @check.reload - assert_equal "My comment", @check.comment + check.reload + assert_equal "My comment", check.comment end test "add a notification" do - visit edit_check_path(@check) + check = create(:check, :with_notifications, domain: "dom-with-notif.net", user: @user) + visit edit_check_path(check) recipient = "recipient2@example.org" fill_in("check[notifications_attributes][2][recipient]", with: recipient) fill_in("check[notifications_attributes][2][interval]", with: 55) - assert_difference "Notification.where(check_id: #{@check.id}).count", +1 do + assert_difference "Notification.where(check_id: #{check.id}).count", +1 do click_button "Update Check" assert_equal checks_path, page.current_path @@ -83,6 +84,176 @@ class ChecksTest < ApplicationSystemTestCase assert notification.pending? end + test "list my checks" do + create(:check, :domain, domain: "dom.com", domain_expires_at: Time.new(2018, 7, 5, 12), user: @user) # rubocop:disable Metrics/LineLength + create(:check, :ssl, domain: "ssldom.com", user: @user) + create(:check, :ssl, domain: "ssldom2.com", user: @user) + + visit checks_path + + within ".checks-table" do + assert page.has_content?("SSL", count: 2) + assert page.has_content?("Domain", count: 1) + end + + within ".check-row:first-of-type" do + assert page.has_content?("Domain") + assert page.has_content?("dom.com") + assert page.has_content?("Thursday, July 5, 2018") + end + end + + test "list filterable by domain and ssl" do + create_list(:check, 2, :domain, domain: "mydom.fr", user: @user) + create_list(:check, 1, :ssl, domain: "ssl.com", user: @user) + + visit checks_path + + assert page.has_css?(".check-row", count: 3) + + within ".checks-filters" do + click_on "Domain" + assert find_link("Domain").matches_css? ".active" + assert find_link("SSL").not_matches_css? ".active" + end + + within ".checks-table" do + assert page.has_css?(".check-row", count: 2) + assert page.has_content?("Domain", count: 2) + end + + within ".checks-filters" do + click_on "SSL" + assert find_link("SSL").matches_css? ".active" + assert find_link("Domain").not_matches_css? ".active" + end + + within ".checks-table" do + assert page.has_css?(".check-row", count: 1) + assert page.has_content?("SSL", count: 1) + assert page.has_content?("ssl.com") + end + end + + test "list filterable by check in error" do + create(:check, user: @user) + create(:check, :last_runs_failed, created_at: 1.week.ago, user: @user) + + visit checks_path + + within ".checks-table" do + assert page.has_css?(".check-row", count: 2) + assert page.has_css?(".in-error", count: 1) + end + + within ".checks-filters" do + click_on(I18n.t("checks.filters.with_error")) + end + + within ".checks-table" do + assert page.has_css?(".check-row", count: 1) + assert page.has_css?(".in-error", count: 1) + end + end + + test "list filterable by name string" do + create(:check, user: @user) + create(:check, domain: "chexpire.org", user: @user) + create(:check, domain: "chexpire.net", user: @user) + + visit checks_path + + within ".checks-filters" do + fill_in("by_domain", with: "chex") + click_button + end + + within ".checks-table" do + assert page.has_css?(".check-row", count: 2) + assert page.has_content?("chexpire.", count: 2) + end + end + + test "list is paginated" do + create(:check, user: @user) + + visit checks_path + assert page.has_no_css?("ul.pagination") + + create_list(:check, 50, user: @user) + + visit checks_path + assert page.has_css?("ul.pagination") + end + + test "list is sortable by name" do + visit checks_path + + create(:check, domain: "a.org", user: @user) + create(:check, domain: "b.org", user: @user) + + visit checks_path + + within ".checks-table thead th:nth-of-type(2)" do + find(".sort-links:first-child").click + end + + within ".check-row:first-of-type" do + page.has_content? "a.org" + end + + within ".checks-table thead th:nth-of-type(2)" do + find(".sort-links:last-child").click + end + + within ".check-row:first-of-type" do + page.has_content? "b.org" + end + end + + test "list is sorted by expiration date by default" do + visit checks_path + + create(:check, domain_expires_at: Time.new(2018, 7, 6, 12), user: @user) + create(:check, domain_expires_at: Time.new(2018, 7, 5, 12), user: @user) + + visit checks_path + + within ".check-row:first-of-type" do + page.has_content? "Thursday, July 5, 2018" + end + end + + test "list is sortable by expiration date" do + visit checks_path + + create(:check, domain_expires_at: Time.new(2018, 7, 5, 12), user: @user) + create(:check, domain_expires_at: Time.new(2018, 7, 6, 12), user: @user) + + visit checks_path + + within ".check-row:first-of-type" do + page.has_content? "Thursday, July 5, 2018" + end + + # only a desc link because of default sort + within ".checks-table thead th:nth-of-type(3)" do + find(".sort-links a").click + end + + within ".check-row:first-of-type" do + page.has_content? "Friday, July 6, 2018" + end + + within ".checks-table thead th:nth-of-type(3)" do + find(".sort-links a").click + end + + within ".check-row:first-of-type" do + page.has_content? "Thursday, July 5, 2018" + end + end + private # rubocop:disable Metrics/AbcSize