diff --git a/Gemfile b/Gemfile index 189d3a5db..1e6a5d305 100644 --- a/Gemfile +++ b/Gemfile @@ -46,6 +46,7 @@ gem "bootsnap", require: false # gem 'image_processing', '~> 1.2' gem "flipflop" +gem "redcarpet", "~> 3.6" gem "govuk-components", "~> 5.0.0" gem "govuk_design_system_formbuilder", "~> 5.0.0" diff --git a/Gemfile.lock b/Gemfile.lock index 2c7b83626..1c1e92c5a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -79,7 +79,7 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) aes_key_wrap (1.1.0) annotate (3.2.0) @@ -176,7 +176,7 @@ GEM activemodel (>= 6.1) activesupport (>= 6.1) html-attributes-utils (~> 1) - haml (6.2.3) + haml (6.3.0) temple (>= 0.8.2) thor tilt @@ -185,7 +185,7 @@ GEM activesupport (>= 6.1.4.4) i18n (1.14.1) concurrent-ruby (~> 1.0) - io-console (0.7.0) + io-console (0.6.0) irb (1.10.1) rdoc reline (>= 0.3.8) @@ -227,7 +227,7 @@ GEM minitest (5.20.0) msgpack (1.7.2) mutex_m (0.2.0) - net-imap (0.4.7) + net-imap (0.4.8) date net-protocol net-pop (0.1.2) @@ -237,7 +237,7 @@ GEM net-smtp (0.4.0) net-protocol nio4r (2.7.0) - nokogiri (1.15.5-arm64-darwin) + nokogiri (1.15.5-x86_64-darwin) racc (~> 1.4) nokogiri (1.15.5-x86_64-linux) racc (~> 1.4) @@ -292,7 +292,7 @@ GEM puma (6.4.0) nio4r (~> 2.0) racc (1.7.3) - rack (2.2.8) + rack (3.0.8) rack-oauth2 (2.2.0) activesupport attr_required @@ -302,13 +302,13 @@ GEM rack (>= 2.1.0) rack-protection (3.0.6) rack - rack-session (1.0.2) - rack (< 3) + rack-session (2.0.0) + rack (>= 3.0.0) rack-test (2.1.0) rack (>= 1.3) - rackup (1.0.0) - rack (< 3) - webrick + rackup (2.1.0) + rack (>= 3) + webrick (~> 1.8) rails (7.1.2) actioncable (= 7.1.2) actionmailbox (= 7.1.2) @@ -352,6 +352,7 @@ GEM rbs (2.8.4) rdoc (6.6.1) psych (>= 4.0.0) + redcarpet (3.6.0) regexp_parser (2.8.3) reline (0.4.1) io-console (~> 0.5) @@ -503,6 +504,7 @@ GEM PLATFORMS arm64-darwin-22 arm64-darwin-23 + x86_64-darwin-22 x86_64-linux DEPENDENCIES @@ -536,6 +538,7 @@ DEPENDENCIES rails (~> 7.1.2) rails-controller-testing rails-erd + redcarpet (~> 3.6) rladr rspec rspec-rails diff --git a/app/assets/components/service_update/view.html.erb b/app/assets/components/service_update/view.html.erb new file mode 100644 index 000000000..295df2014 --- /dev/null +++ b/app/assets/components/service_update/view.html.erb @@ -0,0 +1,5 @@ +
+ <%= title_element %> +

<%= date_pretty %>

+
<%= content_html %>
+
diff --git a/app/assets/components/service_update/view.rb b/app/assets/components/service_update/view.rb new file mode 100644 index 000000000..6b67798f7 --- /dev/null +++ b/app/assets/components/service_update/view.rb @@ -0,0 +1,26 @@ +class ServiceUpdate::View < GovukComponent::Base + attr_reader :service_update + + delegate :title, :content, :date, to: :service_update + + TITLE_CLASS = "govuk-heading-m govuk-!-margin-bottom-2" + + def initialize(service_update:) + @service_update = service_update + end + + def title_element + tag.h2(title, class: TITLE_CLASS) + end + + def date_pretty + date.to_date.strftime("%e %B %Y") + end + + def content_html + custom_render = + Redcarpet::Render::HTML.new(link_attributes: { class: "govuk-link" }) + markdown = Redcarpet::Markdown.new(custom_render) + markdown.render(content).html_safe + end +end diff --git a/app/controllers/service_updates_controller.rb b/app/controllers/service_updates_controller.rb new file mode 100644 index 000000000..8b3acb40a --- /dev/null +++ b/app/controllers/service_updates_controller.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class ServiceUpdatesController < ApplicationController + include ApplicationHelper + + def index + @service_name ||= current_service + @service_updates = ServiceUpdate.where(service: @service_name) + end +end diff --git a/app/models/service_update.rb b/app/models/service_update.rb new file mode 100644 index 000000000..33470d77a --- /dev/null +++ b/app/models/service_update.rb @@ -0,0 +1,34 @@ +class ServiceUpdate + CLAIMS_SERVICE_UPDATES_YAML_FILE = + Rails.root.join("db/claims_service_updates.yml").freeze + PLACEMENTS_SERVICE_UPDATES_YAML_FILE = + Rails.root.join("db/placements_service_updates.yml").freeze + + attr_accessor :date, :title, :content + + def initialize(title:, date:, content:) + @title = title + @date = date + @content = content + end + + def id + title.parameterize + end + + def self.where(service:) + path = file_path(service:) + updates = YAML.load_file(path, symbolize_names: true) || [] + + updates.map { |service_update| new(**service_update) } + end + + def self.file_path(service:) + case service + when :claims + CLAIMS_SERVICE_UPDATES_YAML_FILE + when :placements + PLACEMENTS_SERVICE_UPDATES_YAML_FILE + end + end +end diff --git a/app/views/service_updates/index.html.erb b/app/views/service_updates/index.html.erb new file mode 100644 index 000000000..fb52b41f9 --- /dev/null +++ b/app/views/service_updates/index.html.erb @@ -0,0 +1,17 @@ +
+
+

<%= t("service_updates.#{@service_name}.heading") %>

+ +

<%= t("service_updates.contents") %>

+ + + <% @service_updates.each do |service_update| %> +
+ <%= render ServiceUpdate::View.new(service_update:) %> + <% end %> +
+
diff --git a/config/application.rb b/config/application.rb index 43e34318d..fdcd444d3 100644 --- a/config/application.rb +++ b/config/application.rb @@ -35,6 +35,7 @@ class Application < Rails::Application "node_modules/govuk-frontend/dist/govuk/assets" ) + config.autoload_paths += %W[#{config.root}/app/assets/components] config.exceptions_app = routes end end diff --git a/config/locales/en.yml b/config/locales/en.yml index e3b586c4e..e64e4f518 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -38,3 +38,9 @@ en: roles_include: "%{persona_name}’s role includes:" sign_in_as: "Sign In as %{persona_name}" title: Test users + service_updates: + claims: + heading: Claims news and updates + placements: + heading: Placements news and updates + contents: Contents diff --git a/config/routes.rb b/config/routes.rb index 66a14a6d4..bcfdf0e9e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -17,6 +17,8 @@ post("/auth/developer/callback", to: "sessions#callback") end + resources :service_updates, only: %i[index] + draw :placements draw :claims diff --git a/db/claims_service_updates.yml b/db/claims_service_updates.yml new file mode 100644 index 000000000..79867c3f9 --- /dev/null +++ b/db/claims_service_updates.yml @@ -0,0 +1,11 @@ +# Example structure for adding service updates + +# - date: +# type: String +# format: YYYY-MM-DD +# title: +# type: String +# format: Short text +# content: +# type: String +# format: Multi-line text diff --git a/db/placements_service_updates.yml b/db/placements_service_updates.yml new file mode 100644 index 000000000..79867c3f9 --- /dev/null +++ b/db/placements_service_updates.yml @@ -0,0 +1,11 @@ +# Example structure for adding service updates + +# - date: +# type: String +# format: YYYY-MM-DD +# title: +# type: String +# format: Short text +# content: +# type: String +# format: Multi-line text diff --git a/spec/models/service_update_spec.rb b/spec/models/service_update_spec.rb new file mode 100644 index 000000000..d90c94c54 --- /dev/null +++ b/spec/models/service_update_spec.rb @@ -0,0 +1,72 @@ +# spec/models/service_update_spec.rb + +require "rails_helper" + +RSpec.describe ServiceUpdate do + describe ".where" do + context "when service is :claims" do + it "returns updates for claims" do + allow(YAML).to receive(:load_file).and_return( + [ + { + date: "2023-12-14", + title: "Claim Update", + content: "Some content" + } + ] + ) + updates = ServiceUpdate.where(service: :claims) + expect(updates.length).to eq(1) + expect(updates.first.title).to eq("Claim Update") + end + + it "returns an empty hash when no updates for claims" do + allow(YAML).to receive(:load_file).and_return(nil) + updates = ServiceUpdate.where(service: :claims) + expect(updates).to eq([]) + end + end + + context "when service is :placements" do + it "returns updates for placements" do + allow(YAML).to receive(:load_file).and_return( + [ + { + date: "2023-12-14", + title: "Placement Update", + content: "Some content" + } + ] + ) + updates = ServiceUpdate.where(service: :placements) + expect(updates.length).to eq(1) + expect(updates.first.title).to eq("Placement Update") + end + + it "returns an empty hash when no updates for placements" do + allow(YAML).to receive(:load_file).and_return(nil) + updates = ServiceUpdate.where(service: :placements) + expect(updates).to eq([]) + end + end + end + + describe ".yaml_file" do + it "returns claims YAML file path" do + file_path = ServiceUpdate.file_path(service: :claims) + expect(file_path).to eq(Rails.root.join("db/claims_service_updates.yml")) + end + + it "returns placements YAML file path" do + file_path = ServiceUpdate.file_path(service: :placements) + expect(file_path).to eq( + Rails.root.join("db/placements_service_updates.yml") + ) + end + + it "returns nil for unknown service" do + file_path = ServiceUpdate.file_path(service: :some_other_random_service) + expect(file_path).to be_nil + end + end +end diff --git a/spec/system/service_updates_page_spec.rb b/spec/system/service_updates_page_spec.rb new file mode 100644 index 000000000..c9f553b67 --- /dev/null +++ b/spec/system/service_updates_page_spec.rb @@ -0,0 +1,43 @@ +require "rails_helper" + +RSpec.feature "Service updates page" do + after { Capybara.app_host = nil } + + scenario "User visits the claims Service updates" do + given_i_am_on_the_claims_site + and_i_am_on_the_service_updates_page + i_can_see_the_claims_service_title + end + + scenario "User visits the placements Service updates" do + given_i_am_on_the_placements_site + and_i_am_on_the_service_updates_page + i_can_see_the_placements_service_title + end + + private + + def given_i_am_on_the_claims_site + Capybara.app_host = "http://#{ENV["CLAIMS_HOST"]}" + end + + def given_i_am_on_the_placements_site + Capybara.app_host = "http://#{ENV["PLACEMENTS_HOST"]}" + end + + def and_i_am_on_the_service_updates_page + visit "/service_updates" + end + + def i_can_see_the_claims_service_title + within(".govuk-heading-xl") do + expect(page).to have_content("Claims news and updates") + end + end + + def i_can_see_the_placements_service_title + within(".govuk-heading-xl") do + expect(page).to have_content("Placements news and updates") + end + end +end