Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6590e95
Creation of a new email model for email templates which will be moved…
msquance-stem Nov 6, 2024
f606617
Moved email content to use a dynamic zone, and creating new email spe…
msquance-stem Nov 7, 2024
a91c440
Creating suite of tests for the new cms email components the new emai…
msquance-stem Nov 8, 2024
690b0ea
Moving the new components into a module to make it a bit tidier
msquance-stem Nov 11, 2024
1c094c3
Reducing cache time on staging to speed up QA
msquance-stem Nov 28, 2024
34ec86f
Fixing email url issues
msquance-stem Nov 28, 2024
cba4d33
Fixing cache override for staging
msquance-stem Dec 3, 2024
09d609a
Adding activity code to the administrate drop down
msquance-stem Dec 3, 2024
2f932ca
Sorting schema
msquance-stem Dec 18, 2024
364cd8f
Adding the in Year option to merge_content as last_cpd_completed_year
msquance-stem Jan 2, 2025
53de44c
Adding support for mail merge tags in the subject
msquance-stem Jan 2, 2025
6dc0804
Changed the way render works on email components after discovering an…
msquance-stem Jan 3, 2025
ffc848b
Updating email template to include the slugs for completed groups and…
msquance-stem Jan 6, 2025
ad1170a
Updating course number to deal with flaky test
msquance-stem Jan 7, 2025
f94b53c
Fixing broken namespaces after rebase
msquance-stem Feb 4, 2025
06ad2b3
Applying standard fixes for ruby 3.3
msquance-stem Feb 17, 2025
06fbfe3
Converting the email template system to use new GraphQL interface to …
msquance-stem Feb 19, 2025
830789f
Fixing standard issues and removing my byebug
msquance-stem Feb 19, 2025
7fb85e3
Fixing flaky test
msquance-stem Feb 24, 2025
5d78785
Adding graphql testing for email content components
msquance-stem Feb 28, 2025
3030692
Fixing nil handling for completed programme activity groups
msquance-stem Mar 3, 2025
9500aa0
Fixing nil error in has match
msquance-stem Mar 3, 2025
eeff346
Updating schema and fixing the issue of months shorter than 30 days
msquance-stem Mar 3, 2025
2eb0846
Updating logic to match all achievements not just the last one.
msquance-stem Mar 3, 2025
4c9da0c
Standardrb fixes
msquance-stem Mar 4, 2025
e3f8f97
Addressing PR comments
msquance-stem Mar 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions app/components/cms/email_course_list_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

module Cms
class EmailCourseListComponent < ViewComponent::Base
erb_template <<~ERB
<table>
<% if @section_title %>
<tr>
<td>
<h2><%= @section_title %></h2>
</td>
</tr>
<% end %>
<% @courses.each do |course| %>
<tr>
<td>
<%= link_to display_name(course), course_link(course) %>
</td>
</tr>
<% end %>
</table>
ERB

def initialize(courses:, section_title:)
@courses = courses
@section_title = section_title
end

def display_name(course)
course.display_name.presence || course.activity.title
end

def course_link(course)
course_url(id: course.activity.stem_activity_code, name: course.activity.title.parameterize)
end
end
end
103 changes: 103 additions & 0 deletions app/components/cms/rich_text_block_text_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Due to how ERB interacts with newlines and spaces the markup for any
# SubClasses should not include any indentation and should make use of
# `-` at the end of ERB tags
module Cms
class RichTextBlockTextComponent < ViewComponent::Base
def build(blocks, **)
klass =
case blocks
in { type: "paragraph" } then Paragraph
in { type: "heading" } then Heading
in { type: "text" } then Text
in { type: "link" } then Link
in { type: "list" } then List
in { type: "list-item" } then ListItem
in { type: "quote"} then Quote
end

klass.new(blocks: blocks, **)
end

erb_template <<~ERB
<% @blocks.each do |child| -%>
<%= render build(child) %> \n
<% end -%>
ERB

def initialize(blocks:, **options)
@blocks = blocks
@options = options
end

class Paragraph < RichTextBlockTextComponent
erb_template <<~ERB
<% @blocks[:children].each do |child| -%>
<%= render build(child) -%>
<% end -%>
ERB
end

class Heading < RichTextBlockTextComponent
erb_template <<~ERB
<% @blocks[:children].each do |child| -%>
<%= render build(child) -%>
<% end -%>
ERB
end

class Text < RichTextBlockTextComponent
erb_template <<~ERB
<%= @blocks[:text] -%>
ERB
end

class Link < RichTextBlockTextComponent
erb_template <<~ERB
<% @blocks[:children].each do |child| -%>
<%= render build(child) -%>
<% end -%>
<%= url -%>
ERB

def url
" (#{@blocks[:url]})"
end
end

class List < RichTextBlockTextComponent
erb_template <<~ERB
<% @blocks[:children].each_with_index do |child, index| -%>
<%= render build(child, type:, index:) -%>
<% end -%>
ERB

def type
@blocks[:format]
end
end

class ListItem < RichTextBlockTextComponent
erb_template <<~ERB
<% @blocks[:children].each do |child| -%>
<%= icon -%> <%= render build(child) %>
<% end -%>
ERB

def icon
if @options[:type] == "ordered"
"#{@options[:index] + 1}."
else
"*"
end
end
end

class Quote < RichTextBlockTextComponent
erb_template <<~ERB
<% @blocks[:children].each do |child| -%>
<%= render build(child) -%>
<% end -%>
ERB
end
end
end
2 changes: 2 additions & 0 deletions app/dashboards/achievement_dashboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class AchievementDashboard < BaseDashboard
ATTRIBUTE_TYPES = {
activity: GroupedActivityListField,
user: Field::BelongsTo,
stem_activity_code: Field::String,
self_verification_info: Field::String,
current_state: ValidStatePickerField,
id: Field::String,
Expand All @@ -24,6 +25,7 @@ class AchievementDashboard < BaseDashboard
# Feel free to add, remove, or rearrange items.
COLLECTION_ATTRIBUTES = %i[
user
stem_activity_code
activity
current_state
created_at
Expand Down
2 changes: 1 addition & 1 deletion app/fields/grouped_activity_list_field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def grouped_by_category
Activity.includes([:programmes]).all.group_by(&:category).each_with_object([]) do |(cat, acts), arr|
arr << [cat, acts.sort_by(&:title).map {
[
"#{_1.title} #{_1.remote_delivered_cpd ? "(remote)" : ""} -- (#{_1.programmes.collect(&:slug).join(", ")})",
"#{_1.stem_activity_code} #{_1.title} #{_1.remote_delivered_cpd ? "(remote)" : ""} -- (#{_1.programmes.collect(&:slug).join(", ")})",
_1.id
]
}]
Expand Down
11 changes: 11 additions & 0 deletions app/mailers/cms_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class CmsMailer < ApplicationMailer
def send_template
template_slug = params[:template_slug]
@user = User.find(params[:user_id])
@template = Cms::Collections::EmailTemplate.get(template_slug).template

@subject = @template.subject(@user)

mail(to: @user.email, subject: @subject)
end
end
4 changes: 4 additions & 0 deletions app/models/achievement.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ class Achievement < ApplicationRecord
joins(:activity).where.not(activities: {category:})
}

scope :with_most_recent_transition, -> {
joins(most_recent_transition_join)
}

scope :sort_complete_first, lambda {
select("achievements.*, COALESCE(most_recent_achievement_transition.to_state, 'enrolled') as current_state")
.joins(most_recent_transition_join)
Expand Down
7 changes: 7 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ def self.from_auth(id, credentials, info)
user
end

def sorted_completed_cpd_achievements_by(programme:)
achievements.includes(:activity).with_courses.in_state(:complete)
.belonging_to_programme(programme)
.with_most_recent_transition
.order("most_recent_achievement_transition.updated_at")
end

def enrolments
user_programme_enrolments
end
Expand Down
28 changes: 28 additions & 0 deletions app/services/cms/collections/email_template.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Cms
module Collections
class EmailTemplate < Resource
def self.is_collection = true

def self.collection_attribute_mapping
[
{model: Cms::Models::EmailTemplate, key: nil, param_name: :template}
]
end

def self.resource_attribute_mappings
[
{model: Cms::Models::EmailTemplate, key: nil, param_name: :template}
]
end

def self.cache_expiry
return 10.seconds if Rails.env.staging?
15.minutes
end

def self.resource_key = "email-templates"

def self.graphql_key = "emailTemplates"
end
end
end
17 changes: 17 additions & 0 deletions app/services/cms/email_components/base_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module Cms
module EmailComponents
class BaseComponent
def render?(email_template, user)
true
end

def render(email_template, user)
raise NotImplementedError
end

def render_text(email_template, user)
raise NotImplementedError
end
end
end
end
85 changes: 85 additions & 0 deletions app/services/cms/email_components/course_list.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
module Cms
module EmailComponents
class CourseList < BaseComponent
attr_accessor :section_title, :courses, :remove_on_match

def initialize(section_title:, courses:, remove_on_match:)
@section_title = section_title
@courses = courses
@remove_on_match = remove_on_match
@has_subsitutes = @courses.collect(&:substitute).any?
end

def activity_list(email_template, user)
completed_activities = user.sorted_completed_cpd_achievements_by(programme: email_template.programme).collect(&:activity)
matched = false
display_courses = @courses.select { !_1.substitute }.each_with_object([]) do |course, list|
if completed_activities.include?(course.activity)
matched = true
else
list << course
end
end
display_courses += @courses.select { _1.substitute } if matched
display_courses
end

def matches(email_template, user)
activites = user.sorted_completed_cpd_achievements_by(programme: email_template.programme).collect(&:activity)
@courses.map { activites.include?(_1.activity) }
end

def render?(email_template, user)
course_matches = matches(email_template, user)
return !course_matches.any? if @remove_on_match
return @has_subsitutes if course_matches.all?
true
end

def render(email_template, user)
Cms::EmailCourseListComponent.new(courses: activity_list(email_template, user), section_title:)
end

def render_text(email_template, user)
CourseListText.new(activity_list(email_template, user), section_title:)
end
end

class Course
attr_accessor :activity_code, :display_name, :substitute, :activity

def initialize(activity_code:, display_name:, substitute:)
@activity_code = activity_code
@display_name = display_name
@substitute = substitute
@activity = Activity.find_by(stem_activity_code: activity_code)
end
end

class CourseListText
include Rails.application.routes.url_helpers

def initialize(course_list, section_title:)
@course_list = course_list
@section_title = section_title
end

def render_in(view_context)
view_context.render inline: content
end

def content
course_text = @course_list.map { display(_1) }.join("\n") + "\n"
return "#{@section_title}\n\n#{course_text}" if @section_title

course_text
end

def display(course)
"#{course.display_name.presence || course.activity.title} (#{course_url(id: course.activity.stem_activity_code, name: course.activity.title.parameterize)})"
end

def format = :text
end
end
end
33 changes: 33 additions & 0 deletions app/services/cms/email_components/cta.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module Cms
module EmailComponents
class Cta < BaseComponent
attr_accessor :text, :link

def initialize(text:, link:)
@text = text
@link = link
end

def render(email_template, user)
NcceButtonComponent.new(title: @text, link:)
end

def render_text(email_template, user)
CtaText.new(@text, @link)
end
end

class CtaText
def initialize(text, link)
@text = text
@link = link
end

def render_in(view_context)
view_context.render inline: "#{@text} (#{@link})\n"
end

def format = :text
end
end
end
19 changes: 19 additions & 0 deletions app/services/cms/email_components/text.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module Cms
module EmailComponents
class Text < BaseComponent
attr_accessor :blocks

def initialize(blocks:)
@blocks = blocks
end

def render(email_template, user)
Cms::RichTextBlockComponent.new(blocks: email_template.process_blocks(@blocks, user), with_wrapper: false)
end

def render_text(email_template, user)
Cms::RichTextBlockTextComponent.new(blocks: email_template.process_blocks(@blocks, user))
end
end
end
end
Loading