Skip to content
Draft
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
13 changes: 7 additions & 6 deletions app/models/journal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ class Journal < ApplicationRecord
belongs_to :data, polymorphic: true, dependent: :destroy

has_many :agenda_item_journals, class_name: "Journal::MeetingAgendaItemJournal", dependent: :delete_all
has_many :participant_journals, class_name: "Journal::MeetingParticipantJournal", dependent: :delete_all
has_many :attachable_journals, class_name: "Journal::AttachableJournal", dependent: :delete_all
has_many :customizable_journals, class_name: "Journal::CustomizableJournal", dependent: :delete_all
has_many :custom_comment_journals, class_name: "Journal::CustomCommentJournal", dependent: :delete_all
Expand Down Expand Up @@ -223,12 +224,6 @@ def has_unread_notifications_for_user?(user)
end
end

private

def has_file_links?
journable.respond_to?(:file_links)
end

def predecessor
return @predecessor if defined?(@predecessor)

Expand All @@ -242,4 +237,10 @@ def predecessor
.first
end
end

private

def has_file_links?
journable.respond_to?(:file_links)
end
end
4 changes: 3 additions & 1 deletion app/services/journals/create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,9 @@ def aggregation_active?
end

def within_aggregation_time?(predecessor)
predecessor.updated_at >= (Time.zone.now - Setting.journal_aggregation_time_minutes.to_i.minutes)
minutes = journable.class.try(:journal_aggregation_time_minutes) ||
Setting.journal_aggregation_time_minutes.to_i
predecessor.updated_at >= (Time.zone.now - minutes.minutes)
end

def same_user?(predecessor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def remove_from_upcoming_occurrences(user_id)
next unless participant

MeetingParticipants::DeleteService
.new(user: User.current, model: participant)
.new(user: User.current, model: participant, notify: false)
.call
end
end
Expand All @@ -136,7 +136,7 @@ def add_to_upcoming_occurrences(user_ids)
next if meeting.participants.exists?(user_id:)

MeetingParticipants::CreateService
.new(user: User.current)
.new(user: User.current, notify: false)
.call(meeting:, user_id:, invited: true, attended: false)
end
end
Expand Down
34 changes: 3 additions & 31 deletions modules/meeting/app/mailers/meeting_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ def invited(meeting, user, actor)
end
end

def updated(meeting, user, actor, changes:)
def updated(meeting, user, actor, changes:, added_participants: [], removed_participants: [])
@actor = actor
@user = user
@meeting = meeting
@changes = changes
@added_participants = Array(added_participants)
@removed_participants = Array(removed_participants)

open_project_headers "Project" => @meeting.project.identifier,
"Meeting-Id" => @meeting.id
Expand Down Expand Up @@ -117,36 +119,6 @@ def icalendar_notification(meeting, user, _actor, **)
end
end

def participant_added(meeting, user, actor, added_participant:)
@actor = actor
@meeting = meeting
@user = user
@added_participant = added_participant

open_project_headers "Project" => @meeting.project.identifier,
"Meeting-Id" => @meeting.id

with_attached_ics(meeting, user) do
subject = I18n.t("meeting.email.participant_added.header", title: @meeting.title)
mail(to: user, subject: "[#{@meeting.project.name}] #{subject}")
end
end

def participant_removed(meeting, user, actor, removed_participant:)
@actor = actor
@meeting = meeting
@user = user
@removed_participant = removed_participant

open_project_headers "Project" => @meeting.project.identifier,
"Meeting-Id" => @meeting.id

with_attached_ics(meeting, user) do
subject = I18n.t("meeting.email.participant_removed.header", title: @meeting.title)
mail(to: user, subject: "[#{@meeting.project.name}] #{subject}")
end
end

private

def with_attached_ics(meeting, user, **args)
Expand Down
34 changes: 3 additions & 31 deletions modules/meeting/app/mailers/meeting_series_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ def invited(series, user, actor)
end
end

def updated(series, user, actor, changes:)
def updated(series, user, actor, changes:, added_participants: [], removed_participants: [])
@actor = actor
@series = series
@user = user
@changes = changes
@added_participants = Array(added_participants)
@removed_participants = Array(removed_participants)

set_headers(series)

Expand All @@ -60,36 +62,6 @@ def updated(series, user, actor, changes:)
end
end

def participant_added(series, user, actor, added_participant:)
@actor = actor
@series = series
@template = series.template
@user = user
@added_participant = added_participant

set_headers(series)

with_attached_ics(series, user) do
subject = I18n.t("meeting.email.participant_added.header_series", title: series.title)
mail(to: user, subject: "[#{@series.project.name}] #{subject}")
end
end

def participant_removed(series, user, actor, removed_participant:)
@actor = actor
@series = series
@template = series.template
@user = user
@removed_participant = removed_participant

set_headers(series)

with_attached_ics(series, user) do
subject = I18n.t("meeting.email.participant_removed.header_series", title: series.title)
mail(to: user, subject: "[#{@series.project.name}] #{subject}")
end
end

private

def with_attached_ics(series, user, cancelled: false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def event_title(_journal, data)
end

def journals_includes
super + %i[agenda_item_journals]
super + %i[agenda_item_journals participant_journals]
end

def url_helpers
Expand Down
38 changes: 38 additions & 0 deletions modules/meeting/app/models/journal/meeting_participant_journal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

#-- 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 Journal::MeetingParticipantJournal < ApplicationRecord
self.table_name = "meeting_participant_journals"

belongs_to :journal
belongs_to :user

enum :participation_status, MeetingParticipant.participation_statuses, allow_nil: true
end
34 changes: 18 additions & 16 deletions modules/meeting/app/models/meeting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class Meeting < ApplicationRecord

before_save :add_new_participants_as_watcher

after_update :send_updated_mail, if: -> {
after_commit :send_updated_mail, on: :update, if: -> {
!template? &&
(saved_change_to_start_time? || saved_change_to_duration? || saved_change_to_location? || saved_change_to_title?)
}
Expand All @@ -152,6 +152,12 @@ class Meeting < ApplicationRecord
system: "system"
}, prefix: :sharing, validate: { allow_nil: true }

# Debounce meeting emails by one minute
# this is currently hard coded
def self.journal_aggregation_time_minutes
1
end

def self.templates_visible_in_project(project, user = User.current)
accessible_ids = Project.allowed_to(user, :view_meetings).select(:id)

Expand Down Expand Up @@ -341,22 +347,18 @@ def add_new_participants_as_watcher
def send_updated_mail
return unless send_emails?

MeetingNotificationService
.new(self)
.call :updated,
changes: updated_mail_changes
Meetings::NotificationDebounceJob.debounce(
self,
since_journal_id: last_journal&.predecessor&.id,
since_invited_ids: participants.invited.pluck(:user_id),
since_attributes: updated_mail_since_attributes
)
end

def updated_mail_changes # rubocop:disable Metrics/AbcSize
{
old_start: saved_change_to_start_time? ? saved_change_to_start_time.first : start_time,
new_start: start_time,
old_duration: saved_change_to_duration? ? saved_change_to_duration.first : duration,
new_duration: duration,
old_location: saved_change_to_location? ? saved_change_to_location.first : location,
new_location: location,
old_title: saved_change_to_title? ? saved_change_to_title.first : title,
new_title: title
}
def updated_mail_since_attributes
%w[title location start_time duration].index_with do |attribute|
value = saved_change_to_attribute(attribute)&.first || public_send(attribute)
value.respond_to?(:iso8601) ? value.iso8601 : value
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# frozen_string_literal: true

#-- 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 Journals::CreateService
class Participatable < Association
def associated?
journable.respond_to?(:participants)
end

def cleanup_predecessor(predecessor, notes, cause)
cleanup_predecessor_for(predecessor,
notes,
cause,
"meeting_participant_journals",
:journal_id,
:id)
end

def insert_sql
sanitize(<<~SQL.squish, journable_id:)
INSERT INTO
meeting_participant_journals (
journal_id,
user_id,
invited,
attended,
participation_status
)
SELECT
#{id_from_inserted_journal_sql},
participants.user_id,
COALESCE(participants.invited, false),
COALESCE(participants.attended, false),
participants.participation_status
FROM meeting_participants participants
WHERE
#{only_if_created_sql}
AND participants.meeting_id = :journable_id
ON CONFLICT (journal_id, user_id) DO UPDATE SET
invited = EXCLUDED.invited,
attended = EXCLUDED.attended,
participation_status = EXCLUDED.participation_status
SQL
end

def changes_sql
sanitize(<<~SQL.squish, journable_id:)
SELECT
max_journals.journable_id
FROM
max_journals
LEFT OUTER JOIN
meeting_participant_journals
ON
meeting_participant_journals.journal_id = max_journals.id
FULL JOIN
(SELECT *
FROM meeting_participants
WHERE meeting_participants.meeting_id = :journable_id) participants
ON
participants.user_id = meeting_participant_journals.user_id
WHERE
(participants.user_id IS DISTINCT FROM meeting_participant_journals.user_id)
OR (participants.invited IS DISTINCT FROM meeting_participant_journals.invited)
OR (participants.attended IS DISTINCT FROM meeting_participant_journals.attended)
OR (participants.participation_status IS DISTINCT FROM meeting_participant_journals.participation_status)
SQL
end
end
end
Loading
Loading