Skip to content

Commit

Permalink
feat: Allow managing audits (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
goulvench authored Feb 26, 2025
2 parents e4e82a1 + 8bb1dbb commit f8ce407
Show file tree
Hide file tree
Showing 21 changed files with 440 additions and 37 deletions.
9 changes: 9 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
20 changes: 20 additions & 0 deletions app/components/dsfr/sidemenu_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<nav class="<%= css_classes %>" aria-labelledby="fr-sidemenu-title">
<div class="fr-sidemenu__inner">
<button
class="fr-sidemenu__btn"
hidden
aria-controls="fr-sidemenu-wrapper"
aria-expanded="false"
>
<%= button %>
</button>
<div class="fr-collapse" id="fr-sidemenu-wrapper">
<div class="fr-sidemenu__title" id="fr-sidemenu-title"><%= title %></div>
<ul class="fr-sidemenu__list">
<% items.each do |item| %>
<%= item %>
<% end %>
</ul>
</div>
</div>
</nav>
30 changes: 30 additions & 0 deletions app/components/dsfr/sidemenu_component.rb
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions app/components/dsfr/sidemenu_item_component.rb
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions app/controllers/audits_controller.rb
Original file line number Diff line number Diff line change
@@ -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
5 changes: 4 additions & 1 deletion app/controllers/sites_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,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
Expand Down
8 changes: 7 additions & 1 deletion app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ 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 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 == "/"
Expand Down
4 changes: 1 addition & 3 deletions app/jobs/run_check_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions app/jobs/update_audit_job.rb
Original file line number Diff line number Diff line change
@@ -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
5 changes: 0 additions & 5 deletions app/jobs/update_audit_status_job.rb

This file was deleted.

13 changes: 12 additions & 1 deletion app/models/audit.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -43,4 +49,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
6 changes: 5 additions & 1 deletion app/models/site.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down Expand Up @@ -34,4 +34,8 @@ def name = super.presence || url_without_scheme
alias to_title name
def audit = super || audits.last || audits.build
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
2 changes: 2 additions & 0 deletions app/views/sites/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
<%= l site.audit.checked_at, format: :compact %>
<br>
(<%= time_ago site.audit.checked_at %>)
<% else %>
<%= Audit.human("audit/status.pending") %>
<% end %>
</td>
</tr>
Expand Down
60 changes: 41 additions & 19 deletions app/views/sites/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,24 +1,46 @@
<p>
<strong><%= Site.human :url %> :</strong>
<%= link_to @site.url, @site.url, target: :_blank %>
</p>
<% if @site.audit.checked_at %>
<p>
<strong><%= Site.human :last_audit_at %> :</strong>
<%= l @site.audit.checked_at, format: :long %>
(<%= time_ago @site.audit.checked_at %>).
</p>
<% end %>
<% @site.audit.all_checks.each do |check| %>
<p>
<strong><%= check.human_type %> :</strong>
<%= badge check.to_badge %>
</p>
<% end %>
<div class="fr-grid-row">
<div class="fr-col-12 fr-col-md-8">
<p>
<strong><%= Site.human :url %> :</strong>
<%= link_to @site.url, @site.url, target: :_blank %>
</p>
<p>
<strong><%= Audit.human :created_at %> :</strong>
<%= l @audit.created_at, format: :long %>
(<%= time_ago @audit.created_at %>).
</p>
<% if @audit.checked_at %>
<p>
<strong><%= Audit.human :checked_at %> :</strong>
<%= l @audit.checked_at, format: :long %>
(<%= time_ago @audit.checked_at %>).
</p>
<% @audit.all_checks.each do |check| %>
<p>
<strong><%= check.human_type %> :</strong>
<%= badge check.to_badge %>
</p>
<% end %>
<% else %>
<p>
<strong><%= Audit.human(:status) %> :</strong>
<%= Audit.human("audit/status.pending") %>
</p>
<% end %>
</div>
<div class="fr-col-12 fr-col-md-4">
<%= 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 %>
</div>
</div>

<br>

<div class="fr-btn-group fr-mb-2w">
<%= 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" %>
<%= 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" %>
</div>
39 changes: 39 additions & 0 deletions config/brakeman.ignore
Original file line number Diff line number Diff line change
@@ -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"
}
Loading

0 comments on commit f8ce407

Please sign in to comment.