Skip to content

Commit b29661a

Browse files
committed
Map deprecated version macro to target versions
workPackageValue:X:version now renders all target versions of the work package, single-line by default so legacy macros keep their inline shape within existing content. Applies to both the web and export render paths; the export formatter matches the one on the work package list branch so either merges cleanly.
1 parent 575848a commit b29661a

6 files changed

Lines changed: 175 additions & 1 deletion

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
11+
# Copyright (C) 2006-2013 Jean-Philippe Lang
12+
# Copyright (C) 2010-2013 the ChiliProject Team
13+
#
14+
# This program is free software; you can redistribute it and/or
15+
# modify it under the terms of the GNU General Public License
16+
# as published by the Free Software Foundation; either version 2
17+
# of the License, or (at your option) any later version.
18+
#
19+
# This program is distributed in the hope that it will be useful,
20+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
# GNU General Public License for more details.
23+
#
24+
# You should have received a copy of the GNU General Public License
25+
# along with this program; if not, write to the Free Software
26+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27+
#
28+
# See COPYRIGHT and LICENSE files for more details.
29+
#++
30+
31+
module WorkPackage::Exports
32+
module Formatters
33+
class TargetVersions < ::Exports::Formatters::Default
34+
def self.apply?(attribute, _export_format)
35+
attribute.to_sym == :target_versions
36+
end
37+
38+
def retrieve_value(object)
39+
object.target_versions.map(&:name)
40+
end
41+
end
42+
end
43+
end

app/models/work_package/exports/macros/attributes.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,25 @@ def self.resolve_value(obj, attribute, disabled_rich_text_fields, layout: nil)
175175
custom_field = find_custom_field(obj, attribute)
176176

177177
attribute_name = convert_to_attribute_name(custom_field, attribute, obj)
178+
attribute_name, layout = map_legacy_version(attribute_name, layout, obj)
178179
return " " unless can_view_attribute?(custom_field, obj, attribute_name)
179180

180181
is_rich_text = custom_field&.formattable? || disabled_rich_text_fields.include?(attribute_name.to_sym)
181182
[format_attribute_value(attribute_name, obj.class, obj, is_rich_text, layout), is_rich_text]
182183
end
183184

185+
##
186+
# The deprecated version attribute renders the work package's target
187+
# versions, on a single line by default so legacy macros keep their
188+
# inline shape within existing content.
189+
def self.map_legacy_version(attribute_name, layout, obj)
190+
if obj.is_a?(WorkPackage) && attribute_name == "version"
191+
["target_versions", layout || "singleline"]
192+
else
193+
[attribute_name, layout]
194+
end
195+
end
196+
184197
def self.can_view_attribute?(custom_field, obj, attribute_name)
185198
custom_field || allowed_to_view_attribute?(obj, attribute_name)
186199
end

config/initializers/export_formats.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
formatter WorkPackage, WorkPackage::Exports::Formatters::Id
5151
formatter WorkPackage, WorkPackage::Exports::Formatters::ProjectPhase
5252
formatter WorkPackage, WorkPackage::Exports::Formatters::SpentUnits
53+
formatter WorkPackage, WorkPackage::Exports::Formatters::TargetVersions
5354

5455
list Project, Projects::Exports::CSV
5556
list Project, Projects::Exports::PDF

frontend/src/app/shared/components/fields/macros/attribute-value-macro.component.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,15 @@ export class AttributeValueMacroComponent implements OnInit {
127127

128128
const schema = await this.schemaCache.ensureLoaded(resource);
129129
const proxied = this.schemaCache.proxied(resource, schema);
130-
const attribute = schema.attributeFromLocalizedName(attributeName) ?? this.dateAttribute(resource, proxied, attributeName);
130+
let attribute = schema.attributeFromLocalizedName(attributeName) ?? this.dateAttribute(resource, proxied, attributeName);
131+
132+
// The deprecated version attribute renders the work package's target
133+
// versions, single-line by default so legacy macros keep their inline shape.
134+
if (resource._type === 'WorkPackage' && attribute === 'version') {
135+
attribute = 'targetVersions';
136+
this.layout = this.layout ?? 'singleline';
137+
}
138+
131139
const fieldSchema = proxied.ofProperty(attribute) as IFieldSchema|undefined;
132140

133141
if (fieldSchema) {

spec/models/exports/pdf/common/macro_spec.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,41 @@
369369
end
370370
end
371371

372+
describe "with target versions" do
373+
shared_let(:version_one) { create(:version, project:, name: "1.0") }
374+
shared_let(:version_two) { create(:version, project:, name: "2.0") }
375+
376+
before do
377+
create(:work_package_version, work_package:, version: version_one)
378+
create(:work_package_version, work_package:, version: version_two)
379+
end
380+
381+
describe "with targetVersions attribute" do
382+
let(:markdown) { "workPackageValue:#{work_package.id}:targetVersions" }
383+
384+
it "outputs one version per line" do
385+
# the association carries no order, so compare the lines as a set
386+
expect(formatted.split(" \n")).to match_array(%w[1.0 2.0])
387+
end
388+
end
389+
390+
describe "with legacy version attribute" do
391+
let(:markdown) { "workPackageValue:#{work_package.id}:version" }
392+
393+
it "outputs all target versions on a single line" do
394+
expect(formatted.split(", ")).to match_array(%w[1.0 2.0])
395+
end
396+
end
397+
398+
describe "with legacy version attribute and multiline layout" do
399+
let(:markdown) { "workPackageValue:#{work_package.id}:version:multiline" }
400+
401+
it "outputs one version per line" do
402+
expect(formatted.split(" \n")).to match_array(%w[1.0 2.0])
403+
end
404+
end
405+
end
406+
372407
describe "with specific work package ID and attribute" do
373408
let(:markdown) { "workPackageValue:#{work_package.id}:subject" }
374409

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
11+
# Copyright (C) 2006-2013 Jean-Philippe Lang
12+
# Copyright (C) 2010-2013 the ChiliProject Team
13+
#
14+
# This program is free software; you can redistribute it and/or
15+
# modify it under the terms of the GNU General Public License
16+
# as published by the Free Software Foundation; either version 2
17+
# of the License, or (at your option) any later version.
18+
#
19+
# This program is distributed in the hope that it will be useful,
20+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
# GNU General Public License for more details.
23+
#
24+
# You should have received a copy of the GNU General Public License
25+
# along with this program; if not, write to the Free Software
26+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27+
#
28+
# See COPYRIGHT and LICENSE files for more details.
29+
#++
30+
31+
require "spec_helper"
32+
33+
RSpec.describe WorkPackage::Exports::Formatters::TargetVersions do
34+
let(:formatter_instance) { described_class.new(:target_versions) }
35+
36+
describe ".apply?" do
37+
it "returns true for :target_versions with any export format" do
38+
expect(described_class.apply?(:target_versions, :pdf)).to be true
39+
expect(described_class.apply?(:target_versions, :csv)).to be true
40+
expect(described_class.apply?(:target_versions, :xls)).to be true
41+
end
42+
43+
it "returns false for other attributes" do
44+
expect(described_class.apply?(:version, :pdf)).to be false
45+
end
46+
end
47+
48+
describe "#format" do
49+
let(:versions) { [build_stubbed(:version, name: "1.0"), build_stubbed(:version, name: "2.0")] }
50+
let(:work_package) do
51+
build_stubbed(:work_package) do |wp|
52+
allow(wp)
53+
.to receive(:target_versions)
54+
.and_return(versions)
55+
end
56+
end
57+
58+
it "returns the joined version names" do
59+
expect(formatter_instance.format(work_package)).to eq("1.0, 2.0")
60+
end
61+
62+
it "honors the array_separator option" do
63+
expect(formatter_instance.format(work_package, array_separator: "; ")).to eq("1.0; 2.0")
64+
end
65+
66+
context "without target versions" do
67+
let(:versions) { [] }
68+
69+
it "returns an empty string" do
70+
expect(formatter_instance.format(work_package)).to eq("")
71+
end
72+
end
73+
end
74+
end

0 commit comments

Comments
 (0)