Skip to content

Commit 0395d9a

Browse files
authored
[COMMS-863] WorkPackage <-> Version direct relation cleanup (#24396)
* remove work_package relation from Version.rb model * remove version from the API when multiple versions is enabled add specs * remove 'belongs_to :version' from work_package.rb fix * adjust work package factory to not depend on model association * version association removal specs fix second pass fixing specs third pass second pass fixing specs third pass fixing specs adjust spec assigning work_package.version directly * adjust version read using wrong method * adjust specs that depend on the version_id column * fix in memory changes on specs * fix usage of version on specs * skip tests for now * models without a direct relation still be rendered properly * fix more specs fix even more specs * unskip specs and respective adjustments * attempt to fix baseline spec * remove legacy version filter that was using removed association * remove commented/unused out associations * fix issue with dangling associations between wp and versions because the direct associations between work package and versions has been removed, triggers like dependent: :something won't work anymore. This means we need to handle these manually.
1 parent 71fd50e commit 0395d9a

41 files changed

Lines changed: 308 additions & 263 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/contracts/versions/delete_contract.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,13 @@ class DeleteContract < ::DeleteContract
3737
protected
3838

3939
def validate_no_work_packages_attached
40-
return unless model.work_packages.exists?
40+
return unless work_packages_attached?
4141

4242
errors.add(:base, :undeletable_work_packages_attached)
4343
end
44+
45+
def work_packages_attached?
46+
model.work_package_versions.exists?
47+
end
4448
end
4549
end

app/contracts/work_packages/base_contract.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ class BaseContract < ::ModelContract
4747
attribute :priority_id
4848
attribute :category_id
4949
attribute :version_id,
50-
permission: :assign_versions do
50+
permission: :assign_versions,
51+
writable: ->(*) { !Setting::WorkPackageMultipleVersions.active? } do
5152
validate_version_is_assignable
5253
end
5354
attribute :target_versions,

app/models/queries/work_packages/filter/version_filter.rb

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,6 @@ def human_name
3838
WorkPackage.human_attribute_name("version")
3939
end
4040

41-
def joins
42-
case operator
43-
when "o", "c", "l"
44-
:version
45-
end
46-
end
47-
4841
def self.key
4942
:version_id
5043
end

app/models/version.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ class Version < ApplicationRecord
3333
include ::Scopes::Scoped
3434

3535
belongs_to :project
36-
has_many :work_packages, dependent: :nullify
3736
has_many :work_package_versions, dependent: :delete_all
3837
has_many :targeted_work_packages,
3938
-> { where(work_package_versions: { kind: "target" }) },
@@ -43,6 +42,9 @@ class Version < ApplicationRecord
4342
through: :work_package_versions, source: :work_package
4443
acts_as_customizable
4544

45+
# manually clear association, since the has_many/belongs_to were removed
46+
before_destroy :nullify_work_package_version_mirror
47+
4648
VERSION_STATUSES = %w(open locked closed).freeze
4749
VERSION_SHARINGS = %w(none descendants hierarchy tree system).freeze
4850

@@ -210,6 +212,10 @@ def validate_start_date_before_effective_date
210212
end
211213
end
212214

215+
def nullify_work_package_version_mirror
216+
WorkPackage.where(version_id: id).update_all(version_id: nil)
217+
end
218+
213219
# Returns the average estimated time of assigned issues
214220
# or 1 if no issue has an estimated time
215221
# Used to weight unestimated issues in progress calculation

app/models/work_package/versions.rb

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@ module WorkPackage::Versions
3232
extend ActiveSupport::Concern
3333

3434
included do
35-
# Deprecated single-version column, kept in sync with the first target
36-
# version (see #update_legacy_version_field). Can be dropped once all
37-
# subsystems read target_versions instead.
38-
belongs_to :version, optional: true
39-
4035
has_many :work_package_versions, dependent: :delete_all
4136
has_many :versions, through: :work_package_versions, source: :version
4237
has_many :target_versions,
@@ -145,6 +140,19 @@ def prune_unshared_version_kinds(work_package)
145140
end
146141
end
147142

143+
# Read-only replacement for the former +belongs_to :version+ association.
144+
#
145+
# Returns the work package's single target version, or nil when it has none.
146+
# Raises an error if target_versions has multiple values
147+
def version
148+
if target_versions.size > 1
149+
raise "WorkPackage##{id} has multiple target versions and cannot be " \
150+
"represented as a single version. Use #target_versions instead."
151+
end
152+
153+
target_versions.min
154+
end
155+
148156
# Versions that the work_package can be assigned to
149157
# A work_package can be assigned to:
150158
# * any open, shared version of the project the wp belongs to
@@ -179,8 +187,8 @@ def override_observed_in_versions? = !observed_in_version_ids_replacements.nil?
179187
# * actual written target_versions
180188
def effective_target_versions
181189
if target_version_ids_replacements.nil?
182-
# TODO(COMMS-863)
183-
return version_id_changed? ? Array(version) : target_versions
190+
# TODO(COMMS-863): drop this branch once nothing writes version_id directly
191+
return version_id_changed? ? Array(Version.find_by(id: version_id)) : target_versions
184192
end
185193

186194
versions_by_id = Version.where(id: target_version_ids_replacements).index_by(&:id)

app/services/projects/copy/work_packages_dependent_service.rb

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -136,16 +136,13 @@ def copy_relations(source_wp, new_wp_id, work_packages_map)
136136
end
137137

138138
def copy_work_package_attribute_overrides(source_work_package, parent_id, user_cf_ids)
139-
target_version_ids = work_package_target_version_ids(source_work_package)
139+
target_version_ids = mapped_version_ids(source_work_package.target_versions)
140140

141141
{
142142
project: target,
143143
parent_id:,
144-
# TODO(COMMS-863): The legacy version_id has to agree with the first target
145-
# version (contract validation) until the column is dropped.
146-
version_id: target_version_ids&.first,
147144
target_version_ids:,
148-
observed_in_version_ids: work_package_observed_in_version_ids(source_work_package),
145+
observed_in_version_ids: mapped_version_ids(source_work_package.observed_in_versions),
149146
assigned_to_id: work_package_assigned_to_id(source_work_package),
150147
responsible_id: work_package_responsible_id(source_work_package),
151148
custom_field_values: custom_value_attributes(source_work_package, user_cf_ids),
@@ -154,20 +151,10 @@ def copy_work_package_attribute_overrides(source_work_package, parent_id, user_c
154151
}
155152
end
156153

157-
def work_package_target_version_ids(source_work_package)
158-
lookup = state.version_id_lookup
159-
return if lookup.nil?
154+
def mapped_version_ids(versions)
155+
lookup = state.version_id_lookup || {}
160156

161-
# `.presence` forces return nil instead of an empty array when the source has no target
162-
# versions. This skips writing target versions unnecessarily and avoid conflicts with legacy version_id
163-
source_work_package.target_versions.filter_map { |v| state.version_id_lookup[v.id] }.presence
164-
end
165-
166-
def work_package_observed_in_version_ids(source_work_package)
167-
lookup = state.version_id_lookup
168-
return if lookup.nil?
169-
170-
source_work_package.observed_in_versions.filter_map { |v| state.version_id_lookup[v.id] }.presence
157+
versions.filter_map { |version| lookup[version.id] }
171158
end
172159

173160
def work_package_assigned_to_id(source_work_package)

app/services/work_packages/copy_service.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,11 @@ def version_reference_attributes(work_package, writable_attributes)
106106
attributes = {}
107107

108108
if writable_attributes.include?("target_versions")
109-
attributes["target_version_ids"] = work_package.target_versions.pluck(:version_id).presence
109+
attributes["target_version_ids"] = work_package.target_version_ids.presence
110110
end
111111

112112
if writable_attributes.include?("observed_in_versions")
113-
attributes["observed_in_version_ids"] = work_package.observed_in_versions.pluck(:version_id).presence
113+
attributes["observed_in_version_ids"] = work_package.observed_in_version_ids.presence
114114
end
115115

116116
attributes.compact

app/services/work_packages/set_attributes_service.rb

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ def update_project_dependent_attributes
275275
return unless work_package.project_id_changed? && work_package.project_id
276276

277277
model.change_by_system do
278-
set_versions_to_nil
278+
clear_unassignable_versions
279279
reassign_category
280280
set_parent_to_nil
281281
clear_semantic_identifier
@@ -369,15 +369,6 @@ def derive_progress_values_class
369369
end
370370
end
371371

372-
def set_versions_to_nil
373-
if work_package.version &&
374-
work_package.project&.shared_versions&.exclude?(work_package.version)
375-
work_package.version = nil
376-
end
377-
378-
clear_unassignable_versions
379-
end
380-
381372
def clear_unassignable_versions
382373
assignable_ids = work_package.project&.shared_versions&.pluck(:id) || []
383374

lib/api/v3/work_packages/schema/work_package_schema_representer.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,8 @@ def initialize(schema, self_link:, **context)
305305
},
306306
required: false
307307

308+
# Deprecated in favour of `targetVersions`
309+
# Removed from the API if multiple_versions is enabled on the instance
308310
schema_with_allowed_collection :version,
309311
value_representer: Versions::VersionRepresenter,
310312
link_factory: ->(version) {
@@ -315,6 +317,7 @@ def initialize(schema, self_link:, **context)
315317
},
316318
required: false,
317319
deprecated: true,
320+
show_if: ->(*) { !Setting::WorkPackageMultipleVersions.active? },
318321
description: -> { I18n.t("api_v3.attributes.version.deprecated") }
319322

320323
# While multiple versions is not enabled, the field keeps the label of the

lib/api/v3/work_packages/work_package_representer.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,9 +577,20 @@ def self_v3_path(*)
577577
link: ::API::V3::Principals::PrincipalRepresenterFactory
578578
.create_link_lambda(:assigned_to)
579579

580+
# Deprecated in favour of `targetVersions`
581+
# Removed from the API if multiple_versions is enabled on the instance
580582
associated_resource :version,
581583
v3_path: :version,
582-
representer: ::API::V3::Versions::VersionRepresenter
584+
representer: ::API::V3::Versions::VersionRepresenter,
585+
# representable evaluates the getter before `skip_render`, so we manually
586+
# check if we *can* render the result here before actually doing it
587+
getter: ->(*) {
588+
next if Setting::WorkPackageMultipleVersions.active?
589+
next unless embed_link?(:version) && represented.version
590+
591+
::API::V3::Versions::VersionRepresenter.create(represented.version, current_user:)
592+
},
593+
skip_render: ->(*) { Setting::WorkPackageMultipleVersions.active? }
583594

584595
associated_resources :target_versions,
585596
v3_path: :version,

0 commit comments

Comments
 (0)