Skip to content

Strapi Email Sender Job #2279

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
46 changes: 46 additions & 0 deletions app/controllers/admin/sent_emails_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module Admin
class SentEmailsController < Admin::ApplicationController
# Overwrite any of the RESTful controller actions to implement custom behavior
# For example, you may want to send an email after a foo is updated.
#
# def update
# super
# send_foo_updated_email(requested_resource)
# end

# Override this method to specify custom lookup behavior.
# This will be used to set the resource for the `show`, `edit`, and `update`
# actions.
#
# def find_resource(param)
# Foo.find_by!(slug: param)
# end

# The result of this lookup will be available as `requested_resource`

# Override this if you have certain roles that require a subset
# this will be used to set the records shown on the `index` action.
#
# def scoped_resource
# if current_user.super_admin?
# resource_class
# else
# resource_class.with_less_stuff
# end
# end

# Override `resource_params` if you want to transform the submitted
# data before it's persisted. For example, the following would turn all
# empty values into nil values. It uses other APIs such as `resource_class`
# and `dashboard`:
#
# def resource_params
# params.require(resource_class.model_name.param_key).
# permit(dashboard.permitted_attributes).
# transform_values { |value| value == "" ? nil : value }
# end

# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
# for more information
end
end
54 changes: 54 additions & 0 deletions app/dashboards/sent_email_dashboard.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
class SentEmailDashboard < BaseDashboard
# ATTRIBUTE_TYPES
# a hash that describes the type of each of the model's fields.
#
# Each different type represents an Administrate::Field object,
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
id: Field::String,
user: Field::BelongsTo,
subject: Field::String,
mailer_type: Field::String,
created_at: FORMATTED_DATE_TIME
}.freeze

# COLLECTION_ATTRIBUTES
# an array of attributes that will be displayed on the model's index page.
COLLECTION_ATTRIBUTES = %i[
created_at
user
subject
].freeze

# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = %i[
user
subject
created_at
].freeze

# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = %i[].freeze

# COLLECTION_FILTERS
# a hash that defines filters that can be used while searching via the search
# field of the dashboard.
#
# For example to add an option to search for open resources by typing "open:"
# in the search field:
#
# COLLECTION_FILTERS = {
# open: ->(resources) { resources.where(open: true) }
# }.freeze
COLLECTION_FILTERS = {}.freeze

# Overwrite this method to customize how support_audits are displayed
# across all pages of the admin dashboard.
#
# def display_resource()
# end
end
4 changes: 3 additions & 1 deletion app/dashboards/user_dashboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ class UserDashboard < BaseDashboard
stem_achiever_organisation_no: Field::String,
future_learn_organisation_memberships: Field::Text,
forgotten: Field::Boolean,
audits: Field::HasMany.with_options(sort_by: "created_at", direction: "desc")
audits: Field::HasMany.with_options(sort_by: "created_at", direction: "desc"),
sent_emails: Field::HasMany.with_options(sort_by: "created_at", direction: "desc")
}.freeze

# COLLECTION_ATTRIBUTES
Expand Down Expand Up @@ -59,6 +60,7 @@ class UserDashboard < BaseDashboard
achievements
teacher_reference_number
assessment_attempts
sent_emails
].freeze

# FORM_ATTRIBUTES
Expand Down
17 changes: 17 additions & 0 deletions app/jobs/send_cms_emails_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class SendCmsEmailsJob < ApplicationJob
PER_PAGE = 50
def perform
email_templates = Cms::Collections::EmailTemplate.all_records
email_templates.each { process_template(_1) }
end

def process_template(template)
# Do Query
data = template.template
users = Programmes::ProgressQuery.new(data.programme, data.activity_state, data.enrolled, data.completed_programme_activity_groups).call

users.each do |user|
CmsMailer.with(template_slug: data.slug, user_id: user.id).send_template.deliver_later
end
end
end
13 changes: 9 additions & 4 deletions app/mailers/cms_mailer.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
class CmsMailer < ApplicationMailer
def send_template
template_slug = params[:template_slug]
@template_slug = params[:template_slug]
@user = User.find(params[:user_id])
@template = Cms::Collections::EmailTemplate.get(template_slug).template

@subject = @template.subject(@user)
begin
@template = Cms::Collections::EmailTemplate.get(@template_slug).template

mail(to: @user.email, subject: @subject)
@subject = @template.subject(@user)

mail(to: @user.email, subject: @subject)
rescue ActiveRecord::RecordNotFound
Sentry.capture_message("Failed to load the email template #{@template_slug}")
end
end
end
2 changes: 1 addition & 1 deletion app/services/cms/collections/email_template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Collections
class EmailTemplate < Resource
def self.is_collection = true

def self.collection_attribute_mapping
def self.collection_attribute_mappings
[
{model: Models::Collections::EmailTemplate, key: nil, param_name: :template}
]
Expand Down
5 changes: 3 additions & 2 deletions app/services/cms/models/collections/email_template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ module Cms
module Models
module Collections
class EmailTemplate
attr_accessor :slug, :email_content, :programme, :completed_programme_activity_groups, :activity_state
attr_accessor :slug, :email_content, :programme, :completed_programme_activity_groups, :activity_state, :enrolled

def initialize(slug:, subject:, email_content:, programme_slug:, completed_programme_activity_group_slugs:, activity_state:)
def initialize(slug:, subject:, email_content:, programme_slug:, completed_programme_activity_group_slugs:, activity_state:, enrolled:)
@slug = slug
@subject = subject
@email_content = email_content
Expand All @@ -17,6 +17,7 @@ def initialize(slug:, subject:, email_content:, programme_slug:, completed_progr
[]
end
@activity_state = activity_state
@enrolled = enrolled
end

def subject(user)
Expand Down
3 changes: 2 additions & 1 deletion app/services/cms/providers/strapi/factories/model_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ def self.to_email_template(strapi_data, _all_data)
programme_slug: strapi_data[:programme][:data][:attributes][:slug],
email_content: strapi_data[:emailContent].map { ComponentFactory.process_component(_1) }.compact,
completed_programme_activity_group_slugs: strapi_data.dig(:completedGroupings, :data).nil? ? nil : strapi_data[:completedGroupings][:data].collect { _1[:attributes][:slug] },
activity_state: strapi_data[:activityState]
activity_state: strapi_data[:activityState],
enrolled: strapi_data[:enrolled]
}
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class EmailTemplate < StrapiMock
attribute(:slug) { Faker::Internet.slug }
attribute(:emailContent) { [] }
attribute(:activityState) { "active" }
attribute(:enrolled) { true }
attribute(:programme) {
{data: {attributes: {slug: "primary-certificate"}}}
}
Expand Down
3 changes: 3 additions & 0 deletions config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@
config.view_component.preview_route = "/rails/components"
config.view_component.generate.sidecar = true

config.action_mailer.default_url_options = {host: "teachcomputing.rpfdev.com"}
routes.default_url_options = {host: "teachcomputing.rpfdev.com"}

config.lograge.enabled = true
config.lograge.ignore_actions = [Healthcheck::CONTROLLER_ACTION]
config.lograge.custom_options = lambda do |event|
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
post :report_results
end
end
resources :sent_emails, only: %i[index show]
end

namespace :api do
Expand Down
Binary file modified erd.pdf
Binary file not shown.
31 changes: 31 additions & 0 deletions spec/jobs/send_cms_emails_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require "rails_helper"

RSpec.describe SendCmsEmailsJob, type: :job do
let(:email_template_1_slug) { "send-cms-email-template-1-slug" }
let(:email_template_2_slug) { "send-cms-email-template-2-slug" }
let(:users) { create_list(:user, 2) }

let(:email_template_1) {
Cms::Mocks::Collections::EmailTemplate.generate_raw_data(slug: email_template_1_slug)
}
let(:email_template_2) {
Cms::Mocks::Collections::EmailTemplate.generate_raw_data(slug: email_template_2_slug)
}

before do
stub_strapi_email_template(email_template_1_slug, email_template: email_template_1)
stub_strapi_email_template(email_template_2_slug, email_template: email_template_2)
stub_strapi_email_templates(email_templates: [email_template_1, email_template_2], page: 1, page_size: 50)

allow_any_instance_of(Programmes::ProgressQuery).to receive(:call).and_return(users)
end

it "should enqueue both templates" do
expect do
described_class.perform_now
end.to have_enqueued_mail(CmsMailer, :send_template).with(a_hash_including(params: {user_id: users.first.id, template_slug: email_template_1_slug}))
.and have_enqueued_mail(CmsMailer, :send_template).with(a_hash_including(params: {user_id: users.first.id, template_slug: email_template_2_slug}))
.and have_enqueued_mail(CmsMailer, :send_template).with(a_hash_including(params: {user_id: users.second.id, template_slug: email_template_1_slug}))
.and have_enqueued_mail(CmsMailer, :send_template).with(a_hash_including(params: {user_id: users.second.id, template_slug: email_template_2_slug}))
end
end
30 changes: 23 additions & 7 deletions spec/mailers/cms_mailer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
Cms::Mocks::EmailComponents::Text.generate_raw_data(
text_content: [
{
type: :paragraph,
type: "paragraph",
children: [
{type: :text, text: "Hello {first_name}"}
{type: "text", text: "Hello {first_name}"}
]
},
{
type: :paragraph,
type: "paragraph",
children: [
{type: :text, text: "You completed {last_cpd_title}"}
{type: "text", text: "You completed {last_cpd_title}"}
]
},
{
Expand Down Expand Up @@ -72,7 +72,7 @@

describe "send_template" do
before do
@mail = CmsMailer.with(user_id: user.id, template_slug: slug).send_template
@mail = described_class.with(user_id: user.id, template_slug: slug).send_template
end

it "renders the headers" do
Expand Down Expand Up @@ -131,7 +131,7 @@
before do
travel_to 1.day.from_now do
create(:completed_achievement, activity: second_activity, user:)
@future_mail = CmsMailer.with(user_id: user.id, template_slug: slug).send_template
@future_mail = described_class.with(user_id: user.id, template_slug: slug).send_template
end
end

Expand All @@ -147,7 +147,7 @@

describe "send_template with merged subject" do
before do
@mail = CmsMailer.with(user_id: user.id, template_slug: slug_with_merge_subject).send_template
@mail = described_class.with(user_id: user.id, template_slug: slug_with_merge_subject).send_template
end

it "renders the headers" do
Expand All @@ -156,4 +156,20 @@
expect(@mail.from).to eq(["[email protected]"])
end
end

describe "with missing template" do
let(:missing_slug) { "missing_template_slug" }

before do
stub_strapi_email_template_missing(missing_slug)
allow(Sentry).to receive(:capture_message)
end

it "logs error to sentry" do
described_class.with(user_id: user.id, template_slug: missing_slug).send_template.deliver_now
expect(Sentry)
.to have_received(:capture_message)
.with("Failed to load the email template #{missing_slug}")
end
end
end
29 changes: 29 additions & 0 deletions spec/requests/admin/sent_emails_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require "rails_helper"

RSpec.describe "Admin::SentEmailsController" do
let!(:sent_email) { create(:sent_email) }

before do
allow_any_instance_of(Admin::ApplicationController).to receive(:authenticate_admin).and_return("[email protected]")
end

describe "GET #index" do
before do
get admin_sent_emails_path
end

it "should render correct template" do
expect(response).to render_template("index")
end
end

describe "GET #show" do
before do
get admin_sent_email_path(sent_email)
end

it "should render correct template" do
expect(response).to render_template("show")
end
end
end
17 changes: 16 additions & 1 deletion spec/services/cms/models/collections/email_template_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
email_content: Cms::Mocks::EmailComponents::Text.generate_raw_data(text_content:),
programme_slug: "primary-certificate",
completed_programme_activity_group_slugs: [],
activity_state: :active
activity_state: :active,
enrolled: true
)
end

Expand Down Expand Up @@ -83,4 +84,18 @@
expect(@model.time_diff_words(25.months.ago)).to eq("2 years")
end
end

it "passing nil into completed_programme_activity_group_slugs defaults to array" do
@model = described_class.new(
slug:,
subject:,
email_content: Cms::Mocks::EmailComponents::Text.generate_raw_data(text_content:),
programme_slug: "primary-certificate",
completed_programme_activity_group_slugs: nil,
activity_state: :active,
enrolled: true
)

expect(@model.completed_programme_activity_groups).to eq([])
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
email_content: Cms::Mocks::Text::RichBlocks.generate_data,
programme_slug: programme.slug,
completed_programme_activity_group_slugs: [],
activity_state: :active
activity_state: :active,
enrolled: true
)
}

Expand Down
Loading