diff --git a/app/components/programme_status_component.rb b/app/components/programme_status_component.rb
new file mode 100644
index 000000000..e88a42dc7
--- /dev/null
+++ b/app/components/programme_status_component.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class ProgrammeStatusComponent < ViewComponent::Base
+ def initialize(user_programme_enrolment:)
+ @user_programme_enrolment = user_programme_enrolment
+ @cms_programme_model = Cms::Collections::Programme.get(@user_programme_enrolment.programme.slug)
+ end
+
+ def render?
+ @user_programme_enrolment.in_state?(:pending) || @user_programme_enrolment.in_state?(:complete)
+ end
+
+ def title
+ if @user_programme_enrolment.in_state?(:pending)
+ @cms_programme_model.status_pending_title.value
+ else
+ @cms_programme_model.status_completed_title.value
+ end
+ end
+
+ def content
+ if @user_programme_enrolment.in_state?(:pending)
+ @cms_programme_model.status_pending_text
+ else
+ @cms_programme_model.status_completed_text
+ end
+ end
+end
diff --git a/app/components/programme_status_component/programme_status_component.html.erb b/app/components/programme_status_component/programme_status_component.html.erb
new file mode 100644
index 000000000..72acac5bc
--- /dev/null
+++ b/app/components/programme_status_component/programme_status_component.html.erb
@@ -0,0 +1,20 @@
+
+<%= render GovGridRowComponent.new(additional_classes: "programme-status-wrapper", background_color: "white") do |row| %>
+ <%= row.with_column("two-thirds") do %>
+
+
<%= title %>
+ <% if @user_programme_enrolment.in_state?(:complete) %>
+ <%= image_pack_tag "media/images/icons/programme_completed_icon.svg" %>
+ <% end %>
+
+ <%= render content.render %>
+ <% end %>
+ <%= row.with_column("one-third") do %>
+ <% if @user_programme_enrolment.in_state?(:complete) %>
+
+ <%= link_to 'Download your certificate', @user_programme_enrolment.programme.certificate_path,
+ class: 'govuk-button button', role: 'button' %>
+
+ <% end %>
+ <% end %>
+<% end %>
\ No newline at end of file
diff --git a/app/components/programme_status_component/programme_status_component.scss b/app/components/programme_status_component/programme_status_component.scss
new file mode 100644
index 000000000..a21886a8b
--- /dev/null
+++ b/app/components/programme_status_component/programme_status_component.scss
@@ -0,0 +1,19 @@
+.programme-status-wrapper {
+
+ &__title-row {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ margin-bottom: 15px;
+ gap: 20px;
+
+ h2 {
+ margin-bottom: 0;
+ }
+ }
+
+ &__button-container {
+ padding-top: 50px;
+ text-align: center;
+ }
+}
\ No newline at end of file
diff --git a/app/models/programme.rb b/app/models/programme.rb
index 003ecd389..6a0888b24 100644
--- a/app/models/programme.rb
+++ b/app/models/programme.rb
@@ -151,10 +151,6 @@ def user_is_eligible?(user)
true
end
- def enrichment_enabled?
- false
- end
-
def programme_objectives
programme_activity_groupings.includes(:programme_activities).order(:sort_key)
end
diff --git a/app/models/programmes/a_level.rb b/app/models/programmes/a_level.rb
index f852f51a0..7b4544825 100644
--- a/app/models/programmes/a_level.rb
+++ b/app/models/programmes/a_level.rb
@@ -42,5 +42,9 @@ def achievement_type
def enrolment_confirmation_required?
false
end
+
+ def certificate_path
+ certificate_a_level_path
+ end
end
end
diff --git a/app/models/programmes/cs_accelerator.rb b/app/models/programmes/cs_accelerator.rb
index 61cd957c4..844c70439 100644
--- a/app/models/programmes/cs_accelerator.rb
+++ b/app/models/programmes/cs_accelerator.rb
@@ -135,5 +135,9 @@ def achievement_type
def enrolment_confirmation_required?
false
end
+
+ def certificate_path
+ certificate_cs_accelerator_certificate_path
+ end
end
end
diff --git a/app/models/programmes/i_belong.rb b/app/models/programmes/i_belong.rb
index 39c06e816..3c1425fab 100644
--- a/app/models/programmes/i_belong.rb
+++ b/app/models/programmes/i_belong.rb
@@ -58,5 +58,9 @@ def achievement_type
def enrolment_confirmation_required?
false
end
+
+ def certificate_path
+ certificate_i_belong_path
+ end
end
end
diff --git a/app/models/programmes/primary_certificate.rb b/app/models/programmes/primary_certificate.rb
index f9ed6d764..4a7234e2d 100644
--- a/app/models/programmes/primary_certificate.rb
+++ b/app/models/programmes/primary_certificate.rb
@@ -38,10 +38,6 @@ def pathways?
true
end
- def enrichment_enabled?
- true
- end
-
def send_pending_mail?
true
end
@@ -57,5 +53,9 @@ def achievement_type
def enrolment_confirmation_required?
true
end
+
+ def certificate_path
+ certificate_primary_certificate_path
+ end
end
end
diff --git a/app/models/programmes/secondary_certificate.rb b/app/models/programmes/secondary_certificate.rb
index a1b474afc..b5b971148 100644
--- a/app/models/programmes/secondary_certificate.rb
+++ b/app/models/programmes/secondary_certificate.rb
@@ -38,10 +38,6 @@ def pathways?
true
end
- def enrichment_enabled?
- true
- end
-
def programme_objectives
[
ProgrammeObjectives::ProgrammeCompletionRequired.new(
@@ -68,5 +64,9 @@ def achievement_type
def enrolment_confirmation_required?
false
end
+
+ def certificate_path
+ certificate_secondary_certificate_path
+ end
end
end
diff --git a/app/services/cms/collections/programme.rb b/app/services/cms/collections/programme.rb
new file mode 100644
index 000000000..1cbdbe8b2
--- /dev/null
+++ b/app/services/cms/collections/programme.rb
@@ -0,0 +1,33 @@
+module Cms
+ module Collections
+ class Programme < Resource
+ def to_search_record(index_time)
+ raise NotImplementedError
+ end
+
+ def self.is_collection = true
+
+ def self.collection_attribute_mappings
+ []
+ end
+
+ def self.resource_attribute_mappings
+ [
+ {model: Models::Slug, key: nil, param_name: :slug},
+ {model: Models::TextField, key: :statusPendingTitle},
+ {model: Models::TextBlock, key: :statusPendingText, with_wrapper: false},
+ {model: Models::TextField, key: :statusCompletedTitle},
+ {model: Models::TextBlock, key: :statusCompletedText, with_wrapper: false}
+ ]
+ end
+
+ def self.cache_expiry
+ 15.minutes
+ end
+
+ def self.resource_key
+ "programmes"
+ end
+ end
+ end
+end
diff --git a/app/services/cms/models/text_field.rb b/app/services/cms/models/text_field.rb
new file mode 100644
index 000000000..9ecf6ffd1
--- /dev/null
+++ b/app/services/cms/models/text_field.rb
@@ -0,0 +1,15 @@
+module Cms
+ module Models
+ class TextField
+ attr_accessor :value
+
+ def initialize(value:)
+ @value = value
+ end
+
+ def render
+ # has no render method, this exists just to provide data to model
+ end
+ end
+ end
+end
diff --git a/app/services/cms/providers/strapi/factories/model_factory.rb b/app/services/cms/providers/strapi/factories/model_factory.rb
index 1e9917648..686837c2b 100644
--- a/app/services/cms/providers/strapi/factories/model_factory.rb
+++ b/app/services/cms/providers/strapi/factories/model_factory.rb
@@ -15,10 +15,17 @@ def self.process_model(mapping, all_data)
to_seo(strapi_data)
elsif model_class == Models::Slug
model_class.new(slug: strapi_data[:slug])
+ elsif model_class == Models::TextField
+ model_class.new(value: strapi_data)
elsif model_class == Models::FeaturedImage
to_featured_image(strapi_data[:data][:attributes]) if strapi_data[:data]
elsif model_class == Models::TextBlock
- to_content_block(strapi_data)
+ with_wrapper = if mapping.key?(:with_wrapper)
+ mapping[:with_wrapper]
+ else
+ true
+ end
+ to_content_block(strapi_data, with_wrapper:)
elsif model_class == Models::SimpleTitle
model_class.new(title: strapi_data)
elsif model_class == Models::Aside
diff --git a/app/services/cms/providers/strapi/mocks/programme.rb b/app/services/cms/providers/strapi/mocks/programme.rb
new file mode 100644
index 000000000..b029c6c12
--- /dev/null
+++ b/app/services/cms/providers/strapi/mocks/programme.rb
@@ -0,0 +1,15 @@
+module Cms
+ module Providers
+ module Strapi
+ module Mocks
+ class Programme < StrapiMock
+ attribute(:slug) { Faker::Internet.slug }
+ attribute(:statusPendingTitle) { Faker::Internet.slug }
+ attribute(:statusPendingText) { RichBlocks.generate_data }
+ attribute(:statusCompletedTitle) { Faker::Internet.slug }
+ attribute(:statusCompletedText) { RichBlocks.generate_data }
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/cms/resource.rb b/app/services/cms/resource.rb
index bd66cc440..6b2edbffc 100644
--- a/app/services/cms/resource.rb
+++ b/app/services/cms/resource.rb
@@ -14,6 +14,26 @@ def to_search_record(index_time)
raise NotImplementedError
end
+ def respond_to_missing?(method_name, include_private = false)
+ self.class.param_indexes.key?(method_name) || super
+ end
+
+ def method_missing(method_name)
+ index = self.class.param_indexes[method_name]
+ data_models[index]
+ end
+
+ def self.param_name(mapping)
+ return mapping[:param_name] if mapping.has_key?(:param_name)
+ mapping[:key].to_s.underscore.to_sym
+ end
+
+ def self.param_indexes
+ resource_attribute_mappings.each_with_object({}).with_index do |(mapping, indexes), index|
+ indexes[param_name(mapping)] = index
+ end
+ end
+
def self.is_collection = false
def self.resource_attribute_mappings
diff --git a/app/webpacker/images/icons/programme_completed_icon.svg b/app/webpacker/images/icons/programme_completed_icon.svg
new file mode 100644
index 000000000..95bf94469
--- /dev/null
+++ b/app/webpacker/images/icons/programme_completed_icon.svg
@@ -0,0 +1,6 @@
+
diff --git a/previews/components/cms_split_horizontal_card_component_preview.rb b/previews/components/cms_split_horizontal_card_component_preview.rb
index a153d1598..597e92aa0 100644
--- a/previews/components/cms_split_horizontal_card_component_preview.rb
+++ b/previews/components/cms_split_horizontal_card_component_preview.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-class CmsSplitHorizontialCardComponentPreview < ViewComponent::Preview
+class CmsSplitHorizontalCardComponentPreview < ViewComponent::Preview
def default
- render(CmsSplitHorizontialCardComponent.new(
+ render(CmsSplitHorizontalCardComponent.new(
section_title: Faker::Lorem.sentence,
card_content: Cms::Mocks::RichBlocks.as_model,
aside_content: Cms::Mocks::RichBlocks.as_model,
diff --git a/previews/components/programme_status_component_preview.rb b/previews/components/programme_status_component_preview.rb
new file mode 100644
index 000000000..fc877c55f
--- /dev/null
+++ b/previews/components/programme_status_component_preview.rb
@@ -0,0 +1,27 @@
+class ProgrammeStatusComponentPreview < ViewComponent::Preview
+ class UPEMock
+ def initialize(status)
+ @status = status
+ end
+
+ def in_state?(status)
+ @status == status
+ end
+
+ def programme = Programme.primary_certificate
+ end
+
+ def pending
+ user_programme_enrolment = UPEMock.new(:pending)
+ render(
+ ProgrammeStatusComponent.new(user_programme_enrolment:)
+ )
+ end
+
+ def complete
+ user_programme_enrolment = UPEMock.new(:complete)
+ render(
+ ProgrammeStatusComponent.new(user_programme_enrolment:)
+ )
+ end
+end
diff --git a/spec/components/programme_status_component_spec.rb b/spec/components/programme_status_component_spec.rb
new file mode 100644
index 000000000..2662f4dd2
--- /dev/null
+++ b/spec/components/programme_status_component_spec.rb
@@ -0,0 +1,60 @@
+require "rails_helper"
+
+RSpec.describe ProgrammeStatusComponent, type: :component do
+ let(:programme) { create(:primary_certificate) }
+
+ before do
+ stub_strapi_programme(programme.slug, programme: Cms::Mocks::Programme.generate_raw_data(
+ status_pending_title: "Pending title",
+ status_completed_title: "Completed title"
+ ))
+ end
+
+ context "Enrolment enrolled" do
+ let(:user_programme_enrolment) { create(:user_programme_enrolment, programme:) }
+
+ before do
+ render_inline(described_class.new(user_programme_enrolment:))
+ end
+
+ it "should not render" do
+ expect(page).not_to have_css(".programme-status-wrapper")
+ end
+ end
+
+ context "Enrolment Pending" do
+ let(:user_programme_enrolment) { create(:pending_enrolment, programme:) }
+
+ before do
+ render_inline(described_class.new(user_programme_enrolment:))
+ end
+
+ it "should render the wrapper" do
+ expect(page).to have_css(".programme-status-wrapper")
+ end
+
+ it "should render the correct heading" do
+ expect(page).to have_css("h2.govuk-heading-m", text: "Pending title")
+ end
+ end
+
+ context "Enrolment completed" do
+ let(:user_programme_enrolment) { create(:completed_enrolment, programme:) }
+
+ before do
+ render_inline(described_class.new(user_programme_enrolment:))
+ end
+
+ it "should render the wrapper" do
+ expect(page).to have_css(".programme-status-wrapper")
+ end
+
+ it "should render the correct heading" do
+ expect(page).to have_css("h2.govuk-heading-m", text: "Completed title")
+ end
+
+ it "should render cerficate button" do
+ expect(page).to have_link("Download your certificate", href: "/certificate/primary-certificate/view-certificate")
+ end
+ end
+end
diff --git a/spec/models/programme_spec.rb b/spec/models/programme_spec.rb
index 1398d0ea4..c3d223d3d 100644
--- a/spec/models/programme_spec.rb
+++ b/spec/models/programme_spec.rb
@@ -345,14 +345,6 @@
end
end
- describe "#enrichment_enabled?" do
- let(:programme) { create(:programme) }
-
- it "should return false" do
- expect(programme.enrichment_enabled?).to be false
- end
- end
-
describe "#certificate_name" do
it "should return not implemented error" do
expect { generic_programme.certificate_name }.to raise_error(NotImplementedError)
diff --git a/spec/models/programmes/a_level_spec.rb b/spec/models/programmes/a_level_spec.rb
index 9dba19356..db0f4052a 100644
--- a/spec/models/programmes/a_level_spec.rb
+++ b/spec/models/programmes/a_level_spec.rb
@@ -24,6 +24,12 @@
end
end
+ describe "#enrolment_confirmation_required?" do
+ it "should return false" do
+ expect(subject.enrolment_confirmation_required?).to be false
+ end
+ end
+
describe "#enrol_path" do
it "returns the path for the enrol" do
expect(subject.enrol_path(user_programme_enrolment: {user_id: "user_id",
@@ -52,4 +58,10 @@
expect(subject.programme_objectives[1..]).to match_array(pags)
end
end
+
+ describe "#certificate_path" do
+ it "should return its certificate path" do
+ expect(subject.certificate_path).to eq "/certificate/a-level-certificate/view-certificate"
+ end
+ end
end
diff --git a/spec/models/programmes/cs_accelerator_spec.rb b/spec/models/programmes/cs_accelerator_spec.rb
index fd22e9489..7bc097fdb 100644
--- a/spec/models/programmes/cs_accelerator_spec.rb
+++ b/spec/models/programmes/cs_accelerator_spec.rb
@@ -520,4 +520,16 @@
expect(programme.auto_enrollable?).to be true
end
end
+
+ describe "#enrolment_confirmation_required?" do
+ it "should return false" do
+ expect(programme.enrolment_confirmation_required?).to be false
+ end
+ end
+
+ describe "#certificate_path" do
+ it "should return its certificate path" do
+ expect(programme.certificate_path).to eq "/certificate/subject-knowledge/view-certificate"
+ end
+ end
end
diff --git a/spec/models/programmes/i_belong_spec.rb b/spec/models/programmes/i_belong_spec.rb
index f1168ef26..93e688a22 100644
--- a/spec/models/programmes/i_belong_spec.rb
+++ b/spec/models/programmes/i_belong_spec.rb
@@ -58,4 +58,16 @@
expect(programme.minimum_character_required_community_evidence).to eq(50)
end
end
+
+ describe "#enrolment_confirmation_required?" do
+ it "should return false" do
+ expect(programme.enrolment_confirmation_required?).to be false
+ end
+ end
+
+ describe "#certificate_path" do
+ it "should return its certificate path" do
+ expect(programme.certificate_path).to eq "/certificate/i-belong/view-certificate"
+ end
+ end
end
diff --git a/spec/models/programmes/primary_certificate_spec.rb b/spec/models/programmes/primary_certificate_spec.rb
index c77234919..bc5266659 100644
--- a/spec/models/programmes/primary_certificate_spec.rb
+++ b/spec/models/programmes/primary_certificate_spec.rb
@@ -105,4 +105,10 @@
expect(programme.enrolment_confirmation_required?).to be true
end
end
+
+ describe "#certificate_path" do
+ it "should return its certificate path" do
+ expect(programme.certificate_path).to eq "/certificate/primary-certificate/view-certificate"
+ end
+ end
end
diff --git a/spec/models/programmes/secondary_certificate_spec.rb b/spec/models/programmes/secondary_certificate_spec.rb
index a8a464c92..0ddccd8bf 100644
--- a/spec/models/programmes/secondary_certificate_spec.rb
+++ b/spec/models/programmes/secondary_certificate_spec.rb
@@ -111,4 +111,16 @@
expect(secondary_certificate.auto_enrollable?).to be true
end
end
+
+ describe "#enrolment_confirmation_required?" do
+ it "should return false" do
+ expect(secondary_certificate.enrolment_confirmation_required?).to be false
+ end
+ end
+
+ describe "#certificate_path" do
+ it "should return its certificate path" do
+ expect(secondary_certificate.certificate_path).to eq "/certificate/secondary-certificate/view-certificate"
+ end
+ end
end
diff --git a/spec/services/cms/collections/programme_spec.rb b/spec/services/cms/collections/programme_spec.rb
new file mode 100644
index 000000000..2cc1a1c01
--- /dev/null
+++ b/spec/services/cms/collections/programme_spec.rb
@@ -0,0 +1,36 @@
+require "rails_helper"
+
+RSpec.describe Cms::Collections::Programme do
+ it "should have correct resource_key" do
+ expect(described_class.resource_key).to eq("programmes")
+ end
+
+ it "should have 15 minute cache expiry" do
+ expect(described_class.cache_expiry).to eq(15.minutes)
+ end
+
+ it "defines not collection attribute mappings" do
+ expect(described_class.collection_attribute_mappings).to eq([])
+ end
+
+ context "slug" do
+ let(:slug) { "programme-resource-test" }
+ let!(:programme) { create(:programme, slug:) }
+ before do
+ mock_data = Cms::Mocks::Programme.generate_raw_data(slug:)
+ stub_strapi_programme(slug, programme: mock_data)
+ end
+
+ it "should return correctly for single page" do
+ cms_programme = described_class.get(slug)
+ expect(cms_programme.slug.slug).to eq(slug)
+ end
+
+ it "should raise error for search records" do
+ cms_programme = described_class.get(slug)
+ expect {
+ cms_programme.to_search_record(DateTime.now)
+ }.to raise_exception(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/services/cms/resource_spec.rb b/spec/services/cms/resource_spec.rb
index c94237bc4..74b2085a8 100644
--- a/spec/services/cms/resource_spec.rb
+++ b/spec/services/cms/resource_spec.rb
@@ -99,8 +99,12 @@ def self.resource_key
end
end
- it "calling all gets collection object" do
+ before do
stub_strapi_get_collection_entity("cms-collection-resource-test")
+ stub_strapi_get_single_blog_post("cms-collection-resource-test/test-post")
+ end
+
+ it "calling all gets collection object" do
response = collection_class.all(1, 10)
expect(response).to be_a Cms::Collection
end
@@ -109,19 +113,54 @@ def self.resource_key
expect(collection_class.collection_attribute_mappings).to be_a Array
end
+ describe "#param_name" do
+ it "should use key as param_name" do
+ expect(collection_class.param_name({key: :title, model: Cms::Models::SimpleTitle})).to eq(:title)
+ end
+
+ it "should use param_name as name when specified" do
+ expect(collection_class.param_name({key: :title, model: Cms::Models::SimpleTitle, param_name: :something})).to eq(:something)
+ end
+ end
+
+ describe "#param_indexes" do
+ it "should include key" do
+ expect(collection_class.param_indexes).to have_key(:title)
+ end
+
+ it "should set correct index" do
+ expect(collection_class.param_indexes[:title]).to eq(0)
+ end
+ end
+
it "calling collection_view should create component" do
- stub_strapi_get_collection_entity("cms-collection-resource-test")
response = collection_class.all(1, 10)
resource = response.resources.first
expect(resource.data_models.first).to be_a Cms::Models::SimpleTitle
end
it "calling get should return single record" do
- stub_strapi_get_single_blog_post("cms-collection-resource-test/test-post")
response = collection_class.get("test-post")
expect(response).to be_a described_class
end
+ it "should raise error for search_record" do
+ response = collection_class.get("test-post")
+ expect {
+ response.to_search_record(DateTime.now)
+ }.to raise_exception(NotImplementedError)
+ end
+
+ it "should respond_to mapping key" do
+ response = collection_class.get("test-post")
+ expect(response).to respond_to(:title)
+ end
+
+ it "should respond data model for mapping key" do
+ response = collection_class.get("test-post")
+ expect(response.title).to be_a Cms::Models::SimpleTitle
+ end
+
context "should cache results" do
let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) }
diff --git a/spec/support/cms/providers/strapi/strapi_stubs.rb b/spec/support/cms/providers/strapi/strapi_stubs.rb
index aabbfc88d..0d6841663 100644
--- a/spec/support/cms/providers/strapi/strapi_stubs.rb
+++ b/spec/support/cms/providers/strapi/strapi_stubs.rb
@@ -111,6 +111,10 @@ def stub_strapi_web_page_not_found(key)
stub_request(:get, /^https:\/\/strapi.teachcomputing.org\/api\/web-pages\/#{key}/).to_return_json(body: not_found_response, status: 404)
end
+ def stub_strapi_programme(key, programme: Cms::Mocks::Programme.generate_raw_data)
+ stub_request(:get, /^https:\/\/strapi.teachcomputing.org\/api\/programmes\/#{key}/).to_return_json(body: {data: programme})
+ end
+
private
def to_strapi_collection(records, page: 1, page_size: 10, page_count: 1)