diff --git a/app/assets/stylesheets/notifications.scss b/app/assets/stylesheets/notifications.scss index d9e6a9931d4..e7fd04fe536 100644 --- a/app/assets/stylesheets/notifications.scss +++ b/app/assets/stylesheets/notifications.scss @@ -7,3 +7,14 @@ span.notifications { border-radius: 4px; background-color: $orange; } + +.fr-nav { + &__notifiable { + position: relative; + } + + .notifications { + top: 1rem; + right: 0.25rem; + } +} diff --git a/app/components/main_navigation/announces_link_component.rb b/app/components/main_navigation/announces_link_component.rb new file mode 100644 index 00000000000..e30ee19a28c --- /dev/null +++ b/app/components/main_navigation/announces_link_component.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class MainNavigation::AnnouncesLinkComponent < ApplicationComponent + def render? + @most_recent_released_on = load_most_recent_released_on + return false if @most_recent_released_on.nil? + + # also see app/controllers/release_notes_controller.rb#ensure_access_allowed! + return true if helpers.instructeur_signed_in? + return true if helpers.administrateur_signed_in? + return true if helpers.expert_signed_in? + + false + end + + def something_new? + return true if current_user.announces_seen_at.nil? + + @most_recent_released_on.after? current_user.announces_seen_at + end + + def load_most_recent_released_on + categories = helpers.infer_default_announce_categories + + ReleaseNote.most_recent_announce_date_for_categories(categories) + end +end diff --git a/app/components/main_navigation/announces_link_component/announces_link_component.en.yml b/app/components/main_navigation/announces_link_component/announces_link_component.en.yml new file mode 100644 index 00000000000..996d9939993 --- /dev/null +++ b/app/components/main_navigation/announces_link_component/announces_link_component.en.yml @@ -0,0 +1,4 @@ +--- +en: + news: News + something_new: New informations about the website may be of interest to you. diff --git a/app/components/main_navigation/announces_link_component/announces_link_component.fr.yml b/app/components/main_navigation/announces_link_component/announces_link_component.fr.yml new file mode 100644 index 00000000000..05bcfe2ff7d --- /dev/null +++ b/app/components/main_navigation/announces_link_component/announces_link_component.fr.yml @@ -0,0 +1,4 @@ +--- +fr: + news: Nouveautés + something_new: De nouvelles informations à propos du site pourraient vous intéresser. diff --git a/app/components/main_navigation/announces_link_component/announces_link_component.html.haml b/app/components/main_navigation/announces_link_component/announces_link_component.html.haml new file mode 100644 index 00000000000..1c908b8e516 --- /dev/null +++ b/app/components/main_navigation/announces_link_component/announces_link_component.html.haml @@ -0,0 +1,4 @@ +%li.fr-nav__item.fr-nav__notifiable + = link_to t('.news'), release_notes_path, class: "fr-nav__link",'aria-current': current_page?(release_notes_path) ? 'page' : nil + - if something_new? + %span.notifications{ 'aria-label': t('.something_new') } diff --git a/app/components/main_navigation/instructeur_expert_navigation_component/instructeur_expert_navigation_component.html.haml b/app/components/main_navigation/instructeur_expert_navigation_component/instructeur_expert_navigation_component.html.haml index e5de591e806..f82fad23944 100644 --- a/app/components/main_navigation/instructeur_expert_navigation_component/instructeur_expert_navigation_component.html.haml +++ b/app/components/main_navigation/instructeur_expert_navigation_component/instructeur_expert_navigation_component.html.haml @@ -11,3 +11,5 @@ = Avis.model_name.plural.capitalize - if helpers.current_expert.avis_summary[:unanswered] > 0 %span.badge.warning= helpers.current_expert.avis_summary[:unanswered] + + = render MainNavigation::AnnouncesLinkComponent.new diff --git a/app/controllers/release_notes_controller.rb b/app/controllers/release_notes_controller.rb index 9ed025c06f4..79776bcac00 100644 --- a/app/controllers/release_notes_controller.rb +++ b/app/controllers/release_notes_controller.rb @@ -1,8 +1,9 @@ class ReleaseNotesController < ApplicationController before_action :ensure_access_allowed! + after_action :touch_default_categories_seen_at def index - @categories = params[:categories].presence || infer_default_categories + @categories = params[:categories].presence || helpers.infer_default_announce_categories # Paginate per group of dates, then show all announces for theses dates @paginated_groups = ReleaseNote.published @@ -20,18 +21,26 @@ def index render "scrollable_list" if params[:page].present? end + def nav_bar_profile + # detect context from referer, simple (no detection when refreshing the page) + params = Rails.application.routes.recognize_path(request&.referer) + + controller_class = "#{params[:controller].camelize}Controller".safe_constantize + return if controller_class.nil? + + controller_instance = controller_class.new + controller_instance.try(:nav_bar_profile) + 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 + def touch_default_categories_seen_at + return if params[:categories].present? || params[:page].present? + return if current_user.blank? + + return if current_user.announces_seen_at&.after?(@announces.max_by(&:released_on).released_on) + + current_user.touch(:announces_seen_at) end def ensure_access_allowed! diff --git a/app/helpers/release_notes_helper.rb b/app/helpers/release_notes_helper.rb index af7265a733d..2ca50309a8c 100644 --- a/app/helpers/release_notes_helper.rb +++ b/app/helpers/release_notes_helper.rb @@ -15,4 +15,16 @@ def announce_category_badge(category) content_tag(:span, ReleaseNote.human_attribute_name("categories.#{category}"), class: "fr-badge #{color_class}") end + + def infer_default_announce_categories + if administrateur_signed_in? + ReleaseNote.default_categories_for_role(:administrateur, current_administrateur) + elsif instructeur_signed_in? + ReleaseNote.default_categories_for_role(:instructeur, current_instructeur) + elsif expert_signed_in? + ReleaseNote.default_categories_for_role(:expert, current_expert) + else + ReleaseNote.default_categories_for_role(:usager) + end + end end diff --git a/app/models/release_note.rb b/app/models/release_note.rb index df0d72b69d4..a2a48f1f18a 100644 --- a/app/models/release_note.rb +++ b/app/models/release_note.rb @@ -14,4 +14,21 @@ class ReleaseNote < ApplicationRecord scope :published, -> { where(published: true, released_on: ..Date.current) } scope :for_categories, -> (categories) { where("categories && ARRAY[?]::varchar[]", categories) } + + def self.default_categories_for_role(role, instance = nil) + case role + when :administrateur + ['administrateur', 'usager', instance.api_tokens.exists? ? 'api' : nil] + when :instructeur + ['instructeur', instance.user.expert? ? 'expert' : nil] + when :expert + ['expert', instance.user.instructeur? ? 'instructeur' : nil] + else + ['usager'] + end + end + + def self.most_recent_announce_date_for_categories(categories) + published.for_categories(categories).maximum(:released_on) + end end diff --git a/app/views/administrateurs/_main_navigation.html.haml b/app/views/administrateurs/_main_navigation.html.haml index 9ac911a5dc4..91e78290d15 100644 --- a/app/views/administrateurs/_main_navigation.html.haml +++ b/app/views/administrateurs/_main_navigation.html.haml @@ -4,3 +4,6 @@ %li.fr-nav__item= link_to 'Mes démarches', admin_procedures_path, class:'fr-nav__link', 'aria-current': current_page?(controller: 'administrateurs/procedures', action: :index) ? 'true' : nil - if Rails.application.config.ds_zonage_enabled %li.fr-nav__item= link_to 'Toutes les démarches', all_admin_procedures_path(zone_ids: current_administrateur.zones), class:'fr-nav__link', 'aria-current': current_page?(all_admin_procedures_path) ? 'page' : nil + + = render MainNavigation::AnnouncesLinkComponent.new + diff --git a/spec/components/main_navigation/announces_link_component_spec.rb b/spec/components/main_navigation/announces_link_component_spec.rb new file mode 100644 index 00000000000..0a5d07cc727 --- /dev/null +++ b/spec/components/main_navigation/announces_link_component_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe MainNavigation::AnnouncesLinkComponent, type: :component do + let(:user) { build(:user) } + let!(:admin_release_note) { create(:release_note, released_on: Date.yesterday, categories: ["administrateur"]) } + let!(:instructeur_release_note) { create(:release_note, released_on: Date.yesterday, categories: ["instructeur"]) } + let(:not_published_release_note) { create(:release_note, published: false, released_on: Date.tomorrow) } + + let(:as_administrateur) { false } + let(:as_instructeur) { false } + + before do + if as_administrateur + user.build_administrateur + end + + if as_instructeur + user.build_instructeur + end + + allow(controller).to receive(:current_user).and_return(user) + end + + subject { render_inline(described_class.new) } + + context 'when signed as simple user' do + it 'does not render the announcements link if not signed in' do + expect(subject.to_html).to be_empty + end + end + + context 'when no signed in' do + let(:current_user) { nil } + + it 'does not render the announcements link if not signed in' do + expect(subject.to_html).to be_empty + end + end + + context 'when instructeur signed in' do + let(:as_instructeur) { true } + + it 'renders the announcements link' do + expect(subject).to have_link("Nouveautés") + end + + context 'when there are new announcements' do + before do + user.announces_seen_at = 5.days.ago + end + + it 'does not render the notification badge' do + expect(subject).to have_link("Nouveautés") + expect(subject).to have_css(".notifications") + end + end + + context 'when there are no new announcements' do + before do + user.announces_seen_at = 1.minute.ago + end + + it 'does not render the notification badge' do + expect(subject).to have_link("Nouveautés") + expect(subject).not_to have_css(".notifications") + end + end + + context 'when there are no announcement at all' do + let(:instructeur_release_note) { nil } + + it 'does not render anything' do + expect(subject.to_html).to be_empty + end + end + end + + context 'when administrateur signed in' do + let(:as_administrateur) { true } + + it 'renders the announcements link' do + expect(subject).to have_link("Nouveautés") + end + end +end diff --git a/spec/components/main_navigation/instructeur_expert_navigation_component_spec.rb b/spec/components/main_navigation/instructeur_expert_navigation_component_spec.rb index 5b61a533f8c..886c4490cbf 100644 --- a/spec/components/main_navigation/instructeur_expert_navigation_component_spec.rb +++ b/spec/components/main_navigation/instructeur_expert_navigation_component_spec.rb @@ -39,6 +39,14 @@ expect(subject).not_to have_selector('a[aria-current="page"]', text: 'Avis') end end + + context 'when there are release notes' do + let!(:release_note) { create(:release_note, categories: ['instructeur']) } + + it 'renders a link to Announces page' do + expect(subject).to have_link('Nouveautés') + end + end end describe 'when expert is signed in' do diff --git a/spec/controllers/release_notes_controller_spec.rb b/spec/controllers/release_notes_controller_spec.rb index b43db9778d1..cb6a5824895 100644 --- a/spec/controllers/release_notes_controller_spec.rb +++ b/spec/controllers/release_notes_controller_spec.rb @@ -47,5 +47,31 @@ it { is_expected.to be_redirection } end end + + describe 'touch user announces_seen_at' do + let(:user) { create(:user, administrateur: build(:administrateur)) } + + context 'when default categories' do + it 'touch announces_seen_at' do + expect { subject }.to change { user.reload.announces_seen_at } + end + + context 'when current announces_seen_at is more recent than last announce' do + before { user.update(announces_seen_at: 1.second.ago) } + + it 'does not touch announces_seen_at' do + expect { subject }.not_to change { user.reload.announces_seen_at } + end + end + end + + context 'when specific categories' do + subject { get :index, params: { categories: ['administrateur', 'instructeur'] } } + + it 'does not touch announces_seen_at' do + expect { subject }.not_to change { user.reload.announces_seen_at } + end + end + end end end diff --git a/spec/views/layouts/_header_spec.rb b/spec/views/layouts/_header_spec.rb index 109c93acc81..6e9f5c2407a 100644 --- a/spec/views/layouts/_header_spec.rb +++ b/spec/views/layouts/_header_spec.rb @@ -5,6 +5,8 @@ allow(view).to receive(:multiple_devise_profile_connect?).and_return(false) allow(view).to receive(:instructeur_signed_in?).and_return((profile == :instructeur)) allow(view).to receive(:current_instructeur).and_return(current_instructeur) + allow(view).to receive(:administrateur_signed_in?).and_return(false) + allow(view).to receive(:expert_signed_in?).and_return(false) allow(view).to receive(:localization_enabled?).and_return(false) if user @@ -57,6 +59,7 @@ let(:user) { instructeur.user } let(:profile) { :instructeur } let(:current_instructeur) { instructeur } + let!(:release_note) { create(:release_note, categories: ['instructeur']) } it { is_expected.to have_css(".fr-header__logo") } it { is_expected.to have_selector(:button, user.email, class: "account-btn") } @@ -64,5 +67,9 @@ it 'displays the Help dropdown menu' do expect(subject).to have_css(".help-dropdown") end + + it 'displays the Help dropdown menu' do + expect(subject).to have_link("Nouveautés") + end end end