From 81a44b248461710562b1fd9babe7a58ae30c3481 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 7 May 2024 15:32:25 +0300 Subject: [PATCH 01/56] [#53729] Remove project_custom_field_mapping and the projects aac patch https://community.openproject.org/work_packages/53729 --- app/contracts/base_contract.rb | 2 +- .../projects/acts_as_customizable_patches.rb | 29 ++++++++++++------- app/models/work_package.rb | 2 +- .../lib/acts_as_customizable.rb | 7 ++--- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/app/contracts/base_contract.rb b/app/contracts/base_contract.rb index 18838af93b57..f7f78b7a1298 100644 --- a/app/contracts/base_contract.rb +++ b/app/contracts/base_contract.rb @@ -237,7 +237,7 @@ def collect_available_custom_field_attributes # as the disabled custom fields would be treated as not-writable # # relevant especially for the project API - model.available_custom_fields(global: true).map(&:attribute_name) + model.all_available_custom_fields.map(&:attribute_name) else model.available_custom_fields.map(&:attribute_name) end diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index f2a33c4e974d..0006d173f659 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -122,7 +122,7 @@ def with_all_available_custom_fields result end - def available_custom_fields(global: false) + def available_custom_fields # overrides acts_as_customizable # in contrast to acts_as_customizable, custom_fields are enabled per project # thus we need to check the project_custom_field_project_mappings @@ -140,6 +140,12 @@ def available_custom_fields(global: false) # when accessed with the _query_available_custom_fields_on_global_level flag on. unless _query_available_custom_fields_on_global_level custom_fields = custom_fields.visible + + # Limit the set of available custom fields when the validation is limited to a section + if _limit_custom_fields_validation_to_section_id + custom_fields = + custom_fields.where(custom_field_section_id: _limit_custom_fields_validation_to_section_id) + end end # available_custom_fields is called from within the acts_as_customizable module @@ -149,7 +155,7 @@ def available_custom_fields(global: false) # # additionally we provide the `global` parameter to allow querying the available custom fields on a global level # when we have explicit control over the call of `available_custom_fields` - unless global || new_record? || _query_available_custom_fields_on_global_level + unless new_record? || _query_available_custom_fields_on_global_level custom_fields = custom_fields .where(id: project_custom_field_project_mappings.select(:custom_field_id)) .or(ProjectCustomField.required) @@ -158,15 +164,18 @@ def available_custom_fields(global: false) custom_fields end - def validate_custom_values - # validate custom values only of a specified section - # instead of validating ALL custom values like done in acts_as_customizable - custom_field_section_ids = CustomField - .where(id: custom_field_values.pluck(:custom_field_id)) - .where(custom_field_section_id: _limit_custom_fields_validation_to_section_id) - .pluck(:id) + def all_available_custom_fields + with_all_available_custom_fields { available_custom_fields } + end - super(custom_field_section_ids) + # Important: In order the have the custom values cache working correctly, all the variables + # that are factored in the calculation of the available_custom_fields method should be included + # in the custom_values_cache_key too. + def custom_values_cache_key + [ + _query_available_custom_fields_on_global_level || new_record?, + _limit_custom_fields_validation_to_section_id + ] end # we need to query the available custom fields on a global level when updating custom field values diff --git a/app/models/work_package.rb b/app/models/work_package.rb index a1dd265d12a3..000adb5b6fcd 100644 --- a/app/models/work_package.rb +++ b/app/models/work_package.rb @@ -523,7 +523,7 @@ def self.available_custom_field_key(work_package) private_class_method :available_custom_field_key - def custom_field_cache_key + def custom_values_cache_key [project_id, type_id] end diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index 16aaf44125a2..891562995f89 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -112,7 +112,7 @@ def custom_values_for_custom_field(id:) end def custom_field_values - custom_field_values_cache[custom_field_cache_key] ||= + custom_field_values_cache[custom_values_cache_key] ||= available_custom_fields.flat_map do |custom_field| existing_cvs = custom_values.select { |v| v.custom_field_id == custom_field.id } @@ -132,7 +132,7 @@ def custom_field_values # # i.e.: The work package custom field values are changing based on the project_id and type_id. # The only way to keep the cache updated is to include those ids in the cache key. - def custom_field_cache_key + def custom_values_cache_key 1 end @@ -233,12 +233,11 @@ def set_default_values! self.custom_field_values = new_values end - def validate_custom_values(custom_field_ids = []) + def validate_custom_values set_default_values! if new_record? custom_field_values .reject(&:marked_for_destruction?) - .select { |cv| custom_field_ids.empty? || custom_field_ids.include?(cv.custom_field_id) } .select(&:invalid?) .each { |custom_value| add_custom_value_errors! custom_value } end From d6b5030eb147ac5c58cbe2284e0ed8c325e6ec91 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 7 May 2024 18:58:22 +0300 Subject: [PATCH 02/56] Use previously_new_record? in the available_custom_fields of the aac project patch. --- app/models/projects/acts_as_customizable_patches.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index 0006d173f659..d7dc07070d3f 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -155,7 +155,7 @@ def available_custom_fields # # additionally we provide the `global` parameter to allow querying the available custom fields on a global level # when we have explicit control over the call of `available_custom_fields` - unless new_record? || _query_available_custom_fields_on_global_level + unless global_custom_fields? custom_fields = custom_fields .where(id: project_custom_field_project_mappings.select(:custom_field_id)) .or(ProjectCustomField.required) @@ -164,6 +164,10 @@ def available_custom_fields custom_fields end + def global_custom_fields? + new_record? || previously_new_record? || _query_available_custom_fields_on_global_level + end + def all_available_custom_fields with_all_available_custom_fields { available_custom_fields } end @@ -172,10 +176,7 @@ def all_available_custom_fields # that are factored in the calculation of the available_custom_fields method should be included # in the custom_values_cache_key too. def custom_values_cache_key - [ - _query_available_custom_fields_on_global_level || new_record?, - _limit_custom_fields_validation_to_section_id - ] + [global_custom_fields?, _limit_custom_fields_validation_to_section_id] end # we need to query the available custom fields on a global level when updating custom field values From c2cd7c7e352ee7c09634fd765927711d8a30373d Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 7 May 2024 21:41:03 +0300 Subject: [PATCH 03/56] Use global flag cache keys --- app/models/projects/acts_as_customizable_patches.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index d7dc07070d3f..50455dc4cb06 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -155,7 +155,7 @@ def available_custom_fields # # additionally we provide the `global` parameter to allow querying the available custom fields on a global level # when we have explicit control over the call of `available_custom_fields` - unless global_custom_fields? + unless global_custom_fields_enabled? custom_fields = custom_fields .where(id: project_custom_field_project_mappings.select(:custom_field_id)) .or(ProjectCustomField.required) @@ -164,8 +164,12 @@ def available_custom_fields custom_fields end - def global_custom_fields? - new_record? || previously_new_record? || _query_available_custom_fields_on_global_level + def global_custom_fields_enabled? + global_custom_field_flags.any?(&:present?) + end + + def global_custom_field_flags + [new_record?, previously_new_record?, _query_available_custom_fields_on_global_level] end def all_available_custom_fields @@ -176,7 +180,7 @@ def all_available_custom_fields # that are factored in the calculation of the available_custom_fields method should be included # in the custom_values_cache_key too. def custom_values_cache_key - [global_custom_fields?, _limit_custom_fields_validation_to_section_id] + [*global_custom_field_flags, _limit_custom_fields_validation_to_section_id] end # we need to query the available custom fields on a global level when updating custom field values From 1b5bba17282280ba67316f3378b11b53e20b3de9 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 8 May 2024 15:45:42 +0300 Subject: [PATCH 04/56] Restrict custom value cache key to section --- app/models/projects/acts_as_customizable_patches.rb | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index 50455dc4cb06..8f4fdc7bde33 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -155,7 +155,7 @@ def available_custom_fields # # additionally we provide the `global` parameter to allow querying the available custom fields on a global level # when we have explicit control over the call of `available_custom_fields` - unless global_custom_fields_enabled? + unless new_record? || previously_new_record? || _query_available_custom_fields_on_global_level custom_fields = custom_fields .where(id: project_custom_field_project_mappings.select(:custom_field_id)) .or(ProjectCustomField.required) @@ -164,14 +164,6 @@ def available_custom_fields custom_fields end - def global_custom_fields_enabled? - global_custom_field_flags.any?(&:present?) - end - - def global_custom_field_flags - [new_record?, previously_new_record?, _query_available_custom_fields_on_global_level] - end - def all_available_custom_fields with_all_available_custom_fields { available_custom_fields } end @@ -180,7 +172,7 @@ def all_available_custom_fields # that are factored in the calculation of the available_custom_fields method should be included # in the custom_values_cache_key too. def custom_values_cache_key - [*global_custom_field_flags, _limit_custom_fields_validation_to_section_id] + [_limit_custom_fields_validation_to_section_id] end # we need to query the available custom fields on a global level when updating custom field values From 56a144510f70303b72a81acd8836e718a30b9176 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 8 May 2024 17:06:34 +0300 Subject: [PATCH 05/56] Change available_custom_fields query --- app/models/projects/acts_as_customizable_patches.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index 8f4fdc7bde33..a3366b418ba6 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -140,12 +140,12 @@ def available_custom_fields # when accessed with the _query_available_custom_fields_on_global_level flag on. unless _query_available_custom_fields_on_global_level custom_fields = custom_fields.visible + end - # Limit the set of available custom fields when the validation is limited to a section - if _limit_custom_fields_validation_to_section_id - custom_fields = - custom_fields.where(custom_field_section_id: _limit_custom_fields_validation_to_section_id) - end + # Limit the set of available custom fields when the validation is limited to a section + if _limit_custom_fields_validation_to_section_id + custom_fields = + custom_fields.where(custom_field_section_id: _limit_custom_fields_validation_to_section_id) end # available_custom_fields is called from within the acts_as_customizable module From 0f19b7ce96024722e8ee4f1b44a61cd83824b218 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 8 May 2024 18:48:40 +0300 Subject: [PATCH 06/56] Remove previously_new_record? condition from Project#available_custom_fields --- app/models/projects/acts_as_customizable_patches.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index a3366b418ba6..2ed2e8b8d045 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -155,7 +155,7 @@ def available_custom_fields # # additionally we provide the `global` parameter to allow querying the available custom fields on a global level # when we have explicit control over the call of `available_custom_fields` - unless new_record? || previously_new_record? || _query_available_custom_fields_on_global_level + unless new_record? || _query_available_custom_fields_on_global_level custom_fields = custom_fields .where(id: project_custom_field_project_mappings.select(:custom_field_id)) .or(ProjectCustomField.required) From 377bb80f617fd98e4acdef1cb9c73e34d10034db Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 14 May 2024 21:42:11 +0300 Subject: [PATCH 07/56] Use the global modifier in the custom values cache key --- .../projects/acts_as_customizable_patches.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index 2ed2e8b8d045..47f1d7f6940b 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -44,13 +44,13 @@ module Projects::ActsAsCustomizablePatches # after_save is not touched in this case which causes the flag to stay active after_validation :set_query_available_custom_fields_to_project_level - before_update :set_query_available_custom_fields_to_global_level + before_save :set_query_available_custom_fields_to_global_level, unless: :new_record? before_create :reject_section_scoped_validation_for_creation before_create :build_missing_project_custom_field_project_mappings after_save :reset_section_scoped_validation, :set_query_available_custom_fields_to_project_level - after_save :disable_custom_fields_with_empty_values, if: :previously_new_record? + after_create :disable_custom_fields_with_empty_values def build_missing_project_custom_field_project_mappings # activate custom fields for this project (via mapping table) if values have been provided for custom_fields but no mapping exists @@ -140,12 +140,12 @@ def available_custom_fields # when accessed with the _query_available_custom_fields_on_global_level flag on. unless _query_available_custom_fields_on_global_level custom_fields = custom_fields.visible - end - # Limit the set of available custom fields when the validation is limited to a section - if _limit_custom_fields_validation_to_section_id - custom_fields = - custom_fields.where(custom_field_section_id: _limit_custom_fields_validation_to_section_id) + # Limit the set of available custom fields when the validation is limited to a section + if _limit_custom_fields_validation_to_section_id + custom_fields = + custom_fields.where(custom_field_section_id: _limit_custom_fields_validation_to_section_id) + end end # available_custom_fields is called from within the acts_as_customizable module @@ -172,7 +172,7 @@ def all_available_custom_fields # that are factored in the calculation of the available_custom_fields method should be included # in the custom_values_cache_key too. def custom_values_cache_key - [_limit_custom_fields_validation_to_section_id] + [_query_available_custom_fields_on_global_level, _limit_custom_fields_validation_to_section_id] end # we need to query the available custom fields on a global level when updating custom field values From e5f9455572bb9ee6aa55dcfe5868edb0cd89b532 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 15 May 2024 15:35:05 +0300 Subject: [PATCH 08/56] Change the custom field validation to allow defining fields to validate. --- .../projects/acts_as_customizable_patches.rb | 24 +++++++++---------- .../lib/acts_as_customizable.rb | 6 ++++- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index 47f1d7f6940b..772d07a2f7aa 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -44,13 +44,13 @@ module Projects::ActsAsCustomizablePatches # after_save is not touched in this case which causes the flag to stay active after_validation :set_query_available_custom_fields_to_project_level - before_save :set_query_available_custom_fields_to_global_level, unless: :new_record? + before_update :set_query_available_custom_fields_to_global_level before_create :reject_section_scoped_validation_for_creation before_create :build_missing_project_custom_field_project_mappings after_save :reset_section_scoped_validation, :set_query_available_custom_fields_to_project_level - after_create :disable_custom_fields_with_empty_values + after_save :disable_custom_fields_with_empty_values, if: :previously_new_record? def build_missing_project_custom_field_project_mappings # activate custom fields for this project (via mapping table) if values have been provided for custom_fields but no mapping exists @@ -123,6 +123,7 @@ def with_all_available_custom_fields end def available_custom_fields + # TODO: Add caching here. # overrides acts_as_customizable # in contrast to acts_as_customizable, custom_fields are enabled per project # thus we need to check the project_custom_field_project_mappings @@ -140,12 +141,6 @@ def available_custom_fields # when accessed with the _query_available_custom_fields_on_global_level flag on. unless _query_available_custom_fields_on_global_level custom_fields = custom_fields.visible - - # Limit the set of available custom fields when the validation is limited to a section - if _limit_custom_fields_validation_to_section_id - custom_fields = - custom_fields.where(custom_field_section_id: _limit_custom_fields_validation_to_section_id) - end end # available_custom_fields is called from within the acts_as_customizable module @@ -168,11 +163,14 @@ def all_available_custom_fields with_all_available_custom_fields { available_custom_fields } end - # Important: In order the have the custom values cache working correctly, all the variables - # that are factored in the calculation of the available_custom_fields method should be included - # in the custom_values_cache_key too. - def custom_values_cache_key - [_query_available_custom_fields_on_global_level, _limit_custom_fields_validation_to_section_id] + def custom_fields_to_validate + custom_fields = available_custom_fields + # Limit the set of available custom fields when the validation is limited to a section + if _limit_custom_fields_validation_to_section_id + custom_fields = + custom_fields.where(custom_field_section_id: _limit_custom_fields_validation_to_section_id) + end + custom_fields end # we need to query the available custom fields on a global level when updating custom field values diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index 891562995f89..97c5ae4553f5 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -233,10 +233,14 @@ def set_default_values! self.custom_field_values = new_values end + def custom_fields_to_validate + custom_field_values + end + def validate_custom_values set_default_values! if new_record? - custom_field_values + custom_fields_to_validate .reject(&:marked_for_destruction?) .select(&:invalid?) .each { |custom_value| add_custom_value_errors! custom_value } From 2a21cb00f4b0ad04163daed9cf4583c853528ea4 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 15 May 2024 15:44:38 +0300 Subject: [PATCH 09/56] Undo name change --- app/models/work_package.rb | 2 +- .../plugins/acts_as_customizable/lib/acts_as_customizable.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/work_package.rb b/app/models/work_package.rb index 000adb5b6fcd..a1dd265d12a3 100644 --- a/app/models/work_package.rb +++ b/app/models/work_package.rb @@ -523,7 +523,7 @@ def self.available_custom_field_key(work_package) private_class_method :available_custom_field_key - def custom_values_cache_key + def custom_field_cache_key [project_id, type_id] end diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index 97c5ae4553f5..197fd31b8ad5 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -112,7 +112,7 @@ def custom_values_for_custom_field(id:) end def custom_field_values - custom_field_values_cache[custom_values_cache_key] ||= + custom_field_values_cache[custom_field_cache_key] ||= available_custom_fields.flat_map do |custom_field| existing_cvs = custom_values.select { |v| v.custom_field_id == custom_field.id } @@ -132,7 +132,7 @@ def custom_field_values # # i.e.: The work package custom field values are changing based on the project_id and type_id. # The only way to keep the cache updated is to include those ids in the cache key. - def custom_values_cache_key + def custom_field_cache_key 1 end From 0b3059928285ad71224a425d62738164eba4354a Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Thu, 16 May 2024 18:47:44 +0300 Subject: [PATCH 10/56] Introduce custom_field_values_to_validate method to control section based validation --- app/models/projects/acts_as_customizable_patches.rb | 11 ++++++----- .../acts_as_customizable/lib/acts_as_customizable.rb | 5 ++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index 772d07a2f7aa..dd63a7c89e8c 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -163,14 +163,15 @@ def all_available_custom_fields with_all_available_custom_fields { available_custom_fields } end - def custom_fields_to_validate - custom_fields = available_custom_fields + def custom_field_values_to_validate # Limit the set of available custom fields when the validation is limited to a section if _limit_custom_fields_validation_to_section_id - custom_fields = - custom_fields.where(custom_field_section_id: _limit_custom_fields_validation_to_section_id) + custom_field_values.select do |cfv| + cfv.custom_field.custom_field_section_id == _limit_custom_fields_validation_to_section_id + end + else + custom_field_values end - custom_fields end # we need to query the available custom fields on a global level when updating custom field values diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index 197fd31b8ad5..e95c846b5b23 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -233,14 +233,13 @@ def set_default_values! self.custom_field_values = new_values end - def custom_fields_to_validate + def custom_field_values_to_validate custom_field_values end def validate_custom_values set_default_values! if new_record? - - custom_fields_to_validate + custom_field_values_to_validate .reject(&:marked_for_destruction?) .select(&:invalid?) .each { |custom_value| add_custom_value_errors! custom_value } From 03c4542161c690ff77e53494b564037b5a0abf34 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 22 May 2024 16:10:49 +0300 Subject: [PATCH 11/56] Try moving the aac validation into the contracts --- app/contracts/groups/base_contract.rb | 3 +++ app/contracts/projects/base_contract.rb | 3 +++ app/contracts/users/base_contract.rb | 3 +++ app/contracts/versions/base_contract.rb | 3 +++ app/contracts/work_packages/base_contract.rb | 3 +++ .../plugins/acts_as_customizable/lib/acts_as_customizable.rb | 2 +- 6 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/contracts/groups/base_contract.rb b/app/contracts/groups/base_contract.rb index ee0309e789d3..540089dc77ec 100644 --- a/app/contracts/groups/base_contract.rb +++ b/app/contracts/groups/base_contract.rb @@ -38,6 +38,9 @@ class BaseContract < ::ModelContract validate :validate_unique_users + delegate :validate_custom_values, to: :model + validate :validate_custom_values + private # Validating on the group_users since those are dealt with in the diff --git a/app/contracts/projects/base_contract.rb b/app/contracts/projects/base_contract.rb index a894a0e2ffe8..f9343fa85cc6 100644 --- a/app/contracts/projects/base_contract.rb +++ b/app/contracts/projects/base_contract.rb @@ -57,6 +57,9 @@ class BaseContract < ::ModelContract validate :validate_user_allowed_to_manage + delegate :validate_custom_values, to: :model + validate :validate_custom_values + def assignable_parents Project .allowed_to(user, :add_subprojects) diff --git a/app/contracts/users/base_contract.rb b/app/contracts/users/base_contract.rb index 2dfb83fd0c6f..ac509b515c35 100644 --- a/app/contracts/users/base_contract.rb +++ b/app/contracts/users/base_contract.rb @@ -62,6 +62,9 @@ def self.model delegate :available_custom_fields, to: :model + delegate :validate_custom_values, to: :model + validate :validate_custom_values + def reduce_writable_attributes(attributes) super.tap do |writable| writable << "password" if password_writable? diff --git a/app/contracts/versions/base_contract.rb b/app/contracts/versions/base_contract.rb index 96178fb42ba7..4e28d414557a 100644 --- a/app/contracts/versions/base_contract.rb +++ b/app/contracts/versions/base_contract.rb @@ -42,6 +42,9 @@ def self.model validate :validate_project_is_set validate :validate_sharing_included + delegate :validate_custom_values, to: :model + validate :validate_custom_values + attribute :name attribute :description attribute :start_date diff --git a/app/contracts/work_packages/base_contract.rb b/app/contracts/work_packages/base_contract.rb index a43c9ef95279..334de075fe59 100644 --- a/app/contracts/work_packages/base_contract.rb +++ b/app/contracts/work_packages/base_contract.rb @@ -154,6 +154,9 @@ class BaseContract < ::ModelContract validate :validate_duration_and_dates_are_not_derivable + delegate :validate_custom_values, to: :model + validate :validate_custom_values + def initialize(work_package, user, options: {}) super diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index e95c846b5b23..dcc6c0fbf1df 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -49,7 +49,7 @@ def acts_as_customizable(options = {}) dependent: :delete_all, validate: false, autosave: true - validate :validate_custom_values + send :include, Redmine::Acts::Customizable::InstanceMethods before_save :ensure_custom_values_complete From 9ae205a4ed03c92dcc68ea3483a75e86a530e104 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 22 May 2024 16:38:34 +0300 Subject: [PATCH 12/56] Revert "Try moving the aac validation into the contracts" This reverts commit 1db410c00ae5bd0ba2bf432395a9adcaae0e9b10. --- app/contracts/groups/base_contract.rb | 3 --- app/contracts/projects/base_contract.rb | 3 --- app/contracts/users/base_contract.rb | 3 --- app/contracts/versions/base_contract.rb | 3 --- app/contracts/work_packages/base_contract.rb | 3 --- .../plugins/acts_as_customizable/lib/acts_as_customizable.rb | 2 +- 6 files changed, 1 insertion(+), 16 deletions(-) diff --git a/app/contracts/groups/base_contract.rb b/app/contracts/groups/base_contract.rb index 540089dc77ec..ee0309e789d3 100644 --- a/app/contracts/groups/base_contract.rb +++ b/app/contracts/groups/base_contract.rb @@ -38,9 +38,6 @@ class BaseContract < ::ModelContract validate :validate_unique_users - delegate :validate_custom_values, to: :model - validate :validate_custom_values - private # Validating on the group_users since those are dealt with in the diff --git a/app/contracts/projects/base_contract.rb b/app/contracts/projects/base_contract.rb index f9343fa85cc6..a894a0e2ffe8 100644 --- a/app/contracts/projects/base_contract.rb +++ b/app/contracts/projects/base_contract.rb @@ -57,9 +57,6 @@ class BaseContract < ::ModelContract validate :validate_user_allowed_to_manage - delegate :validate_custom_values, to: :model - validate :validate_custom_values - def assignable_parents Project .allowed_to(user, :add_subprojects) diff --git a/app/contracts/users/base_contract.rb b/app/contracts/users/base_contract.rb index ac509b515c35..2dfb83fd0c6f 100644 --- a/app/contracts/users/base_contract.rb +++ b/app/contracts/users/base_contract.rb @@ -62,9 +62,6 @@ def self.model delegate :available_custom_fields, to: :model - delegate :validate_custom_values, to: :model - validate :validate_custom_values - def reduce_writable_attributes(attributes) super.tap do |writable| writable << "password" if password_writable? diff --git a/app/contracts/versions/base_contract.rb b/app/contracts/versions/base_contract.rb index 4e28d414557a..96178fb42ba7 100644 --- a/app/contracts/versions/base_contract.rb +++ b/app/contracts/versions/base_contract.rb @@ -42,9 +42,6 @@ def self.model validate :validate_project_is_set validate :validate_sharing_included - delegate :validate_custom_values, to: :model - validate :validate_custom_values - attribute :name attribute :description attribute :start_date diff --git a/app/contracts/work_packages/base_contract.rb b/app/contracts/work_packages/base_contract.rb index 334de075fe59..a43c9ef95279 100644 --- a/app/contracts/work_packages/base_contract.rb +++ b/app/contracts/work_packages/base_contract.rb @@ -154,9 +154,6 @@ class BaseContract < ::ModelContract validate :validate_duration_and_dates_are_not_derivable - delegate :validate_custom_values, to: :model - validate :validate_custom_values - def initialize(work_package, user, options: {}) super diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index dcc6c0fbf1df..e95c846b5b23 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -49,7 +49,7 @@ def acts_as_customizable(options = {}) dependent: :delete_all, validate: false, autosave: true - + validate :validate_custom_values send :include, Redmine::Acts::Customizable::InstanceMethods before_save :ensure_custom_values_complete From 0a6d3905b9852765ccad7ac49a0f525257c89019 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 22 May 2024 19:21:01 +0300 Subject: [PATCH 13/56] Move disable_custom_fields_with_empty_values to the NewProjectService concern. --- .../projects/acts_as_customizable_patches.rb | 23 ------------------- .../projects/concerns/new_project_service.rb | 21 +++++++++++++++++ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index dd63a7c89e8c..22c34dcdd0b1 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -50,7 +50,6 @@ module Projects::ActsAsCustomizablePatches before_create :build_missing_project_custom_field_project_mappings after_save :reset_section_scoped_validation, :set_query_available_custom_fields_to_project_level - after_save :disable_custom_fields_with_empty_values, if: :previously_new_record? def build_missing_project_custom_field_project_mappings # activate custom fields for this project (via mapping table) if values have been provided for custom_fields but no mapping exists @@ -90,28 +89,6 @@ def reject_section_scoped_validation_for_creation end end - def disable_custom_fields_with_empty_values - # run only on initial creation! (otherwise we would deactivate custom fields with empty values on every update!) - # - # ideally, `build_missing_project_custom_field_project_mappings` would not activate custom fields with empty values - # but: - # this hook is required as acts_as_customizable build custom values with their default value even if a blank value was provided in the project creation form - # `build_missing_project_custom_field_project_mappings` will then activate the custom field although the user explicitly provided a blank value - # in order to not patch `acts_as_customizable` further, we simply identify these custom values and deactivate the custom field - - # This callback should be an after_save callback, because the custom_values association has autosave - # and it has after_create callbacks in the model (CustomValue#activate_custom_field_in_customized_project). - # The after_create callback in the children objects are ran after the after_create callbacks on the parent. - # In order to make sure we execute this callback after the children's callbacks, the after_save hook must be used. - custom_field_ids = project.custom_values.select { |cv| cv.value.blank? && !cv.required? }.pluck(:custom_field_id) - - project_custom_field_project_mappings - .where(custom_field_id: custom_field_ids) - .or(project_custom_field_project_mappings - .where.not(custom_field_id: available_custom_fields.select(:id))) - .destroy_all - end - def with_all_available_custom_fields # query the available custom fields on a global level when updating custom field values # in order to support implicit activation of custom fields when values are provided during an update diff --git a/app/services/projects/concerns/new_project_service.rb b/app/services/projects/concerns/new_project_service.rb index 4b8dd4d90de7..a70c34680034 100644 --- a/app/services/projects/concerns/new_project_service.rb +++ b/app/services/projects/concerns/new_project_service.rb @@ -34,6 +34,7 @@ def after_perform(attributes_call) new_project = attributes_call.result set_default_role(new_project) unless user.admin? + disable_custom_fields_with_empty_values(new_project) notify_project_created(new_project) super @@ -68,5 +69,25 @@ def notify_project_created(new_project) project: new_project ) end + + def disable_custom_fields_with_empty_values(new_project) + # run only on initial creation! (otherwise we would deactivate custom fields with empty values on every update!) + # + # ideally, `build_missing_project_custom_field_project_mappings` would not activate custom fields with empty values + # but: + # this hook is required as acts_as_customizable build custom values with their default value even if a blank value + # was provided in the project creation form `build_missing_project_custom_field_project_mappings` will then activate + # the custom field although the user explicitly provided a blank value in order to not patch `acts_as_customizable` + # further, we simply identify these custom values and deactivate the custom field + + custom_field_ids = new_project.custom_values.select { |cv| cv.value.blank? && !cv.required? }.pluck(:custom_field_id) + custom_field_project_mappings = new_project.project_custom_field_project_mappings + + custom_field_project_mappings + .where(custom_field_id: custom_field_ids) + .or(custom_field_project_mappings + .where.not(custom_field_id: new_project.available_custom_fields.select(:id))) + .destroy_all + end end end From 69b146e82f29e00e048031da577321ffcc172afa Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 28 May 2024 12:16:59 +0300 Subject: [PATCH 14/56] Add specs for moving callbacks from AAC patch to the contract. --- spec/models/projects/customizable_spec.rb | 41 ------------- .../behaves_like_create_service.rb | 6 +- spec/services/projects/create_service_spec.rb | 60 +++++++++++++++++++ 3 files changed, 65 insertions(+), 42 deletions(-) diff --git a/spec/models/projects/customizable_spec.rb b/spec/models/projects/customizable_spec.rb index 6dc4d64fe222..2f47c31bec8d 100644 --- a/spec/models/projects/customizable_spec.rb +++ b/spec/models/projects/customizable_spec.rb @@ -429,45 +429,4 @@ it_behaves_like "implicitly enabled and saved custom values" end end - - context "with hidden custom fields" do - let!(:hidden_custom_field) do - create(:text_project_custom_field, project_custom_field_section: section, visible: false) - end - let(:project) do - create(:project, custom_field_values: { - text_custom_field.id => "foo", - bool_custom_field.id => true, - hidden_custom_field.id => "hidden" - }) - end - - before do - User.current = user # needs to be executed before project creation! - end - - context "with admin permission" do - let(:user) { create(:admin) } - - it "does activate hidden custom fields" do - # project creation happens with an admin user as let(:project) called after setting the current user to an admin - expect(project.project_custom_field_project_mappings.pluck(:custom_field_id)) - .to contain_exactly(text_custom_field.id, bool_custom_field.id, hidden_custom_field.id) - - expect(project.custom_value_for(hidden_custom_field).typed_value).to eq("hidden") - end - end - - context "without admin permission" do - let(:user) { create(:user) } - - it "does not activate hidden custom fields" do - # project creation happens with an non-admin user as let(:project) called after setting the current user to an non-admin - expect(project.project_custom_field_project_mappings.pluck(:custom_field_id)) - .to contain_exactly(text_custom_field.id, bool_custom_field.id) - - expect(project.custom_value_for(hidden_custom_field)).to be_nil - end - end - end end diff --git a/spec/services/base_services/behaves_like_create_service.rb b/spec/services/base_services/behaves_like_create_service.rb index 8ac453932734..22ff7db435cb 100644 --- a/spec/services/base_services/behaves_like_create_service.rb +++ b/spec/services/base_services/behaves_like_create_service.rb @@ -56,6 +56,9 @@ end let!(:model_instance) { build_stubbed(factory) } let!(:set_attributes_service) do + # Do not stub the SetAttributesService when the model is not stubbed + next unless stub_model_instance + service = double("set_attributes_service_instance") allow(set_attributes_class) @@ -75,10 +78,11 @@ let(:model_save_result) { true } let(:contract_validate_result) { true } + let(:stub_model_instance) { true } before do allow(model_instance).to receive(:save).and_return(model_save_result) - allow(instance).to receive(:instance).and_return(model_instance) + allow(instance).to receive(:instance).and_return(model_instance) if stub_model_instance end subject { instance.call(call_attributes) } diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 24cc058eac2d..b4dd2aa38e08 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -70,5 +70,65 @@ .not_to(have_received(:call)) end end + + context "with hidden custom fields" do + # Skip stubbing in order to execute the real service call + let(:stub_model_instance) { false } + let(:call_attributes) do + attributes_for(:project, + custom_field_values: { + text_custom_field.id => "foo", + bool_custom_field.id => true, + hidden_custom_field.id => "hidden" + }).except(:created_at, :updated_at) + end + let!(:section) { create(:project_custom_field_section) } + let!(:bool_custom_field) do + create(:boolean_project_custom_field, project_custom_field_section: section) + end + let!(:text_custom_field) do + create(:text_project_custom_field, project_custom_field_section: section) + end + let!(:list_custom_field) do + create(:list_project_custom_field, project_custom_field_section: section) + end + let!(:hidden_custom_field) do + create(:text_project_custom_field, project_custom_field_section: section, visible: false) + end + let(:project) { subject.result } + + before do + User.current = user + end + + context "with admin permission" do + let(:user) { build_stubbed(:admin) } + + it "does activate hidden custom fields" do + subject + expect(project.project_custom_field_project_mappings.pluck(:custom_field_id)) + .to contain_exactly(text_custom_field.id, bool_custom_field.id, hidden_custom_field.id) + expect(project.custom_value_for(hidden_custom_field).typed_value).to eq("hidden") + end + end + + context "without admin permission" do + let(:user) { create(:user) } + + before do + mock_permissions_for(user) do |mock| + mock.allow_globally :add_project + end + end + + it "does not activate hidden custom fields" do + subject + + expect(project.project_custom_field_project_mappings.pluck(:custom_field_id)) + .to contain_exactly(text_custom_field.id, bool_custom_field.id) + expect(project.custom_value_for(hidden_custom_field)).to be_nil + end + end + end end end From d839f8339b0727b89745fa596aa7e3e0199ac303 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 28 May 2024 13:42:19 +0300 Subject: [PATCH 15/56] Update the Projects::CopyService to execute the after_perform super first --- app/services/projects/copy_service.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/projects/copy_service.rb b/app/services/projects/copy_service.rb index cbd8098ae8ea..9983adeb566a 100644 --- a/app/services/projects/copy_service.rb +++ b/app/services/projects/copy_service.rb @@ -93,9 +93,9 @@ def before_perform(params, service_call) end def after_perform(call) - copy_activated_custom_fields(call) - - super + super.tap do |super_call| + copy_activated_custom_fields(super_call) + end end def copy_activated_custom_fields(call) From ae717e7c5f8c140edf76d3ca1e11a8bea771dff1 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 28 May 2024 14:04:54 +0300 Subject: [PATCH 16/56] Move AAC patch parts to the project services --- .../reset_global_query_after_validate_hook.rb | 39 +++++++++++++++++++ app/services/projects/copy_service.rb | 1 + app/services/projects/create_service.rb | 1 + app/services/projects/update_service.rb | 1 + 4 files changed, 42 insertions(+) create mode 100644 app/services/projects/concerns/reset_global_query_after_validate_hook.rb diff --git a/app/services/projects/concerns/reset_global_query_after_validate_hook.rb b/app/services/projects/concerns/reset_global_query_after_validate_hook.rb new file mode 100644 index 000000000000..3c7855fda9dc --- /dev/null +++ b/app/services/projects/concerns/reset_global_query_after_validate_hook.rb @@ -0,0 +1,39 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-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 Projects::Concerns + module ResetGlobalQueryAfterValidateHook + private + + def after_validate(params, service_call) + model._query_available_custom_fields_on_global_level = nil + + super + end + end +end diff --git a/app/services/projects/copy_service.rb b/app/services/projects/copy_service.rb index 9983adeb566a..4dcad38b4040 100644 --- a/app/services/projects/copy_service.rb +++ b/app/services/projects/copy_service.rb @@ -31,6 +31,7 @@ module Projects class CopyService < ::BaseServices::Copy include Projects::Concerns::NewProjectService + include Projects::Concerns::ResetGlobalQueryAfterValidateHook def self.copy_dependencies [ diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 6252b7861997..52508a256ac7 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -29,5 +29,6 @@ module Projects class CreateService < ::BaseServices::Create include Projects::Concerns::NewProjectService + include Projects::Concerns::ResetGlobalQueryAfterValidateHook end end diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index e7e658eb491e..418ae8624c46 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -29,6 +29,7 @@ module Projects class UpdateService < ::BaseServices::Update prepend Projects::Concerns::UpdateDemoData + include Projects::Concerns::ResetGlobalQueryAfterValidateHook private From cadf6543086319696e6d0dec155d8256510693b7 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 28 May 2024 14:10:26 +0300 Subject: [PATCH 17/56] Remove set_query_available_custom_fields_to_project_level after validate hook from the AAC patch --- app/models/projects/acts_as_customizable_patches.rb | 5 ----- .../concerns/reset_global_query_after_validate_hook.rb | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index 22c34dcdd0b1..05fb4b360ded 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -39,11 +39,6 @@ module Projects::ActsAsCustomizablePatches dependent: :destroy, inverse_of: :project has_many :project_custom_fields, through: :project_custom_field_project_mappings, class_name: "ProjectCustomField" - # we need to reset the query_available_custom_fields_on_global_level already after validation - # as the update service just calls .valid? and returns if invalid - # after_save is not touched in this case which causes the flag to stay active - after_validation :set_query_available_custom_fields_to_project_level - before_update :set_query_available_custom_fields_to_global_level before_create :reject_section_scoped_validation_for_creation diff --git a/app/services/projects/concerns/reset_global_query_after_validate_hook.rb b/app/services/projects/concerns/reset_global_query_after_validate_hook.rb index 3c7855fda9dc..3333e6b17b4e 100644 --- a/app/services/projects/concerns/reset_global_query_after_validate_hook.rb +++ b/app/services/projects/concerns/reset_global_query_after_validate_hook.rb @@ -31,6 +31,10 @@ module ResetGlobalQueryAfterValidateHook private def after_validate(params, service_call) + # we need to reset the query_available_custom_fields_on_global_level already after validation + # as the update service just calls .valid? and returns if invalid + # after_save is not touched in this case which causes the flag to stay active + model = service_call.result model._query_available_custom_fields_on_global_level = nil super From 2b85ab95ee642c63084f3eaffa1c8b44736e8af6 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 28 May 2024 15:46:33 +0300 Subject: [PATCH 18/56] Use the after_perform hook to reset global query on the Projects services --- .../projects/acts_as_customizable_patches.rb | 8 +------- .../projects/concerns/new_project_service.rb | 2 ++ ...date_hook.rb => reset_global_query_hooks.rb} | 17 ++++++++++++++--- app/services/projects/copy_service.rb | 1 - app/services/projects/create_service.rb | 1 - app/services/projects/update_service.rb | 5 +++-- 6 files changed, 20 insertions(+), 14 deletions(-) rename app/services/projects/concerns/{reset_global_query_after_validate_hook.rb => reset_global_query_hooks.rb} (77%) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index 05fb4b360ded..49b6e0fdbeeb 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -44,7 +44,7 @@ module Projects::ActsAsCustomizablePatches before_create :reject_section_scoped_validation_for_creation before_create :build_missing_project_custom_field_project_mappings - after_save :reset_section_scoped_validation, :set_query_available_custom_fields_to_project_level + after_save :reset_section_scoped_validation def build_missing_project_custom_field_project_mappings # activate custom fields for this project (via mapping table) if values have been provided for custom_fields but no mapping exists @@ -71,12 +71,6 @@ def set_query_available_custom_fields_to_global_level self._query_available_custom_fields_on_global_level = true end - def set_query_available_custom_fields_to_project_level - # reset the query_available_custom_fields_on_global_level after saving - # in order not to silently carry this setting in this instance - self._query_available_custom_fields_on_global_level = nil - end - def reject_section_scoped_validation_for_creation if _limit_custom_fields_validation_to_section_id.present? raise ArgumentError, diff --git a/app/services/projects/concerns/new_project_service.rb b/app/services/projects/concerns/new_project_service.rb index a70c34680034..92f3b04f68df 100644 --- a/app/services/projects/concerns/new_project_service.rb +++ b/app/services/projects/concerns/new_project_service.rb @@ -28,6 +28,8 @@ module Projects::Concerns module NewProjectService + include Projects::Concerns::ResetGlobalQueryHooks + private def after_perform(attributes_call) diff --git a/app/services/projects/concerns/reset_global_query_after_validate_hook.rb b/app/services/projects/concerns/reset_global_query_hooks.rb similarity index 77% rename from app/services/projects/concerns/reset_global_query_after_validate_hook.rb rename to app/services/projects/concerns/reset_global_query_hooks.rb index 3333e6b17b4e..d678994067ef 100644 --- a/app/services/projects/concerns/reset_global_query_after_validate_hook.rb +++ b/app/services/projects/concerns/reset_global_query_hooks.rb @@ -27,17 +27,28 @@ #++ module Projects::Concerns - module ResetGlobalQueryAfterValidateHook + module ResetGlobalQueryHooks private def after_validate(params, service_call) # we need to reset the query_available_custom_fields_on_global_level already after validation # as the update service just calls .valid? and returns if invalid # after_save is not touched in this case which causes the flag to stay active - model = service_call.result - model._query_available_custom_fields_on_global_level = nil + set_query_available_custom_fields_to_project_level(service_call.result) + + super + end + + def after_perform(service_call) + set_query_available_custom_fields_to_project_level(service_call.result) super end + + def set_query_available_custom_fields_to_project_level(model) + # reset the query_available_custom_fields_on_global_level after saving + # in order not to silently carry this setting in this instance + model._query_available_custom_fields_on_global_level = nil + end end end diff --git a/app/services/projects/copy_service.rb b/app/services/projects/copy_service.rb index 4dcad38b4040..9983adeb566a 100644 --- a/app/services/projects/copy_service.rb +++ b/app/services/projects/copy_service.rb @@ -31,7 +31,6 @@ module Projects class CopyService < ::BaseServices::Copy include Projects::Concerns::NewProjectService - include Projects::Concerns::ResetGlobalQueryAfterValidateHook def self.copy_dependencies [ diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 52508a256ac7..6252b7861997 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -29,6 +29,5 @@ module Projects class CreateService < ::BaseServices::Create include Projects::Concerns::NewProjectService - include Projects::Concerns::ResetGlobalQueryAfterValidateHook end end diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 418ae8624c46..573b90ce8fc7 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -29,7 +29,7 @@ module Projects class UpdateService < ::BaseServices::Update prepend Projects::Concerns::UpdateDemoData - include Projects::Concerns::ResetGlobalQueryAfterValidateHook + include Projects::Concerns::ResetGlobalQueryHooks private @@ -46,13 +46,14 @@ def set_attributes(params) end def after_perform(service_call) + ret = super touch_on_custom_values_update notify_on_identifier_renamed send_update_notification update_wp_versions_on_parent_change handle_archiving - service_call + ret end def touch_on_custom_values_update From a0367277f67acbce943cf45bc488a4eae228b1c4 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 28 May 2024 16:46:05 +0300 Subject: [PATCH 19/56] Move reject_section_scoped_validation to the Projects::CreateContract --- .../projects/acts_as_customizable_patches.rb | 8 -------- .../projects/concerns/new_project_service.rb | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index 49b6e0fdbeeb..6b1dc672129f 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -41,7 +41,6 @@ module Projects::ActsAsCustomizablePatches before_update :set_query_available_custom_fields_to_global_level - before_create :reject_section_scoped_validation_for_creation before_create :build_missing_project_custom_field_project_mappings after_save :reset_section_scoped_validation @@ -71,13 +70,6 @@ def set_query_available_custom_fields_to_global_level self._query_available_custom_fields_on_global_level = true end - def reject_section_scoped_validation_for_creation - if _limit_custom_fields_validation_to_section_id.present? - raise ArgumentError, - "Section scoped validation is not supported for project creation, only for project updates" - end - end - def with_all_available_custom_fields # query the available custom fields on a global level when updating custom field values # in order to support implicit activation of custom fields when values are provided during an update diff --git a/app/services/projects/concerns/new_project_service.rb b/app/services/projects/concerns/new_project_service.rb index 92f3b04f68df..eb7a6cc2c2dc 100644 --- a/app/services/projects/concerns/new_project_service.rb +++ b/app/services/projects/concerns/new_project_service.rb @@ -42,6 +42,15 @@ def after_perform(attributes_call) super end + def after_validate(params, service_call) + # we need to reset the query_available_custom_fields_on_global_level already after validation + # as the update service just calls .valid? and returns if invalid + # after_save is not touched in this case which causes the flag to stay active + reject_section_scoped_validation(service_call.result) + + super + end + # Add default role to the newly created project # based on the setting ('new_project_user_role_id') # defined in the administration. Will either create a new membership @@ -91,5 +100,12 @@ def disable_custom_fields_with_empty_values(new_project) .where.not(custom_field_id: new_project.available_custom_fields.select(:id))) .destroy_all end + + def reject_section_scoped_validation(new_project) + if new_project._limit_custom_fields_validation_to_section_id.present? + raise ArgumentError, + "Section scoped validation is not supported for project creation, only for project updates" + end + end end end From 3640a74485df705d5f60ffd41a04f5365e5778ed Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 28 May 2024 19:01:06 +0300 Subject: [PATCH 20/56] Move section based validation check to the Projects::CreateService --- .../projects/acts_as_customizable_patches.rb | 15 ---- .../projects/concerns/new_project_service.rb | 53 +++++++++----- spec/models/projects/customizable_spec.rb | 10 --- spec/services/projects/create_service_spec.rb | 71 +++++++++++-------- 4 files changed, 80 insertions(+), 69 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index 6b1dc672129f..dec980b246ff 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -41,23 +41,8 @@ module Projects::ActsAsCustomizablePatches before_update :set_query_available_custom_fields_to_global_level - before_create :build_missing_project_custom_field_project_mappings - after_save :reset_section_scoped_validation - def build_missing_project_custom_field_project_mappings - # activate custom fields for this project (via mapping table) if values have been provided for custom_fields but no mapping exists - custom_field_ids = project.custom_values - .select { |cv| cv.value.present? } - .pluck(:custom_field_id).uniq - activated_custom_field_ids = project_custom_field_project_mappings.pluck(:custom_field_id).uniq - - mappings = (custom_field_ids - activated_custom_field_ids).uniq - .map { |pcf_id| { project_id: id, custom_field_id: pcf_id } } - - project_custom_field_project_mappings.build(mappings) - end - def reset_section_scoped_validation # reset the section scope after saving # in order not to silently carry this setting in this instance diff --git a/app/services/projects/concerns/new_project_service.rb b/app/services/projects/concerns/new_project_service.rb index eb7a6cc2c2dc..cf8ba4b497fc 100644 --- a/app/services/projects/concerns/new_project_service.rb +++ b/app/services/projects/concerns/new_project_service.rb @@ -32,21 +32,30 @@ module NewProjectService private - def after_perform(attributes_call) - new_project = attributes_call.result - - set_default_role(new_project) unless user.admin? - disable_custom_fields_with_empty_values(new_project) - notify_project_created(new_project) - - super + def before_perform(params, service_call) + # we need to reset the query_available_custom_fields_on_global_level already after validation + # as the update service just calls .valid? and returns if invalid + # after_save is not touched in this case which causes the flag to stay active + super.tap do |super_call| + reject_section_scoped_validation(super_call.result) + end end def after_validate(params, service_call) # we need to reset the query_available_custom_fields_on_global_level already after validation # as the update service just calls .valid? and returns if invalid # after_save is not touched in this case which causes the flag to stay active - reject_section_scoped_validation(service_call.result) + super.tap do |super_call| + build_missing_project_custom_field_project_mappings(super_call.result) + end + end + + def after_perform(attributes_call) + new_project = attributes_call.result + + set_default_role(new_project) unless user.admin? + disable_custom_fields_with_empty_values(new_project) + notify_project_created(new_project) super end @@ -81,9 +90,14 @@ def notify_project_created(new_project) ) end + def reject_section_scoped_validation(new_project) + if new_project._limit_custom_fields_validation_to_section_id.present? + raise ArgumentError, + "Section scoped validation is not supported for project creation, only for project updates" + end + end + def disable_custom_fields_with_empty_values(new_project) - # run only on initial creation! (otherwise we would deactivate custom fields with empty values on every update!) - # # ideally, `build_missing_project_custom_field_project_mappings` would not activate custom fields with empty values # but: # this hook is required as acts_as_customizable build custom values with their default value even if a blank value @@ -101,11 +115,18 @@ def disable_custom_fields_with_empty_values(new_project) .destroy_all end - def reject_section_scoped_validation(new_project) - if new_project._limit_custom_fields_validation_to_section_id.present? - raise ArgumentError, - "Section scoped validation is not supported for project creation, only for project updates" - end + def build_missing_project_custom_field_project_mappings(project) + # activate custom fields for this project (via mapping table) if values have been provided + # for custom_fields but no mapping exists + custom_field_ids = project.custom_values + .select { |cv| cv.value.present? } + .pluck(:custom_field_id).uniq + activated_custom_field_ids = project.project_custom_field_project_mappings.pluck(:custom_field_id).uniq + + mappings = (custom_field_ids - activated_custom_field_ids).uniq + .map { |pcf_id| { project_id: project.id, custom_field_id: pcf_id } } + + project.project_custom_field_project_mappings.build(mappings) end end end diff --git a/spec/models/projects/customizable_spec.rb b/spec/models/projects/customizable_spec.rb index 2f47c31bec8d..9ac961dab439 100644 --- a/spec/models/projects/customizable_spec.rb +++ b/spec/models/projects/customizable_spec.rb @@ -190,16 +190,6 @@ expect { project.save! }.to raise_error(ActiveRecord::RecordInvalid) end - it "rejects section validation scoping for project creation" do - project = build(:project, custom_field_values: { - text_custom_field.id => "foo", - bool_custom_field.id => true - }, - _limit_custom_fields_validation_to_section_id: section.id) - - expect { project.save! }.to raise_error(ArgumentError) - end - it "temporarly validates only custom values of a section if section scope is provided while updating" do project = create(:project, custom_field_values: { text_custom_field.id => "foo", diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index b4dd2aa38e08..4c277922d812 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -71,17 +71,8 @@ end end - context "with hidden custom fields" do - # Skip stubbing in order to execute the real service call + context "with a real service call" do let(:stub_model_instance) { false } - let(:call_attributes) do - attributes_for(:project, - custom_field_values: { - text_custom_field.id => "foo", - bool_custom_field.id => true, - hidden_custom_field.id => "hidden" - }).except(:created_at, :updated_at) - end let!(:section) { create(:project_custom_field_section) } let!(:bool_custom_field) do create(:boolean_project_custom_field, project_custom_field_section: section) @@ -101,32 +92,56 @@ User.current = user end - context "with admin permission" do - let(:user) { build_stubbed(:admin) } + context "with hidden custom fields" do + let(:call_attributes) do + attributes_for(:project, + custom_field_values: { + text_custom_field.id => "foo", + bool_custom_field.id => true, + hidden_custom_field.id => "hidden" + }).except(:created_at, :updated_at) + end + + context "with admin permission" do + let(:user) { build_stubbed(:admin) } - it "does activate hidden custom fields" do - subject - expect(project.project_custom_field_project_mappings.pluck(:custom_field_id)) - .to contain_exactly(text_custom_field.id, bool_custom_field.id, hidden_custom_field.id) - expect(project.custom_value_for(hidden_custom_field).typed_value).to eq("hidden") + it "does activate hidden custom fields" do + subject + expect(project.project_custom_field_project_mappings.pluck(:custom_field_id)) + .to contain_exactly(text_custom_field.id, bool_custom_field.id, hidden_custom_field.id) + expect(project.custom_value_for(hidden_custom_field).typed_value).to eq("hidden") + end end - end - context "without admin permission" do - let(:user) { create(:user) } + context "without admin permission" do + let(:user) { create(:user) } - before do - mock_permissions_for(user) do |mock| - mock.allow_globally :add_project + before do + mock_permissions_for(user) do |mock| + mock.allow_globally :add_project + end + end + + it "does not activate hidden custom fields" do + subject + + expect(project.project_custom_field_project_mappings.pluck(:custom_field_id)) + .to contain_exactly(text_custom_field.id, bool_custom_field.id) + expect(project.custom_value_for(hidden_custom_field)).to be_nil end end + end - it "does not activate hidden custom fields" do - subject + context "with a section scoped validation" do + let(:call_attributes) do + attributes_for(:project, + custom_field_values: { text_custom_field.id => "foo" }, + _limit_custom_fields_validation_to_section_id: section.id + ).except(:created_at, :updated_at) + end - expect(project.project_custom_field_project_mappings.pluck(:custom_field_id)) - .to contain_exactly(text_custom_field.id, bool_custom_field.id) - expect(project.custom_value_for(hidden_custom_field)).to be_nil + it "rejects section validation scoping for project creation" do + expect { subject }.to raise_error(ArgumentError) end end end From 2fd2fb897b8b09fa31dc56b26b7b1c3796640409 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 28 May 2024 20:07:25 +0300 Subject: [PATCH 21/56] Move specs for activating custom fields with default values to Projects::CreateContract --- .../projects/concerns/new_project_service.rb | 2 +- spec/models/projects/customizable_spec.rb | 27 -------- spec/services/projects/create_service_spec.rb | 68 +++++++++++++++---- 3 files changed, 55 insertions(+), 42 deletions(-) diff --git a/app/services/projects/concerns/new_project_service.rb b/app/services/projects/concerns/new_project_service.rb index cf8ba4b497fc..53b0a392c750 100644 --- a/app/services/projects/concerns/new_project_service.rb +++ b/app/services/projects/concerns/new_project_service.rb @@ -124,7 +124,7 @@ def build_missing_project_custom_field_project_mappings(project) activated_custom_field_ids = project.project_custom_field_project_mappings.pluck(:custom_field_id).uniq mappings = (custom_field_ids - activated_custom_field_ids).uniq - .map { |pcf_id| { project_id: project.id, custom_field_id: pcf_id } } + .map { |custom_field_id| { custom_field_id: } } project.project_custom_field_project_mappings.build(mappings) end diff --git a/spec/models/projects/customizable_spec.rb b/spec/models/projects/customizable_spec.rb index 9ac961dab439..2dffb1136d2b 100644 --- a/spec/models/projects/customizable_spec.rb +++ b/spec/models/projects/customizable_spec.rb @@ -221,33 +221,6 @@ expect { project.save! }.to raise_error(ActiveRecord::RecordInvalid) end end - - context "with correct handling of custom fields with default values" do - let!(:text_custom_field_with_default) do - create(:text_project_custom_field, - default_value: "default", - project_custom_field_section: section) - end - - it "activates custom fields with default values if not explicitly set to blank" do - project = create(:project, custom_field_values: { - text_custom_field.id => "foo", - bool_custom_field.id => true - }) - expect(project.project_custom_field_project_mappings.pluck(:custom_field_id)) - .to contain_exactly(text_custom_field.id, bool_custom_field.id, text_custom_field_with_default.id) - end - - it "does not activate custom fields with default values if explicitly set to blank" do - project = create(:project, custom_field_values: { - text_custom_field.id => "foo", - bool_custom_field.id => true, - text_custom_field_with_default.id => "" - }) - expect(project.project_custom_field_project_mappings.pluck(:custom_field_id)) - .to contain_exactly(text_custom_field.id, bool_custom_field.id) - end - end end context "when updating with custom field values" do diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 4c277922d812..42eea35d9ea3 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -87,24 +87,66 @@ create(:text_project_custom_field, project_custom_field_section: section, visible: false) end let(:project) { subject.result } + let(:project_attributes) { {} } + let(:call_attributes) do + attributes_for(:project, project_attributes).except(:created_at, :updated_at) + end + + let(:user) { build_stubbed(:admin) } before do User.current = user end + context "with correct handling of custom fields with default values" do + let!(:text_custom_field_with_default) do + create(:text_project_custom_field, + default_value: "default", + project_custom_field_section: section) + end + + context "if the default value is not explicitly set to blank" do + let(:project_attributes) do + { custom_field_values: { + text_custom_field.id => "foo", + bool_custom_field.id => true + } } + end + + it "activates custom fields with default values" do + subject + expect(project.project_custom_field_project_mappings.pluck(:custom_field_id)) + .to contain_exactly(text_custom_field.id, bool_custom_field.id, text_custom_field_with_default.id) + end + end + + context "if the default value is explicitly set to blank" do + let(:project_attributes) do + { custom_field_values: { + text_custom_field.id => "foo", + bool_custom_field.id => true, + text_custom_field_with_default.id => "" + } } + end + + it "does not activate custom fields with default values" do + subject + expect(project.project_custom_field_project_mappings.pluck(:custom_field_id)) + .to contain_exactly(text_custom_field.id, bool_custom_field.id) + end + end + end + context "with hidden custom fields" do - let(:call_attributes) do - attributes_for(:project, - custom_field_values: { - text_custom_field.id => "foo", - bool_custom_field.id => true, - hidden_custom_field.id => "hidden" - }).except(:created_at, :updated_at) + let(:project_attributes) do + { custom_field_values: { + text_custom_field.id => "foo", + bool_custom_field.id => true, + hidden_custom_field.id => "hidden" + } } end context "with admin permission" do - let(:user) { build_stubbed(:admin) } - it "does activate hidden custom fields" do subject expect(project.project_custom_field_project_mappings.pluck(:custom_field_id)) @@ -133,11 +175,9 @@ end context "with a section scoped validation" do - let(:call_attributes) do - attributes_for(:project, - custom_field_values: { text_custom_field.id => "foo" }, - _limit_custom_fields_validation_to_section_id: section.id - ).except(:created_at, :updated_at) + let(:project_attributes) do + { custom_field_values: { text_custom_field.id => "foo" }, + _limit_custom_fields_validation_to_section_id: section.id } end it "rejects section validation scoping for project creation" do From 152b26aa72e0eb932f114ffc27a2eb43a32c0727 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 29 May 2024 12:05:13 +0300 Subject: [PATCH 22/56] Fix copy spec setup. --- spec/features/projects/copy_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/features/projects/copy_spec.rb b/spec/features/projects/copy_spec.rb index fcb488a02bb2..10fccf9dfb44 100644 --- a/spec/features/projects/copy_spec.rb +++ b/spec/features/projects/copy_spec.rb @@ -47,6 +47,10 @@ # Enable wiki p.enabled_module_names += ["wiki"] + + # Enable the project custom field mappings + p.project_custom_field_project_mappings + .create(custom_field_id: optional_project_custom_field_with_default.id) end end From b392af07827abd8c53fc556d0ad583b3ee969853 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 29 May 2024 13:35:13 +0300 Subject: [PATCH 23/56] Move reset_section_scoped_validations method to Projects::UpdateService --- app/models/projects/acts_as_customizable_patches.rb | 8 -------- app/services/projects/update_service.rb | 7 +++++++ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index dec980b246ff..b6d26e7f8a31 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -41,14 +41,6 @@ module Projects::ActsAsCustomizablePatches before_update :set_query_available_custom_fields_to_global_level - after_save :reset_section_scoped_validation - - def reset_section_scoped_validation - # reset the section scope after saving - # in order not to silently carry this setting in this instance - self._limit_custom_fields_validation_to_section_id = nil - end - def set_query_available_custom_fields_to_global_level # query the available custom fields on a global level when updating custom field values # in order to support implicit activation of custom fields when values are provided during an update diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 573b90ce8fc7..3394f75973fe 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -47,6 +47,7 @@ def set_attributes(params) def after_perform(service_call) ret = super + reset_section_scoped_validation touch_on_custom_values_update notify_on_identifier_renamed send_update_notification @@ -97,5 +98,11 @@ def handle_archiving service = service_class.new(user:, model:, contract_class: EmptyContract) service.call end + + def reset_section_scoped_validation + # reset the section scope after saving + # in order not to silently carry this setting in this instance + model._limit_custom_fields_validation_to_section_id = nil + end end end From 96f7ab53eca63b1f79e89b3fb2b38d156ccc3b24 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 29 May 2024 13:56:11 +0300 Subject: [PATCH 24/56] Remove accidental comments --- app/services/projects/concerns/new_project_service.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/services/projects/concerns/new_project_service.rb b/app/services/projects/concerns/new_project_service.rb index 53b0a392c750..8de279e09a19 100644 --- a/app/services/projects/concerns/new_project_service.rb +++ b/app/services/projects/concerns/new_project_service.rb @@ -33,18 +33,12 @@ module NewProjectService private def before_perform(params, service_call) - # we need to reset the query_available_custom_fields_on_global_level already after validation - # as the update service just calls .valid? and returns if invalid - # after_save is not touched in this case which causes the flag to stay active super.tap do |super_call| reject_section_scoped_validation(super_call.result) end end def after_validate(params, service_call) - # we need to reset the query_available_custom_fields_on_global_level already after validation - # as the update service just calls .valid? and returns if invalid - # after_save is not touched in this case which causes the flag to stay active super.tap do |super_call| build_missing_project_custom_field_project_mappings(super_call.result) end From 8cf0732f24571f8634bf22d490edca420a4edfa8 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 29 May 2024 18:05:05 +0300 Subject: [PATCH 25/56] Fix specs for removing _limi_custom_fields_validation_to_section_id in Projects::UpdateContract --- spec/models/projects/customizable_spec.rb | 5 +++-- spec/services/projects/update_service_spec.rb | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/spec/models/projects/customizable_spec.rb b/spec/models/projects/customizable_spec.rb index 2dffb1136d2b..b9d5d72ffb32 100644 --- a/spec/models/projects/customizable_spec.rb +++ b/spec/models/projects/customizable_spec.rb @@ -190,7 +190,7 @@ expect { project.save! }.to raise_error(ActiveRecord::RecordInvalid) end - it "temporarly validates only custom values of a section if section scope is provided while updating" do + it "validates only custom values of a section if section scope is provided while updating" do project = create(:project, custom_field_values: { text_custom_field.id => "foo", bool_custom_field.id => true, @@ -217,7 +217,8 @@ expect { project.save! }.not_to raise_error - # section scope is resetted after each update + # Removing the section scoped limitation should result a validation error again. + project._limit_custom_fields_validation_to_section_id = nil expect { project.save! }.to raise_error(ActiveRecord::RecordInvalid) end end diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 53c5bad035b8..3c511e35298e 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -79,5 +79,14 @@ subject end end + + describe "section based validation" do + it "is reset after the save is done" do + model_instance._limit_custom_fields_validation_to_section_id = 1 + subject + # section scope is reset after the update + expect(model_instance._limit_custom_fields_validation_to_section_id).to be_nil + end + end end end From 86a01bfe2ff81f60b2860fc36e1bfba1a7f01e21 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 29 May 2024 18:12:07 +0300 Subject: [PATCH 26/56] Move the set_query_available_custom_fields_to_global_level to the Projects::UpdateContract --- app/models/projects/acts_as_customizable_patches.rb | 8 -------- app/services/projects/update_service.rb | 12 ++++++++++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index b6d26e7f8a31..92467c2e30bc 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -39,14 +39,6 @@ module Projects::ActsAsCustomizablePatches dependent: :destroy, inverse_of: :project has_many :project_custom_fields, through: :project_custom_field_project_mappings, class_name: "ProjectCustomField" - before_update :set_query_available_custom_fields_to_global_level - - def set_query_available_custom_fields_to_global_level - # query the available custom fields on a global level when updating custom field values - # in order to support implicit activation of custom fields when values are provided during an update - self._query_available_custom_fields_on_global_level = true - end - def with_all_available_custom_fields # query the available custom fields on a global level when updating custom field values # in order to support implicit activation of custom fields when values are provided during an update diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 3394f75973fe..4a60a3c8f168 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -45,6 +45,12 @@ def set_attributes(params) ret end + def after_validate(params, service_call) + super.tap do + set_query_available_custom_fields_to_global_level + end + end + def after_perform(service_call) ret = super reset_section_scoped_validation @@ -104,5 +110,11 @@ def reset_section_scoped_validation # in order not to silently carry this setting in this instance model._limit_custom_fields_validation_to_section_id = nil end + + def set_query_available_custom_fields_to_global_level + # query the available custom fields on a global level when updating custom field values + # in order to support implicit activation of custom fields when values are provided during an update + model._query_available_custom_fields_on_global_level = true + end end end From ed36d46c7db797dcc83b428c502134518e5c3ec6 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Thu, 30 May 2024 14:35:17 +0300 Subject: [PATCH 27/56] Try removing unused callbacks --- .../projects/concerns/new_project_service.rb | 2 - .../concerns/reset_global_query_hooks.rb | 54 ------------------- app/services/projects/update_service.rb | 13 ----- 3 files changed, 69 deletions(-) delete mode 100644 app/services/projects/concerns/reset_global_query_hooks.rb diff --git a/app/services/projects/concerns/new_project_service.rb b/app/services/projects/concerns/new_project_service.rb index 8de279e09a19..c22c0993ccbf 100644 --- a/app/services/projects/concerns/new_project_service.rb +++ b/app/services/projects/concerns/new_project_service.rb @@ -28,8 +28,6 @@ module Projects::Concerns module NewProjectService - include Projects::Concerns::ResetGlobalQueryHooks - private def before_perform(params, service_call) diff --git a/app/services/projects/concerns/reset_global_query_hooks.rb b/app/services/projects/concerns/reset_global_query_hooks.rb deleted file mode 100644 index d678994067ef..000000000000 --- a/app/services/projects/concerns/reset_global_query_hooks.rb +++ /dev/null @@ -1,54 +0,0 @@ -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) 2012-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 Projects::Concerns - module ResetGlobalQueryHooks - private - - def after_validate(params, service_call) - # we need to reset the query_available_custom_fields_on_global_level already after validation - # as the update service just calls .valid? and returns if invalid - # after_save is not touched in this case which causes the flag to stay active - set_query_available_custom_fields_to_project_level(service_call.result) - - super - end - - def after_perform(service_call) - set_query_available_custom_fields_to_project_level(service_call.result) - - super - end - - def set_query_available_custom_fields_to_project_level(model) - # reset the query_available_custom_fields_on_global_level after saving - # in order not to silently carry this setting in this instance - model._query_available_custom_fields_on_global_level = nil - end - end -end diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 4a60a3c8f168..acb644a13c0a 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -29,7 +29,6 @@ module Projects class UpdateService < ::BaseServices::Update prepend Projects::Concerns::UpdateDemoData - include Projects::Concerns::ResetGlobalQueryHooks private @@ -45,12 +44,6 @@ def set_attributes(params) ret end - def after_validate(params, service_call) - super.tap do - set_query_available_custom_fields_to_global_level - end - end - def after_perform(service_call) ret = super reset_section_scoped_validation @@ -110,11 +103,5 @@ def reset_section_scoped_validation # in order not to silently carry this setting in this instance model._limit_custom_fields_validation_to_section_id = nil end - - def set_query_available_custom_fields_to_global_level - # query the available custom fields on a global level when updating custom field values - # in order to support implicit activation of custom fields when values are provided during an update - model._query_available_custom_fields_on_global_level = true - end end end From d8d45d2b821f474ca5f1d2d59d3ef7acc9bc3812 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Fri, 31 May 2024 12:42:36 +0300 Subject: [PATCH 28/56] Update Project service comments. --- .../projects/concerns/new_project_service.rb | 17 +++++++++-------- app/services/projects/update_service.rb | 4 ++-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/app/services/projects/concerns/new_project_service.rb b/app/services/projects/concerns/new_project_service.rb index c22c0993ccbf..e9fb5a71a784 100644 --- a/app/services/projects/concerns/new_project_service.rb +++ b/app/services/projects/concerns/new_project_service.rb @@ -90,12 +90,13 @@ def reject_section_scoped_validation(new_project) end def disable_custom_fields_with_empty_values(new_project) - # ideally, `build_missing_project_custom_field_project_mappings` would not activate custom fields with empty values - # but: - # this hook is required as acts_as_customizable build custom values with their default value even if a blank value - # was provided in the project creation form `build_missing_project_custom_field_project_mappings` will then activate - # the custom field although the user explicitly provided a blank value in order to not patch `acts_as_customizable` - # further, we simply identify these custom values and deactivate the custom field + # Ideally, `build_missing_project_custom_field_project_mappings` would not activate custom fields + # with empty values, but: + # This hook is required as acts_as_customizable build custom values with their default value + # even if a blank value was provided in the project creation form. + # `build_missing_project_custom_field_project_mappings` will then activate the custom field, + # although the user explicitly provided a blank value. In order to not patch `acts_as_customizable` + # further, we simply identify these custom values and deactivate the custom field. custom_field_ids = new_project.custom_values.select { |cv| cv.value.blank? && !cv.required? }.pluck(:custom_field_id) custom_field_project_mappings = new_project.project_custom_field_project_mappings @@ -108,8 +109,8 @@ def disable_custom_fields_with_empty_values(new_project) end def build_missing_project_custom_field_project_mappings(project) - # activate custom fields for this project (via mapping table) if values have been provided - # for custom_fields but no mapping exists + # Activate custom fields for this project (via mapping table) if values have been provided + # for custom_fields, but no mapping exists. custom_field_ids = project.custom_values .select { |cv| cv.value.present? } .pluck(:custom_field_id).uniq diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index acb644a13c0a..b90659d5bf93 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -99,8 +99,8 @@ def handle_archiving end def reset_section_scoped_validation - # reset the section scope after saving - # in order not to silently carry this setting in this instance + # Reset the section scope after saving in order to not silently + # carry this setting in this instance. model._limit_custom_fields_validation_to_section_id = nil end end From b589dcce0de8bc70dd4cfe059c8bab5569e534a1 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Tue, 11 Jun 2024 22:12:26 +0300 Subject: [PATCH 29/56] Remove the for_custom_field_accessor patch only --- .../projects/acts_as_customizable_patches.rb | 17 ++++------------- .../lib/acts_as_customizable.rb | 6 +++++- .../project_representer_rendering_spec.rb | 1 + 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index 92467c2e30bc..00d7ad8b4363 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -54,9 +54,7 @@ def available_custom_fields # overrides acts_as_customizable # in contrast to acts_as_customizable, custom_fields are enabled per project # thus we need to check the project_custom_field_project_mappings - custom_fields = ProjectCustomField - .includes(:project_custom_field_section) - .order("custom_field_sections.position", :position_in_custom_field_section) + custom_fields = all_available_custom_fields # Do not hide the invisble fields when accessing via the _query_available_custom_fields_on_global_level # flag. Due to the internal working of the acts_as_customizable plugin, when a project admin updates @@ -87,7 +85,9 @@ def available_custom_fields end def all_available_custom_fields - with_all_available_custom_fields { available_custom_fields } + ProjectCustomField + .includes(:project_custom_field_section) + .order("custom_field_sections.position", :position_in_custom_field_section) end def custom_field_values_to_validate @@ -106,14 +106,5 @@ def custom_field_values_to_validate def custom_field_values=(values) with_all_available_custom_fields { super } end - - # We need to query the available custom fields on a global level when - # trying to set a custom field which is not enabled via the API e.g. custom_field_123="foo" - # This implies implicit activation of the disabled custom fields via the API. As a side effect, - # we will have empty CustomValue objects created for each custom field, regardless of its - # enabled/disabled state in the project. - def for_custom_field_accessor(method_symbol) - with_all_available_custom_fields { super } - end end end diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index e95c846b5b23..9c4320799932 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -72,6 +72,10 @@ def available_custom_fields self.class.available_custom_fields(self) end + def all_available_custom_fields + available_custom_fields + end + # Sets the values of the object's custom fields # values is an array like [{'id' => 1, 'value' => 'foo'}, {'id' => 2, 'value' => 'bar'}] def custom_fields=(values) @@ -333,7 +337,7 @@ def build_custom_value(custom_field, value:) def for_custom_field_accessor(method_symbol) match = /\Acustom_field_(?\d+)=?\z/.match(method_symbol.to_s) if match - custom_field = available_custom_fields.find { |cf| cf.id.to_s == match[:id] } + custom_field = all_available_custom_fields.find { |cf| cf.id.to_s == match[:id] } if custom_field yield custom_field end diff --git a/spec/lib/api/v3/projects/project_representer_rendering_spec.rb b/spec/lib/api/v3/projects/project_representer_rendering_spec.rb index daa71e7ca66e..bf420ce5ad13 100644 --- a/spec/lib/api/v3/projects/project_representer_rendering_spec.rb +++ b/spec/lib/api/v3/projects/project_representer_rendering_spec.rb @@ -39,6 +39,7 @@ parent: parent_project, description: "some description").tap do |p| allow(p).to receive_messages(available_custom_fields: [int_custom_field, version_custom_field], + all_available_custom_fields: [int_custom_field, version_custom_field], ancestors_from_root: ancestors) allow(p) From 0e01fb4f4a7817360996e4d56866da91099cb2d1 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Fri, 14 Jun 2024 23:40:09 +0300 Subject: [PATCH 30/56] Remove aac patch custom_field_values= method --- .../projects/acts_as_customizable_patches.rb | 6 ------ .../lib/acts_as_customizable.rb | 14 ++++++++------ .../api/v3/projects/update_resource_spec.rb | 16 +++++++++++----- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index 00d7ad8b4363..cdb7b29eac2b 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -100,11 +100,5 @@ def custom_field_values_to_validate custom_field_values end end - - # we need to query the available custom fields on a global level when updating custom field values - # in order to support implicit activation of custom fields when values are provided during an update - def custom_field_values=(values) - with_all_available_custom_fields { super } - end end end diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index 9c4320799932..cc23ec17cdac 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -98,7 +98,7 @@ def custom_field_values=(values) return unless values.is_a?(Hash) && values.any? values.with_indifferent_access.each do |custom_field_id, val| - existing_cv_by_value = custom_values_for_custom_field(id: custom_field_id) + existing_cv_by_value = custom_values_for_custom_field(id: custom_field_id, all: true) .group_by(&:value) .transform_values(&:first) new_values = Array(val).map { |v| v.respond_to?(:id) ? v.id.to_s : v.to_s } @@ -111,13 +111,14 @@ def custom_field_values=(values) end end - def custom_values_for_custom_field(id:) - custom_field_values.select { |cv| cv.custom_field_id == id.to_i } + def custom_values_for_custom_field(id:, all: false) + custom_field_values(all:).select { |cv| cv.custom_field_id == id.to_i } end - def custom_field_values - custom_field_values_cache[custom_field_cache_key] ||= - available_custom_fields.flat_map do |custom_field| + def custom_field_values(all: false) + custom_field_values_cache[custom_field_cache_key] ||= begin + current_custom_fields = all ? all_available_custom_fields : available_custom_fields + current_custom_fields.flat_map do |custom_field| existing_cvs = custom_values.select { |v| v.custom_field_id == custom_field.id } if existing_cvs.empty? @@ -126,6 +127,7 @@ def custom_field_values existing_cvs end end + end end # Returns the cache key for caching @custom_field_values_cache. diff --git a/spec/requests/api/v3/projects/update_resource_spec.rb b/spec/requests/api/v3/projects/update_resource_spec.rb index 9a34d40a0cc3..b9aa431487a1 100644 --- a/spec/requests/api/v3/projects/update_resource_spec.rb +++ b/spec/requests/api/v3/projects/update_resource_spec.rb @@ -47,11 +47,6 @@ let(:invisible_custom_field) do create(:text_project_custom_field, visible: false) end - let(:custom_value) do - CustomValue.create(custom_field:, - value: "1234", - customized: project) - end let(:permissions) { [:edit_project] } let(:path) { api_v3_paths.project(project.id) } let(:body) do @@ -154,6 +149,17 @@ .to be_empty end + context "when the hidden field has a value already" do + it "does not change the cf value" do + project.custom_field_values = { invisible_custom_field.id => "1234" } + project.save + patch path, body.to_json + + expect(project.reload.custom_values.find_by(custom_field: invisible_custom_field).value) + .to eq "1234" + end + end + it "does not activate the cf for project" do expect(project.reload.project_custom_fields) .to be_empty From 1e43ab7dd10380ae080af82ab5fed248c8012c26 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Sat, 15 Jun 2024 00:03:04 +0300 Subject: [PATCH 31/56] Remove more additional methods from the aac patch --- .../projects/acts_as_customizable_patches.rb | 36 ++----------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/acts_as_customizable_patches.rb index cdb7b29eac2b..3ce2f0eb656e 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/acts_as_customizable_patches.rb @@ -29,7 +29,7 @@ module Projects::ActsAsCustomizablePatches extend ActiveSupport::Concern - attr_accessor :_limit_custom_fields_validation_to_section_id, :_query_available_custom_fields_on_global_level + attr_accessor :_limit_custom_fields_validation_to_section_id # attr_accessor :_limit_custom_fields_validation_to_field_id # not needed for now, but might be relevant if we want to have edit dialogs just for one custom field @@ -39,43 +39,13 @@ module Projects::ActsAsCustomizablePatches dependent: :destroy, inverse_of: :project has_many :project_custom_fields, through: :project_custom_field_project_mappings, class_name: "ProjectCustomField" - def with_all_available_custom_fields - # query the available custom fields on a global level when updating custom field values - # in order to support implicit activation of custom fields when values are provided during an update - self._query_available_custom_fields_on_global_level = true - result = yield - self._query_available_custom_fields_on_global_level = nil - - result - end - def available_custom_fields - # TODO: Add caching here. # overrides acts_as_customizable # in contrast to acts_as_customizable, custom_fields are enabled per project # thus we need to check the project_custom_field_project_mappings - custom_fields = all_available_custom_fields - - # Do not hide the invisble fields when accessing via the _query_available_custom_fields_on_global_level - # flag. Due to the internal working of the acts_as_customizable plugin, when a project admin updates - # the custom fields, it will clear out all the hidden fields that are not visible for them. - # This happens because the `#ensure_custom_values_complete` will gather all the `custom_field_values` - # and assigns them to the custom_fields association. If the `custom_field_values` do not contain the - # hidden fields, they will be cleared from the association. The `custom_field_values` will contain the - # hidden fields, only if they are returned from this method. Hence we should not hide them, - # when accessed with the _query_available_custom_fields_on_global_level flag on. - unless _query_available_custom_fields_on_global_level - custom_fields = custom_fields.visible - end + custom_fields = all_available_custom_fields.visible - # available_custom_fields is called from within the acts_as_customizable module - # we don't want to adjust these calls, but need a way to query the available custom fields on a global level in some cases - # thus we pass in this parameter as an instance flag implicitly here, - # which is not nice but helps us to touch acts_as_customizable as little as possible - # - # additionally we provide the `global` parameter to allow querying the available custom fields on a global level - # when we have explicit control over the call of `available_custom_fields` - unless new_record? || _query_available_custom_fields_on_global_level + unless new_record? custom_fields = custom_fields .where(id: project_custom_field_project_mappings.select(:custom_field_id)) .or(ProjectCustomField.required) From 2f42f97f36b5210a754661f032d887782b92d6cd Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Sat, 15 Jun 2024 01:47:27 +0300 Subject: [PATCH 32/56] Create Projects::CustomField and Projects::WorkPackageCustomFields concerns. --- app/models/project.rb | 20 ++------ ...stomizable_patches.rb => custom_fields.rb} | 5 +- .../projects/work_package_custom_fields.rb | 47 +++++++++++++++++++ 3 files changed, 52 insertions(+), 20 deletions(-) rename app/models/projects/{acts_as_customizable_patches.rb => custom_fields.rb} (92%) create mode 100644 app/models/projects/work_package_custom_fields.rb diff --git a/app/models/project.rb b/app/models/project.rb index 2cfd86fd350b..4a94b25291b2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -33,9 +33,10 @@ class Project < ApplicationRecord include Projects::Activity include Projects::Hierarchy include Projects::AncestorsFromRoot + include Projects::CustomFields + include Projects::WorkPackageCustomFields include ::Scopes::Scoped - include Projects::ActsAsCustomizablePatches # Maximum length for project identifiers IDENTIFIER_MAX_LENGTH = 100 @@ -79,11 +80,6 @@ class Project < ApplicationRecord has_one :repository, dependent: :destroy has_many :changesets, through: :repository has_one :wiki, dependent: :destroy - # Custom field for the project's work_packages - has_and_belongs_to_many :work_package_custom_fields, - -> { order("#{CustomField.table_name}.position") }, - join_table: :custom_fields_projects, - association_foreign_key: "custom_field_id" has_many :budgets, dependent: :destroy has_many :notification_settings, dependent: :destroy has_many :project_storages, dependent: :destroy, class_name: "Storages::ProjectStorage" @@ -93,8 +89,8 @@ class Project < ApplicationRecord acts_as_favorable - acts_as_customizable # partially overridden via Projects::ActsAsCustomizablePatches in order to support sections and - # project-leval activation of custom fields + acts_as_customizable # extended in Projects::CustomFields in order to support sections + # and project-level activation of custom fields acts_as_searchable columns: %W(#{table_name}.name #{table_name}.identifier #{table_name}.description), date_column: "#{table_name}.created_at", @@ -282,14 +278,6 @@ def assignable_versions(only_open: true) end end - # Returns an AR scope of all custom fields enabled for project's work packages - # (explicitly associated custom fields and custom fields enabled for all projects) - def all_work_package_custom_fields - WorkPackageCustomField - .for_all - .or(WorkPackageCustomField.where(id: work_package_custom_fields)) - end - def project self end diff --git a/app/models/projects/acts_as_customizable_patches.rb b/app/models/projects/custom_fields.rb similarity index 92% rename from app/models/projects/acts_as_customizable_patches.rb rename to app/models/projects/custom_fields.rb index 3ce2f0eb656e..55369e14c72e 100644 --- a/app/models/projects/acts_as_customizable_patches.rb +++ b/app/models/projects/custom_fields.rb @@ -26,14 +26,11 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module Projects::ActsAsCustomizablePatches +module Projects::CustomFields extend ActiveSupport::Concern attr_accessor :_limit_custom_fields_validation_to_section_id - # attr_accessor :_limit_custom_fields_validation_to_field_id - # not needed for now, but might be relevant if we want to have edit dialogs just for one custom field - included do has_many :project_custom_field_project_mappings, class_name: "ProjectCustomFieldProjectMapping", foreign_key: :project_id, dependent: :destroy, inverse_of: :project diff --git a/app/models/projects/work_package_custom_fields.rb b/app/models/projects/work_package_custom_fields.rb new file mode 100644 index 000000000000..9df42bbe268a --- /dev/null +++ b/app/models/projects/work_package_custom_fields.rb @@ -0,0 +1,47 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-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 Projects::WorkPackageCustomFields + extend ActiveSupport::Concern + + included do + # Custom field for the project's work_packages + has_and_belongs_to_many :work_package_custom_fields, # rubocop:disable Rails/HasAndBelongsToMany + -> { order("#{CustomField.table_name}.position") }, + join_table: :custom_fields_projects, + association_foreign_key: "custom_field_id" + + # Returns an AR scope of all custom fields enabled for project's work packages + # (explicitly associated custom fields and custom fields enabled for all projects) + def all_work_package_custom_fields + WorkPackageCustomField + .for_all + .or(WorkPackageCustomField.where(id: work_package_custom_fields)) + end + end +end From 6b3bd43329d4cbc29396715d3ad8c7b90567ca6d Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Sat, 15 Jun 2024 01:55:26 +0300 Subject: [PATCH 33/56] Move project hierarchy related code to the Projects::Hierarchy concern --- app/models/project.rb | 83 -------------------------------- app/models/projects/hierarchy.rb | 83 ++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 83 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 4a94b25291b2..61c3e1af24fb 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -325,89 +325,6 @@ def enabled_module_names enabled_modules.map(&:name) end - # Returns an array of projects that are in this project's hierarchy - # - # Example: parents, children, siblings - def hierarchy - parents = project.self_and_ancestors || [] - descendants = project.descendants || [] - parents | descendants # Set union - end - - # Returns an array of active subprojects. - def active_subprojects - project.descendants.where(active: true) - end - - class << self - # builds up a project hierarchy helper structure for use with #project_tree_from_hierarchy - # - # it expects a simple list of projects with a #lft column (awesome_nested_set) - # and returns a hierarchy based on #lft - # - # the result is a nested list of root level projects that contain their child projects - # but, each entry is actually a ruby hash wrapping the project and child projects - # the keys are :project and :children where :children is in the same format again - # - # result = [ root_level_project_info_1, root_level_project_info_2, ... ] - # - # where each entry has the form - # - # project_info = { project: the_project, children: [ child_info_1, child_info_2, ... ] } - # - # if a project has no children the :children array is just empty - # - def build_projects_hierarchy(projects) - ancestors = [] - result = [] - - projects.sort_by(&:lft).each do |project| - while ancestors.any? && !project.is_descendant_of?(ancestors.last[:project]) - # before we pop back one level, we sort the child projects by name - ancestors.last[:children] = sort_by_name(ancestors.last[:children]) - ancestors.pop - end - - current_hierarchy = { project:, children: [] } - current_tree = ancestors.any? ? ancestors.last[:children] : result - - current_tree << current_hierarchy - ancestors << current_hierarchy - end - - # When the last project is deeply nested, we need to sort - # all layers we are in. - ancestors.each do |level| - level[:children] = sort_by_name(level[:children]) - end - # we need one extra element to ensure sorting at the end - # at the end the root level must be sorted as well - sort_by_name(result) - end - - def project_tree_from_hierarchy(projects_hierarchy, level, &) - projects_hierarchy.each do |hierarchy| - project = hierarchy[:project] - children = hierarchy[:children] - yield project, level - # recursively show children - project_tree_from_hierarchy(children, level + 1, &) if children.any? - end - end - - # Yields the given block for each project with its level in the tree - def project_tree(projects, &) - projects_hierarchy = build_projects_hierarchy(projects) - project_tree_from_hierarchy(projects_hierarchy, 0, &) - end - - private - - def sort_by_name(project_hashes) - project_hashes.sort_by { |h| h[:project].name&.downcase } - end - end - def allowed_permissions @allowed_permissions ||= begin diff --git a/app/models/projects/hierarchy.rb b/app/models/projects/hierarchy.rb index facebf4b20e1..4cccfbeb0918 100644 --- a/app/models/projects/hierarchy.rb +++ b/app/models/projects/hierarchy.rb @@ -29,6 +29,75 @@ module Projects::Hierarchy extend ActiveSupport::Concern + class_methods do + # builds up a project hierarchy helper structure for use with #project_tree_from_hierarchy + # + # it expects a simple list of projects with a #lft column (awesome_nested_set) + # and returns a hierarchy based on #lft + # + # the result is a nested list of root level projects that contain their child projects + # but, each entry is actually a ruby hash wrapping the project and child projects + # the keys are :project and :children where :children is in the same format again + # + # result = [ root_level_project_info_1, root_level_project_info_2, ... ] + # + # where each entry has the form + # + # project_info = { project: the_project, children: [ child_info_1, child_info_2, ... ] } + # + # if a project has no children the :children array is just empty + # + def build_projects_hierarchy(projects) # rubocop:disable Metrics/AbcSize + ancestors = [] + result = [] + + projects.sort_by(&:lft).each do |project| + while ancestors.any? && !project.is_descendant_of?(ancestors.last[:project]) + # before we pop back one level, we sort the child projects by name + ancestors.last[:children] = sort_by_name(ancestors.last[:children]) + ancestors.pop + end + + current_hierarchy = { project:, children: [] } + current_tree = ancestors.any? ? ancestors.last[:children] : result + + current_tree << current_hierarchy + ancestors << current_hierarchy + end + + # When the last project is deeply nested, we need to sort + # all layers we are in. + ancestors.each do |level| + level[:children] = sort_by_name(level[:children]) + end + # we need one extra element to ensure sorting at the end + # at the end the root level must be sorted as well + sort_by_name(result) + end + + def project_tree_from_hierarchy(projects_hierarchy, level, &) + projects_hierarchy.each do |hierarchy| + project = hierarchy[:project] + children = hierarchy[:children] + yield project, level + # recursively show children + project_tree_from_hierarchy(children, level + 1, &) if children.any? + end + end + + # Yields the given block for each project with its level in the tree + def project_tree(projects, &) + projects_hierarchy = build_projects_hierarchy(projects) + project_tree_from_hierarchy(projects_hierarchy, 0, &) + end + + private + + def sort_by_name(project_hashes) + project_hashes.sort_by { |h| h[:project].name&.downcase } + end + end + included do acts_as_nested_set order_column: :lft, dependent: :destroy @@ -36,6 +105,20 @@ module Projects::Hierarchy before_save :remember_reorder after_save :reorder_by_name, if: -> { @reorder_nested_set } + # Returns an array of projects that are in this project's hierarchy + # + # Example: parents, children, siblings + def hierarchy + parents = project.self_and_ancestors || [] + descendants = project.descendants || [] + parents | descendants # Set union + end + + # Returns an array of active subprojects. + def active_subprojects + project.descendants.where(active: true) + end + def reorder_by_name @reorder_nested_set = nil return unless siblings.any? From 03ef711d5f5738ec4613d3a5ed7933d6d5b9359f Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Sat, 15 Jun 2024 09:48:23 +0300 Subject: [PATCH 34/56] Move more hierarchy related methods to Projects::Hierarchy --- .../activities/base_activity_provider.rb | 2 +- app/models/project.rb | 23 ++----------------- app/models/projects/hierarchy.rb | 19 +++++++++++++++ 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app/models/activities/base_activity_provider.rb b/app/models/activities/base_activity_provider.rb index ff537b9dcc9d..9c7e90fac571 100644 --- a/app/models/activities/base_activity_provider.rb +++ b/app/models/activities/base_activity_provider.rb @@ -219,7 +219,7 @@ def restrict_projects_query(user, options) def restrict_projects_by_selection(options, query) if (project = options[:project]) - query = query.where(project.project_condition(options[:with_subprojects])) + query = query.where(project.with_subprojects(options[:with_subprojects])) end query diff --git a/app/models/project.rb b/app/models/project.rb index 61c3e1af24fb..2b00fb259fef 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -29,11 +29,11 @@ class Project < ApplicationRecord extend FriendlyId - include Projects::Storage include Projects::Activity - include Projects::Hierarchy include Projects::AncestorsFromRoot include Projects::CustomFields + include Projects::Hierarchy + include Projects::Storage include Projects::WorkPackageCustomFields include ::Scopes::Scoped @@ -202,25 +202,6 @@ def self.selectable_projects Project.visible.select { |p| User.current.member_of? p }.sort_by(&:to_s) end - # Returns a :conditions SQL string that can be used to find the issues associated with this project. - # - # Examples: - # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))" - # project.project_condition(false) => "projects.id = 1" - def project_condition(with_subprojects) - projects_table = Project.arel_table - - stmt = projects_table[:id].eq(id) - if with_subprojects && has_subprojects? - stmt = stmt.or(projects_table[:lft].gt(lft).and(projects_table[:rgt].lt(rgt))) - end - stmt - end - - def has_subprojects? - !leaf? - end - def types_used_by_work_packages ::Type.where(id: WorkPackage.where(project_id: project.id) .select(:type_id) diff --git a/app/models/projects/hierarchy.rb b/app/models/projects/hierarchy.rb index 4cccfbeb0918..aaa262bc0f30 100644 --- a/app/models/projects/hierarchy.rb +++ b/app/models/projects/hierarchy.rb @@ -114,6 +114,10 @@ def hierarchy parents | descendants # Set union end + def has_subprojects? + !leaf? + end + # Returns an array of active subprojects. def active_subprojects project.descendants.where(active: true) @@ -150,5 +154,20 @@ def left_neighbor_by_name_order def remember_reorder @reorder_nested_set = new_record? || name_changed? end + + # Returns a :conditions SQL string that can be used to find the issues associated with this project. + # + # Examples: + # project.with_subprojects(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))" + # project.with_subprojects(false) => "projects.id = 1" + def with_subprojects(with_subprojects) + projects_table = Project.arel_table + + stmt = projects_table[:id].eq(id) + if with_subprojects && has_subprojects? + stmt = stmt.or(projects_table[:lft].gt(lft).and(projects_table[:rgt].lt(rgt))) + end + stmt + end end end From 736cce6377acaa7b1959826d1ade4d24449b28c4 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Sat, 15 Jun 2024 10:02:03 +0300 Subject: [PATCH 35/56] Create Projects::Types and Projects::Versions concerns. --- app/models/project.rb | 60 ++------------------------ app/models/projects/types.rb | 49 +++++++++++++++++++++ app/models/projects/versions.rb | 76 +++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 57 deletions(-) create mode 100644 app/models/projects/types.rb create mode 100644 app/models/projects/versions.rb diff --git a/app/models/project.rb b/app/models/project.rb index 2b00fb259fef..cd7c1a4cf46a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -34,7 +34,10 @@ class Project < ApplicationRecord include Projects::CustomFields include Projects::Hierarchy include Projects::Storage + include Projects::Types + include Projects::Versions include Projects::WorkPackageCustomFields + include ::Scopes::Scoped @@ -202,63 +205,6 @@ def self.selectable_projects Project.visible.select { |p| User.current.member_of? p }.sort_by(&:to_s) end - def types_used_by_work_packages - ::Type.where(id: WorkPackage.where(project_id: project.id) - .select(:type_id) - .distinct) - end - - # Returns a scope of the types used by the project and its active sub projects - def rolled_up_types - ::Type - .joins(:projects) - .select("DISTINCT #{::Type.table_name}.*") - .where(projects: { id: self_and_descendants.select(:id) }) - .merge(Project.active) - .order("#{::Type.table_name}.position") - end - - # Closes open and locked project versions that are completed - def close_completed_versions - Version.transaction do - versions.where(status: %w(open locked)).find_each do |version| - if version.completed? - version.update_attribute(:status, "closed") - end - end - end - end - - # Returns a scope of the Versions on subprojects - def rolled_up_versions - Version.rolled_up(self) - end - - # Returns a scope of the Versions used by the project - def shared_versions - Version.shared_with(self) - end - - # Returns all versions a work package can be assigned to. Opposed to - # #shared_versions this returns an array of Versions, not a scope. - # - # The main benefit is in scenarios where work packages' projects are eager - # loaded. Because eager loading the project e.g. via - # WorkPackage.includes(:project).where(type: 5) will assign the same instance - # (same object_id) for every work package having the same project this will - # reduce the number of db queries when performing operations including the - # project's versions. - # - # For custom fields configured with "Allow non-open versions" this can be called - # with only_open: false, in which case locked and closed versions are returned as well. - def assignable_versions(only_open: true) - if only_open - @assignable_versions ||= shared_versions.references(:project).with_status_open.order_by_semver_name.to_a - else - @assignable_versions_including_non_open ||= shared_versions.references(:project).order_by_semver_name.to_a - end - end - def project self end diff --git a/app/models/projects/types.rb b/app/models/projects/types.rb new file mode 100644 index 000000000000..0f0f7e429f57 --- /dev/null +++ b/app/models/projects/types.rb @@ -0,0 +1,49 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-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 Projects::Types + extend ActiveSupport::Concern + + included do + def types_used_by_work_packages + ::Type.where(id: WorkPackage.where(project_id: project.id) + .select(:type_id) + .distinct) + end + + # Returns a scope of the types used by the project and its active sub projects + def rolled_up_types + ::Type + .joins(:projects) + .select("DISTINCT #{::Type.table_name}.*") + .where(projects: { id: self_and_descendants.select(:id) }) + .merge(Project.active) + .order("#{::Type.table_name}.position") + end + end +end diff --git a/app/models/projects/versions.rb b/app/models/projects/versions.rb new file mode 100644 index 000000000000..31be3b3594b4 --- /dev/null +++ b/app/models/projects/versions.rb @@ -0,0 +1,76 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-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 Projects::Versions + extend ActiveSupport::Concern + + included do + # Closes open and locked project versions that are completed + def close_completed_versions + Version.transaction do + versions.where(status: %w(open locked)).find_each do |version| + if version.completed? + version.update_attribute(:status, "closed") + end + end + end + end + + # Returns a scope of the Versions on subprojects + def rolled_up_versions + Version.rolled_up(self) + end + + # Returns a scope of the Versions used by the project + def shared_versions + Version.shared_with(self) + end + + # Returns all versions a work package can be assigned to. Opposed to + # #shared_versions this returns an array of Versions, not a scope. + # + # The main benefit is in scenarios where work packages' projects are eager + # loaded. Because eager loading the project e.g. via + # WorkPackage.includes(:project).where(type: 5) will assign the same instance + # (same object_id) for every work package having the same project this will + # reduce the number of db queries when performing operations including the + # project's versions. + # + # For custom fields configured with "Allow non-open versions" this can be called + # with only_open: false, in which case locked and closed versions are returned as well. + def assignable_versions(only_open: true) + if only_open + @assignable_versions ||= + shared_versions.references(:project).with_status_open.order_by_semver_name.to_a + else + @assignable_versions_including_non_open ||= # rubocop:disable Naming/MemoizedInstanceVariableName + shared_versions.references(:project).order_by_semver_name.to_a + end + end + end +end From 02d890bc0caef9807130f41c988c5aad51c142f2 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Sat, 15 Jun 2024 14:25:16 +0300 Subject: [PATCH 36/56] Refactor and cache available_custom_fields methods --- app/models/projects/custom_fields.rb | 14 +++++--------- .../lib/acts_as_customizable.rb | 3 ++- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/app/models/projects/custom_fields.rb b/app/models/projects/custom_fields.rb index 55369e14c72e..1b6c92bacaf1 100644 --- a/app/models/projects/custom_fields.rb +++ b/app/models/projects/custom_fields.rb @@ -40,19 +40,15 @@ def available_custom_fields # overrides acts_as_customizable # in contrast to acts_as_customizable, custom_fields are enabled per project # thus we need to check the project_custom_field_project_mappings - custom_fields = all_available_custom_fields.visible + visible_fields = all_available_custom_fields.visible + return visible_fields if new_record? - unless new_record? - custom_fields = custom_fields - .where(id: project_custom_field_project_mappings.select(:custom_field_id)) - .or(ProjectCustomField.required) - end - - custom_fields + visible_fields.where(id: project_custom_field_project_mappings.select(:custom_field_id)) + .or(ProjectCustomField.required) end def all_available_custom_fields - ProjectCustomField + @all_available_custom_fields ||= ProjectCustomField .includes(:project_custom_field_section) .order("custom_field_sections.position", :position_in_custom_field_section) end diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index cc23ec17cdac..7b94e34a5f2a 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -73,7 +73,7 @@ def available_custom_fields end def all_available_custom_fields - available_custom_fields + @all_available_custom_fields ||= available_custom_fields end # Sets the values of the object's custom fields @@ -214,6 +214,7 @@ def reload(*args) def reset_custom_values_change_tracker @custom_field_values_cache = nil + @all_available_custom_fields = nil self.custom_value_destroyed = false end From 4c2f5f340de40bca1a724780761eb25545450b94 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Sat, 15 Jun 2024 14:49:28 +0300 Subject: [PATCH 37/56] Improve cache key method comment --- .../acts_as_customizable/lib/acts_as_customizable.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index 7b94e34a5f2a..a9131efd0847 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -130,11 +130,11 @@ def custom_field_values(all: false) end end - # Returns the cache key for caching @custom_field_values_cache. + # Override to extend the cache key for caching @custom_field_values_cache. # - # In certain cases, the implementing models have a changing list of custom field values - # depending on certain attributes. By overriding this method, we can include the - # dependent attributes in the cache key, providing a more flexible key caching mechanism. + # In some cases, the implementing class has a changing list of custom field values + # depending on certain attributes. When those attributes are changed, the cache can + # be kept up to date by including them in the overriden custom_field_cache_key method. # # i.e.: The work package custom field values are changing based on the project_id and type_id. # The only way to keep the cache updated is to include those ids in the cache key. From aa7c2eed5d9f108d5307da14fc0b0c4f9c1e8915 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Sat, 15 Jun 2024 15:00:32 +0300 Subject: [PATCH 38/56] Add all_available_custom_fields method comment. --- app/models/projects/custom_fields.rb | 12 +++++++++--- .../acts_as_customizable/lib/acts_as_customizable.rb | 8 ++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/models/projects/custom_fields.rb b/app/models/projects/custom_fields.rb index 1b6c92bacaf1..2bbac4108178 100644 --- a/app/models/projects/custom_fields.rb +++ b/app/models/projects/custom_fields.rb @@ -37,9 +37,6 @@ module Projects::CustomFields has_many :project_custom_fields, through: :project_custom_field_project_mappings, class_name: "ProjectCustomField" def available_custom_fields - # overrides acts_as_customizable - # in contrast to acts_as_customizable, custom_fields are enabled per project - # thus we need to check the project_custom_field_project_mappings visible_fields = all_available_custom_fields.visible return visible_fields if new_record? @@ -47,6 +44,15 @@ def available_custom_fields .or(ProjectCustomField.required) end + # Note: + # + # The UI allows the enabled attributes only via the project_custom_field_project_mappings. + # The API still provides the old behaviour where all the custom fields are available regardless + # of the enabled mapping. Once the api behaviour is aligned to the UI behaviour, this method + # can be removed in favour of the available_custom_fields. + # As a future improvement a flag `via_api=true` can be set on the project when the + # modification happens via the api, then set the available_custom_fields accordingly. This allows + # the extension to be completely removed from the acts_as_customizable plugin. def all_available_custom_fields @all_available_custom_fields ||= ProjectCustomField .includes(:project_custom_field_section) diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index a9131efd0847..adb8e4c4bcfb 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -72,6 +72,14 @@ def available_custom_fields self.class.available_custom_fields(self) end + # Note: + # + # The role of this method is to provide flexibility on enabling just a subset of + # available_custom_fields on the UI while enabling all_available_custom_fields via the api. + # A good example is the Project's attributes, the UI allows the enabled attributes only, + # and the Projects API still provides the old behaviour where all the custom fields are available. + # Once the api behaviour is aligned to the UI behaviour, this method can be removed in favor of + # the available_custom_fields method. def all_available_custom_fields @all_available_custom_fields ||= available_custom_fields end From f9422567f106b9783b5c429f53f25268491cfd0e Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Wed, 19 Jun 2024 16:30:48 +0300 Subject: [PATCH 39/56] Update Ruby to 3.3.3 --- .github/workflows/danger.yml | 2 +- .ruby-version | 2 +- Gemfile.lock | 2 +- docker/dev/backend/Dockerfile | 2 +- docker/prod/Dockerfile | 2 +- docs/development/development-environment-osx/README.md | 10 +++++----- .../development-environment-ubuntu/README.md | 10 +++++----- .../installation/manual/README.md | 6 +++--- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index c8d4c92ad4b9..841d0c523acc 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - ruby-version: '3.3.2' + ruby-version: '3.3.3' - uses: MeilCli/danger-action@v5 with: danger_file: 'Dangerfile' diff --git a/.ruby-version b/.ruby-version index 477254331794..619b53766848 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.2 +3.3.3 diff --git a/Gemfile.lock b/Gemfile.lock index d40724ad2a2a..873d1d42c697 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1352,7 +1352,7 @@ DEPENDENCIES with_advisory_lock (~> 5.1.0) RUBY VERSION - ruby 3.3.2p78 + ruby 3.3.3p89 BUNDLED WITH 2.5.11 diff --git a/docker/dev/backend/Dockerfile b/docker/dev/backend/Dockerfile index a4a06c642a3a..230ea7373900 100644 --- a/docker/dev/backend/Dockerfile +++ b/docker/dev/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:3.3.2-bullseye as develop +FROM ruby:3.3.3-bullseye as develop MAINTAINER operations@openproject.com ARG DEV_UID=1000 diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile index 771e53748745..4eaeac445be8 100644 --- a/docker/prod/Dockerfile +++ b/docker/prod/Dockerfile @@ -1,4 +1,4 @@ -ARG RUBY_VERSION="3.3.2" +ARG RUBY_VERSION="3.3.3" FROM ruby:${RUBY_VERSION}-bookworm as base LABEL maintainer="operations@openproject.com" diff --git a/docs/development/development-environment-osx/README.md b/docs/development/development-environment-osx/README.md index c89e5afc7522..529a200a8128 100644 --- a/docs/development/development-environment-osx/README.md +++ b/docs/development/development-environment-osx/README.md @@ -27,7 +27,7 @@ their homepage. Use [rbenv](https://github.com/rbenv/rbenv) and [ruby-build](https://github.com/rbenv/ruby-build#readme) to install Ruby. We always require the latest ruby versions, and you can check which version is required by [checking the Gemfile](https://github.com/opf/openproject/blob/dev/Gemfile#L31) for the `ruby "~> X.Y"` statement. At -the time of writing, this version is "3.3.2" +the time of writing, this version is "3.3.3" #### Install rbenv and ruby-build @@ -45,7 +45,7 @@ $ rbenv init With both installed, we can now install the actual ruby version. You can check available ruby versions with `rbenv install --list`. -At the time of this writing, the latest stable version is `3.3.2`, which we also require. +At the time of this writing, the latest stable version is `3.3.3`, which we also require. We suggest you install the version we require in the [Gemfile](https://github.com/opf/openproject/blob/dev/Gemfile). Search for the `ruby '~> X.Y.Z'` line @@ -53,14 +53,14 @@ and install that version. ```shell # Install the required version as read from the Gemfile -rbenv install 3.3.2 +rbenv install 3.3.3 ``` This might take a while depending on whether ruby is built from source. After it is complete, you need to tell rbenv to globally activate this version ```shell -rbenv global 3.3.2 +rbenv global 3.3.3 ``` You also need to install [bundler](https://github.com/bundler/bundler/), the ruby gem bundler. @@ -134,7 +134,7 @@ You should now have an active ruby and node installation. Verify that it works w ```shell $ ruby --version -ruby 3.3.2 (2024-05-30 revision e5a195edf6) [arm64-darwin23] +ruby 3.3.3 (2024-06-12 revision f1c7b6f435) [arm64-darwin23] $ bundler --version Bundler version 2.5.11 diff --git a/docs/development/development-environment-ubuntu/README.md b/docs/development/development-environment-ubuntu/README.md index 256b6dab4d91..de5142b0d81c 100644 --- a/docs/development/development-environment-ubuntu/README.md +++ b/docs/development/development-environment-ubuntu/README.md @@ -41,7 +41,7 @@ sudo apt-get install git curl build-essential zlib1g-dev libyaml-dev libssl-dev Use [rbenv](https://github.com/rbenv/rbenv) and [ruby-build](https://github.com/rbenv/ruby-build#readme) to install Ruby. We always require the latest ruby versions, and you can check which version is required by [checking the Gemfile](https://github.com/opf/openproject/blob/dev/Gemfile#L31) for the `ruby "~> X.Y"` statement. At -the time of writing, this version is "3.3.2" +the time of writing, this version is "3.3.3" #### Install rbenv and ruby-build @@ -75,7 +75,7 @@ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build #### Installing ruby With both installed, we can now install ruby. You can check available ruby versions with `rbenv install --list`. -At the time of this writing, the latest stable version is `3.3.2` which we also require. +At the time of this writing, the latest stable version is `3.3.3` which we also require. We suggest you install the version we require in the [Gemfile](https://github.com/opf/openproject/blob/dev/Gemfile). Search for the `ruby '~> X.Y.Z'` line @@ -83,14 +83,14 @@ and install that version. ```shell # Install the required version as read from the Gemfile -rbenv install 3.3.2 +rbenv install 3.3.3 ``` This might take a while depending on whether ruby is built from source. After it is complete, you need to tell rbenv to globally activate this version ```shell -rbenv global 3.3.2 +rbenv global 3.3.3 rbenv rehash ``` @@ -180,7 +180,7 @@ You should now have an active ruby and node installation. Verify that it works w ```shell ruby --version -ruby 3.3.2 (2024-05-30 revision e5a195edf6) [arm64-darwin23] +ruby 3.3.3 (2024-06-12 revision f1c7b6f435) [arm64-darwin23] bundler --version Bundler version 2.5.11 diff --git a/docs/installation-and-operations/installation/manual/README.md b/docs/installation-and-operations/installation/manual/README.md index ad7c1df7f879..ec82628f31a6 100644 --- a/docs/installation-and-operations/installation/manual/README.md +++ b/docs/installation-and-operations/installation/manual/README.md @@ -108,16 +108,16 @@ time to finish. [openproject@host] source ~/.profile [openproject@host] git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build -[openproject@host] rbenv install 3.3.2 +[openproject@host] rbenv install 3.3.3 [openproject@host] rbenv rehash -[openproject@host] rbenv global 3.3.2 +[openproject@host] rbenv global 3.3.3 ``` To check our Ruby installation we run `ruby --version`. It should output something very similar to: ```text -ruby 3.3.2 (2024-05-30 revision e5a195edf6) [arm64-darwin23] +ruby 3.3.3 (2024-06-12 revision f1c7b6f435) [arm64-darwin23] ``` ## Installation of Node From cda7aeebb8131b103cb4f784d72d21c87c37cbcf Mon Sep 17 00:00:00 2001 From: OpenProject Actions CI Date: Thu, 20 Jun 2024 07:03:40 +0000 Subject: [PATCH 40/56] update locales from crowdin [ci skip] --- config/locales/crowdin/js-zh-TW.yml | 4 ++-- modules/backlogs/config/locales/crowdin/af.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/locales/crowdin/js-zh-TW.yml b/config/locales/crowdin/js-zh-TW.yml index 6fe9911c7dca..0f8313569a23 100644 --- a/config/locales/crowdin/js-zh-TW.yml +++ b/config/locales/crowdin/js-zh-TW.yml @@ -820,8 +820,8 @@ zh-TW: duplicated: "重複於" blocks: "攔阻" blocked: "攔阻於" - precedes: "前置項目" - follows: "後置項目" + precedes: "後置項目" + follows: "前置項目" includes: "包括" partof: "一部分" requires: "需要" diff --git a/modules/backlogs/config/locales/crowdin/af.yml b/modules/backlogs/config/locales/crowdin/af.yml index 92746f07290b..4ca665958eb0 100644 --- a/modules/backlogs/config/locales/crowdin/af.yml +++ b/modules/backlogs/config/locales/crowdin/af.yml @@ -101,7 +101,7 @@ af: error_intro_singular: "Die volgende fout was teegekom:" error_outro: "Korrigeer asseblief die bogenoemde foute voordat u weer indien." event_sprint_description: "%{summary}: %{url}\n%{description}" - event_sprint_summary: "%{type}: %{summary}" + event_sprint_summary: "%{project}: %{summary}" ideal: "ideale" inclusion: "is nie by die lys ingesluit nie" label_back_to_project: "Terug na projekbladsy" From 8bfe53573fbeda29a8add148ca7a814623fff4e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Jun 2024 07:22:30 +0000 Subject: [PATCH 41/56] Bump rspec-rails from 6.1.2 to 6.1.3 Bumps [rspec-rails](https://github.com/rspec/rspec-rails) from 6.1.2 to 6.1.3. - [Changelog](https://github.com/rspec/rspec-rails/blob/main/Changelog.md) - [Commits](https://github.com/rspec/rspec-rails/compare/v6.1.2...v6.1.3) --- updated-dependencies: - dependency-name: rspec-rails dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index d40724ad2a2a..9d469f638a36 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -424,7 +424,7 @@ GEM rb_sys (~> 0.9) compare-xml (0.66) nokogiri (~> 1.8) - concurrent-ruby (1.3.1) + concurrent-ruby (1.3.3) connection_pool (2.4.1) cookiejar (0.3.4) cose (1.3.0) @@ -505,7 +505,7 @@ GEM rubocop smart_properties erblint-github (1.0.1) - erubi (1.12.0) + erubi (1.13.0) escape_utils (1.3.0) et-orbi (1.2.11) tzinfo @@ -638,7 +638,7 @@ GEM ice_nine (0.11.2) interception (0.5) io-console (0.7.2) - irb (1.13.1) + irb (1.13.2) rdoc (>= 4.0.0) reline (>= 0.4.2) iso8601 (0.13.0) @@ -723,7 +723,7 @@ GEM mini_magick (4.12.0) mini_mime (1.1.5) mini_portile2 (2.8.7) - minitest (5.23.1) + minitest (5.24.0) msgpack (1.7.2) multi_json (1.15.0) mustermann (3.0.0) @@ -925,7 +925,7 @@ GEM redis-client (0.22.2) connection_pool regexp_parser (2.9.2) - reline (0.5.8) + reline (0.5.9) io-console (~> 0.5) representable (3.2.0) declarative (< 0.1.0) @@ -950,13 +950,13 @@ GEM rspec-mocks (~> 3.13.0) rspec-core (3.13.0) rspec-support (~> 3.13.0) - rspec-expectations (3.13.0) + rspec-expectations (3.13.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (6.1.2) + rspec-rails (6.1.3) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) @@ -1057,7 +1057,7 @@ GEM store_attribute (1.2.0) activerecord (>= 6.0) stringex (2.8.6) - stringio (3.1.0) + stringio (3.1.1) strscan (3.1.0) structured_warnings (0.4.0) svg-graph (2.2.2) @@ -1144,7 +1144,7 @@ GEM xpath (3.2.0) nokogiri (~> 1.8) yard (0.9.36) - zeitwerk (2.6.15) + zeitwerk (2.6.16) PLATFORMS ruby From d933b41fd19bdd754c45db6833a918c306917b91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Jun 2024 07:23:04 +0000 Subject: [PATCH 42/56] Bump pry-rails from 0.3.9 to 0.3.11 Bumps [pry-rails](https://github.com/rweng/pry-rails) from 0.3.9 to 0.3.11. - [Commits](https://github.com/rweng/pry-rails/compare/v0.3.9...v0.3.11) --- updated-dependencies: - dependency-name: pry-rails dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index d40724ad2a2a..3aba8ebe453c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -821,8 +821,8 @@ GEM pry-doc (1.5.0) pry (~> 0.11) yard (~> 0.9.11) - pry-rails (0.3.9) - pry (>= 0.10.4) + pry-rails (0.3.11) + pry (>= 0.13.0) pry-rescue (1.6.0) interception (>= 0.5) pry (>= 0.12.0) From f23461a604424a1bee6d157444b4d0fb6c73bb6e Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Thu, 20 Jun 2024 10:34:45 +0300 Subject: [PATCH 43/56] Update Bundler to 2.5.13 --- Gemfile.lock | 2 +- docker/ci/Dockerfile | 2 +- docker/dev/backend/Dockerfile | 2 +- docker/prod/Dockerfile | 2 +- docs/development/development-environment-osx/README.md | 2 +- docs/development/development-environment-ubuntu/README.md | 2 +- script/github_pr_errors | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 873d1d42c697..2f6d22b46f66 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1355,4 +1355,4 @@ RUBY VERSION ruby 3.3.3p89 BUNDLED WITH - 2.5.11 + 2.5.13 diff --git a/docker/ci/Dockerfile b/docker/ci/Dockerfile index 6dd5cd31787a..a8f8aa3de6da 100644 --- a/docker/ci/Dockerfile +++ b/docker/ci/Dockerfile @@ -3,7 +3,7 @@ ARG RUBY_VERSION FROM ruby:${RUBY_VERSION}-bookworm ENV NODE_VERSION="20.9.0" -ENV BUNDLER_VERSION="2.5.11" +ENV BUNDLER_VERSION="2.5.13" ENV DEBIAN_FRONTEND=noninteractive ENV BUNDLE_WITHOUT="development:production:docker" diff --git a/docker/dev/backend/Dockerfile b/docker/dev/backend/Dockerfile index 230ea7373900..01a1cd5d9c34 100644 --- a/docker/dev/backend/Dockerfile +++ b/docker/dev/backend/Dockerfile @@ -8,7 +8,7 @@ ENV USER=dev ENV RAILS_ENV=development ENV NODE_MAJOR=20 -ENV BUNDLER_VERSION "2.5.11" +ENV BUNDLER_VERSION "2.5.13" # `--no-log-init` is required as a workaround to avoid disk exhaustion. # diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile index 4eaeac445be8..80ce747ebce4 100644 --- a/docker/prod/Dockerfile +++ b/docker/prod/Dockerfile @@ -2,7 +2,7 @@ ARG RUBY_VERSION="3.3.3" FROM ruby:${RUBY_VERSION}-bookworm as base LABEL maintainer="operations@openproject.com" -ARG BUNDLER_VERSION="2.5.11" +ARG BUNDLER_VERSION="2.5.13" ARG NODE_VERSION="20.9.0" ARG BIM_SUPPORT=true ENV DEBIAN_FRONTEND=noninteractive diff --git a/docs/development/development-environment-osx/README.md b/docs/development/development-environment-osx/README.md index 529a200a8128..9c4c3509d4dc 100644 --- a/docs/development/development-environment-osx/README.md +++ b/docs/development/development-environment-osx/README.md @@ -137,7 +137,7 @@ $ ruby --version ruby 3.3.3 (2024-06-12 revision f1c7b6f435) [arm64-darwin23] $ bundler --version -Bundler version 2.5.11 +Bundler version 2.5.13 node --version v20.9.0 diff --git a/docs/development/development-environment-ubuntu/README.md b/docs/development/development-environment-ubuntu/README.md index de5142b0d81c..7a3c860abe5d 100644 --- a/docs/development/development-environment-ubuntu/README.md +++ b/docs/development/development-environment-ubuntu/README.md @@ -183,7 +183,7 @@ ruby --version ruby 3.3.3 (2024-06-12 revision f1c7b6f435) [arm64-darwin23] bundler --version -Bundler version 2.5.11 +Bundler version 2.5.13 node --version v20.9.0 diff --git a/script/github_pr_errors b/script/github_pr_errors index 44c8fd7747fc..5af12dbb487a 100755 --- a/script/github_pr_errors +++ b/script/github_pr_errors @@ -285,7 +285,7 @@ end # rubocop:disable Layout/LineLength # Looks like this in the job log: -# Process 28: TEST_ENV_NUMBER=28 RUBYOPT=-I/usr/local/bundle/bundler/gems/turbo_tests-3148ae6c3482/lib -r/usr/local/bundle/gems/bundler-2.5.11/lib/bundler/setup -W0 RSPEC_SILENCE_FILTER_ANNOUNCEMENTS=1 /usr/local/bundle/gems/bundler-2.5.11/exe/bundle exec rspec --seed 52674 --format TurboTests::JsonRowsFormatter --out tmp/test-pipes/subprocess-28 --format ParallelTests::RSpec::RuntimeLogger --out spec/support/turbo_runtime_features.log spec/features/api_docs/index_spec.rb spec/features/custom_fields/reorder_options_spec.rb spec/features/projects/projects_portfolio_spec.rb spec/features/projects/template_spec.rb spec/features/versions/edit_spec.rb spec/features/work_packages/details/markdown/description_editor_spec.rb spec/features/work_packages/table/hierarchy/hierarchy_parent_below_spec.rb spec/features/work_packages/table/inline_create/inline_create_refresh_spec.rb spec/features/work_packages/table/invalid_query_spec.rb spec/features/work_packages/tabs/activity_revisions_spec.rb +# Process 28: TEST_ENV_NUMBER=28 RUBYOPT=-I/usr/local/bundle/bundler/gems/turbo_tests-3148ae6c3482/lib -r/usr/local/bundle/gems/bundler-2.5.13/lib/bundler/setup -W0 RSPEC_SILENCE_FILTER_ANNOUNCEMENTS=1 /usr/local/bundle/gems/bundler-2.5.13/exe/bundle exec rspec --seed 52674 --format TurboTests::JsonRowsFormatter --out tmp/test-pipes/subprocess-28 --format ParallelTests::RSpec::RuntimeLogger --out spec/support/turbo_runtime_features.log spec/features/api_docs/index_spec.rb spec/features/custom_fields/reorder_options_spec.rb spec/features/projects/projects_portfolio_spec.rb spec/features/projects/template_spec.rb spec/features/versions/edit_spec.rb spec/features/work_packages/details/markdown/description_editor_spec.rb spec/features/work_packages/table/hierarchy/hierarchy_parent_below_spec.rb spec/features/work_packages/table/inline_create/inline_create_refresh_spec.rb spec/features/work_packages/table/invalid_query_spec.rb spec/features/work_packages/tabs/activity_revisions_spec.rb # rubocop:enable Layout/LineLength class TestsGroup attr_accessor :test_env_number, :seed, :files From 7977dbf2be70fb9d1fa015e71be5aee60363c47b Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 20 Jun 2024 10:49:15 +0200 Subject: [PATCH 44/56] bump aws-partitions --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index d03fccc52fb8..e08cb6807bc9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -341,7 +341,7 @@ GEM activerecord (>= 4.0.0, < 7.2) awrence (1.2.1) aws-eventstream (1.3.0) - aws-partitions (1.944.0) + aws-partitions (1.945.0) aws-sdk-core (3.197.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) From 0363d5883a38ccf15198360d7802116987d9cd9b Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 20 Jun 2024 10:49:24 +0200 Subject: [PATCH 45/56] bump aws-sdk-core --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e08cb6807bc9..a694cc6d13a9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -342,7 +342,7 @@ GEM awrence (1.2.1) aws-eventstream (1.3.0) aws-partitions (1.945.0) - aws-sdk-core (3.197.0) + aws-sdk-core (3.197.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) From 96142cbac49b4a4da1f5e7e2a64d58cc24941345 Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 20 Jun 2024 10:49:33 +0200 Subject: [PATCH 46/56] bump aws-sdk-kms --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index a694cc6d13a9..e7a09a3fd922 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -347,7 +347,7 @@ GEM aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.84.0) + aws-sdk-kms (1.85.0) aws-sdk-core (~> 3, >= 3.197.0) aws-sigv4 (~> 1.1) aws-sdk-s3 (1.152.3) From 54df02f11124f289a0c589477ba85726929b8164 Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 20 Jun 2024 10:50:10 +0200 Subject: [PATCH 47/56] bump faraday --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e7a09a3fd922..58dcb43abb9b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -517,7 +517,7 @@ GEM factory_bot_rails (6.4.3) factory_bot (~> 6.4) railties (>= 5.0.0) - faraday (2.9.0) + faraday (2.9.2) faraday-net_http (>= 2.0, < 3.2) faraday-follow_redirects (0.3.0) faraday (>= 1, < 3) From c8b6fea00c4b2ebf614c4ea0db7b55e75f1b4ac0 Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 20 Jun 2024 10:51:12 +0200 Subject: [PATCH 48/56] bump jwt --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 58dcb43abb9b..6d36b23884b8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -662,7 +662,7 @@ GEM json_spec (1.1.5) multi_json (~> 1.0) rspec (>= 2.0, < 4.0) - jwt (2.8.1) + jwt (2.8.2) base64 ladle (1.0.1) open4 (~> 1.0) From f300d031650cda3f051215d11eeb1f7ad7e3c5a9 Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 20 Jun 2024 10:51:27 +0200 Subject: [PATCH 49/56] bump lefthook --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 6d36b23884b8..bbf3412ebd64 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -670,7 +670,7 @@ GEM launchy (3.0.1) addressable (~> 2.8) childprocess (~> 5.0) - lefthook (1.6.16) + lefthook (1.6.17) letter_opener (1.10.0) launchy (>= 2.2, < 4) letter_opener_web (3.0.0) From 0c3eeb46feb55b05b3fb65e5f712b432339f2404 Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 20 Jun 2024 10:51:55 +0200 Subject: [PATCH 50/56] bump net-imap --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index bbf3412ebd64..2369397f1363 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -733,7 +733,7 @@ GEM mutex_m (0.2.0) net-http (0.4.1) uri - net-imap (0.4.12) + net-imap (0.4.13) date net-protocol net-ldap (0.19.0) From dfe386547cf4abb729323ba4e34f2968a6eb0a49 Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 20 Jun 2024 11:00:08 +0200 Subject: [PATCH 51/56] bump acts_as_list --- Gemfile | 2 +- Gemfile.lock | 9 +++++---- modules/backlogs/openproject-backlogs.gemspec | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index efa07ef3fd08..3a119f78f3a2 100644 --- a/Gemfile +++ b/Gemfile @@ -59,7 +59,7 @@ gem "will_paginate", "~> 4.0.0" gem "friendly_id", "~> 5.5.0" -gem "acts_as_list", "~> 1.1.0" +gem "acts_as_list", "~> 1.2.0" gem "acts_as_tree", "~> 2.9.0" gem "awesome_nested_set", "~> 3.6.0" gem "closure_tree", "~> 7.4.0" diff --git a/Gemfile.lock b/Gemfile.lock index 2369397f1363..303a3df516aa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -81,7 +81,7 @@ PATH remote: modules/backlogs specs: openproject-backlogs (1.0.0) - acts_as_list (~> 1.1.0) + acts_as_list (~> 1.2.0) PATH remote: modules/bim @@ -318,8 +318,9 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - acts_as_list (1.1.0) - activerecord (>= 4.2) + acts_as_list (1.2.1) + activerecord (>= 6.1) + activesupport (>= 6.1) acts_as_tree (2.9.1) activerecord (>= 3.0.0) addressable (2.8.6) @@ -1155,7 +1156,7 @@ DEPENDENCIES activerecord-import (~> 1.7.0) activerecord-nulldb-adapter (~> 1.0.0) activerecord-session_store (~> 2.1.0) - acts_as_list (~> 1.1.0) + acts_as_list (~> 1.2.0) acts_as_tree (~> 2.9.0) addressable (~> 2.8.0) airbrake (~> 13.0.0) diff --git a/modules/backlogs/openproject-backlogs.gemspec b/modules/backlogs/openproject-backlogs.gemspec index c6ba6b8fc6bf..abe51255ed7a 100644 --- a/modules/backlogs/openproject-backlogs.gemspec +++ b/modules/backlogs/openproject-backlogs.gemspec @@ -8,7 +8,7 @@ Gem::Specification.new do |s| s.description = "This module adds features enabling agile teams to work with OpenProject in Scrum projects." s.files = Dir["{app,config,db,lib,doc}/**/*", "README.md"] - s.add_dependency "acts_as_list", "~> 1.1.0" + s.add_dependency "acts_as_list", "~> 1.2.0" s.add_development_dependency "factory_girl_rails", "~> 4.0" s.metadata["rubygems_mfa_required"] = "true" From e8c3f54c35d2dbcb5d8c3288a7b9e503b31cba33 Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 20 Jun 2024 11:01:19 +0200 Subject: [PATCH 52/56] bump aws-sdk-sns --- Gemfile.lock | 6 +++--- .../openproject-two_factor_authentication.gemspec | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 303a3df516aa..7125e75a754a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -206,7 +206,7 @@ PATH remote: modules/two_factor_authentication specs: openproject-two_factor_authentication (1.0.0) - aws-sdk-sns (~> 1.75.0) + aws-sdk-sns (~> 1.77.0) messagebird-rest (~> 1.4.2) rotp (~> 6.1) webauthn (~> 3.0) @@ -355,8 +355,8 @@ GEM aws-sdk-core (~> 3, >= 3.197.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) - aws-sdk-sns (1.75.0) - aws-sdk-core (~> 3, >= 3.193.0) + aws-sdk-sns (1.77.0) + aws-sdk-core (~> 3, >= 3.197.0) aws-sigv4 (~> 1.1) aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) diff --git a/modules/two_factor_authentication/openproject-two_factor_authentication.gemspec b/modules/two_factor_authentication/openproject-two_factor_authentication.gemspec index 06f3e668997f..64a0e2f60d8d 100644 --- a/modules/two_factor_authentication/openproject-two_factor_authentication.gemspec +++ b/modules/two_factor_authentication/openproject-two_factor_authentication.gemspec @@ -14,6 +14,6 @@ Gem::Specification.new do |s| s.add_dependency "rotp", "~> 6.1" s.add_dependency "webauthn", "~> 3.0" - s.add_dependency "aws-sdk-sns", "~> 1.75.0" + s.add_dependency "aws-sdk-sns", "~> 1.77.0" s.metadata["rubygems_mfa_required"] = "true" end From d441baba75cc091c233bef50071372b7865778d9 Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 20 Jun 2024 11:16:57 +0200 Subject: [PATCH 53/56] bump json_schemer --- Gemfile | 2 +- Gemfile.lock | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 3a119f78f3a2..3340e141fd2b 100644 --- a/Gemfile +++ b/Gemfile @@ -107,7 +107,7 @@ gem "svg-graph", "~> 2.2.0" gem "date_validator", "~> 0.12.0" gem "email_validator", "~> 2.2.3" -gem "json_schemer", "~> 2.2.0" +gem "json_schemer", "~> 2.3.0" gem "ruby-duration", "~> 3.2.0" # `config/initializers/mail_starttls_patch.rb` has also been patched to diff --git a/Gemfile.lock b/Gemfile.lock index 7125e75a754a..6476bd9e0315 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -654,8 +654,7 @@ GEM faraday-follow_redirects json-schema (4.3.0) addressable (>= 2.8) - json_schemer (2.2.1) - base64 + json_schemer (2.3.0) bigdecimal hana (~> 1.3) regexp_parser (~> 2.0) @@ -1218,7 +1217,7 @@ DEPENDENCIES httpx i18n-js (~> 4.2.3) i18n-tasks (~> 1.0.13) - json_schemer (~> 2.2.0) + json_schemer (~> 2.3.0) json_spec (~> 1.1.4) ladle launchy (~> 3.0.0) From d932ecccad1bf9647ab01d1cc362739dc052f8e8 Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 20 Jun 2024 11:17:31 +0200 Subject: [PATCH 54/56] bump mini_magick --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 3340e141fd2b..e9908e8b84a3 100644 --- a/Gemfile +++ b/Gemfile @@ -204,7 +204,7 @@ gem "plaintext", "~> 0.3.2" gem "ruby-progressbar", "~> 1.13.0", require: false -gem "mini_magick", "~> 4.12.0", require: false +gem "mini_magick", "~> 4.13.0", require: false gem "validate_url" diff --git a/Gemfile.lock b/Gemfile.lock index 6476bd9e0315..c14f026e570e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -720,7 +720,7 @@ GEM mime-types (3.5.2) mime-types-data (~> 3.2015) mime-types-data (3.2024.0604) - mini_magick (4.12.0) + mini_magick (4.13.1) mini_mime (1.1.5) mini_portile2 (2.8.7) minitest (5.24.0) @@ -1231,7 +1231,7 @@ DEPENDENCIES matrix (~> 0.4.2) md_to_pdf! meta-tags (~> 2.21.0) - mini_magick (~> 4.12.0) + mini_magick (~> 4.13.0) multi_json (~> 1.15.0) my_page! net-ldap (~> 0.19.0) From 0c43c726f12357f7b4b4c370e94a6586e166dbc4 Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 20 Jun 2024 11:18:25 +0200 Subject: [PATCH 55/56] bump rouge --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index e9908e8b84a3..aa3d951f8498 100644 --- a/Gemfile +++ b/Gemfile @@ -93,7 +93,7 @@ gem "deckar01-task_list", "~> 2.3.1" # Requires escape-utils for faster escaping gem "escape_utils", "~> 1.3" # Syntax highlighting used in html-pipeline with rouge -gem "rouge", "~> 4.2.0" +gem "rouge", "~> 4.3.0" # HTML sanitization used for html-pipeline gem "sanitize", "~> 6.1.0" # HTML autolinking for mails and urls (replaces autolink) diff --git a/Gemfile.lock b/Gemfile.lock index c14f026e570e..bd014cba4a79 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -943,7 +943,7 @@ GEM roar (1.2.0) representable (~> 3.1) rotp (6.3.0) - rouge (4.2.1) + rouge (4.3.0) rspec (3.13.0) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) @@ -1301,7 +1301,7 @@ DEPENDENCIES retriable (~> 3.1.1) rinku (~> 2.0.4) roar (~> 1.2.0) - rouge (~> 4.2.0) + rouge (~> 4.3.0) rspec (~> 3.13.0) rspec-rails (~> 6.1.0) rspec-retry (~> 0.6.1) From 6996f9804983353a94686890fa9fac3dbe50b576 Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 20 Jun 2024 11:19:34 +0200 Subject: [PATCH 56/56] bump sys-filesystem --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index aa3d951f8498..e8a9595ae48e 100644 --- a/Gemfile +++ b/Gemfile @@ -118,7 +118,7 @@ gem "mail", "= 2.8.1" gem "csv", "~> 3.3" # provide compatible filesystem information for available storage -gem "sys-filesystem", "~> 1.4.0", require: false +gem "sys-filesystem", "~> 1.5.0", require: false gem "bcrypt", "~> 3.1.6" diff --git a/Gemfile.lock b/Gemfile.lock index bd014cba4a79..80d93ec7e167 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1066,7 +1066,7 @@ GEM attr_required (>= 0.0.5) faraday (~> 2.0) faraday-follow_redirects - sys-filesystem (1.4.5) + sys-filesystem (1.5.0) ffi (~> 1.1) table_print (1.5.7) terminal-table (3.0.2) @@ -1333,7 +1333,7 @@ DEPENDENCIES stringex (~> 2.8.5) structured_warnings (~> 0.4.0) svg-graph (~> 2.2.0) - sys-filesystem (~> 1.4.0) + sys-filesystem (~> 1.5.0) table_print (~> 1.5.6) test-prof (~> 1.3.0) timecop (~> 0.9.0)