From 4dd445be575cc4af228eb148b98537028e575212 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 16 May 2025 19:02:33 +0200 Subject: [PATCH] wip: wcag structure --- Procfile.dev | 2 +- README.md | 4 + app/controllers/application_controller.rb | 7 +- app/controllers/checks_controller.rb | 2 +- app/controllers/concerns/backoffice_menu.rb | 1 + app/controllers/guidelines_controller.rb | 58 +++++++++++++ .../controllers/toast_controller.js | 8 +- app/models/check.rb | 22 ++++- app/models/guideline.rb | 18 +++++ app/models/report.rb | 4 + app/views/backoffice/show.html.erb | 8 +- app/views/checks/_check.html.slim | 6 +- app/views/checks/_form.html.slim | 4 +- app/views/elements/_form.html.slim | 3 +- app/views/exports/show.html.slim | 31 +------ app/views/guidelines/_form.html.erb | 7 ++ app/views/guidelines/_guideline.html.erb | 15 ++++ app/views/guidelines/_guideline.json.jbuilder | 2 + app/views/guidelines/edit.html.erb | 8 ++ app/views/guidelines/index.html.erb | 28 +++++++ app/views/guidelines/index.json.jbuilder | 1 + app/views/guidelines/new.html.erb | 7 ++ app/views/guidelines/show.html.erb | 9 +++ app/views/guidelines/show.json.jbuilder | 1 + app/views/home/show.html.slim | 6 ++ app/views/layouts/_toast.html.slim | 2 +- app/views/layouts/application.html.slim | 2 +- app/views/pages/_form.html.slim | 3 +- app/views/success_criteria/_form.html.slim | 1 + bin/dev | 2 +- bin/dev_entrypoint | 4 +- config/credentials.yml.enc | 2 +- config/initializers/deepl.rb | 6 ++ config/routes.rb | 1 + .../20250101163808_create_guidelines.rb | 12 +++ ...250101163835_add_guideline_id_to_checks.rb | 6 ++ ...move_unique_number_constraint_on_checks.rb | 5 ++ db/schema.rb | 20 +++-- db/seeds.rb | 3 +- docker-compose.yml | 62 -------------- lib/tasks/import.rake | 81 ++++++++++++++++--- test/controllers/checks_controller_test.rb | 6 +- .../controllers/guidelines_controller_test.rb | 49 +++++++++++ test/fixtures/checks.yml | 6 +- test/fixtures/guidelines.yml | 11 +++ test/models/guideline_test.rb | 7 ++ test/system/guidelines_test.rb | 45 +++++++++++ tmp/pids/.keep | 0 48 files changed, 461 insertions(+), 137 deletions(-) create mode 100644 app/controllers/guidelines_controller.rb create mode 100644 app/models/guideline.rb create mode 100644 app/views/guidelines/_form.html.erb create mode 100644 app/views/guidelines/_guideline.html.erb create mode 100644 app/views/guidelines/_guideline.json.jbuilder create mode 100644 app/views/guidelines/edit.html.erb create mode 100644 app/views/guidelines/index.html.erb create mode 100644 app/views/guidelines/index.json.jbuilder create mode 100644 app/views/guidelines/new.html.erb create mode 100644 app/views/guidelines/show.html.erb create mode 100644 app/views/guidelines/show.json.jbuilder create mode 100644 config/initializers/deepl.rb create mode 100644 db/migrate/20250101163808_create_guidelines.rb create mode 100644 db/migrate/20250101163835_add_guideline_id_to_checks.rb create mode 100644 db/migrate/20250101171836_remove_unique_number_constraint_on_checks.rb delete mode 100644 docker-compose.yml create mode 100644 test/controllers/guidelines_controller_test.rb create mode 100644 test/fixtures/guidelines.yml create mode 100644 test/models/guideline_test.rb create mode 100644 test/system/guidelines_test.rb delete mode 100644 tmp/pids/.keep diff --git a/Procfile.dev b/Procfile.dev index 5c7ab2e..8cec4c8 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -1,3 +1,3 @@ web: RUBY_DEBUG_OPEN=true bin/rails server -b 0 -js: yarn build --watch +js: yarn build --watch=forever css: yarn watch:css diff --git a/README.md b/README.md index 4f83e3b..ec40b05 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,7 @@ `rails new -n a11yist -d sqlite3 --skip-action-mailbox --css bootstrap --js esbuild .` + +## Installation + +To [install](readme) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7a1bd32..41d97ec 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -25,10 +25,6 @@ class ApplicationController < ActionController::Base label: Project.model_name.human(count: 2), icon: :'folder', path: :projects }, - # { - # label: Report.model_name.human(count: 2), - # icon: :'journal-text', - # path: :reports }, { label: I18n.t("backoffice"), icon: :gear, @@ -36,7 +32,8 @@ class ApplicationController < ActionController::Base active: %w[backoffice checklists checks links link_categories].include?(controller_name) }, { label: "Konto", - path: profile_path + path: profile_path, + icon: "person-circle" } ] else diff --git a/app/controllers/checks_controller.rb b/app/controllers/checks_controller.rb index 303415e..f1c1e3e 100644 --- a/app/controllers/checks_controller.rb +++ b/app/controllers/checks_controller.rb @@ -81,7 +81,7 @@ class ChecksController < ApplicationController # Only allow a list of trusted parameters through. def check_params - params.require(:check).permit(:principle_id, + params.require(:check).permit(:guideline_id, :number, :name_de, :name_en, diff --git a/app/controllers/concerns/backoffice_menu.rb b/app/controllers/concerns/backoffice_menu.rb index e2f72fb..9ab5278 100644 --- a/app/controllers/concerns/backoffice_menu.rb +++ b/app/controllers/concerns/backoffice_menu.rb @@ -5,6 +5,7 @@ module BackofficeMenu [ { label: "Einstellungen", icon: :sliders, path: :backoffice }, { label: Checklist.model_name.human(count: 2), icon: :'list-check', path: :checklists }, + { label: Guideline.model_name.human(count: 2), icon: :'rulers', path: :guidelines }, { label: Check.model_name.human(count: 2), icon: :check2, path: :checks }, { label: Link.model_name.human(count: 2), icon: :link, path: :links }, { label: LinkCategory.model_name.human(count: 2), icon: :folder, path: :link_categories } ] diff --git a/app/controllers/guidelines_controller.rb b/app/controllers/guidelines_controller.rb new file mode 100644 index 0000000..e69449d --- /dev/null +++ b/app/controllers/guidelines_controller.rb @@ -0,0 +1,58 @@ +class GuidelinesController < BackofficeController + before_action :set_guideline, only: %i[ show edit update destroy ] + + # GET /guidelines + def index + @guidelines = Guideline.all + end + + # GET /guidelines/1 + def show + end + + # GET /guidelines/new + def new + @guideline = Guideline.new + end + + # GET /guidelines/1/edit + def edit + end + + # POST /guidelines + def create + @guideline = Guideline.new(guideline_params) + + if @guideline.save + redirect_to @guideline, notice: "Guideline was successfully created." + else + render :new, status: :unprocessable_entity + end + end + + # PATCH/PUT /guidelines/1 + def update + if @guideline.update(guideline_params) + redirect_to @guideline, notice: "Guideline was successfully updated.", status: :see_other + else + render :edit, status: :unprocessable_entity + end + end + + # DELETE /guidelines/1 + def destroy + @guideline.destroy! + redirect_to guidelines_url, notice: "Guideline was successfully destroyed.", status: :see_other + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_guideline + @guideline = Guideline.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def guideline_params + params.require(:guideline).permit(:principle_id, :number, :name_de, :name_en, :description_de, :description_en) + end +end diff --git a/app/javascript/controllers/toast_controller.js b/app/javascript/controllers/toast_controller.js index 55d8ea1..e8b23e8 100644 --- a/app/javascript/controllers/toast_controller.js +++ b/app/javascript/controllers/toast_controller.js @@ -4,7 +4,11 @@ import * as bootstrap from "bootstrap" // Connects to data-controller="toast" export default class extends Controller { connect() { - const toastBootstrap = bootstrap.Toast.getOrCreateInstance(this.element) - toastBootstrap.show() + const shownKey = `toastsShown[${this.element.getAttribute("data-ts")}]` + if(!window.sessionStorage.getItem(shownKey)) { + window.sessionStorage.setItem(shownKey, Date.now()); + const toastBootstrap = bootstrap.Toast.getOrCreateInstance(this.element) + toastBootstrap.show() + } } } diff --git a/app/models/check.rb b/app/models/check.rb index a201aa8..a60d5e2 100644 --- a/app/models/check.rb +++ b/app/models/check.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Check < ApplicationRecord - belongs_to :principle + belongs_to :guideline has_and_belongs_to_many :links has_and_belongs_to_many :standards @@ -51,9 +51,9 @@ class Check < ApplicationRecord :standard_text, :powerpoint_text - validates :number, uniqueness: true, presence: true + validates :number, uniqueness: { scope: :guideline_id }, presence: true - before_validation { self.number = self.class.maximum(:id).to_i + 1 unless self.number.present? } + # before_validation { self.number = self.class.maximum(:id).to_i + 1 unless self.number.present? } scope(:search, lambda { |terms| # TODO: Search only fields for current locale. @@ -129,4 +129,20 @@ class Check < ApplicationRecord def external_number [ external_number_1, external_number_2, external_number_3 ].compact_blank.join(".") end + + def external_number + [ + guideline.principle.id, + guideline.number, + number + ].compact.join(".") + end + + def full_number + external_number + end + + def to_s + display_label + end end diff --git a/app/models/guideline.rb b/app/models/guideline.rb new file mode 100644 index 0000000..baeac74 --- /dev/null +++ b/app/models/guideline.rb @@ -0,0 +1,18 @@ +class Guideline < ApplicationRecord + belongs_to :principle + + has_many :checks + + has_rich_text :description_de + has_rich_text :description_en + + translates_attributes :name, :description + + def to_s + "#{full_number} #{t_name}" + end + + def full_number + [ principle.id, number ].join(".") + end +end diff --git a/app/models/report.rb b/app/models/report.rb index 68a36ab..df65fc4 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -21,4 +21,8 @@ class Report < ApplicationRecord success_criteria: export_success_criteria } end + + def test + 139 + end end diff --git a/app/views/backoffice/show.html.erb b/app/views/backoffice/show.html.erb index c16d394..8bcbd75 100644 --- a/app/views/backoffice/show.html.erb +++ b/app/views/backoffice/show.html.erb @@ -7,6 +7,12 @@ <%= link_to Checklist.model_name.human(count: Checklist.count), :checklists %>

+

+ +<%= Guideline.count %> +<%= link_to Guideline.model_name.human(count: Guideline.count), :guidelines %> +

+

<%= Check.count %> @@ -27,4 +33,4 @@

  • <%= link_to "ZIP Backup herunterladen", admin_backup_url(format: :zip), class: " ", data: { turbo_prefetch: false, frame: "_top", turbo: false } %>
  • - \ No newline at end of file + diff --git a/app/views/checks/_check.html.slim b/app/views/checks/_check.html.slim index 62996f0..7f99880 100644 --- a/app/views/checks/_check.html.slim +++ b/app/views/checks/_check.html.slim @@ -11,8 +11,8 @@ div id=dom_id(check) th = Check.human_attribute_name(:number) td = check.number tr - th = Principle.model_name.human - td = check.principle&.t_name + th = Guideline.model_name.human + td = check.guideline&.name_en tr th = Standard.model_name.human(count: check.standard_ids.size) td = check.standards.map(&:t_name).sort_by(&:downcase).join(", ") @@ -80,4 +80,4 @@ div id=dom_id(check) ul - check.links.select{ _1.link_category == category }.map { |link| link_to link.text, link.url, target: :_blank }.each do |link| li = link - \ No newline at end of file + diff --git a/app/views/checks/_form.html.slim b/app/views/checks/_form.html.slim index 53e4bb7..9f5bb2c 100644 --- a/app/views/checks/_form.html.slim +++ b/app/views/checks/_form.html.slim @@ -1,9 +1,9 @@ = bootstrap_form_with(model: check, remote: true, data: { controller: "unsaved-changes" }) do |form| h2 Details = multilang_form_field(form, :name) - = form.text_field :number, required: false .row - = form.collection_radio_buttons(:principle_id, Principle.all.sort_by(&:t_name), :id, :t_name) { |b| b.label(class: "col-md-2") { b.radio_button + b.text } } + = form.collection_radio_buttons(:guideline_id, Guideline.all.sort_by(&:full_number), :id, :to_s) { |b| b.label(class: "col-md-2") { b.radio_button + b.text } } + = form.text_field :number, required: false = form.collection_check_boxes :standard_ids, Standard.all.sort_by{ _1.t_name.downcase }, :id, :t_name, include_blank: true h2 Einschränkung/Zugänglichkeit diff --git a/app/views/elements/_form.html.slim b/app/views/elements/_form.html.slim index 4feb80d..b821401 100644 --- a/app/views/elements/_form.html.slim +++ b/app/views/elements/_form.html.slim @@ -6,4 +6,5 @@ - if element.persisted? = safe_display(element.screenshot) { tag.div(link_to(_1.filename.to_s, _1), class: "mb-3") } = form.submit class: "btn btn-primary" - = link_to("Abbrechen", element.persisted? ? element : element.report, class: "btn btn-outline-secondary") + - unless modal? + = link_to("Abbrechen", element.persisted? ? element : element.report, class: "btn btn-outline-secondary") diff --git a/app/views/exports/show.html.slim b/app/views/exports/show.html.slim index d7ca86d..1fe71d1 100644 --- a/app/views/exports/show.html.slim +++ b/app/views/exports/show.html.slim @@ -1,26 +1,3 @@ -/nav - = link_to(@report.name, "##{dom_id(@report)}") - ul - li = link_to("Inhaltsverzeichnis", "#toc") - li - = link_to('Testbericht') - ul - - @report.pages.select { |p| p.elements.any? { |e| e.success_criteria.any?(&:failed?) } }.each do |page| - li - = link_to("#{page.position} #{page.path}", "##{dom_id(page)}") - ul - - page.elements.select { |e| e.success_criteria.any?(&:failed?) }.each do |element| - li - = link_to("#{element.number} #{element.title}") - ul - - element.success_criteria.select(&:failed?).each do |sc| - li = link_to("#{sc.number} #{sc.title}", "##{dom_id(sc)}") - li - = link_to("Anhang") - ul - - @failed_success_criteria.group_by(&:check).each do |check, scs| - li = link_to(check.display_label) - h1.title id=dom_id(@report) = @report.name h2 1 Einschätzung @@ -42,18 +19,18 @@ h2 2 Protokoll - current_page_pos += 1 - current_element_pos = 0 h3 = "2.#{current_page_pos} #{page.path}" + p + strong URL + = page.url - page.elements.select { |e| e.success_criteria.any? { _1.failed? } }.each do |element| - current_element_pos += 1 - current_abs_element_pos += 1 - current_sc_pos = 0 h4 = "2.#{current_page_pos}.#{current_element_pos} #{element.title}" /h3 = "2.#{current_abs_element_pos} #{element.title}" - p - strong Pfad: - span =< page.path = safe_display(element.screenshot) { image_tag(_1.representation(resize_to_fit: [250, 250]))} = element.description - - element.success_criteria.select{ _1.failed? }.each do |sc| + - element.success_criteria.select(&:failed?).each do |sc| - current_sc_pos += 1 /h4 = "2.#{current_abs_element_pos}.#{current_sc_pos} #{sc.title}" diff --git a/app/views/guidelines/_form.html.erb b/app/views/guidelines/_form.html.erb new file mode 100644 index 0000000..d3c952a --- /dev/null +++ b/app/views/guidelines/_form.html.erb @@ -0,0 +1,7 @@ +<%= bootstrap_form_with(model: guideline) do |form| %> + <%= form.collection_select :principle_id, Principle.all.sort_by(&:id), :id, :t_name %> + <%= form.number_field :number %> + <%= form.text_field :name_de %> + <%= form.rich_text_area :description_de %> + <%= form.submit %> +<% end %> diff --git a/app/views/guidelines/_guideline.html.erb b/app/views/guidelines/_guideline.html.erb new file mode 100644 index 0000000..0560533 --- /dev/null +++ b/app/views/guidelines/_guideline.html.erb @@ -0,0 +1,15 @@ +
    +

    + Principle: + <%= guideline.principle.t_name %> +

    + + <%= guideline.description_de %> +

    Richtlinien

    + <% guideline.checks.each do |check| %> +
    +

    <%= link_to check %>

    + <%= check.t_criterion %> +
    + <% end %> +
    diff --git a/app/views/guidelines/_guideline.json.jbuilder b/app/views/guidelines/_guideline.json.jbuilder new file mode 100644 index 0000000..72c091d --- /dev/null +++ b/app/views/guidelines/_guideline.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! guideline, :id, :principle_id, :number, :name_de, :created_at, :updated_at +json.url guideline_url(guideline, format: :json) diff --git a/app/views/guidelines/edit.html.erb b/app/views/guidelines/edit.html.erb new file mode 100644 index 0000000..2411616 --- /dev/null +++ b/app/views/guidelines/edit.html.erb @@ -0,0 +1,8 @@ +

    <%= t("scaffold.pagetitle_edit", model: Guideline.model_name.human) %>

    + +<%= render "form", guideline: @guideline %> + +
    + <%= link_to t("scaffold.link_show", model: Guideline.model_name.human), @guideline %> + <%= link_to t("scaffold.link_index", model: Guideline.model_name.human(count: 2)), guidelines_path %> +
    diff --git a/app/views/guidelines/index.html.erb b/app/views/guidelines/index.html.erb new file mode 100644 index 0000000..fe34cbf --- /dev/null +++ b/app/views/guidelines/index.html.erb @@ -0,0 +1,28 @@ +

    <%= t("scaffold.pagetitle_index", model: Guideline.model_name.human(count: 2)) %>

    + + + + + + + + + + + + <% @guidelines.each do |guideline| %> + + + + + + + + + <% end %> + +
    <%= Guideline.human_attribute_name(:name_de) %><%= Guideline.human_attribute_name(:description_de) %>
    <%= link_to(guideline, url_for(guideline)) %><%= link_to(guideline.description_de, url_for(guideline)) %>
    + +
    + <%= link_to t("scaffold.link_new", model: Guideline.model_name.human), new_guideline_path %> +
    diff --git a/app/views/guidelines/index.json.jbuilder b/app/views/guidelines/index.json.jbuilder new file mode 100644 index 0000000..cd94a2f --- /dev/null +++ b/app/views/guidelines/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @guidelines, partial: "guidelines/guideline", as: :guideline diff --git a/app/views/guidelines/new.html.erb b/app/views/guidelines/new.html.erb new file mode 100644 index 0000000..7e57546 --- /dev/null +++ b/app/views/guidelines/new.html.erb @@ -0,0 +1,7 @@ +

    <%= t("scaffold.pagetitle_new", model: Guideline.model_name.human) %>

    + +<%= render "form", guideline: @guideline %> + +
    + <%= link_to t("scaffold.link_index", model: Guideline.model_name.human(count: 2)), guidelines_path %> +
    diff --git a/app/views/guidelines/show.html.erb b/app/views/guidelines/show.html.erb new file mode 100644 index 0000000..7334fa3 --- /dev/null +++ b/app/views/guidelines/show.html.erb @@ -0,0 +1,9 @@ +

    <%= @guideline %>

    + +<%= render @guideline %> + +
    + <%= link_to t("scaffold.link_edit", model: @guideline.model_name.human), edit_guideline_path(@guideline) %> + <%= link_to t("scaffold.link_index", model: @guideline.model_name.human(count: 2)), guidelines_path %> + <%= button_to t("scaffold.link_destroy", model: @guideline.model_name.human), @guideline, method: :delete, class: "btn btn-outline-danger" %> +
    diff --git a/app/views/guidelines/show.json.jbuilder b/app/views/guidelines/show.json.jbuilder new file mode 100644 index 0000000..a0b1efe --- /dev/null +++ b/app/views/guidelines/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "guidelines/guideline", guideline: @guideline diff --git a/app/views/home/show.html.slim b/app/views/home/show.html.slim index 1ab4d19..2d5443c 100644 --- a/app/views/home/show.html.slim +++ b/app/views/home/show.html.slim @@ -19,6 +19,12 @@ h1 Dashboard li = link_to(r.name, r) .col-md-6 + h2 Browser Erweiterungen + p = link_to "Language Tools KI Korrektur", "https://languagetool.org/services#browsers" + p = link_to "DeepL Firefox Extension", "https://www.deepl.com/en/firefox-extension" + p = link_to "DeepL Chrome Extension", "https://www.deepl.com/en/chrome-extension" + p = link_to "DeepL Edge Extension", "https://www.deepl.com/en/edge-extension" + h2 Hotkeys p Auf der Bericht-Ausfüllen Seite können folgende Shortcuts verwendet werden: dl diff --git a/app/views/layouts/_toast.html.slim b/app/views/layouts/_toast.html.slim index 5917715..600e969 100644 --- a/app/views/layouts/_toast.html.slim +++ b/app/views/layouts/_toast.html.slim @@ -1,4 +1,4 @@ -.toast class="#{alert ? "text-bg-danger" : "text-bg-info"}" role="alert" aria-live="assertive" aria-atomic="true" data={ controller: "toast" } +.toast class="#{alert ? "text-bg-danger" : "text-bg-info"}" role="alert" aria-live="assertive" aria-atomic="true" data={ controller: "toast", ts: Time.now.to_f } .toast-header /img src="..." class="rounded me-2" alt="..."> /strong.me-auto = heading diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim index ac756a6..a53d602 100644 --- a/app/views/layouts/application.html.slim +++ b/app/views/layouts/application.html.slim @@ -1,5 +1,5 @@ doctype html -html data-bs-theme="#{cookies[:"modeTheme"] || "light"}" data-controller="set-theme" +html lang=:de data-bs-theme="#{cookies[:"modeTheme"] || "light"}" data-controller="set-theme" head title a11ydive meta[name="viewport" content="width=device-width,initial-scale=1"] diff --git a/app/views/pages/_form.html.slim b/app/views/pages/_form.html.slim index 0d0beda..a909d1f 100644 --- a/app/views/pages/_form.html.slim +++ b/app/views/pages/_form.html.slim @@ -2,4 +2,5 @@ = form.text_field :path = form.text_field :url = form.submit - = link_to("Abbrechen", report_path(@page.report), class: "btn btn-outline-secondary") + - unless modal? + = link_to("Abbrechen", report_path(@page.report), class: "btn btn-outline-secondary") diff --git a/app/views/success_criteria/_form.html.slim b/app/views/success_criteria/_form.html.slim index c67c2b0..b459173 100644 --- a/app/views/success_criteria/_form.html.slim +++ b/app/views/success_criteria/_form.html.slim @@ -7,4 +7,5 @@ = form.rich_text_area :test_comment = form.submit class: "btn btn-primary" - unless modal? + p Not MODAL =< link_to "Abbrechen", success_criterion.persisted? ? success_criterion : success_criterion.element, class: "btn btn-outline-secondary" diff --git a/bin/dev b/bin/dev index edcb0e0..eda330c 100755 --- a/bin/dev +++ b/bin/dev @@ -8,4 +8,4 @@ fi # Default to port 3000 if not specified export PORT="${PORT:-3000}" -exec foreman start -f /app/Procfile.dev "$@" +exec foreman start -f Procfile.dev "$@" diff --git a/bin/dev_entrypoint b/bin/dev_entrypoint index 3062b49..1ca3bf7 100755 --- a/bin/dev_entrypoint +++ b/bin/dev_entrypoint @@ -1,7 +1,7 @@ #!/bin/bash -if [ -f /app/tmp/pids/server.pid ]; then - rm /app/tmp/pids/server.pid +if [ -f tmp/pids/server.pid ]; then + rm tmp/pids/server.pid fi exec "$@" diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc index 7741360..d5e9f74 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -vRtWWWCynlFuZljZsCQOV5D6xZlwp/LkuGZ2bPp1IuZiCRfJ2F8CXbubRavkqM0xxpHWkC7hiQ0lSv/dCPtaujG+aJ2Q0+KSw81kJ3xiYd/AiR3ZxO9prk5Zsf0LPLoONGBbYurJIb37hAWQ7h1r1qORLbtQGLb9pYlynObhXvkaYQR5E7Jr0Rcnon0d2uKJ13ukDAHUiIgwCE5/f+Y6UVllTLBCk0a+YDZvkv7xsYNpTTD9bgjpAU/pg6jv1xY0k/gZ8SFRsSQaJs8CYtbhMsyZBvImtguGfc7U7B4cFrgdzUPY473vBKBqm6O0StNagS5muP3/YLLN7xcrRDTBi9n3qllATlfJ9cxZ+JCWZJmyqINI4X+T/sc3lpzlo/grW0W+HwQ/4CZ3LkCN/OOqZlG1HL1u--NhnYNqgGpqSnh7Ut--KGndHl8kyj/uAjRG12R3zg== \ No newline at end of file +TjpgJe/DwNAzOraDbzXUo20hxCLkOn72DlXYkIouG0crCD3m4/LAiwDOlOWwsCO5Je6FzqKZniLUqSfVthChxewvZn+PY7XGTLECbB9gjSpRC6hERUxKIirLot+CH7lkMlM3f4o3NPf2I4vs8j6hooXcc8Vd8l1uMHOU1RHd+8EfPmTetqd0IETnEUdXigL50yjcbpxy8jHGdaeA8hHU3F+jwk7gRv11uVgEfn069qD9tc5AlAFWdnYJj/ZadAX2+bimHyne5Y12gmRAiqu95KXzUs0OlI+Vx9lpFyMQNacuKPPx0AeeBafjnMBozXOSU7MMCBUGzig07Kg+/tJSiekeLX0X2VEj3Ecqe9nL54fAGVSqJmwT19KvjeS4WfjvFDff9KAY4H+vKzfkBQzMFOTJjnMTNHa+dtNly0kUphwDtsFWDhF3PaZQIRCeI7RIXKhFMgrRFE6UL4AFFzhocQ1DAwaZElXFJaKjiCWWCY+acLCRz2AnriR08KMsEGN54nMtcGcWnUS2ewr/txLQDkipjADYmjoxsz+rDqp2IH+4y4ZFJf8=--jX6TlsuL0nwliAuN--lnbA1H3K7bwrmIjgJzAT9A== \ No newline at end of file diff --git a/config/initializers/deepl.rb b/config/initializers/deepl.rb new file mode 100644 index 0000000..007121a --- /dev/null +++ b/config/initializers/deepl.rb @@ -0,0 +1,6 @@ +if Rails.application.credentials[:deepl_api_key] + DeepL.configure do |config| + config.auth_key = Rails.application.credentials[:deepl_api_key] + config.host = "https://api-free.deepl.com" + end +end diff --git a/config/routes.rb b/config/routes.rb index 7b3bcee..1c19c13 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,6 @@ # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html Rails.application.routes.draw do + resources :guidelines resource :session resources :passwords, param: :token namespace :admin do diff --git a/db/migrate/20250101163808_create_guidelines.rb b/db/migrate/20250101163808_create_guidelines.rb new file mode 100644 index 0000000..197cb17 --- /dev/null +++ b/db/migrate/20250101163808_create_guidelines.rb @@ -0,0 +1,12 @@ +class CreateGuidelines < ActiveRecord::Migration[8.0] + def change + create_table :guidelines do |t| + t.references :principle, null: false, foreign_key: true + t.integer :number + t.string :name_de + t.string :name_en + + t.timestamps + end + end +end diff --git a/db/migrate/20250101163835_add_guideline_id_to_checks.rb b/db/migrate/20250101163835_add_guideline_id_to_checks.rb new file mode 100644 index 0000000..09662e8 --- /dev/null +++ b/db/migrate/20250101163835_add_guideline_id_to_checks.rb @@ -0,0 +1,6 @@ +class AddGuidelineIdToChecks < ActiveRecord::Migration[8.0] + def change + add_reference :checks, :guideline, null: false, foreign_key: true + remove_reference :checks, :principle + end +end diff --git a/db/migrate/20250101171836_remove_unique_number_constraint_on_checks.rb b/db/migrate/20250101171836_remove_unique_number_constraint_on_checks.rb new file mode 100644 index 0000000..1406d2b --- /dev/null +++ b/db/migrate/20250101171836_remove_unique_number_constraint_on_checks.rb @@ -0,0 +1,5 @@ +class RemoveUniqueNumberConstraintOnChecks < ActiveRecord::Migration[8.0] + def change + remove_index :checks, :number + end +end diff --git a/db/schema.rb b/db/schema.rb index 754674b..55b94e9 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: 2024_11_24_183406) do +ActiveRecord::Schema[8.0].define(version: 2025_01_01_171836) do create_table "account_remember_keys", force: :cascade do |t| t.string "key", null: false t.datetime "deadline", null: false @@ -94,7 +94,6 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_24_183406) do t.boolean "cognitive", default: false, null: false t.boolean "applicable_to_web", default: false, null: false t.boolean "applicable_to_app", default: false, null: false - t.integer "principle_id" t.integer "conformity_level" t.integer "priority" t.boolean "manual_test", default: true, null: false @@ -106,8 +105,8 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_24_183406) do t.integer "external_number_1" t.integer "external_number_2" t.integer "external_number_3" - t.index ["number"], name: "index_checks_on_number", unique: true - t.index ["principle_id"], name: "index_checks_on_principle_id" + t.integer "guideline_id", null: false + t.index ["guideline_id"], name: "index_checks_on_guideline_id" end create_table "checks_links", id: false, force: :cascade do |t| @@ -131,6 +130,16 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_24_183406) do t.index ["page_id"], name: "index_elements_on_page_id" end + create_table "guidelines", force: :cascade do |t| + t.integer "principle_id", null: false + t.integer "number" + t.string "name_de" + t.string "name_en" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["principle_id"], name: "index_guidelines_on_principle_id" + end + create_table "link_categories", force: :cascade do |t| t.string "name" t.text "description" @@ -230,8 +239,9 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_24_183406) do add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "checklist_entries", "checklists" add_foreign_key "checklist_entries", "checks" - add_foreign_key "checks", "principles" + add_foreign_key "checks", "guidelines" add_foreign_key "elements", "pages" + add_foreign_key "guidelines", "principles" add_foreign_key "links", "link_categories" add_foreign_key "pages", "reports" add_foreign_key "reports", "projects" diff --git a/db/seeds.rb b/db/seeds.rb index b0ddeb2..bf0bfc7 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -72,6 +72,8 @@ Principle.create!(name_de: "Verständlich", name_en: "Understandable") Principle.create!(name_de: "Robust", name_en: "Robust") Principle.create!(name_de: "Sonstige", name_en: "Other") +LinkCategory.create!(name: "Verstehen") +LinkCategory.create!(name: "WCAG Quick Reference") LinkCategory.create!(name: "Tools") LinkCategory.create!(name: "Beispiele") LinkCategory.create!(name: "Artikel") @@ -92,4 +94,3 @@ Link.create!(url: "https://www.a11yproject.com/", link_category: LinkCategory.find_by(name: "Artikel")) User.find_or_initialize_by(email_address: "admin@example.com").update!(password: "password") -User.find_or_initialize_by(email_address: "goran@quiet.ch").update!(password: "password") \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 5ce4a06..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,62 +0,0 @@ -# This is available in $COMPOSE_PROJECT_NAME -name: a11yist - -networks: - traefik: - external: true - -services: - app: &app - build: - context: . - restart: "unless-stopped" - command: ["/bin/sh", "-c", "/app/bin/dev"] - volumes: - - ./:/app:cached - - ${PWD}:${PWD} - - ${SSH_AUTH_SOCK}:/ssh-agent - - .devenv/helix:/home/app/.config/helix - - .devenv/fish:/home/app/.config/fish - env_file: - - .env - environment: - RAILS_ENV: development - LOG_LEVEL: debug - TRUSTED_IP: 172.16.0.0/12,192.168.0.0/16,10.0.0.0/24 - SSH_AUTH_SOCK: /ssh-agent - RAILS_SERVE_STATIC_FILES: 1 - APP_HOST: ${COMPOSE_PROJECT_NAME}.localhost - HISTFILE: /app/tmp/.bash_history - PSQL_HISTORY: /app/tmp/.psql_history - IRBRC: /app/.irbrc - SELENIUM_REMOTE_URL: http://chrome:4444/wd/hub - labels: - - traefik.http.routers.app-${COMPOSE_PROJECT_NAME}.entrypoints=http - - traefik.http.routers.app-${COMPOSE_PROJECT_NAME}.rule=Host(`${COMPOSE_PROJECT_NAME}.localhost`) - - traefik.http.services.app-${COMPOSE_PROJECT_NAME}.loadbalancer.server.port=3000 - - traefik.docker.network=traefik - networks: - - traefik - - default - - edit: - <<: *app - restart: "no" - labels: [] - depends_on: [] - command: ["hx", "."] - entrypoint: null - networks: - - default - - chrome: - image: selenium/standalone-chrome - shm_size: 2g - labels: - - traefik.http.routers.chrome-${COMPOSE_PROJECT_NAME}.entrypoints=http - - traefik.http.routers.chrome-${COMPOSE_PROJECT_NAME}.rule=Host(`chrome.${COMPOSE_PROJECT_NAME}.localhost`) - - traefik.http.services.chrome-${COMPOSE_PROJECT_NAME}.loadbalancer.server.port=4444 - - traefik.docker.network=traefik - networks: - - traefik - - default diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake index dccb8d4..ef0cc5a 100644 --- a/lib/tasks/import.rake +++ b/lib/tasks/import.rake @@ -1,20 +1,66 @@ # frozen_string_literal: true + URL = "https://outline-rocks.github.io/wcag/translations/WCAG21-de/" WCAG_22_EN_URL ="https://www.w3.org/TR/WCAG22/" +WCAG_22_EN_QREF_URL = "https://www.w3.org/WAI/WCAG22/quickref/" + +def translate(input) + DeepL.translate(input, "EN", "DE") +end def import_wcag22en doc = Nokogiri::HTML5(URI.open(WCAG_22_EN_URL)) - standards = [Standard.find_by(name_de: "WCAG 2.2"), Standard.find_by(name_de: "EN 301 549")] + qrefdoc = Nokogiri::HTML5(URI.open(WCAG_22_EN_QREF_URL)) + quick_criteria = qrefdoc.css(".guidelines section:has(h4)").each_with_object({}) do |node, h| + h[node.css("h4 strong").first.content] = { + quick_criterion_en: node.css(".sc-content .sc-text p").first.content, + link_url: node.css(".understanding a").first.attribute("href").value, + perm_url: "#{WCAG_22_EN_QREF_URL}##{node.css("article").first.attribute("id").value}" + } + rescue + debugger + end + link_category = LinkCategory.find_by(name: "Verstehen") + qr_category = LinkCategory.find_by(name: "WCAG Quick Reference") + # debugger + # raise ActiveRecord::Rollback + + standards = [ Standard.find_by(name_de: "WCAG 2.2"), Standard.find_by(name_de: "EN 301 549") ] doc.css("section.principle").each do |principle_node| _principle_id = principle_node.attributes["id"].value principle_title = principle_node.css("h2").first.content.scan(/([a-zA-Z]+)/) - principle = Principle.find_by!(name_en: principle_title) + principle = Principle.find_or_create_by!(name_en: principle_title) + principle_node.css("section.guideline").each do |guideline_node| + next unless guideline_node.css("h3").first + + puts guideline_node.css("h3").first&.content + puts guideline_node.css_path + + g_title = guideline_node.css("h3") + .first + .content + .scan(/Guideline \d+\.\d+ (.*)/) + .first + .first + g_number = guideline_node.css("h3 bdi") + .first + .content + .scan(/\d+\.(\d+)/) + .first + .first + g_text = guideline_node.css("> p").first.content + + guideline = Guideline.find_or_create_by(principle: principle, number: g_number, name_en: g_title) + guideline.update(description_en: g_text, description_de: translate(g_text), name_de: translate(g_title)) if guideline.description_de.blank? + guideline_node.css("section.guideline").each do |sc| + puts sc.css_path + puts sc.css("h4 bdi") sc_number, sc_title = sc.css("h4") .first .content - .scan(/Success Criterion (\d+\.\d+\.\d+) (.*)/) + .scan(/Success Criterion \d+\.\d+\.(\d+) (.*)/) .first sc_level = sc.css("p.conformance-level").first&.content&.scan(/\(Level (A+)\)/)&.first&.last sc_url = sc.css("a.self-link").first.attr("href") @@ -23,20 +69,35 @@ def import_wcag22en .select { _1.class == Nokogiri::XML::Element } .select { _1.attr("class").nil? || _1.attr("class") == "note" } .reduce("") { |str, node| str + node.to_s } - - check = Check.find_or_initialize_by(external_number: sc_number) + + check = Check.find_or_initialize_by(guideline_id: guideline.id, number: sc_number) new_standards = Set.new(check.standards) new_standards += standards - check.name_de = sc_title + check.name_de = translate sc_title if check.name_de.blank? check.name_en = sc_title - check.principle = principle check.standards = new_standards check.applicable_to_app = check.applicable_to_web = true - check.external_number = sc_number check.external_url = "#{WCAG_22_EN_URL}#{sc_url}" check.conformity_level = sc_level&.to_sym - check.conformity_notice_de = sc_conformity_notice - check.criterion_de = "
    #{full_text}
    ".gsub('href="#', %(href="#{WCAG_22_EN_URL}#)) + check.conformity_notice_de = translate sc_conformity_notice + check.criterion_de = "
    #{translate full_text}
    ".gsub('href="#', %(href="#{WCAG_22_EN_URL}#)) if check.criterion_de.blank? + qref_attrs = quick_criteria["#{principle.id}.#{guideline.number}.#{sc_number}"] + check.quick_criterion_en = qref_attrs&.dig :quick_criterion_en + check.quick_criterion_de = qref_attrs&.dig :quick_criterion_en + + if qref_attrs&.dig(:link_url) + link = Link.find_or_create_by(link_category:, url: qref_attrs[:link_url]) + link.update(text: "#{check.full_number} Verstehen") + check.links << link + end + + if qref_attrs&.dig(:perm_url) + link = Link.find_or_create_by(link_category: qr_category, url: qref_attrs[:perm_url]) + link.update(text: "#{check.full_number} Quick Reference") + check.links << link + end + # check.criterion_details_en = qref_attrs&.dig :criterion_details_en + # check.criterion_details_de = qref_attrs&.dig :criterion_details_en check.save! end end diff --git a/test/controllers/checks_controller_test.rb b/test/controllers/checks_controller_test.rb index f66c842..669e480 100644 --- a/test/controllers/checks_controller_test.rb +++ b/test/controllers/checks_controller_test.rb @@ -8,7 +8,7 @@ class ChecksControllerTest < ::ControllerTest end setup do - @principle = principles(:one) + @guideline = guidelines(:one) @check = checks(:deletable) User.create!(email_address: "test@example.com", password: "password") login("test@example.com", "password") @@ -27,7 +27,7 @@ class ChecksControllerTest < ::ControllerTest test "should create check" do assert_difference("Check.count") do post checks_url, - params: { check: { principle_id: @principle.id, number: Check.maximum(:number) + 1, level: @check.level, name_de: @check.name_de, position: @check.position, + params: { check: { guideline_id: @guideline.id, number: Check.maximum(:number) + 1, level: @check.level, name_de: @check.name_de, position: @check.position, criterion_de: @check.criterion_de } } end @@ -46,7 +46,7 @@ class ChecksControllerTest < ::ControllerTest test "should update check" do patch check_url(@check), - params: { check: { principle_id: @principle.id, level: @check.level, name_de: @check.t_name, position: @check.position, + params: { check: { guideline_id: @guideline.id, level: @check.level, name_de: @check.t_name, position: @check.position, criterion_de: @check.criterion_de } } assert_redirected_to check_url(@check) end diff --git a/test/controllers/guidelines_controller_test.rb b/test/controllers/guidelines_controller_test.rb new file mode 100644 index 0000000..cf37063 --- /dev/null +++ b/test/controllers/guidelines_controller_test.rb @@ -0,0 +1,49 @@ +require "test_helper" + +class GuidelinesControllerTest < ActionDispatch::IntegrationTest + setup do + skip "login" + @guideline = guidelines(:one) + end + + test "should get index" do + get guidelines_url + assert_response :success + end + + test "should get new" do + get new_guideline_url + assert_response :success + end + + test "should create guideline" do + assert_difference("Guideline.count") do + post guidelines_url, params: { guideline: { name_de: @guideline.name_de, number: @guideline.number, principle_id: @guideline.principle_id } } + end + + assert_redirected_to guideline_url(Guideline.last) + end + + test "should show guideline" do + get guideline_url(@guideline) + assert_response :success + end + + test "should get edit" do + get edit_guideline_url(@guideline) + assert_response :success + end + + test "should update guideline" do + patch guideline_url(@guideline), params: { guideline: { name_de: @guideline.name_de, number: @guideline.number, principle_id: @guideline.principle_id } } + assert_redirected_to guideline_url(@guideline) + end + + test "should destroy guideline" do + assert_difference("Guideline.count", -1) do + delete guideline_url(@guideline) + end + + assert_redirected_to guidelines_url + end +end diff --git a/test/fixtures/checks.yml b/test/fixtures/checks.yml index afadaf9..29d2a96 100644 --- a/test/fixtures/checks.yml +++ b/test/fixtures/checks.yml @@ -5,18 +5,18 @@ one: name_de: MyString level: 1 number: 1 - principle: one + guideline: one two: position: MyString name_de: MyString level: 1 number: 2 - principle: one + guideline: one deletable: position: MyString name_de: MyString level: 1 number: 3 - principle: one + guideline: one diff --git a/test/fixtures/guidelines.yml b/test/fixtures/guidelines.yml new file mode 100644 index 0000000..63db8d4 --- /dev/null +++ b/test/fixtures/guidelines.yml @@ -0,0 +1,11 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + principle: one + number: 1 + name_de: MyString + +two: + principle: two + number: 1 + name_de: MyString diff --git a/test/models/guideline_test.rb b/test/models/guideline_test.rb new file mode 100644 index 0000000..eb514f4 --- /dev/null +++ b/test/models/guideline_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class GuidelineTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/system/guidelines_test.rb b/test/system/guidelines_test.rb new file mode 100644 index 0000000..f108ac9 --- /dev/null +++ b/test/system/guidelines_test.rb @@ -0,0 +1,45 @@ +require "application_system_test_case" + +class GuidelinesTest < ApplicationSystemTestCase + setup do + @guideline = guidelines(:one) + end + + test "visiting the index" do + visit guidelines_url + assert_selector "h1", text: "Guidelines" + end + + test "should create guideline" do + visit guidelines_url + click_on "New guideline" + + fill_in "Name de", with: @guideline.name_de + fill_in "Number", with: @guideline.number + fill_in "Principle", with: @guideline.principle_id + click_on "Create Guideline" + + assert_text "Guideline was successfully created" + click_on "Back" + end + + test "should update Guideline" do + visit guideline_url(@guideline) + click_on "Edit this guideline", match: :first + + fill_in "Name de", with: @guideline.name_de + fill_in "Number", with: @guideline.number + fill_in "Principle", with: @guideline.principle_id + click_on "Update Guideline" + + assert_text "Guideline was successfully updated" + click_on "Back" + end + + test "should destroy Guideline" do + visit guideline_url(@guideline) + click_on "Destroy this guideline", match: :first + + assert_text "Guideline was successfully destroyed" + end +end diff --git a/tmp/pids/.keep b/tmp/pids/.keep deleted file mode 100644 index e69de29..0000000