From 0048c3310157170287d34fd56ed96c9c4bafbc8e Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Wed, 26 Feb 2025 15:29:53 +0100 Subject: [PATCH 01/12] Remove parenthesis in application dsfr_table helper for readability --- app/helpers/application_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 5be70be..fbc9e8d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -24,7 +24,7 @@ def head_title end def dsfr_table(caption:, size: :md, scroll: true, border: false, **html_attributes, &block) - render(Dsfr::TableComponent.new(caption:, size:, scroll:, border:, html_attributes:), &block) + render Dsfr::TableComponent.new(caption:, size:, scroll:, border:, html_attributes:), &block end def root? = request.path == "/" From 8cc6854a8e1743ae3c944dc4ac5e396cc44bb43d Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Wed, 26 Feb 2025 15:17:49 +0100 Subject: [PATCH 02/12] Add a Dsfr Sidemenu component --- .../dsfr/sidemenu_component.html.erb | 20 ++++ app/components/dsfr/sidemenu_component.rb | 30 ++++++ .../dsfr/sidemenu_item_component.rb | 24 +++++ app/helpers/application_helper.rb | 6 ++ .../dsfr/sidemenu_component_spec.rb | 91 +++++++++++++++++++ .../dsfr/sidemenu_item_component_spec.rb | 81 +++++++++++++++++ 6 files changed, 252 insertions(+) create mode 100644 app/components/dsfr/sidemenu_component.html.erb create mode 100644 app/components/dsfr/sidemenu_component.rb create mode 100644 app/components/dsfr/sidemenu_item_component.rb create mode 100644 spec/components/dsfr/sidemenu_component_spec.rb create mode 100644 spec/components/dsfr/sidemenu_item_component_spec.rb diff --git a/app/components/dsfr/sidemenu_component.html.erb b/app/components/dsfr/sidemenu_component.html.erb new file mode 100644 index 0000000..0c030e2 --- /dev/null +++ b/app/components/dsfr/sidemenu_component.html.erb @@ -0,0 +1,20 @@ + diff --git a/app/components/dsfr/sidemenu_component.rb b/app/components/dsfr/sidemenu_component.rb new file mode 100644 index 0000000..00fd526 --- /dev/null +++ b/app/components/dsfr/sidemenu_component.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Dsfr + class SidemenuComponent < ApplicationComponent + DEFAULT_BUTTON_TEXT = "Dans cette rubrique".freeze + + renders_many :items, "Dsfr::SidemenuItemComponent" + + attr_reader :title, :button, :sticky, :full_height, :right + + def initialize(title:, button: DEFAULT_BUTTON_TEXT, sticky: false, full_height: false, right: false) + @title = title + @button = button + @full_height = full_height + @sticky = full_height || sticky + @right = right + end + + def css_classes + token_list( + "fr-sidemenu", + "fr-sidemenu--sticky" => sticky, + "fr-sidemenu--sticky-full-height" => full_height, + "fr-sidemenu--right" => right + ) + end + + def render? = items.any? + end +end diff --git a/app/components/dsfr/sidemenu_item_component.rb b/app/components/dsfr/sidemenu_item_component.rb new file mode 100644 index 0000000..3bb5047 --- /dev/null +++ b/app/components/dsfr/sidemenu_item_component.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Dsfr + # This class is used by the Sidemenu component via renders_many + class SidemenuItemComponent < ApplicationComponent + attr_reader :href, :text, :active + + def initialize(href:, text:, active: nil) + @href = href + @text = text + @active = active + end + + def active + @active.nil? ? helpers.current_page?(href) : @active + end + + def call + content_tag :li, class: token_list("fr-sidemenu__item", "fr-sidemenu__item--active" => active) do + content_tag :a, href:, class: "fr-sidemenu__link", "aria-current": active ? :page : nil do text end + end + end + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index fbc9e8d..dfef2cf 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -27,6 +27,12 @@ def dsfr_table(caption:, size: :md, scroll: true, border: false, **html_attribut render Dsfr::TableComponent.new(caption:, size:, scroll:, border:, html_attributes:), &block end + def dsfr_sidemenu(title:, button: nil, sticky: false, full_height: false, right: false, &block) + component = Dsfr::SidemenuComponent.new(title:, button:, sticky:, full_height:, right:) + yield(component) if block_given? + render component + end + def root? = request.path == "/" def time_ago(datetime) diff --git a/spec/components/dsfr/sidemenu_component_spec.rb b/spec/components/dsfr/sidemenu_component_spec.rb new file mode 100644 index 0000000..e8c84bc --- /dev/null +++ b/spec/components/dsfr/sidemenu_component_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Dsfr::SidemenuComponent, type: :component do + let(:title) { "Title" } + let(:component) { described_class.new(title:) } + let(:rendered_component) { render_inline(component) } + + describe "rendering" do + context "with no items" do + it "renders nothing" do + expect(rendered_component.to_s).to be_empty + end + end + + context "with items" do + before do + component.with_item(href: "/page1", text: "Page 1") + component.with_item(href: "/page2", text: "Page 2") + end + + it "renders the component" do + expect(rendered_component.css(".fr-sidemenu")).to be_present + end + + it "renders the title" do + expect(rendered_component.css("#fr-sidemenu-title").text).to eq(title) + end + + it "renders the default button text" do + expect(rendered_component.css(".fr-sidemenu__btn").text.strip).to eq(Dsfr::SidemenuComponent::DEFAULT_BUTTON_TEXT) + end + + it "renders the items" do + expect(rendered_component.css(".fr-sidemenu__item").count).to eq(2) + expect(rendered_component.css(".fr-sidemenu__link").first.text).to eq("Page 1") + expect(rendered_component.css(".fr-sidemenu__link").last.text).to eq("Page 2") + end + end + end + + describe "CSS classes" do + before do + component.with_item(href: "/page", text: "Page") + end + + context "with default options" do + it "renders with the base class" do + expect(rendered_component.css(".fr-sidemenu")).to be_present + expect(rendered_component.css(".fr-sidemenu--sticky")).not_to be_present + expect(rendered_component.css(".fr-sidemenu--sticky-full-height")).not_to be_present + expect(rendered_component.css(".fr-sidemenu--right")).not_to be_present + end + end + + context "with sticky option" do + let(:component) { described_class.new(title: title, sticky: true) } + + it "adds the sticky class" do + expect(rendered_component.css(".fr-sidemenu--sticky")).to be_present + end + end + + context "with full_height option" do + let(:component) { described_class.new(title: title, full_height: true) } + + it "adds both sticky and full-height classes" do + expect(rendered_component.css(".fr-sidemenu--sticky")).to be_present + expect(rendered_component.css(".fr-sidemenu--sticky-full-height")).to be_present + end + end + + context "with right option" do + let(:component) { described_class.new(title: title, right: true) } + + it "adds the right class" do + expect(rendered_component.css(".fr-sidemenu--right")).to be_present + end + end + + context "with custom button text" do + let(:button_text) { "Custom Button" } + let(:component) { described_class.new(title: title, button: button_text) } + + it "renders the custom button text" do + expect(rendered_component.css(".fr-sidemenu__btn").text.strip).to eq(button_text) + end + end + end +end diff --git a/spec/components/dsfr/sidemenu_item_component_spec.rb b/spec/components/dsfr/sidemenu_item_component_spec.rb new file mode 100644 index 0000000..d34e4ca --- /dev/null +++ b/spec/components/dsfr/sidemenu_item_component_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Dsfr::SidemenuItemComponent, type: :component do + let(:href) { "/test-page" } + let(:text) { "Test Page" } + let(:active) { nil } + let(:component) { described_class.new(href:, text:, active:) } + let(:rendered_component) { render_inline(component) } + + describe "rendering" do + it "renders a list item with the proper classes" do + expect(rendered_component.css("li.fr-sidemenu__item")).to be_present + end + + it "renders a link with the proper attributes" do + link = rendered_component.css("a.fr-sidemenu__link").first + expect(link).to be_present + expect(link["href"]).to eq(href) + expect(link.text).to eq(text) + end + end + + describe "active state" do + context "when active is explicitly set to true" do + let(:active) { true } + + it "adds the active class to the list item" do + expect(rendered_component.css("li.fr-sidemenu__item--active")).to be_present + end + + it "sets aria-current attribute on the link" do + expect(rendered_component.css("a[aria-current='page']")).to be_present + end + end + + context "when active is explicitly set to false" do + let(:active) { false } + + it "does not add the active class to the list item" do + expect(rendered_component.css("li.fr-sidemenu__item--active")).not_to be_present + end + + it "does not set aria-current attribute on the link" do + expect(rendered_component.css("a[aria-current]")).not_to be_present + end + end + + context "when active is not set" do + before do + helpers = instance_double(ActionView::Helpers::UrlHelper, current_page?: current_page) + allow(component).to receive(:helpers).and_return(helpers) + end + + context "and the current page matches the href" do + let(:current_page) { true } + + it "adds the active class to the list item" do + expect(rendered_component.css("li.fr-sidemenu__item--active")).to be_present + end + + it "sets aria-current attribute on the link" do + expect(rendered_component.css("a[aria-current='page']")).to be_present + end + end + + context "and the current page does not match the href" do + let(:current_page) { false } + + it "does not add the active class to the list item" do + expect(rendered_component.css("li.fr-sidemenu__item--active")).not_to be_present + end + + it "does not set aria-current attribute on the link" do + expect(rendered_component.css("a[aria-current]")).not_to be_present + end + end + end + end +end From 289a12d9ec17fe7ed8504a9452fdf9e1973ea285 Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Wed, 26 Feb 2025 11:11:41 +0100 Subject: [PATCH 03/12] Add audits_count to Site model --- app/models/audit.rb | 2 +- db/migrate/20250226100820_add_audits_count_to_site.rb | 11 +++++++++++ db/schema.rb | 3 ++- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20250226100820_add_audits_count_to_site.rb diff --git a/app/models/audit.rb b/app/models/audit.rb index e919644..523f13d 100644 --- a/app/models/audit.rb +++ b/app/models/audit.rb @@ -1,5 +1,5 @@ class Audit < ApplicationRecord - belongs_to :site, touch: true + belongs_to :site, touch: true, counter_cache: true Check.types.each do |name, klass| has_one name, class_name: klass.name, dependent: :destroy end diff --git a/db/migrate/20250226100820_add_audits_count_to_site.rb b/db/migrate/20250226100820_add_audits_count_to_site.rb new file mode 100644 index 0000000..f220bb1 --- /dev/null +++ b/db/migrate/20250226100820_add_audits_count_to_site.rb @@ -0,0 +1,11 @@ +class AddAuditsCountToSite < ActiveRecord::Migration[8.0] + def change + add_column :sites, :audits_count, :integer, null: false, default: 0 + unless reverting? + # Set counter for all objects in one query, without instantiating all models + execute <<-SQL.squish + UPDATE sites SET audits_count = (SELECT count(1) FROM audits WHERE audits.site_id = sites.id) + SQL + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 2bd4b1c..f2701ff 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_02_17_085742) do +ActiveRecord::Schema[8.0].define(version: 2025_02_26_100820) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -60,6 +60,7 @@ t.string "slug", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "audits_count", default: 0, null: false t.index ["slug"], name: "index_sites_on_slug", unique: true end From cc661d57fee25009265cac8549a94145ef1f51e9 Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Wed, 26 Feb 2025 15:18:04 +0100 Subject: [PATCH 04/12] Relax Rubocop rules --- .rubocop.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index 9f0026b..3780795 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -57,6 +57,15 @@ RSpec/MultipleExpectations: RSpec/MultipleMemoizedHelpers: AllowSubject: true Max: 10 +RSpec/NestedGroups: + Max: 4 +RSpec/ContextWording: + Prefixes: + - when + - and + - with + - without + - for Style/StringLiterals: EnforcedStyle: "double_quotes" From 56bddc31f4cc3d6a3dc23046f8171c667da840e6 Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Wed, 26 Feb 2025 15:29:11 +0100 Subject: [PATCH 05/12] Use audit scope in Site.has_one_of_many for readability --- app/models/site.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/site.rb b/app/models/site.rb index a1cabd3..4e158a2 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -2,7 +2,7 @@ class Site < ApplicationRecord extend FriendlyId has_many :audits, dependent: :destroy - has_one_of_many :audit, -> { past.order("audits.created_at DESC") }, dependent: :destroy + has_one_of_many :audit, -> { past.sort_by_newest }, dependent: :destroy friendly_id :url_without_scheme, use: [:slugged, :history] From 877c88b44751f660a757e743c79c4e5528d49956 Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Wed, 26 Feb 2025 15:28:31 +0100 Subject: [PATCH 06/12] Allow displaying an arbitrary audit on site page --- app/controllers/sites_controller.rb | 4 ++- app/views/sites/index.html.erb | 2 ++ app/views/sites/show.html.erb | 48 +++++++++++++++++++---------- config/locales/fr.yml | 10 ++++++ 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/app/controllers/sites_controller.rb b/app/controllers/sites_controller.rb index 62e8197..9fe383a 100644 --- a/app/controllers/sites_controller.rb +++ b/app/controllers/sites_controller.rb @@ -8,7 +8,9 @@ def index end # GET /sites/1 - def show; end + def show + @audit = @site.audit + end # GET /sites/new def new; end diff --git a/app/views/sites/index.html.erb b/app/views/sites/index.html.erb index 048c3dc..5104b73 100644 --- a/app/views/sites/index.html.erb +++ b/app/views/sites/index.html.erb @@ -22,6 +22,8 @@ <%= l site.audit.checked_at, format: :compact %>
(<%= time_ago site.audit.checked_at %>) + <% else %> + <%= Audit.human("audit/status.pending") %> <% end %> diff --git a/app/views/sites/show.html.erb b/app/views/sites/show.html.erb index 7e9cffc..6eb4ed4 100644 --- a/app/views/sites/show.html.erb +++ b/app/views/sites/show.html.erb @@ -1,20 +1,34 @@ -

- <%= Site.human :url %> : - <%= link_to @site.url, @site.url, target: :_blank %> -

-<% if @site.audit.checked_at %> -

- <%= Site.human :last_audit_at %> : - <%= l @site.audit.checked_at, format: :long %> - (<%= time_ago @site.audit.checked_at %>). -

-<% end %> -<% @site.audit.all_checks.each do |check| %> -

- <%= check.human_type %> : - <%= badge check.to_badge %> -

-<% end %> +
+
+

+ <%= Site.human :url %> : + <%= link_to @site.url, @site.url, target: :_blank %> +

+

+ <%= Audit.human :created_at %> : + <%= l @audit.created_at, format: :long %> + (<%= time_ago @audit.created_at %>). +

+ <% if @audit.checked_at %> +

+ <%= Audit.human :checked_at %> : + <%= l @audit.checked_at, format: :long %> + (<%= time_ago @audit.checked_at %>). +

+ <% @audit.all_checks.each do |check| %> +

+ <%= check.human_type %> : + <%= badge check.to_badge %> +

+ <% end %> + <% else %> +

+ <%= Audit.human(:status) %> : + <%= Audit.human("audit/status.pending") %> +

+ <% end %> +
+

diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 0f2be50..d6026f4 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -21,6 +21,16 @@ fr: activerecord: attributes: + audit: + created_at: Enregistré le + checked_at: Executé le + status: État + audit/status: + pending: Planifié + running: En cours + passed: Effectué + retryable: À retenter + failed: Échoué check: type: Type de contrôle status: État From 9bc3e7216cc91a625b5e8b7640f9e780fc94897d Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Wed, 26 Feb 2025 15:41:26 +0100 Subject: [PATCH 07/12] Allow viewing previous audits --- app/controllers/audits_controller.rb | 26 ++++++++++++++++++++++++++ app/views/sites/show.html.erb | 9 ++++++++- config/locales/fr.yml | 15 +++++++++++---- config/routes.rb | 4 +++- 4 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 app/controllers/audits_controller.rb diff --git a/app/controllers/audits_controller.rb b/app/controllers/audits_controller.rb new file mode 100644 index 0000000..2759e75 --- /dev/null +++ b/app/controllers/audits_controller.rb @@ -0,0 +1,26 @@ +class AuditsController < ApplicationController + before_action :set_site + + # POST /sites/1/audits + def create + @audit = @site.audit! + if @audit.persisted? + redirect_to @site, notice: t(".notice") + else + render "sites/show", status: :unprocessable_entity + end + end + + # GET /sites/1/audits/1 + def show + @audit = @site.audits.find(params[:id]) + @title = @site.to_title + render "sites/show" + end + + private + + def set_site + @site = Site.friendly.find(params[:site_id]) + end +end diff --git a/app/views/sites/show.html.erb b/app/views/sites/show.html.erb index 6eb4ed4..bc73788 100644 --- a/app/views/sites/show.html.erb +++ b/app/views/sites/show.html.erb @@ -28,11 +28,18 @@

<% end %> +
+ <%= dsfr_sidemenu(button: Site.human(:audits), title: Site.human(:audit_history, total: @site.audits_count), right: true) do |sidemenu| %> + <% @site.audits.past.sort_by_newest.each do |audit| %> + <% sidemenu.with_item text: l(audit.created_at, format: :long).upcase_first, href: url_for([@site, audit]), active: @audit == audit %> + <% end %> + <% end %> +

<%= button_to "Supprimer ce site", @site, method: :delete, class: "fr-btn fr-btn--tertiary", form: { data: { turbo_confirm: t("shared.confirm") } } %> - <%= link_to "Revenir à la liste", { action: :index }, class: "fr-btn fr-btn--tertiary-no-outline" %> + <%= link_to "Revenir à la liste", { controller: :sites, action: :index }, class: "fr-btn fr-btn--tertiary-no-outline" %>
diff --git a/config/locales/fr.yml b/config/locales/fr.yml index d6026f4..ae374ac 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -25,22 +25,23 @@ fr: created_at: Enregistré le checked_at: Executé le status: État + count: + zero: Aucun audit + one: Un audit + other: "%{count} audits" audit/status: pending: Planifié running: En cours passed: Effectué retryable: À retenter failed: Échoué + check: type: Type de contrôle status: État run_at: Contrôle prévu le checked_at: Contrôle effectué le attempts: Tentatives - count: - zero: Aucun contrôle - one: Un contrôle - other: "%{count} contrôles" check/status: pending: Planifié running: En cours @@ -70,6 +71,9 @@ fr: created_at: Date de création updated_at: Date de mise à jour url: Adresse du site + audits: Tous les audits + audit_history: Historique des audits (%{total}) + audits_count: Audits last_audit_at: Dernier audit detail: Détails view: Voir la fiche @@ -112,3 +116,6 @@ fr: notice: Site modifié destroy: notice: Site supprimé + audits: + create: + notice: Audit créé diff --git a/config/routes.rb b/config/routes.rb index ccfe971..95ad781 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,8 @@ # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html Rails.application.routes.draw do - resources :sites + resources :sites do + resources :audits, only: [:create, :show] + end # Static pages scope controller: :pages do From f4afe56bd10d831df2ab680dfc6bfa775f8733a8 Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Wed, 26 Feb 2025 15:44:24 +0100 Subject: [PATCH 08/12] Use I18n for site actions --- app/views/sites/show.html.erb | 4 ++-- config/locales/fr.yml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/sites/show.html.erb b/app/views/sites/show.html.erb index bc73788..7db5138 100644 --- a/app/views/sites/show.html.erb +++ b/app/views/sites/show.html.erb @@ -40,6 +40,6 @@
- <%= button_to "Supprimer ce site", @site, method: :delete, class: "fr-btn fr-btn--tertiary", form: { data: { turbo_confirm: t("shared.confirm") } } %> - <%= link_to "Revenir à la liste", { controller: :sites, action: :index }, class: "fr-btn fr-btn--tertiary-no-outline" %> + <%= button_to Site.human(:delete), @site, method: :delete, class: "fr-btn fr-btn--tertiary", form: { data: { turbo_confirm: t("shared.confirm") } } %> + <%= link_to t("shared.back_to_list"), { controller: :sites, action: :index }, class: "fr-btn fr-btn--tertiary-no-outline" %>
diff --git a/config/locales/fr.yml b/config/locales/fr.yml index ae374ac..ccd016b 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -9,6 +9,7 @@ fr: actions: "Actions" confirm: "Êtes-vous sûr ? Cette action ne peut pas être annulée." time_ago: "il y a %{time}" + back_to_list: "Revenir à la liste" time: formats: @@ -78,6 +79,7 @@ fr: detail: Détails view: Voir la fiche view_name: Voir la fiche de %{name} + delete: Supprimer ce site count: zero: Aucun site one: Un site From 5baa463dc5dee40a9cb9c6f05e5d59d4b2163f28 Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Wed, 26 Feb 2025 15:47:40 +0100 Subject: [PATCH 09/12] Allow creating new audits --- app/models/site.rb | 1 + app/views/sites/show.html.erb | 1 + config/locales/fr.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/app/models/site.rb b/app/models/site.rb index 4e158a2..df04e4d 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -33,5 +33,6 @@ def url=(new_url) def name = super.presence || url_without_scheme alias to_title name def audit = super || audits.last || audits.build + def audit! = audits.create(url:) def should_generate_new_friendly_id? = new_record? || (audit && slug != url_without_scheme.parameterize) end diff --git a/app/views/sites/show.html.erb b/app/views/sites/show.html.erb index 7db5138..321b5bf 100644 --- a/app/views/sites/show.html.erb +++ b/app/views/sites/show.html.erb @@ -40,6 +40,7 @@
+ <%= button_to Audit.human(:new), [@site, :audits], method: :post, class: "fr-btn" %> <%= button_to Site.human(:delete), @site, method: :delete, class: "fr-btn fr-btn--tertiary", form: { data: { turbo_confirm: t("shared.confirm") } } %> <%= link_to t("shared.back_to_list"), { controller: :sites, action: :index }, class: "fr-btn fr-btn--tertiary-no-outline" %>
diff --git a/config/locales/fr.yml b/config/locales/fr.yml index ccd016b..3b585a0 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -26,6 +26,7 @@ fr: created_at: Enregistré le checked_at: Executé le status: État + new: "Nouvel audit" count: zero: Aucun audit one: Un audit From 7bbcad0cfd57726592aaa66f16c0dd20ab3842f3 Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Wed, 26 Feb 2025 16:09:41 +0100 Subject: [PATCH 10/12] Refactor setting audit checked_at --- app/jobs/run_check_job.rb | 4 +--- app/jobs/update_audit_job.rb | 8 ++++++++ app/jobs/update_audit_status_job.rb | 5 ----- app/models/audit.rb | 5 +++++ 4 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 app/jobs/update_audit_job.rb delete mode 100644 app/jobs/update_audit_status_job.rb diff --git a/app/jobs/run_check_job.rb b/app/jobs/run_check_job.rb index 16289bf..b0b1ee0 100644 --- a/app/jobs/run_check_job.rb +++ b/app/jobs/run_check_job.rb @@ -4,8 +4,6 @@ def perform(check) check.run - check.audit.update(checked_at: Time.zone.now) - - UpdateAuditStatusJob.perform_later(check.audit) + UpdateAuditJob.perform_later(check.audit) end end diff --git a/app/jobs/update_audit_job.rb b/app/jobs/update_audit_job.rb new file mode 100644 index 0000000..7e7fca1 --- /dev/null +++ b/app/jobs/update_audit_job.rb @@ -0,0 +1,8 @@ +class UpdateAuditJob < ApplicationJob + def perform(audit) + Audit.transaction do + audit.derive_status_from_checks + audit.set_checked_at + end + end +end diff --git a/app/jobs/update_audit_status_job.rb b/app/jobs/update_audit_status_job.rb deleted file mode 100644 index 349dcb8..0000000 --- a/app/jobs/update_audit_status_job.rb +++ /dev/null @@ -1,5 +0,0 @@ -class UpdateAuditStatusJob < ApplicationJob - def perform(audit) - audit.derive_status_from_checks - end -end diff --git a/app/models/audit.rb b/app/models/audit.rb index 523f13d..08c8361 100644 --- a/app/models/audit.rb +++ b/app/models/audit.rb @@ -43,4 +43,9 @@ def derive_status_from_checks end update(status: new_status) end + + def set_checked_at + latest_checked_at = all_checks.collect(&:checked_at).sort.last + update(checked_at: latest_checked_at) + end end From c105ee14761e0cca6c7190cb6bba42b55ac610ce Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Wed, 26 Feb 2025 16:10:25 +0100 Subject: [PATCH 11/12] Run newly-created audit immediately --- app/controllers/sites_controller.rb | 1 + app/models/audit.rb | 6 ++++++ app/models/site.rb | 5 ++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/controllers/sites_controller.rb b/app/controllers/sites_controller.rb index 9fe383a..bde7e3e 100644 --- a/app/controllers/sites_controller.rb +++ b/app/controllers/sites_controller.rb @@ -22,6 +22,7 @@ def edit; end def create @site = Site.find_or_create_by_url(site_params) if @site.persisted? + @site.audit.run! if @site.audit.pending? redirect_to @site, notice: t(".notice") else render :new, status: :unprocessable_entity diff --git a/app/models/audit.rb b/app/models/audit.rb index 08c8361..4dba6c2 100644 --- a/app/models/audit.rb +++ b/app/models/audit.rb @@ -33,6 +33,12 @@ def create_checks Check.names.map { |name| send(name) || send(:"create_#{name}") } end + def run! + all_checks.each(&:run) + derive_status_from_checks + set_checked_at + end + def derive_status_from_checks new_status = if all_checks.any?(&:new_record?) :pending diff --git a/app/models/site.rb b/app/models/site.rb index df04e4d..ed6220f 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -33,6 +33,9 @@ def url=(new_url) def name = super.presence || url_without_scheme alias to_title name def audit = super || audits.last || audits.build - def audit! = audits.create(url:) def should_generate_new_friendly_id? = new_record? || (audit && slug != url_without_scheme.parameterize) + + def audit! + audits.create(url:).tap(&:run!).tap(&:persisted?) + end end From 8bb1dbb08aae562f231af8d0ea8e240bc4533ecc Mon Sep 17 00:00:00 2001 From: Goulven Champenois Date: Wed, 26 Feb 2025 17:12:34 +0100 Subject: [PATCH 12/12] Silence brakeman warning (URLs are parsed before being displayed) --- config/brakeman.ignore | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 config/brakeman.ignore diff --git a/config/brakeman.ignore b/config/brakeman.ignore new file mode 100644 index 0000000..bbb167d --- /dev/null +++ b/config/brakeman.ignore @@ -0,0 +1,39 @@ +{ + "ignored_warnings": [ + { + "warning_type": "Cross-Site Scripting", + "warning_code": 4, + "fingerprint": "cb8384ab051ae32f84795a297fae3519a66a98325b1b7aa214dd5657e4df9e47", + "check_name": "LinkToHref", + "message": "Potentially unsafe model attribute in `link_to` href", + "file": "app/views/sites/show.html.erb", + "line": 5, + "link": "https://brakemanscanner.org/docs/warning_types/link_to_href", + "code": "link_to(Site.friendly.find(params[:site_id]).url, Site.friendly.find(params[:site_id]).url, :target => :_blank)", + "render_path": [ + { + "type": "controller", + "class": "AuditsController", + "method": "create", + "line": 10, + "file": "app/controllers/audits_controller.rb", + "rendered": { + "name": "sites/show", + "file": "app/views/sites/show.html.erb" + } + } + ], + "location": { + "type": "template", + "template": "sites/show" + }, + "user_input": "Site.friendly.find(params[:site_id]).url", + "confidence": "Weak", + "cwe_id": [ + 79 + ], + "note": "" + } + ], + "brakeman_version": "7.0.0" +}