Skip to content

Commit

Permalink
Merge branch 'release/14.6' into task/58113-146-documentation-progres…
Browse files Browse the repository at this point in the history
…s-reporting-changes
  • Loading branch information
MayaBerd authored Oct 8, 2024
2 parents 1bfdd1f + a391ef5 commit 4a6e023
Show file tree
Hide file tree
Showing 140 changed files with 895 additions and 721 deletions.
6 changes: 0 additions & 6 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,6 @@ def labeled_check_box_tags(name, collection, options = {})
end.join.html_safe
end

def html_hours(text)
html_safe_gsub(text,
%r{(\d+)\.(\d+)},
'<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
end

def html_safe_gsub(string, *gsub_args, &)
html_safe = string.html_safe?
result = string.gsub(*gsub_args, &)
Expand Down
4 changes: 2 additions & 2 deletions app/views/versions/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ See COPYRIGHT and LICENSE files for more details.
<table>
<tr>
<td width="130px" align="right"><%= Version.human_attribute_name(:estimated_hours) %></td>
<td width="240px" class="total-hours" align="right"><%= html_hours(l_hours(@version.estimated_hours)) %></td>
<td width="240px" class="total-hours" align="right"><%= l_hours(@version.estimated_hours) %></td>
</tr>
<% if User.current.allowed_in_project?(:view_time_entries, @project) %>
<tr>
<td width="130px" align="right"><%= t(:label_spent_time) %></td>
<td width="240px" class="total-hours" align="right"><%= html_hours(l_hours(@version.spent_hours)) %></td>
<td width="240px" class="total-hours" align="right"><%= l_hours(@version.spent_hours) %></td>
</tr>
<% end %>
</table>
Expand Down
46 changes: 1 addition & 45 deletions app/workers/application_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,7 @@

class ApplicationJob < ActiveJob::Base
include ::JobStatus::ApplicationJobWithStatus

##
# By default, do not log the arguments of a background job
# to avoid leaking sensitive information to logs
self.log_arguments = false

around_perform :prepare_job_context
include SharedJobSetup

##
# Return a priority number on the given payload
Expand Down Expand Up @@ -65,45 +59,7 @@ def self.queue_with_priority(value = :default)
end
end

# Resets the thread local request store.
# This should be done, because normal application code expects the RequestStore to be
# invalidated between multiple requests and does usually not care whether it is executed
# from a request or from a delayed job.
# For a delayed job, each job execution is the thing that comes closest to
# the concept of a new request.
def with_clean_request_store
store = RequestStore.store

begin
RequestStore.clear!
yield
ensure
# Reset to previous value
RequestStore.clear!
RequestStore.store.merge! store
end
end

# Reloads the thread local ActionMailer configuration.
# Since the email configuration is now done in the web app, we need to
# make sure that any changes to the configuration is correctly picked up
# by the background jobs at runtime.
def reload_mailer_settings!
Setting.reload_mailer_settings!
end

def job_scheduled_at
GoodJob::Job.where(id: job_id).pick(:scheduled_at)
end

private

def prepare_job_context
with_clean_request_store do
::OpenProject::Appsignal.tag_request
reload_mailer_settings!

yield
end
end
end
70 changes: 34 additions & 36 deletions app/workers/mails/mailer_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,46 +26,44 @@
# See COPYRIGHT and LICENSE files for more details.
#++

##
# This job gets called when internally using
# OpenProject is configured to use this job when sending emails like this:
#
# ```
# UserMailer.some_mail("some param").deliver_later
# ```
#
# because we want to have the sending of the email run in an `ApplicationJob`
# as opposed to using `ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper`.
# We want it to run in an `ApplicationJob` because of the shared setup required
# such as reloading the mailer configuration and resetting the request store.
class Mails::MailerJob < ApplicationJob
queue_as { ActionMailer::Base.deliver_later_queue_name }

# Retry mailing jobs three times with polinomial backoff
retry_on StandardError, wait: :polynomially_longer, attempts: 3

# If exception is handled in mail handler
# retry_on will be ignored
rescue_from StandardError, with: :handle_exception_with_mailer_class

def perform(mailer, mail_method, delivery, args:)
mailer.constantize.public_send(mail_method, *args).send(delivery)
end

private

# "Deserialize" the mailer class name by hand in case another argument
# (like a Global ID reference) raised DeserializationError.
def mailer_class
if mailer = Array(@serialized_arguments).first || Array(arguments).first
mailer.constantize
end
end
# This job is used because all our `XxxMailer` classes inherit from
# `ApplicationMailer`, and `ApplicationMailer.delivery_job` is set to
# `::Mails::MailerJob`.
#
# The `delivery_job` is customized to add the shared job setup required for
# OpenProject such as reloading the mailer configuration and resetting the
# request store on each job execution.
#
# It also adds retry logic to the job.
class Mails::MailerJob < ActionMailer::MailDeliveryJob
include SharedJobSetup

def handle_exception_with_mailer_class(exception)
if klass = mailer_class
klass.handle_exception exception
else
raise exception
end
end
# Retry mailing jobs 14 times with polynomial backoff (retries for ~ 1.5 days).
#
# with polynomial backoff, the formula to get wait_duration is:
#
# ((executions**4) + (Kernel.rand * (executions**4) * jitter)) + 2
#
# as the default jitter is 0.0, the formula becomes:
#
# ((executions**4) + 2)
#
# To get the numbers, run this:
#
# (1..20).reduce(0) do |total_wait, i|
# wait = (i**4) + 2
# total_wait += wait
# puts "Execution #{i} waits #{wait} secs. Total wait: #{total_wait} secs"
# total_wait
# end
#
# We set attemps to 14 to have it retry for 127715 seconds which is more than
# 1 day (~= 1 day 11 hours 30 min)
retry_on StandardError, wait: :polynomially_longer, attempts: 14
end
92 changes: 92 additions & 0 deletions app/workers/shared_job_setup.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# 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.
#++

# Shared setup for jobs.
#
# This module is included in `ApplicationJob` and `Mails::MailerJob` and does
# the following:
#
# - disable logging of arguments
# - before each job execution:
# - reloads the mailer settings
# - resets the request store
# - tags the request for AppSignal
module SharedJobSetup
extend ActiveSupport::Concern

included do
# By default, do not log the arguments of a background job
# to avoid leaking sensitive information to logs
self.log_arguments = false

around_perform :prepare_job_context
end

# Prepare the job execution by cleaning the request store, reloading the
# mailer settings and tagging the request
def prepare_job_context
with_clean_request_store do
::OpenProject::Appsignal.tag_request
reload_mailer_settings!

yield
end
end

# Resets the thread local request store.
#
# This should be done, because normal application code expects the
# RequestStore to be invalidated between multiple requests and does usually
# not care whether it is executed from a request or from a job.
#
# For a job, each job execution is the thing that comes closest to the concept
# of a new request.
def with_clean_request_store
store = RequestStore.store

begin
RequestStore.clear!
yield
ensure
# Reset to previous value
RequestStore.clear!
RequestStore.store.merge! store
end
end

# Reloads the thread local ActionMailer configuration.
#
# Since the email configuration is done in the web app, it makes sure that any
# changes to the configuration is correctly picked up by the background jobs
# at runtime.
def reload_mailer_settings!
Setting.reload_mailer_settings!
end
end
2 changes: 1 addition & 1 deletion config/locales/crowdin/af.yml
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ af:
blank: "can't be blank."
blank_nested: "needs to have the property '%{property}' set."
cannot_delete_mapping: "is required. Cannot be deleted."
is_for_all_cannot_modify: "is for all. Cannot be modified."
is_for_all_cannot_modify: "is for all projects and can therefore not be modified."
cant_link_a_work_package_with_a_descendant: "A work package cannot be linked to one of its subtasks."
circular_dependency: "This relation would create a circular dependency."
confirmation: "doesn't match %{attribute}."
Expand Down
2 changes: 1 addition & 1 deletion config/locales/crowdin/ar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,7 @@ ar:
blank: "لا يمكن أن يكون فارغا."
blank_nested: "needs to have the property '%{property}' set."
cannot_delete_mapping: "is required. Cannot be deleted."
is_for_all_cannot_modify: "is for all. Cannot be modified."
is_for_all_cannot_modify: "is for all projects and can therefore not be modified."
cant_link_a_work_package_with_a_descendant: "حزمة العمل لا يمكن أن تكون مرتبطة إلى واحدة من المهام الفرعية."
circular_dependency: "ان هذه العلاقة خلق تبعية دائرية."
confirmation: "لا يتطابق مع %{attribute}."
Expand Down
2 changes: 1 addition & 1 deletion config/locales/crowdin/az.yml
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ az:
blank: "can't be blank."
blank_nested: "needs to have the property '%{property}' set."
cannot_delete_mapping: "is required. Cannot be deleted."
is_for_all_cannot_modify: "is for all. Cannot be modified."
is_for_all_cannot_modify: "is for all projects and can therefore not be modified."
cant_link_a_work_package_with_a_descendant: "A work package cannot be linked to one of its subtasks."
circular_dependency: "This relation would create a circular dependency."
confirmation: "doesn't match %{attribute}."
Expand Down
2 changes: 1 addition & 1 deletion config/locales/crowdin/be.yml
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@ be:
blank: "can't be blank."
blank_nested: "needs to have the property '%{property}' set."
cannot_delete_mapping: "is required. Cannot be deleted."
is_for_all_cannot_modify: "is for all. Cannot be modified."
is_for_all_cannot_modify: "is for all projects and can therefore not be modified."
cant_link_a_work_package_with_a_descendant: "A work package cannot be linked to one of its subtasks."
circular_dependency: "This relation would create a circular dependency."
confirmation: "doesn't match %{attribute}."
Expand Down
2 changes: 1 addition & 1 deletion config/locales/crowdin/bg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ bg:
blank: "не може да бъде празно."
blank_nested: "трябва да бъде зададено свойството '%{property}'."
cannot_delete_mapping: "е необходимо. Не може да бъде изтрит."
is_for_all_cannot_modify: "is for all. Cannot be modified."
is_for_all_cannot_modify: "is for all projects and can therefore not be modified."
cant_link_a_work_package_with_a_descendant: "Работния пакет не може да бъде свързан с една от неговите подзадачи."
circular_dependency: "Тази връзка ще доведе до циклична зависимост."
confirmation: "не съвпада с %{attribute}."
Expand Down
2 changes: 1 addition & 1 deletion config/locales/crowdin/ca.yml
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,7 @@ ca:
blank: "no pot estar en blanc."
blank_nested: "és necessari de definir la propietat '%{property}' ."
cannot_delete_mapping: "is required. Cannot be deleted."
is_for_all_cannot_modify: "is for all. Cannot be modified."
is_for_all_cannot_modify: "is for all projects and can therefore not be modified."
cant_link_a_work_package_with_a_descendant: "Un paquet de treball no es pot enllaçar a una de les seves subtasques."
circular_dependency: "Aquesta relació crearia una dependència circular."
confirmation: "no coincideix amb el %{attribute}."
Expand Down
2 changes: 1 addition & 1 deletion config/locales/crowdin/ckb-IR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ ckb-IR:
blank: "can't be blank."
blank_nested: "needs to have the property '%{property}' set."
cannot_delete_mapping: "is required. Cannot be deleted."
is_for_all_cannot_modify: "is for all. Cannot be modified."
is_for_all_cannot_modify: "is for all projects and can therefore not be modified."
cant_link_a_work_package_with_a_descendant: "A work package cannot be linked to one of its subtasks."
circular_dependency: "This relation would create a circular dependency."
confirmation: "doesn't match %{attribute}."
Expand Down
2 changes: 1 addition & 1 deletion config/locales/crowdin/cs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@ cs:
blank: "nemůže být prázdné."
blank_nested: "musí mít nastavenou vlastnost '%{property}'."
cannot_delete_mapping: "je povinné. Nelze odstranit."
is_for_all_cannot_modify: "is for all. Cannot be modified."
is_for_all_cannot_modify: "is for all projects and can therefore not be modified."
cant_link_a_work_package_with_a_descendant: "Pracovní balíček nemůže být propojen s jedním z jeho podúkolů."
circular_dependency: "Tento vztah by vytvořil kruhovou závislost."
confirmation: "neshoduje se s %{attribute}."
Expand Down
2 changes: 1 addition & 1 deletion config/locales/crowdin/da.yml
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ da:
blank: "må ikke være tomt."
blank_nested: "needs to have the property '%{property}' set."
cannot_delete_mapping: "is required. Cannot be deleted."
is_for_all_cannot_modify: "is for all. Cannot be modified."
is_for_all_cannot_modify: "is for all projects and can therefore not be modified."
cant_link_a_work_package_with_a_descendant: "En arbejdspakke kan ikke knyttes til en af dens underopgaver."
circular_dependency: "Denne relation vil skabe en cirkulær afhængighed."
confirmation: "matcher ikke %{attribute}."
Expand Down
12 changes: 6 additions & 6 deletions config/locales/crowdin/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,7 @@ de:
blank: "muss ausgefüllt werden."
blank_nested: "muss die Eigenschaft '%{property}' gesetzt haben."
cannot_delete_mapping: "ist erforderlich. Kann nicht gelöscht werden."
is_for_all_cannot_modify: "ist für alle. Kann nicht geändert werden."
is_for_all_cannot_modify: "is for all projects and can therefore not be modified."
cant_link_a_work_package_with_a_descendant: "Ein Arbeitspaket kann nicht mit einer seiner Unteraufgaben verlinkt werden."
circular_dependency: "Diese Beziehung würde eine zyklische Abhängigkeit erzeugen."
confirmation: "stimmt nicht mit %{attribute} überein."
Expand Down Expand Up @@ -1797,8 +1797,8 @@ de:
progress_mode_changed_to_status_based: "Fortschrittsberechnung aktualisiert"
status_changed: "Status '%{status_name}'"
system_update: "OpenProject-Systemaktualisierung:"
total_percent_complete_mode_changed_to_work_weighted_average: "Calculation of % Complete totals now weighted by Work."
total_percent_complete_mode_changed_to_simple_average: "Calculation of % Complete totals now based on a simple average of only % Complete values."
total_percent_complete_mode_changed_to_work_weighted_average: "Die Berechnung der Gesamtwerte für % abgeschlossen wird jetzt nach Aufwand gewichtet."
total_percent_complete_mode_changed_to_simple_average: "Die Berechnung der Gesamtwerte für % abgeschlossen basiert jetzt auf dem einfachen Durchschnitt der % abgeschlossen-Werte."
cause_descriptions:
work_package_predecessor_changed_times: durch Änderungen am Vorgänger %{link}
work_package_parent_changed_times: durch Änderungen am übergeordneten Arbeitspaket %{link}
Expand Down Expand Up @@ -3199,13 +3199,13 @@ de:
setting_sys_api_enabled: "Den Web-Service zur Projektarchiv-Verwaltung aktivieren"
setting_sys_api_description: "Der Web-Service für Projektarchiv-Verwaltung erlaubt Integration und Nutzer-Autorisierung für den Zugriff auf Projektarchive."
setting_time_format: "Zeit"
setting_total_percent_complete_mode: "Calculation of % Complete hierarchy totals"
setting_total_percent_complete_mode: "Berechnung des Gesamtwerts % abgeschlossen in Hierarchien"
setting_total_percent_complete_mode_work_weighted_average: "Nach Arbeit gewichtet"
setting_total_percent_complete_mode_work_weighted_average_caption_html: >-
The <i>total % Complete</i> will be weighted against the <i>Work</i> of each work package in the hierarchy. Work packages without <i>Work</i> will be ignored.
Der <i>Gesamtwert % abgeschlossen</i> wird gegen den <i>Aufwand</i> der einzelnen Arbeitspakete in der Hierarchie gewichtet. Arbeitspakete ohne <i>Aufwand</i> werden ignoriert.
setting_total_percent_complete_mode_simple_average: "Einfacher Durchschnitt"
setting_total_percent_complete_mode_simple_average_caption_html: >-
<i>Work</i> is ignored and the <i>total % Complete</i> will be a simple average of <i>% Complete</i> values of work packages in the hierarchy.
Der <i>Aufwand</i> wird ignoriert und der <i>Gesamtwert % abgeschlossen</i> ist der einfache Durchschnitt der <i>% abgeschlossen</i> Werte der Arbeitspakete in der Hierarchie.
setting_accessibility_mode_for_anonymous: "Barrierefreien Modus für nicht angemeldete Nutzer aktivieren"
setting_user_format: "Format des Benutzernamens"
setting_user_default_timezone: "Standard-Zeitzone für Benutzer"
Expand Down
2 changes: 1 addition & 1 deletion config/locales/crowdin/el.yml
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,7 @@ el:
blank: "δεν πρέπει να είναι κενό."
blank_nested: "needs to have the property '%{property}' set."
cannot_delete_mapping: "is required. Cannot be deleted."
is_for_all_cannot_modify: "is for all. Cannot be modified."
is_for_all_cannot_modify: "is for all projects and can therefore not be modified."
cant_link_a_work_package_with_a_descendant: "Ένα πακέτο εργασίας δεν μπορεί να συνδεθεί με μια από τις υποεργασίες του."
circular_dependency: "Αυτή η σχέση θα δημιουργήσει κυκλική εξάρτηση."
confirmation: "δεν ταιριάζει με %{attribute}."
Expand Down
2 changes: 1 addition & 1 deletion config/locales/crowdin/eo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ eo:
blank: "ne povas esti malplena."
blank_nested: "needs to have the property '%{property}' set."
cannot_delete_mapping: "is required. Cannot be deleted."
is_for_all_cannot_modify: "is for all. Cannot be modified."
is_for_all_cannot_modify: "is for all projects and can therefore not be modified."
cant_link_a_work_package_with_a_descendant: "Laborpakaĵo ne povis esti ligita al unu el siaj subtaskoj."
circular_dependency: "Tiu ĉi rilato povus krei cirklan dependon."
confirmation: "ne kongruas kun %{attribute}"
Expand Down
Loading

0 comments on commit 4a6e023

Please sign in to comment.