Skip to content

Commit aaaf445

Browse files
Merge pull request #2212 from NCCE/2896-email-system-content-processing
Email content processing from Strapi
2 parents cd424e3 + e3f8f97 commit aaaf445

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1434
-4
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
module Cms
4+
class EmailCourseListComponent < ViewComponent::Base
5+
erb_template <<~ERB
6+
<table>
7+
<% if @section_title %>
8+
<tr>
9+
<td>
10+
<h2><%= @section_title %></h2>
11+
</td>
12+
</tr>
13+
<% end %>
14+
<% @courses.each do |course| %>
15+
<tr>
16+
<td>
17+
<%= link_to display_name(course), course_link(course) %>
18+
</td>
19+
</tr>
20+
<% end %>
21+
</table>
22+
ERB
23+
24+
def initialize(courses:, section_title:)
25+
@courses = courses
26+
@section_title = section_title
27+
end
28+
29+
def display_name(course)
30+
course.display_name.presence || course.activity.title
31+
end
32+
33+
def course_link(course)
34+
course_url(id: course.activity.stem_activity_code, name: course.activity.title.parameterize)
35+
end
36+
end
37+
end
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Due to how ERB interacts with newlines and spaces the markup for any
2+
# SubClasses should not include any indentation and should make use of
3+
# `-` at the end of ERB tags
4+
module Cms
5+
class RichTextBlockTextComponent < ViewComponent::Base
6+
def build(blocks, **)
7+
klass =
8+
case blocks
9+
in { type: "paragraph" } then Paragraph
10+
in { type: "heading" } then Heading
11+
in { type: "text" } then Text
12+
in { type: "link" } then Link
13+
in { type: "list" } then List
14+
in { type: "list-item" } then ListItem
15+
in { type: "quote"} then Quote
16+
end
17+
18+
klass.new(blocks: blocks, **)
19+
end
20+
21+
erb_template <<~ERB
22+
<% @blocks.each do |child| -%>
23+
<%= render build(child) %> \n
24+
<% end -%>
25+
ERB
26+
27+
def initialize(blocks:, **options)
28+
@blocks = blocks
29+
@options = options
30+
end
31+
32+
class Paragraph < RichTextBlockTextComponent
33+
erb_template <<~ERB
34+
<% @blocks[:children].each do |child| -%>
35+
<%= render build(child) -%>
36+
<% end -%>
37+
ERB
38+
end
39+
40+
class Heading < RichTextBlockTextComponent
41+
erb_template <<~ERB
42+
<% @blocks[:children].each do |child| -%>
43+
<%= render build(child) -%>
44+
<% end -%>
45+
ERB
46+
end
47+
48+
class Text < RichTextBlockTextComponent
49+
erb_template <<~ERB
50+
<%= @blocks[:text] -%>
51+
ERB
52+
end
53+
54+
class Link < RichTextBlockTextComponent
55+
erb_template <<~ERB
56+
<% @blocks[:children].each do |child| -%>
57+
<%= render build(child) -%>
58+
<% end -%>
59+
<%= url -%>
60+
ERB
61+
62+
def url
63+
" (#{@blocks[:url]})"
64+
end
65+
end
66+
67+
class List < RichTextBlockTextComponent
68+
erb_template <<~ERB
69+
<% @blocks[:children].each_with_index do |child, index| -%>
70+
<%= render build(child, type:, index:) -%>
71+
<% end -%>
72+
ERB
73+
74+
def type
75+
@blocks[:format]
76+
end
77+
end
78+
79+
class ListItem < RichTextBlockTextComponent
80+
erb_template <<~ERB
81+
<% @blocks[:children].each do |child| -%>
82+
<%= icon -%> <%= render build(child) %>
83+
<% end -%>
84+
ERB
85+
86+
def icon
87+
if @options[:type] == "ordered"
88+
"#{@options[:index] + 1}."
89+
else
90+
"*"
91+
end
92+
end
93+
end
94+
95+
class Quote < RichTextBlockTextComponent
96+
erb_template <<~ERB
97+
<% @blocks[:children].each do |child| -%>
98+
<%= render build(child) -%>
99+
<% end -%>
100+
ERB
101+
end
102+
end
103+
end

app/dashboards/achievement_dashboard.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class AchievementDashboard < BaseDashboard
88
ATTRIBUTE_TYPES = {
99
activity: GroupedActivityListField,
1010
user: Field::BelongsTo,
11+
stem_activity_code: Field::String,
1112
self_verification_info: Field::String,
1213
current_state: ValidStatePickerField,
1314
id: Field::String,
@@ -24,6 +25,7 @@ class AchievementDashboard < BaseDashboard
2425
# Feel free to add, remove, or rearrange items.
2526
COLLECTION_ATTRIBUTES = %i[
2627
user
28+
stem_activity_code
2729
activity
2830
current_state
2931
created_at

app/fields/grouped_activity_list_field.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ def grouped_by_category
55
Activity.includes([:programmes]).all.group_by(&:category).each_with_object([]) do |(cat, acts), arr|
66
arr << [cat, acts.sort_by(&:title).map {
77
[
8-
"#{_1.title} #{_1.remote_delivered_cpd ? "(remote)" : ""} -- (#{_1.programmes.collect(&:slug).join(", ")})",
8+
"#{_1.stem_activity_code} #{_1.title} #{_1.remote_delivered_cpd ? "(remote)" : ""} -- (#{_1.programmes.collect(&:slug).join(", ")})",
99
_1.id
1010
]
1111
}]

app/mailers/cms_mailer.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class CmsMailer < ApplicationMailer
2+
def send_template
3+
template_slug = params[:template_slug]
4+
@user = User.find(params[:user_id])
5+
@template = Cms::Collections::EmailTemplate.get(template_slug).template
6+
7+
@subject = @template.subject(@user)
8+
9+
mail(to: @user.email, subject: @subject)
10+
end
11+
end

app/models/achievement.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ class Achievement < ApplicationRecord
3939
joins(:activity).where.not(activities: {category:})
4040
}
4141

42+
scope :with_most_recent_transition, -> {
43+
joins(most_recent_transition_join)
44+
}
45+
4246
scope :sort_complete_first, lambda {
4347
select("achievements.*, COALESCE(most_recent_achievement_transition.to_state, 'enrolled') as current_state")
4448
.joins(most_recent_transition_join)

app/models/user.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ def self.from_auth(id, credentials, info)
6767
user
6868
end
6969

70+
def sorted_completed_cpd_achievements_by(programme:)
71+
achievements.includes(:activity).with_courses.in_state(:complete)
72+
.belonging_to_programme(programme)
73+
.with_most_recent_transition
74+
.order("most_recent_achievement_transition.updated_at")
75+
end
76+
7077
def enrolments
7178
user_programme_enrolments
7279
end
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module Cms
2+
module Collections
3+
class EmailTemplate < Resource
4+
def self.is_collection = true
5+
6+
def self.collection_attribute_mapping
7+
[
8+
{model: Cms::Models::EmailTemplate, key: nil, param_name: :template}
9+
]
10+
end
11+
12+
def self.resource_attribute_mappings
13+
[
14+
{model: Cms::Models::EmailTemplate, key: nil, param_name: :template}
15+
]
16+
end
17+
18+
def self.cache_expiry
19+
return 10.seconds if Rails.env.staging?
20+
15.minutes
21+
end
22+
23+
def self.resource_key = "email-templates"
24+
25+
def self.graphql_key = "emailTemplates"
26+
end
27+
end
28+
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module Cms
2+
module EmailComponents
3+
class BaseComponent
4+
def render?(email_template, user)
5+
true
6+
end
7+
8+
def render(email_template, user)
9+
raise NotImplementedError
10+
end
11+
12+
def render_text(email_template, user)
13+
raise NotImplementedError
14+
end
15+
end
16+
end
17+
end
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
module Cms
2+
module EmailComponents
3+
class CourseList < BaseComponent
4+
attr_accessor :section_title, :courses, :remove_on_match
5+
6+
def initialize(section_title:, courses:, remove_on_match:)
7+
@section_title = section_title
8+
@courses = courses
9+
@remove_on_match = remove_on_match
10+
@has_subsitutes = @courses.collect(&:substitute).any?
11+
end
12+
13+
def activity_list(email_template, user)
14+
completed_activities = user.sorted_completed_cpd_achievements_by(programme: email_template.programme).collect(&:activity)
15+
matched = false
16+
display_courses = @courses.select { !_1.substitute }.each_with_object([]) do |course, list|
17+
if completed_activities.include?(course.activity)
18+
matched = true
19+
else
20+
list << course
21+
end
22+
end
23+
display_courses += @courses.select { _1.substitute } if matched
24+
display_courses
25+
end
26+
27+
def matches(email_template, user)
28+
activites = user.sorted_completed_cpd_achievements_by(programme: email_template.programme).collect(&:activity)
29+
@courses.map { activites.include?(_1.activity) }
30+
end
31+
32+
def render?(email_template, user)
33+
course_matches = matches(email_template, user)
34+
return !course_matches.any? if @remove_on_match
35+
return @has_subsitutes if course_matches.all?
36+
true
37+
end
38+
39+
def render(email_template, user)
40+
Cms::EmailCourseListComponent.new(courses: activity_list(email_template, user), section_title:)
41+
end
42+
43+
def render_text(email_template, user)
44+
CourseListText.new(activity_list(email_template, user), section_title:)
45+
end
46+
end
47+
48+
class Course
49+
attr_accessor :activity_code, :display_name, :substitute, :activity
50+
51+
def initialize(activity_code:, display_name:, substitute:)
52+
@activity_code = activity_code
53+
@display_name = display_name
54+
@substitute = substitute
55+
@activity = Activity.find_by(stem_activity_code: activity_code)
56+
end
57+
end
58+
59+
class CourseListText
60+
include Rails.application.routes.url_helpers
61+
62+
def initialize(course_list, section_title:)
63+
@course_list = course_list
64+
@section_title = section_title
65+
end
66+
67+
def render_in(view_context)
68+
view_context.render inline: content
69+
end
70+
71+
def content
72+
course_text = @course_list.map { display(_1) }.join("\n") + "\n"
73+
return "#{@section_title}\n\n#{course_text}" if @section_title
74+
75+
course_text
76+
end
77+
78+
def display(course)
79+
"#{course.display_name.presence || course.activity.title} (#{course_url(id: course.activity.stem_activity_code, name: course.activity.title.parameterize)})"
80+
end
81+
82+
def format = :text
83+
end
84+
end
85+
end

0 commit comments

Comments
 (0)