Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/release/14.1' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
ulferts committed May 17, 2024
2 parents e54ca03 + 071f005 commit 5f1aed5
Show file tree
Hide file tree
Showing 114 changed files with 1,739 additions and 741 deletions.
43 changes: 25 additions & 18 deletions app/models/users/scopes/having_reminder_mail_to_send.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ module HavingReminderMailToSend
extend ActiveSupport::Concern

class_methods do
# Returns all users for which a reminder mails should be sent now. A user
# Returns all users for which a reminder mails should be sent. A user
# will be included if:
#
# * That user has an unread notification
# * That user has an unread notification that is not older than the latest_time value.
# * The user hasn't been informed about the unread notification before
# * The user has configured reminder mails to be within the time frame
# between the provided time and now.
# between the provided earliest_time and latest_time.
#
# This assumes that users only have full hours specified for the times
# they desire to receive a reminder mail at.
Expand All @@ -48,9 +48,14 @@ module HavingReminderMailToSend
# Only the time part is used which is moved forward to the next quarter
# hour (e.g. 2021-05-03 10:34:12+02:00 -> 08:45:00). This is done
# because time zones always have a mod(15) == 0 minutes offset. Needs to
# be before the current time.
def having_reminder_mail_to_send(earliest_time)
local_times = local_times_from(earliest_time)
# be before the latest_time.
# @param [DateTime] latest_time The latest time to consider as a matching
# slot.
#
# Only the time part is used which is moved back to the last quarter hour
# less than the latest_time value.
def having_reminder_mail_to_send(earliest_time, latest_time)
local_times = local_times_from(earliest_time, latest_time)

return none if local_times.empty?

Expand All @@ -62,13 +67,15 @@ def having_reminder_mail_to_send(earliest_time)
.joins(local_time_join(local_times))

subscriber_ids = Notification
.unsent_reminders_before(recipient: recipient_candidates, time: Time.current)
.unsent_reminders_before(recipient: recipient_candidates, time: latest_time)
.group(:recipient_id)
.select(:recipient_id)

where(id: subscriber_ids)
end

private

def local_time_join(local_times)
# Joins the times local to the user preferences and then checks whether:
#
Expand Down Expand Up @@ -129,8 +136,8 @@ def local_time_join(local_times)
SQL
end

def local_times_from(earliest_time)
times = quarters_between_earliest_and_now(earliest_time)
def local_times_from(earliest_time, latest_time)
times = quarters_between_earliest_and_latest(earliest_time, latest_time)

times_for_zones(times)
end
Expand Down Expand Up @@ -167,20 +174,20 @@ def build_local_times(times, zone)
end
end

def quarters_between_earliest_and_now(earliest_time)
latest_time = Time.current
def quarters_between_earliest_and_latest(earliest_time, latest_time) # rubocop:disable Metrics/AbcSize
raise ArgumentError if latest_time < earliest_time || (latest_time - earliest_time) > 1.day

quarters = ((latest_time - earliest_time) / 60 / 15).floor
# The first quarter is equal or greater to the earliest time
first_quarter = earliest_time.change(min: (earliest_time.min.to_f / 15).ceil * 15)
# The last quarter is the one smaller than the latest time. But needs to be at least equal to the first quarter.
last_quarter = [first_quarter, latest_time.change(min: latest_time.min / 15 * 15)].max

(1..quarters).each_with_object([next_quarter_hour(earliest_time)]) do |_, times|
times << (times.last + 15.minutes)
(first_quarter.to_i..last_quarter.to_i)
.step(15.minutes)
.map do |time|
Time.zone.at(time)
end
end

def next_quarter_hour(time)
(time + (time.min % 15 == 0 ? 0.minutes : (15 - (time.min % 15)).minutes))
end
end
end
end
15 changes: 15 additions & 0 deletions app/seeders/standard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,21 @@ projects:
- to: :follow_up_tasks
type: follows
schedule_manually: false
t_wiki: |
_In this wiki you can collaboratively create and edit pages and sub-pages to create a project wiki._
**You can:**
* Insert text and images, also with copy and paste from other documents
* Create a page hierarchy with parent pages
* Include wiki pages to the project menu
* Use macros to include, e.g. table of contents, work package lists, or Gantt charts
* Include wiki pages in other text fields, e.g. project overview page
* Include links to other documents
* View the change history
* View as Markdown
More information: [https://www.openproject.org/docs/user-guide/wiki/](https://www.openproject.org/docs/user-guide/wiki/)
scrum-project:
t_name: Scrum project
identifier: your-scrum-project
Expand Down
1 change: 1 addition & 0 deletions app/views/custom_styles/_inline_css.erb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ See COPYRIGHT and LICENSE files for more details.
<% if design_color.variable == "primary-button-color" %>
--primary-button-color--major1: <%= design_color.darken 0.18 %>;
--primary-button-color--minor1: <%= design_color.lighten 0.8 %>;
--primary-button-color--minor2: <%= design_color.lighten 0.6 %>;
<% end %>
<% if design_color.variable == "accent-color" %>
--accent-color--major1: <%= design_color.darken 0.2 %>;
Expand Down
1 change: 1 addition & 0 deletions app/views/custom_styles/_primer_color_mapping.erb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
--button-primary-bgColor-rest: var(--button--primary-background-color) !important;
--button-primary-bgColor-hover: var(--button--primary-background-hover-color) !important;
--button-primary-bgColor-disabled: var(--button--primary-background-disabled-color) !important;
--button-primary-borderColor-disabled: var(--button--primary-border-disabled-color) !important;
}
[data-light-theme=light_high_contrast]{
--avatar-border-color: var(--avatar-borderColor);
Expand Down
75 changes: 75 additions & 0 deletions app/workers/cron/quarter_hour_schedule_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2024 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.
# ++

module Cron::QuarterHourScheduleJob
extend ActiveSupport::Concern

included do
include GoodJob::ActiveJobExtensions::Concurrency

# With good_job and the limit of only allowing a single job to be enqueued and
# also a single job being performed at the same time we end up having
# up to two jobs that are not yet finished.

good_job_control_concurrency_with(
enqueue_limit: 1,
perform_limit: 1
)

# The job is scheduled to run every 15 minutes. If the job before takes longer
# than expected we retry two more times (at cron_at + 5 and cron_at + 15). Then the job is discarded.
# Once the job is discarded, the next job will be scheduled to run at the next quarter hour.
retry_on GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError,
wait: 5.minutes,
attempts: 3
end

private

def upper_boundary
@upper_boundary ||= GoodJob::Job
.find(job_id)
.cron_at
end

def lower_boundary
@lower_boundary ||= begin
predecessor = GoodJob::Job
.succeeded
.where(job_class: self.class.name)
.order(cron_at: :desc)
.first

if predecessor
predecessor.cron_at + 15.minutes
else
upper_boundary
end
end
end
end
60 changes: 41 additions & 19 deletions app/workers/notifications/schedule_date_alerts_notifications_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,54 @@

module Notifications
# Creates date alert jobs for users whose local time is 1:00 am.
# The job is scheduled to run every 15 minutes.
class ScheduleDateAlertsNotificationsJob < ApplicationJob
include Cron::QuarterHourScheduleJob

def perform
return unless EnterpriseToken.allows_to?(:date_alerts)

Service.new(times_from_scheduled_to_execution).call
Service.new(every_quater_hour_between_predecessor_cron_at_and_own_cron_at).call
end

# Returns times from scheduled execution time to current time in 15 minutes
# steps.
# What cannot be controlled is the time at which a job is actually performed.
# A high load on the system can lead to a job being performed later than expected.
#
# As scheduled execution time can be different from current time by more
# than 15 minutes when workers are busy, all times at 15 minutes interval
# between scheduled time and current time need to be considered to match
# with 1:00am in a time zone.
def times_from_scheduled_to_execution
time = scheduled_time
times = []
begin
times << time
time += 15.minutes
end while time < Time.current
times
end

def scheduled_time
job_scheduled_at.then { |t| t.change(min: t.min / 15 * 15) }
# It might also happen that due to some outage of the background workers, cron
# jobs are not enqueued as they should be.
#
# But we want to achieve even under these circumstances that date alerts are sent out
# for all the users even if we cannot guarantee that they are sent out at the time where we want it to happen
# which would be 1:00 am. At the same time we want to prevent date alerts being sent out multiple times.
#
# There are three scenarios to consider which mostly circle around the predecessor
# of the job that is currently run:
# * The predecessor ran successfully within the 15 minutes interval between it being scheduled and the current job
# having to run. If this is the case, then the current job will only have to handle only the point in time of
# its cron_at value.
# * The predecessor took longer to run than 15 minutes or was scheduled to run at a later time (because its)
# predecessor was delayed. This will potentially lead to the current job also being delayed. If this is the case,
# then the current job will have to handle all the 15 minute slots between its cron_at value and the cron_at value
# of its predecessor + 15 minutes.
# * There is no predecessor since it is the first job ever to run or for some reasons the GoodJob::Execution where
# salvaged. In this case there is no certainty as to what is the right choice which is why the cron_at value of
# the current job is again used as the sole point in time to consider.
#
# In other words, the current job will take the
# * cron_at value of the current job as the maximum time to consider.
# * Take the cron_at of the previous job + 15 minutes as the minimum time to consider.
# If no previous job exists, then the cron_at of the current job is the minimum.
#
# Using this pattern, between all the instances of this job, every time slot where there is the potential
# for 1:00 am local time are covered. The sole exception would be the case where previously finished jobs
# have been removed from the database and the current job took longer to start executing. This is for now
# an accepted shortcoming as the likelihood of this happening is considered to be very low.
def every_quater_hour_between_predecessor_cron_at_and_own_cron_at
(lower_boundary.to_i..upper_boundary.to_i)
.step(15.minutes)
.map do |time|
Time.zone.at(time)
end
end
end
end
6 changes: 5 additions & 1 deletion app/workers/notifications/schedule_reminder_mails_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@

module Notifications
class ScheduleReminderMailsJob < ApplicationJob
include Cron::QuarterHourScheduleJob

def perform
User.having_reminder_mail_to_send(job_scheduled_at)
return unless lower_boundary.present? && upper_boundary.present?

User.having_reminder_mail_to_send(lower_boundary, upper_boundary)
.pluck(:id)
.each do |user_id|
Mails::ReminderJob.perform_later(user_id)
Expand Down
21 changes: 21 additions & 0 deletions config/i18n-tasks-all-files.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# i18n-tasks finds and manages missing and unused translations: https://github.com/glebm/i18n-tasks
#
# This configuration file is only used to check interpolations consistency with command
# `i18n-tasks check-consistent-interpolations --config config/i18n-tasks-all-files.yml`

data:
# Locale files patterns where translations are read from:
read:
- config/locales/en.seeders.yml
- config/locales/en.yml
- config/locales/js-en.yml
- config/locales/crowdin/%{locale}.seeders.yml
- config/locales/crowdin/%{locale}.yml
- config/locales/crowdin/js-%{locale}.yml
- config/locales/generated/%{locale}.yml
- modules/*/config/locales/en.seeders.yml
- modules/*/config/locales/en.yml
- modules/*/config/locales/js-en.yml
- modules/*/config/locales/crowdin/%{locale}.seeders.yml
- modules/*/config/locales/crowdin/%{locale}.yml
- modules/*/config/locales/crowdin/js-%{locale}.yml
19 changes: 8 additions & 11 deletions config/locales/js-en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -397,21 +397,18 @@ en:
learn_about: "Learn more about the new features"
# Include the version to invalidate outdated translations in other locales.
# Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release.
"14_0":
"14_1":
standard:
learn_about_link: https://www.openproject.org/blog/openproject-14-0-release/
learn_about_link: https://www.openproject.org/docs/release-notes/14-1-0/
new_features_html: >
The release contains various new features and improvements: <br>
<ul class="%{list_styling_class}">
<li>Implement progress reporting across work package hierarchies.</li>
<li>Manage project attributes in a structured way on the project overview page.</li>
<li>Streamlined view of custom fields in project list and project overview.</li>
<li>Unified page headers with the sleek Primer design.</li>
<li>Enhance meetings with new features like attachment uploads, responsible, and meeting history.</li>
<li>Automatic alerts for unhealthy file storages, and gain control with toggle options.</li>
<li>Copy automatically managed project folder for OneDrive/SharePoint when copying projects.</li>
<li>Ask admins to remove shares on work packages when revoking a project membership.</li>
<li>Enhance planning with 4 and 8-week display modes in the team planner.</li>
<li>PDF export of Gantt view, e.g. for printing (Enterprise add-on)</li>
<li>Favorite projects</li>
<li>Sections in Meetings</li>
<li>Showing meetings on the My page and project overview pages</li>
<li>Possibility to hide attachments in the Files tab</li>
<li>Custom fields of the type Link (URL)</li>
</ul>
ical_sharing_modal:
Expand Down
Binary file modified docs/getting-started/my-page/My-page-default-view.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/getting-started/my-page/image-20200211154602328.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/getting-started/my-page/my-page-add-widget.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/getting-started/my-page/my-page-remove-widget.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/getting-started/my-page/navigate-to-my-page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 5f1aed5

Please sign in to comment.