Skip to content

Commit

Permalink
Add series mailer implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverguenther committed Dec 18, 2024
1 parent a8fb073 commit 9921781
Show file tree
Hide file tree
Showing 12 changed files with 361 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,24 @@
},
'aria-label': t(:label_recurring_meeting_series_edit),
test_selector: "edit-meeting-details-button",
)
) do |item|
item.with_leading_visual_icon(icon: :pencil)
end

menu.with_item(
label: t(:label_icalendar_download),
href: download_ics_recurring_meeting_path(@meeting)
)
) do |item|
item.with_leading_visual_icon(icon: :calendar)
end

menu.with_item(
label: t('meeting.label_mail_all_participants'),
href: notify_recurring_meeting_path(@meeting),
form_arguments: { method: :post }
) do |item|
item.with_leading_visual_icon(icon: :mail)
end

menu.with_item(
label: I18n.t(:label_recurring_meeting_series_delete),
Expand All @@ -44,7 +56,9 @@
form_arguments: {
method: :delete, data: { confirm: t("text_are_you_sure"), turbo: 'false' }
}
)
) do |item|
item.with_leading_visual_icon(icon: :trash)
end
end
end
end %>
26 changes: 24 additions & 2 deletions modules/meeting/app/controllers/recurring_meetings_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ class RecurringMeetingsController < ApplicationController

before_action :find_meeting,
only: %i[show update details_dialog destroy edit init
delete_scheduled template_completed download_ics]
before_action :find_optional_project, only: %i[index show new create update details_dialog destroy edit delete_scheduled]
delete_scheduled template_completed download_ics notify]
before_action :find_optional_project,
only: %i[index show new create update details_dialog destroy edit delete_scheduled notify]
before_action :authorize_global, only: %i[index new create]
before_action :authorize, except: %i[index new create]
before_action :get_scheduled_meeting, only: %i[delete_scheduled]
Expand Down Expand Up @@ -151,6 +152,7 @@ def template_completed
.call(start_time: @first_occurrence.to_time)

if call.success?
deliver_invitation_mails
flash[:success] = I18n.t("recurring_meeting.occurrence.first_created")
else
flash[:error] = call.message
Expand Down Expand Up @@ -186,8 +188,28 @@ def download_ics # rubocop:disable Metrics/AbcSize
end
end

def notify
deliver_invitation_mails
flash[:notice] = I18n.t(:notice_successful_notification)
redirect_to action: :show
end

private

def deliver_invitation_mails
@recurring_meeting
.template
.participants
.invited
.find_each do |participant|
MeetingSeriesMailer.template_completed(
@recurring_meeting,
participant.user,
User.current
).deliver_later
end
end

def upcoming_meetings(count:)
meetings = @recurring_meeting
.scheduled_instances(upcoming: true)
Expand Down
70 changes: 70 additions & 0 deletions modules/meeting/app/mailers/meeting_series_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

class MeetingSeriesMailer < UserMailer
def template_completed(series, user, actor)
@actor = actor
@series = series
@template = series.template
@next_occurrence = series.next_occurrence&.to_time
@user = user

set_headers(series)

with_attached_ics(series, user) do
subject = I18n.t("meeting.email.series.title", title: series.title, project_name: series.project.name)
mail(to: user, subject:)
end
end

private

def with_attached_ics(series, user)
User.execute_as(user) do
call = ::RecurringMeetings::ICalService
.new(user:, series: series)
.generate_series

call.on_success do
attachments["meeting.ics"] = call.result

yield
end

call.on_failure do
Rails.logger.error { "Failed to create ICS attachment for meeting #{series.id}: #{call.message}" }
end
end
end

def set_headers(series)
open_project_headers "Project" => series.project.identifier, "Meeting-Id" => series.id
headers["Content-Type"] = 'text/calendar; charset=utf-8; method="PUBLISH"; name="meeting.ics"'
headers["Content-Transfer-Encoding"] = "8bit"
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>

<%= render layout: 'mailer/spacer_table' do %>
<%= render partial: 'mailer/mailer_header',
locals: {
user: @user,
summary: I18n.t('meeting.email.series.summary', title: @series.title, actor: @actor),
bottom_spacing: false
} %>

<%= render layout: 'mailer/border_table' do %>
<tr>
<%= placeholder_cell('24px', vertical: true) %>
<td>
<table>
<tr>
<td style="<%= placeholder_text_styles(width: '30%') %>">
<%= I18n.t(:label_recurring_meeting_schedule) %>
</td>
<td style="<%= placeholder_text_styles %>">
<%= @series.full_schedule_in_words %> (<%= formatted_time_zone_offset %>)
</td>
</tr>
<% if @next_occurrence %>
<tr>
<td style="<%= placeholder_text_styles(width: '30%') %>">
<%= I18n.t(:label_recurring_meeting_next_occurrence) %>
</td>
<td style="<%= placeholder_text_styles %>">
<%= format_time_as_date @next_occurrence %> <%= format_time @next_occurrence, include_date: false %>
(<%= formatted_time_zone_offset %>)
</td>
</tr>
<% end %>
<% if @template.location.present? %>
<tr>
<td style="<%= placeholder_text_styles(width: '30%') %>">
<%= Meeting.human_attribute_name(:location) %>
</td>
<td style="<%= placeholder_text_styles %>">
<%= auto_link @template.location %>
</td>
</tr>
<% end %>
<tr>
<td style="<%= placeholder_text_styles(width: '30%') %>">
<%= Meeting.human_attribute_name(:project) %>
</td>
<td style="<%= placeholder_text_styles %>">
<%= link_to @series.project.name, project_url(@series.project) %>
</td>
</tr>
<tr>
<td style="<%= placeholder_text_styles(width: '30%') %>">
<%= Meeting.human_attribute_name(:author) %>
</td>
<td style="<%= placeholder_text_styles %>">
<%= @series.author %>
</td>
</tr>
<% if @template.participants.exists? %>
<tr>
<td style="<%= placeholder_text_styles(width: '30%') %>">
<%= Meeting.human_attribute_name(:participants_invited) %>
</td>
<td style="<%= placeholder_text_styles %>">
<%= @template.participants.invited.sort.join("; ") %>
</td>
</tr>
<% end %>
</table>
</td>
</tr>
<% end %>

<table>
<tr>
<%= placeholder_cell('20px', vertical: false) %>
</tr>
</table>

<%= action_button do %>
<%= link_to I18n.t(:'meeting.email.open_meeting_link'),
recurring_meeting_url(@series),
target: '_blank',
style: "color: #333333; text-decoration: none; font-size: 14px;white-space: nowrap;" %>
<% end %>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>

<%= I18n.t('meeting.email.series.summary', title: @series.title, actor: @actor) %>

<%= @series.project.name %>: <%= @series.title %> (<%= meeting_url(@series) %>)
<%= @series.author %>

<%=t :label_meeting_date_time %>: <%= format_time_as_date @template.start_time %> <%= format_time @template.start_time, include_date: false %>-<%= format_time @template.end_time, include_date: false %> (<%= formatted_time_zone_offset %>)
<%= Meeting.human_attribute_name(:location) %>: <%= @template.location %>
<%= Meeting.human_attribute_name(:participants_invited) %>: <%= @template.participants.invited.sort.join("; ") %>
5 changes: 5 additions & 0 deletions modules/meeting/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ en:
Any meeting information not in the template will be lost.
Do you want to continue?
label_recurring_meeting_restore: "Restore this occurrence"
label_recurring_meeting_schedule: "Schedule"
label_recurring_meeting_next_occurrence: "Next occurrence"
label_recurring_meeting_more: "There are %{count} more scheduled meetings (%{schedule})."
label_recurring_meeting_more_singular: "There is %{count} more scheduled meeting (%{schedule})."
label_recurring_meeting_more_past: "There are %{count} more past meetings."
Expand Down Expand Up @@ -194,6 +196,9 @@ en:
Send an email invitation immediately to the participants selected above. You can also do this manually at any time later.
send_invitation_emails_structured: "Send an email invitation immediately to all participants. You can also do this manually at any time later."
open_meeting_link: "Open meeting"
series:
title: "[%{project_name}] Meeting series %{title}"
summary: "%{actor} has set up a new meeting series %{title}"
invited:
summary: "%{actor} has sent you an invitation for the meeting %{title}"
rescheduled:
Expand Down
1 change: 1 addition & 0 deletions modules/meeting/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
post :init
post :delete_scheduled
post :template_completed
post :notify
end
collection do
get :humanize_schedule, controller: "recurring_meetings/schedule", action: :humanize_schedule
Expand Down
3 changes: 2 additions & 1 deletion modules/meeting/lib/open_project/meeting/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ class Engine < ::Rails::Engine
permission :edit_meetings,
{
meetings: %i[edit cancel_edit update update_title details_dialog update_details update_participants],
recurring_meetings: %i[edit cancel_edit update update_title details_dialog update_details],
recurring_meetings: %i[edit cancel_edit update update_title details_dialog update_details
notify],
work_package_meetings_tab: %i[add_work_package_to_meeting_dialog add_work_package_to_meeting]
},
permissible_on: :project,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
let(:current_user) { user }
let(:meeting) { RecurringMeeting.last }
let(:show_page) { Pages::RecurringMeeting::Show.new(meeting) }
let(:template_page) { Pages::StructuredMeeting::Show.new(meeting.template) }
let(:meetings_page) { Pages::Meetings::Index.new(project:) }

context "with a user with permissions" do
Expand Down Expand Up @@ -93,13 +94,36 @@
# Use is redirected to the template
expect(page).to have_current_path(project_meeting_path(project, meeting.template))
expect(page).to have_content(I18n.t("recurring_meeting.template.description"))

# Add participants
template_page.open_participant_form
template_page.in_participant_form do
template_page.expect_participant_invited(user, invited: true)
template_page.expect_participant_invited(other_user, invited: false)
template_page.expect_available_participants(count: 2)
expect(page).to have_button("Save")

template_page.invite_participant(other_user)

template_page.expect_participant_invited(user, invited: true)
template_page.expect_participant_invited(other_user, invited: true)

click_on("Save")
end
wait_for_network_idle

expect(page).to have_css("#meetings-side-panel-participants-component", text: 2)

expect(page).to have_link("Finish template")

click_link_or_button "Finish template"
wait_for_network_idle

# Does not send invitation mails by default
# Sends out an invitation to the series
perform_enqueued_jobs
expect(ActionMailer::Base.deliveries.size).to eq 0
expect(ActionMailer::Base.deliveries.size).to eq 2
title = ActionMailer::Base.deliveries.map(&:subject).uniq.first
expect(title).to eq "[#{project.name}] Meeting series Some title"

show_page.visit!
expect(page).to have_css(".start_time", count: 3)
Expand Down
Loading

0 comments on commit 9921781

Please sign in to comment.