Merge pull request #9638 from colinux/poc-release-notes
ETQ super-admin je peux informer les administrateurs, instructeurs et experts des évolutions du site
This commit is contained in:
commit
7a4456efeb
33 changed files with 598 additions and 6 deletions
|
@ -2,6 +2,8 @@ class ApplicationComponent < ViewComponent::Base
|
||||||
include ViewComponent::Translatable
|
include ViewComponent::Translatable
|
||||||
include FlipperHelper
|
include FlipperHelper
|
||||||
|
|
||||||
|
delegate :rich_text_area_tag, to: :helpers
|
||||||
|
|
||||||
def current_user
|
def current_user
|
||||||
controller.current_user
|
controller.current_user
|
||||||
end
|
end
|
||||||
|
|
31
app/components/release_note/form_component.rb
Normal file
31
app/components/release_note/form_component.rb
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ReleaseNote::FormComponent < ApplicationComponent
|
||||||
|
attr_reader :release_note
|
||||||
|
|
||||||
|
def initialize(release_note:)
|
||||||
|
@release_note = release_note
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def categories_fieldset_class
|
||||||
|
class_names(
|
||||||
|
"fr-fieldset--error": categories_error?
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def categories_error?
|
||||||
|
release_note.errors.key?(:categories)
|
||||||
|
end
|
||||||
|
|
||||||
|
def categories_errors_describedby_id
|
||||||
|
return nil if !categories_error?
|
||||||
|
|
||||||
|
dom_id(release_note, "categories_errors")
|
||||||
|
end
|
||||||
|
|
||||||
|
def categories_full_messages_errors
|
||||||
|
release_note.errors.full_messages_for(:categories)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
en:
|
||||||
|
delete: Delete this note
|
||||||
|
new: New note
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
fr:
|
||||||
|
delete: Supprimer cette note
|
||||||
|
new: Ajouter une note
|
|
@ -0,0 +1,53 @@
|
||||||
|
= form_for [:super_admins, release_note] do |f|
|
||||||
|
%fieldset#release_notes_fieldset.fr-fieldset{ 'data-controller': 'trix' }
|
||||||
|
.fr-fieldset__element
|
||||||
|
= render Dsfr::InputComponent.new(form: f, attribute: :released_on, input_type: :date_field) do |c|
|
||||||
|
- c.with_label { ReleaseNote.human_attribute_name(:released_on) }
|
||||||
|
|
||||||
|
.fr-fieldset__element
|
||||||
|
.fr-toggle
|
||||||
|
= f.check_box :published, class: "fr-toggle-input", id: dom_id(release_note, :published)
|
||||||
|
%label.fr-toggle__label{ for: dom_id(release_note, :published), data: { fr_checked_label: "Publié", fr_unchecked_label: "Brouillon" } }
|
||||||
|
Publier
|
||||||
|
|
||||||
|
.fr-fieldset__element
|
||||||
|
%fieldset.fr-fieldset{ "aria-labelledby": token_list(dom_id(release_note, "category_legend"), categories_errors_describedby_id), class: categories_fieldset_class, role: categories_error? ? "group" : nil }
|
||||||
|
%legend.fr-fieldset__legend.fr-fieldset__legend--regular{ id: dom_id(release_note, :category_legend) }
|
||||||
|
= ReleaseNote.human_attribute_name(:categories)
|
||||||
|
= render EditableChamp::AsteriskMandatoryComponent.new
|
||||||
|
|
||||||
|
- ReleaseNote::CATEGORIES.each do |category|
|
||||||
|
.fr-fieldset__element.fr-fieldset__element--inline
|
||||||
|
.fr-checkbox-group
|
||||||
|
= f.check_box :categories, { multiple: true, include_hidden: false, id: dom_id(release_note, "category_#{category}"), "aria-describedby" => "#{dom_id(release_note, "category_#{category}_messages")}" }, category, nil
|
||||||
|
%label.fr-label{ for: dom_id(release_note, "category_#{category}") }
|
||||||
|
= category.humanize
|
||||||
|
|
||||||
|
- if categories_error?
|
||||||
|
.fr-messages-group{ id: "checkboxes-error-messages", aria_live: "assertive" }
|
||||||
|
- if categories_full_messages_errors.one?
|
||||||
|
%p.fr-message.fr-message--error{ id: categories_errors_describedby_id }= categories_full_messages_errors.first
|
||||||
|
- else
|
||||||
|
.fr-error-text{ id: categories_errors_describedby_id }
|
||||||
|
%ul.list-style-type-none.fr-pl-0
|
||||||
|
- categories_full_messages_errors.map do |error_message|
|
||||||
|
%li= error_message
|
||||||
|
|
||||||
|
|
||||||
|
.fr-fieldset__element
|
||||||
|
.fr-input-group
|
||||||
|
= render Dsfr::InputComponent.new(form: f, attribute: :body, input_type: :rich_text_area)
|
||||||
|
|
||||||
|
|
||||||
|
.fr-fieldset__element
|
||||||
|
%ul.fr-btns-group.fr-btns-group--inline.fr-btns-group--icon-left
|
||||||
|
%li= f.button "Valider", class: "fr-btn fr-icon-check-line"
|
||||||
|
|
||||||
|
- if release_note.persisted?
|
||||||
|
%li= link_to t(".new"), new_super_admins_release_note_path(date: release_note.released_on), class: "fr-btn fr-btn--secondary fr-icon-add-line"
|
||||||
|
|
||||||
|
- if release_note.persisted?
|
||||||
|
%li
|
||||||
|
= link_to t('.delete'), super_admins_release_note_path(release_note),
|
||||||
|
class: "fr-btn fr-btn--secondary fr-icon-delete-line",
|
||||||
|
data: { method: :delete, confirm: "Supprimer cette note ?" }
|
45
app/controllers/release_notes_controller.rb
Normal file
45
app/controllers/release_notes_controller.rb
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
class ReleaseNotesController < ApplicationController
|
||||||
|
before_action :ensure_access_allowed!
|
||||||
|
|
||||||
|
def index
|
||||||
|
@categories = params[:categories].presence || infer_default_categories
|
||||||
|
|
||||||
|
# Paginate per group of dates, then show all announces for theses dates
|
||||||
|
@paginated_groups = ReleaseNote.published
|
||||||
|
.for_categories(@categories)
|
||||||
|
.select(:released_on)
|
||||||
|
.group(:released_on)
|
||||||
|
.order(released_on: :desc)
|
||||||
|
.page(params[:page]).per(5)
|
||||||
|
|
||||||
|
@announces = ReleaseNote.where(released_on: @paginated_groups.map(&:released_on))
|
||||||
|
.with_rich_text_body
|
||||||
|
.for_categories(@categories)
|
||||||
|
.order(released_on: :desc, id: :asc)
|
||||||
|
|
||||||
|
render "scrollable_list" if params[:page].present?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def infer_default_categories
|
||||||
|
if administrateur_signed_in?
|
||||||
|
['administrateur', 'usager', current_administrateur.api_tokens.exists? ? 'api' : nil]
|
||||||
|
elsif instructeur_signed_in?
|
||||||
|
['instructeur', 'expert']
|
||||||
|
elsif expert_signed_in?
|
||||||
|
['expert']
|
||||||
|
else
|
||||||
|
['usager']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_access_allowed!
|
||||||
|
return if administrateur_signed_in?
|
||||||
|
return if instructeur_signed_in?
|
||||||
|
return if expert_signed_in?
|
||||||
|
|
||||||
|
flash[:alert] = t('release_notes.index.forbidden')
|
||||||
|
redirect_to root_path
|
||||||
|
end
|
||||||
|
end
|
62
app/controllers/super_admins/release_notes_controller.rb
Normal file
62
app/controllers/super_admins/release_notes_controller.rb
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
class SuperAdmins::ReleaseNotesController < ApplicationController
|
||||||
|
before_action :authenticate_super_admin!
|
||||||
|
before_action :set_note, only: [:edit, :update, :destroy]
|
||||||
|
|
||||||
|
def nav_bar_profile
|
||||||
|
:superadmin
|
||||||
|
end
|
||||||
|
|
||||||
|
def index
|
||||||
|
@release_notes = ReleaseNote
|
||||||
|
.order(released_on: :desc, id: :asc)
|
||||||
|
.with_rich_text_body
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
# allows refreshing a submitted page in error
|
||||||
|
redirect_to edit_super_admins_release_note_path(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
@release_note = ReleaseNote.new(released_on: params[:date].presence || Date.current, published: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@release_note = ReleaseNote.new(release_note_params)
|
||||||
|
if @release_note.save
|
||||||
|
redirect_to edit_super_admins_release_note_path(@release_note), notice: t('.success')
|
||||||
|
else
|
||||||
|
flash.now[:alert] = [t('.error'), @release_note.errors.full_messages].flatten
|
||||||
|
render :new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit
|
||||||
|
@release_note = ReleaseNote.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
if @release_note.update(release_note_params)
|
||||||
|
redirect_to edit_super_admins_release_note_path(@release_note), notice: t('.success')
|
||||||
|
else
|
||||||
|
flash.now[:alert] = [t('.error'), @release_note.errors.full_messages].flatten
|
||||||
|
render :edit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@release_note.destroy!
|
||||||
|
|
||||||
|
redirect_to super_admins_release_notes_path, notice: t('.success')
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def release_note_params
|
||||||
|
params.require(:release_note).permit(:released_on, :published, :body, categories: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_note
|
||||||
|
@release_note = ReleaseNote.find(params[:id])
|
||||||
|
end
|
||||||
|
end
|
18
app/helpers/release_notes_helper.rb
Normal file
18
app/helpers/release_notes_helper.rb
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
module ReleaseNotesHelper
|
||||||
|
def announce_category_badge(category)
|
||||||
|
color_class = case category.to_sym
|
||||||
|
when :administrateur
|
||||||
|
'fr-background-flat--blue-france fr-text-inverted--blue-france'
|
||||||
|
when :instructeur
|
||||||
|
'fr-background-contrast--yellow-tournesol'
|
||||||
|
when :expert
|
||||||
|
'fr-background-contrast--purple-glycine'
|
||||||
|
when :usager
|
||||||
|
'fr-background-contrast--green-emeraude'
|
||||||
|
when :api
|
||||||
|
'fr-background-contrast--blue-ecume'
|
||||||
|
end
|
||||||
|
|
||||||
|
content_tag(:span, ReleaseNote.human_attribute_name("categories.#{category}"), class: "fr-badge #{color_class}")
|
||||||
|
end
|
||||||
|
end
|
17
app/models/release_note.rb
Normal file
17
app/models/release_note.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
class ReleaseNote < ApplicationRecord
|
||||||
|
has_rich_text :body
|
||||||
|
|
||||||
|
CATEGORIES = [
|
||||||
|
'administrateur',
|
||||||
|
'instructeur',
|
||||||
|
'expert',
|
||||||
|
'usager',
|
||||||
|
'api'
|
||||||
|
]
|
||||||
|
|
||||||
|
validates :categories, presence: true, inclusion: { in: CATEGORIES }
|
||||||
|
validates :body, presence: true
|
||||||
|
|
||||||
|
scope :published, -> { where(published: true, released_on: ..Date.current) }
|
||||||
|
scope :for_categories, -> (categories) { where("categories && ARRAY[?]::varchar[]", categories) }
|
||||||
|
end
|
|
@ -36,7 +36,7 @@
|
||||||
%span.fr-icon-refresh-line.fr-icon--sm
|
%span.fr-icon-refresh-line.fr-icon--sm
|
||||||
= t('go_gestionnaire', scope: [:layouts])
|
= t('go_gestionnaire', scope: [:layouts])
|
||||||
|
|
||||||
- if super_admin_signed_in?
|
- if super_admin_signed_in? && nav_bar_profile != :superadmin
|
||||||
%li
|
%li
|
||||||
= link_to manager_root_path, class: "fr-nav__link" do
|
= link_to manager_root_path, class: "fr-nav__link" do
|
||||||
%span.fr-icon-shield-line.fr-icon--sm
|
%span.fr-icon-shield-line.fr-icon--sm
|
||||||
|
|
|
@ -99,4 +99,8 @@
|
||||||
- if current_user.expert && current_expert.avis_summary[:total] > 0
|
- if current_user.expert && current_expert.avis_summary[:total] > 0
|
||||||
= render partial: 'layouts/header/avis_tab', locals: { current_expert: current_expert }
|
= render partial: 'layouts/header/avis_tab', locals: { current_expert: current_expert }
|
||||||
|
|
||||||
|
- if content_for?(:navigation_principale)
|
||||||
|
.fr-container
|
||||||
|
= yield(:navigation_principale)
|
||||||
|
|
||||||
= yield(:notice_info)
|
= yield(:notice_info)
|
||||||
|
|
|
@ -24,8 +24,9 @@ as defined by the routes in the `admin/` namespace
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<%= link_to "Delayed Jobs", manager_delayed_job_path, class: "navigation__link" %>
|
<%= link_to "Delayed Jobs", manager_delayed_job_path, class: "navigation__link" %>
|
||||||
<%= link_to "Features", manager_flipper_path, class: "navigation__link" %>
|
|
||||||
<%= link_to "Maintenance Tasks", manager_maintenance_tasks_path, class: "navigation__link" %>
|
<%= link_to "Maintenance Tasks", manager_maintenance_tasks_path, class: "navigation__link" %>
|
||||||
|
<%= link_to "Features", manager_flipper_path, class: "navigation__link" %>
|
||||||
|
<%= link_to "Annonces", super_admins_release_notes_path, class: "navigation__link" %>
|
||||||
<%= link_to "Import data via CSV", manager_import_procedure_tags_path, class: "navigation__link" %>
|
<%= link_to "Import data via CSV", manager_import_procedure_tags_path, class: "navigation__link" %>
|
||||||
<% if Rails.application.secrets.sendinblue[:enabled] && ENV["SAML_IDP_ENABLED"] == "enabled" %>
|
<% if Rails.application.secrets.sendinblue[:enabled] && ENV["SAML_IDP_ENABLED"] == "enabled" %>
|
||||||
<%= link_to "Sendinblue", ENV.fetch("SENDINBLUE_LOGIN_URL"), class: "navigation__link", target: '_blank' %>
|
<%= link_to "Sendinblue", ENV.fetch("SENDINBLUE_LOGIN_URL"), class: "navigation__link", target: '_blank' %>
|
||||||
|
|
10
app/views/release_notes/_announce.html.haml
Normal file
10
app/views/release_notes/_announce.html.haml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
.fr-mb-4w
|
||||||
|
%h3= l(notes[0].released_on, format: :long)
|
||||||
|
|
||||||
|
- notes.each do |note|
|
||||||
|
.fr-mb-4w.fr-px-2w.fr-py-2w.fr-background-alt--grey
|
||||||
|
%p
|
||||||
|
- note.categories.each do |category|
|
||||||
|
= announce_category_badge(category)
|
||||||
|
|
||||||
|
= note.body
|
5
app/views/release_notes/_page.html.haml
Normal file
5
app/views/release_notes/_page.html.haml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
= render partial: 'announce', collection: announces.group_by(&:released_on).values, as: :notes
|
||||||
|
|
||||||
|
- if !paginated_groups.last_page?
|
||||||
|
= turbo_frame_tag "announces-page-#{paginated_groups.next_page}", loading: :lazy, src: next_page_path(paginated_groups) do
|
||||||
|
= link_to t('.previous_page'), next_page_path(paginated_groups), class: "fr-btn fr-btn--secondary"
|
27
app/views/release_notes/index.html.haml
Normal file
27
app/views/release_notes/index.html.haml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
.fr-container.fr-my-5w
|
||||||
|
%h2.fr-mb-5w= t('.title', app_name: APPLICATION_NAME)
|
||||||
|
|
||||||
|
.fr-grid-row.fr-grid-row--gutters{ data: { turbo: 'true' } }
|
||||||
|
.fr-col-md-3
|
||||||
|
= form_with(url: release_notes_path, method: :get, data: { turbo_frame: 'announces', controller: 'autosubmit' }, html: { role: 'search' }) do |f|
|
||||||
|
%fieldset.sidebar-filter
|
||||||
|
%legend
|
||||||
|
%ul
|
||||||
|
%li.fr-py-2w.fr-pl-2w
|
||||||
|
%fieldset.fr-fieldset{ "aria-labelledby": "sidebar_category_legend" }
|
||||||
|
%legend.fr-fieldset__legend{ id: "sidebar_category_legend" }
|
||||||
|
= ReleaseNote.human_attribute_name(:categories)
|
||||||
|
|
||||||
|
- ReleaseNote::CATEGORIES.each do |category|
|
||||||
|
.fr-fieldset__element
|
||||||
|
.fr-checkbox-group
|
||||||
|
= f.check_box :categories, { multiple: true, include_hidden: false, id: "filter_category_#{category}", checked: @categories.include?(category) }, category, nil
|
||||||
|
%label.fr-label{ for: "filter_category_#{category}" }
|
||||||
|
= ReleaseNote.human_attribute_name("categories.#{category}")
|
||||||
|
|
||||||
|
.fr-col-md-9
|
||||||
|
= turbo_frame_tag "announces", data: { turbo_action: :advance } do
|
||||||
|
- if @announces.any?
|
||||||
|
= render "page", announces: @announces, paginated_groups: @paginated_groups
|
||||||
|
- else
|
||||||
|
%p.fr-my-4w.text-center= t('.no_content')
|
2
app/views/release_notes/scrollable_list.html.haml
Normal file
2
app/views/release_notes/scrollable_list.html.haml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
= turbo_frame_tag "announces-page-#{@paginated_groups.current_page}" do
|
||||||
|
= render 'page', announces: @announces, paginated_groups: @paginated_groups
|
|
@ -0,0 +1,17 @@
|
||||||
|
- content_for(:navigation_principale) do
|
||||||
|
.fr-container
|
||||||
|
%nav.fr-nav#header-navigation{ role: "navigation", aria: { label: 'Menu principal annonces' } }
|
||||||
|
%ul.fr-nav__list
|
||||||
|
%li.fr-nav__item
|
||||||
|
= link_to "Toutes les annonces", super_admins_release_notes_path, class: "fr-nav__link", target: "_self", aria: { current: action == :index ? "page" : nil }
|
||||||
|
|
||||||
|
%li.fr-nav__item
|
||||||
|
= link_to("Nouvelle annonce", new_super_admins_release_note_path(date: @release_note&.released_on), class: "fr-nav__link", target: "_self", aria: { current: action == :new ? "page" : nil })
|
||||||
|
|
||||||
|
- if action == :edit
|
||||||
|
%li.fr-nav__item
|
||||||
|
= link_to "Annonce", '', class: "fr-nav__link", target: "_self", aria: { current: "page" }
|
||||||
|
|
||||||
|
%li.fr-nav__item
|
||||||
|
= link_to "Annonces publiées", release_notes_path, class: "fr-nav__link", target: "_self"
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
= turbo_stream.append 'release_notes_fieldset', render(ReleaseNote::NoteFormComponent.new(note: @release_note))
|
8
app/views/super_admins/release_notes/edit.html.haml
Normal file
8
app/views/super_admins/release_notes/edit.html.haml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
= render "main_navigation", action: :edit
|
||||||
|
|
||||||
|
.fr-container.fr-my-5w
|
||||||
|
.fr-grid-row.fr-grid-row--center
|
||||||
|
.fr-col-lg-10
|
||||||
|
%h1.fr-h2 Annonce
|
||||||
|
= render ReleaseNote::FormComponent.new(release_note: @release_note)
|
||||||
|
|
31
app/views/super_admins/release_notes/index.html.haml
Normal file
31
app/views/super_admins/release_notes/index.html.haml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
= render "main_navigation", action: :index
|
||||||
|
|
||||||
|
.fr-container.fr-my-5w
|
||||||
|
.fr-grid-row.fr-grid-row--center
|
||||||
|
.fr-col-lg-10
|
||||||
|
|
||||||
|
%h1.fr-h2 Liste des annonces
|
||||||
|
|
||||||
|
-# haml-lint:disable ApplicationNameLinter
|
||||||
|
= link_to "Releases sur GitHub", "https://github.com/demarches-simplifiees/demarches-simplifiees.fr/releases", **external_link_attributes
|
||||||
|
-# haml-lint:enable ApplicationNameLinter
|
||||||
|
|
||||||
|
.fr-table
|
||||||
|
%table
|
||||||
|
%thead
|
||||||
|
%th Annoncé le
|
||||||
|
%th Publié ?
|
||||||
|
%th Notes
|
||||||
|
%th Actions
|
||||||
|
%tbody
|
||||||
|
- @release_notes.each do |note|
|
||||||
|
%tr
|
||||||
|
%td= l(note.released_on) if note.released_on
|
||||||
|
%td
|
||||||
|
- if note.published?
|
||||||
|
%span.fr-badge.fr-badge--success.fr-badge--no-icon Publié
|
||||||
|
- else
|
||||||
|
%span.fr-badge.fr-badge--warning.fr-badge--no-icon Brouillon
|
||||||
|
%td= note.body.to_plain_text.truncate_words(12)
|
||||||
|
%td
|
||||||
|
= link_to 'Modifier', edit_super_admins_release_note_path(note), class: 'fr-btn fr-btn--secondary'
|
7
app/views/super_admins/release_notes/new.html.haml
Normal file
7
app/views/super_admins/release_notes/new.html.haml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
= render "main_navigation", action: :new
|
||||||
|
|
||||||
|
.fr-container.fr-my-5w
|
||||||
|
.fr-grid-row.fr-grid-row--center
|
||||||
|
.fr-col-lg-10
|
||||||
|
%h1.fr-h2 Nouvelle Annonce
|
||||||
|
= render ReleaseNote::FormComponent.new(release_note: @release_note)
|
|
@ -86,10 +86,10 @@ module TPS
|
||||||
# @see https://guides.rubyonrails.org/configuring.html#custom-configuration
|
# @see https://guides.rubyonrails.org/configuring.html#custom-configuration
|
||||||
config.x.clamav.enabled = ENV.fetch("CLAMAV_ENABLED", "enabled") == "enabled"
|
config.x.clamav.enabled = ENV.fetch("CLAMAV_ENABLED", "enabled") == "enabled"
|
||||||
|
|
||||||
config.view_component.generate_sidecar = true
|
config.view_component.generate.sidecar = true
|
||||||
config.view_component.generate_locale = true
|
config.view_component.generate.locale = true
|
||||||
config.view_component.generate_distinct_locale_files = true
|
config.view_component.generate.distinct_locale_files = true
|
||||||
config.view_component.generate_preview = true
|
config.view_component.generate.preview = true
|
||||||
config.view_component.show_previews_source = true
|
config.view_component.show_previews_source = true
|
||||||
config.view_component.default_preview_layout = 'component_preview'
|
config.view_component.default_preview_layout = 'component_preview'
|
||||||
config.view_component.preview_paths << "#{Rails.root}/spec/components/previews"
|
config.view_component.preview_paths << "#{Rails.root}/spec/components/previews"
|
||||||
|
|
31
config/locales/release_notes.en.yml
Normal file
31
config/locales/release_notes.en.yml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
en:
|
||||||
|
release_notes:
|
||||||
|
index:
|
||||||
|
title: What’s new on %{app_name} ? 🚀
|
||||||
|
forbidden: You are not authorized to view the News page.
|
||||||
|
no_content: No announcement here.
|
||||||
|
page:
|
||||||
|
previous_page: Previous announcements
|
||||||
|
super_admins:
|
||||||
|
release_notes:
|
||||||
|
create: &save
|
||||||
|
success: Release note was successfully saved.
|
||||||
|
error: Release note was not saved.
|
||||||
|
update:
|
||||||
|
<<: *save
|
||||||
|
destroy:
|
||||||
|
success: Release note was successfully deleted.
|
||||||
|
activerecord:
|
||||||
|
attributes:
|
||||||
|
release_note:
|
||||||
|
body: Announce
|
||||||
|
categories: Categories
|
||||||
|
released_on: Publication date
|
||||||
|
release_note/categories:
|
||||||
|
administrateur: Administrator
|
||||||
|
instructeur: Instructor
|
||||||
|
expert: Expert
|
||||||
|
api: API
|
||||||
|
usager: User
|
||||||
|
|
31
config/locales/release_notes.fr.yml
Normal file
31
config/locales/release_notes.fr.yml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
fr:
|
||||||
|
release_notes:
|
||||||
|
index:
|
||||||
|
title: Quoi de neuf sur %{app_name} ? 🚀
|
||||||
|
forbidden: Vous n’êtes pas autorisé(e) à consulter la page des Nouveautés.
|
||||||
|
no_content: Aucune nouveauté annoncée de ce côté là.
|
||||||
|
page:
|
||||||
|
previous_page: Annonces précédentes
|
||||||
|
super_admins:
|
||||||
|
release_notes:
|
||||||
|
create: &save
|
||||||
|
success: L’annonce a été sauvegardée.
|
||||||
|
error: L’annonce n’a pas pu être sauvegardée.
|
||||||
|
update:
|
||||||
|
<<: *save
|
||||||
|
destroy:
|
||||||
|
success: La note a été supprimée.
|
||||||
|
activerecord:
|
||||||
|
attributes:
|
||||||
|
release_note:
|
||||||
|
body: Annonce
|
||||||
|
categories: Catégories
|
||||||
|
released_on: Date de publication
|
||||||
|
release_note/categories:
|
||||||
|
administrateur: Administrateur
|
||||||
|
instructeur: Instructeur
|
||||||
|
expert: Expert
|
||||||
|
api: API
|
||||||
|
usager: Usager
|
||||||
|
|
|
@ -16,6 +16,7 @@ en:
|
||||||
instructeur: instructor
|
instructeur: instructor
|
||||||
administrateur: admin
|
administrateur: admin
|
||||||
gestionnaire: admins group manager
|
gestionnaire: admins group manager
|
||||||
|
superadmin: super-admin
|
||||||
expert: expert
|
expert: expert
|
||||||
user: user
|
user: user
|
||||||
guest: guest
|
guest: guest
|
||||||
|
|
|
@ -16,6 +16,7 @@ fr:
|
||||||
instructeur: instructeur
|
instructeur: instructeur
|
||||||
administrateur: administrateur
|
administrateur: administrateur
|
||||||
gestionnaire: gestionnaire
|
gestionnaire: gestionnaire
|
||||||
|
superadmin: super-admin
|
||||||
expert: expert
|
expert: expert
|
||||||
user: usager
|
user: usager
|
||||||
guest: invité
|
guest: invité
|
||||||
|
|
|
@ -125,6 +125,10 @@ Rails.application.routes.draw do
|
||||||
passwords: 'super_admins/passwords'
|
passwords: 'super_admins/passwords'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace :super_admins do
|
||||||
|
resources :release_notes
|
||||||
|
end
|
||||||
|
|
||||||
get 'super_admins/edit_otp', to: 'super_admins#edit_otp', as: 'edit_super_admin_otp'
|
get 'super_admins/edit_otp', to: 'super_admins#edit_otp', as: 'edit_super_admin_otp'
|
||||||
put 'super_admins/enable_otp', to: 'super_admins#enable_otp', as: 'enable_super_admin_otp'
|
put 'super_admins/enable_otp', to: 'super_admins#enable_otp', as: 'enable_super_admin_otp'
|
||||||
|
|
||||||
|
@ -630,6 +634,8 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :release_notes, only: [:index]
|
||||||
|
|
||||||
if Rails.env.test?
|
if Rails.env.test?
|
||||||
scope 'test/api_geo' do
|
scope 'test/api_geo' do
|
||||||
get 'regions' => 'api_geo_test#regions'
|
get 'regions' => 'api_geo_test#regions'
|
||||||
|
|
15
db/migrate/20230912155425_create_release_notes.rb
Normal file
15
db/migrate/20230912155425_create_release_notes.rb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
class CreateReleaseNotes < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
create_table :release_notes do |t|
|
||||||
|
t.date :released_on
|
||||||
|
t.boolean :published, default: false, null: false
|
||||||
|
t.string :categories, array: true, default: []
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :release_notes, :released_on
|
||||||
|
add_index :release_notes, :published
|
||||||
|
add_index :release_notes, :categories, using: :gin
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddAnnouncesSeenAtToUsers < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
add_column :users, :announces_seen_at, :datetime, null: true, default: nil, precision: 6
|
||||||
|
end
|
||||||
|
end
|
12
db/schema.rb
12
db/schema.rb
|
@ -896,6 +896,17 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_26_161609) do
|
||||||
t.index ["procedure_id"], name: "index_refused_mails_on_procedure_id"
|
t.index ["procedure_id"], name: "index_refused_mails_on_procedure_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "release_notes", force: :cascade do |t|
|
||||||
|
t.string "categories", default: [], array: true
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.boolean "published", default: false, null: false
|
||||||
|
t.date "released_on"
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["categories"], name: "index_release_notes_on_categories", using: :gin
|
||||||
|
t.index ["published"], name: "index_release_notes_on_published"
|
||||||
|
t.index ["released_on"], name: "index_release_notes_on_released_on"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "safe_mailers", force: :cascade do |t|
|
create_table "safe_mailers", force: :cascade do |t|
|
||||||
t.datetime "created_at", precision: 6, null: false
|
t.datetime "created_at", precision: 6, null: false
|
||||||
t.string "forced_delivery_method"
|
t.string "forced_delivery_method"
|
||||||
|
@ -1021,6 +1032,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_26_161609) do
|
||||||
create_table "users", id: :serial, force: :cascade do |t|
|
create_table "users", id: :serial, force: :cascade do |t|
|
||||||
t.datetime "confirmation_sent_at", precision: 6
|
t.datetime "confirmation_sent_at", precision: 6
|
||||||
t.datetime "blocked_at", precision: 6
|
t.datetime "blocked_at", precision: 6
|
||||||
|
t.datetime "announces_seen_at", precision: 6
|
||||||
t.text "blocked_reason"
|
t.text "blocked_reason"
|
||||||
t.string "confirmation_token"
|
t.string "confirmation_token"
|
||||||
t.datetime "confirmed_at", precision: 6
|
t.datetime "confirmed_at", precision: 6
|
||||||
|
|
51
spec/controllers/release_notes_controller_spec.rb
Normal file
51
spec/controllers/release_notes_controller_spec.rb
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe ReleaseNotesController, type: :controller do
|
||||||
|
let!(:note_admin) { create(:release_note, categories: ['administrateur'], body: "Pour l'admin", released_on: Date.new(2023, 10, 15)) }
|
||||||
|
let!(:note_instructeur) { create(:release_note, categories: ['instructeur'], body: "Pour l'instructeur", released_on: Date.new(2023, 10, 13)) }
|
||||||
|
|
||||||
|
let(:user) { nil }
|
||||||
|
let(:admin) { create(:user, administrateur: build(:administrateur)) }
|
||||||
|
let(:instructeur) { create(:user, instructeur: build(:instructeur)) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:current_user).and_return(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'GET index' do
|
||||||
|
subject { get :index }
|
||||||
|
|
||||||
|
describe 'filtering' do
|
||||||
|
before { subject }
|
||||||
|
context 'user is admininistrateur' do
|
||||||
|
let(:user) { admin }
|
||||||
|
it { is_expected.to have_http_status(:ok) }
|
||||||
|
it { expect(assigns(:announces)).to eq([note_admin]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'user is instructeur' do
|
||||||
|
let(:user) { instructeur }
|
||||||
|
it { is_expected.to have_http_status(:ok) }
|
||||||
|
it { expect(assigns(:announces)).to eq([note_instructeur]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'user is expert' do
|
||||||
|
let(:user) { create(:user, expert: build(:expert)) }
|
||||||
|
it { expect(assigns(:announces)).to eq([]) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'acl' do
|
||||||
|
before { subject }
|
||||||
|
context 'user is normal' do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
|
||||||
|
it { is_expected.to be_redirection }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'no user' do
|
||||||
|
it { is_expected.to be_redirection }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,82 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe SuperAdmins::ReleaseNotesController, type: :controller do
|
||||||
|
let(:super_admin) { create(:super_admin) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in super_admin if super_admin.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "acl" do
|
||||||
|
context 'when user is not signed as super admin' do
|
||||||
|
let(:super_admin) { nil }
|
||||||
|
let!(:release_note) { create(:release_note, published: false) }
|
||||||
|
|
||||||
|
it 'is not allowed to post' do
|
||||||
|
expect { post :create, params: { release_note: { released_on: Date.current, published: "0", body: "bam" } } }.not_to change(ReleaseNote, :count)
|
||||||
|
expect(response.status).to eq(302)
|
||||||
|
expect(flash[:alert]).to be_present
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is not allowed to put' do
|
||||||
|
expect {
|
||||||
|
put :update, params: {
|
||||||
|
id: release_note.id,
|
||||||
|
release_note: {
|
||||||
|
released_on: Date.current,
|
||||||
|
published: "1",
|
||||||
|
categories: release_note.categories,
|
||||||
|
body: "hacked body"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.not_to change { release_note.reload.body }
|
||||||
|
expect(response.status).to eq(302)
|
||||||
|
expect(flash[:alert]).to be_present
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is not allowed to index' do
|
||||||
|
get :index
|
||||||
|
expect(response.status).to eq(302)
|
||||||
|
expect(flash[:alert]).to be_present
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is not allowed to destroy' do
|
||||||
|
delete :destroy, params: { id: release_note.id }
|
||||||
|
expect(response.status).to eq(302)
|
||||||
|
expect(flash[:alert]).to be_present
|
||||||
|
expect(release_note.reload).to be_persisted
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is signed as super admin' do
|
||||||
|
let(:release_note) { create(:release_note, published: false) }
|
||||||
|
|
||||||
|
it 'is allowed to post' do
|
||||||
|
expect { post :create, params: { release_note: { categories: ['api'], released_on: Date.current, published: "0", body: "bam" } } }.to change(ReleaseNote, :count).by(1)
|
||||||
|
expect(flash[:notice]).to be_present
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is allowed to put' do
|
||||||
|
put :update, params: {
|
||||||
|
id: release_note.id,
|
||||||
|
release_note: {
|
||||||
|
released_on: Date.current,
|
||||||
|
published: "1",
|
||||||
|
categories: release_note.categories,
|
||||||
|
body: "new body"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
release_note.reload
|
||||||
|
expect(release_note.body.to_plain_text).to eq("new body")
|
||||||
|
expect(release_note.published).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is allowed to destroy' do
|
||||||
|
delete :destroy, params: { id: release_note.id }
|
||||||
|
expect(flash[:notice]).to be_present
|
||||||
|
expect { release_note.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
8
spec/factories/release_notes.rb
Normal file
8
spec/factories/release_notes.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
FactoryBot.define do
|
||||||
|
factory :release_note do
|
||||||
|
body { "Sample release note body" }
|
||||||
|
categories { ReleaseNote::CATEGORIES.sample(1) }
|
||||||
|
published { true }
|
||||||
|
released_on { 1.day.ago.to_date }
|
||||||
|
end
|
||||||
|
end
|
Loading…
Add table
Reference in a new issue