diff --git a/app/models/custom_field/hierarchy/item.rb b/app/models/custom_field/hierarchy/item.rb
index 7bfd41af2159..3490e7009ef3 100644
--- a/app/models/custom_field/hierarchy/item.rb
+++ b/app/models/custom_field/hierarchy/item.rb
@@ -37,4 +37,8 @@ class CustomField::Hierarchy::Item < ApplicationRecord
scope :including_children, -> { includes(children: :children) }
def to_s = short.nil? ? label : "#{label} (#{short})"
+
+ def ancestry_path
+ self_and_ancestors.filter_map(&:to_s).reverse.join(" / ")
+ end
end
diff --git a/lib/open_project/journal_formatter/custom_field/plain.rb b/lib/open_project/journal_formatter/custom_field/plain.rb
index 4eae0a2665d5..a0ed17f05b27 100644
--- a/lib/open_project/journal_formatter/custom_field/plain.rb
+++ b/lib/open_project/journal_formatter/custom_field/plain.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -53,6 +55,8 @@ def get_formatted_values(custom_field, values)
def get_modifier_function(custom_field)
case custom_field.field_format
+ when "hierarchy"
+ :find_item_value
when "list"
:find_list_value
when "user"
@@ -79,13 +83,23 @@ def find_user_value(value, _custom_field)
# Lookup any visible user we can find
user_lookup = Principal
- .in_visible_project_or_me(User.current)
- .where(id: ids)
- .index_by(&:id)
+ .in_visible_project_or_me(User.current)
+ .where(id: ids)
+ .index_by(&:id)
ids_to_names(ids, user_lookup)
end
+ def find_item_value(value, _custom_field)
+ ids = value.split(",").map(&:to_i)
+
+ CustomField::Hierarchy::Item.where(id: ids).map do |item|
+ next I18n.t(:label_deleted_custom_option) unless ids.include?(item.id)
+
+ item.ancestry_path
+ end.join(", ")
+ end
+
def find_list_value(value, custom_field)
ids = value.split(",").map(&:to_i)
@@ -105,9 +119,9 @@ def find_version_value(value, _custom_field)
# Lookup visible versions we can find
version_lookup = Version
- .visible(User.current)
- .where(id: ids)
- .index_by(&:id)
+ .visible(User.current)
+ .where(id: ids)
+ .index_by(&:id)
ids_to_names(ids, version_lookup)
end
diff --git a/spec/lib/journal_formatter/custom_field_spec.rb b/spec/lib/journal_formatter/custom_field_spec.rb
index 2398bd319a94..e86d3fb90999 100644
--- a/spec/lib/journal_formatter/custom_field_spec.rb
+++ b/spec/lib/journal_formatter/custom_field_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -50,8 +52,8 @@ def url_helper = Rails.application.routes.url_helpers
allow(CustomField).to receive(:find_by).and_return(nil)
allow(CustomField)
.to receive(:find_by)
- .with(id: custom_field.id)
- .and_return(custom_field)
+ .with(id: custom_field.id)
+ .and_return(custom_field)
end
context "with html requested by default" do
@@ -196,8 +198,8 @@ def url_helper = Rails.application.routes.url_helpers
allow(wherestub)
.to receive(:where)
- .with(id: [user1.id, user2.id])
- .and_return(visible_users)
+ .with(id: [user1.id, user2.id])
+ .and_return(visible_users)
end
describe "with two visible users" do
@@ -248,37 +250,37 @@ def url_helper = Rails.application.routes.url_helpers
allow(custom_field)
.to receive(:custom_options)
- .and_return(cf_options)
+ .and_return(cf_options)
allow(cf_options)
.to receive(:where)
- .with(id: [1, 2])
- .and_return(old_options)
+ .with(id: [1, 2])
+ .and_return(old_options)
allow(cf_options)
.to receive(:where)
- .with(id: [3, 4])
- .and_return(new_options)
+ .with(id: [3, 4])
+ .and_return(new_options)
allow(old_options)
.to receive(:order)
- .with(:position)
- .and_return(old_options)
+ .with(:position)
+ .and_return(old_options)
allow(new_options)
.to receive(:order)
- .with(:position)
- .and_return(new_options)
+ .with(:position)
+ .and_return(new_options)
allow(old_options)
.to receive(:pluck)
- .with(:id, :value)
- .and_return(old_custom_option_names)
+ .with(:id, :value)
+ .and_return(old_custom_option_names)
allow(new_options)
.to receive(:pluck)
- .with(:id, :value)
- .and_return(new_custom_option_names)
+ .with(:id, :value)
+ .and_return(new_custom_option_names)
end
describe "with both values being a comma separated list of ids" do
@@ -395,4 +397,78 @@ def url_helper = Rails.application.routes.url_helpers
end
end
end
+
+ context "for hierarchy custom field" do
+ shared_let(:custom_field) { create(:hierarchy_wp_custom_field) }
+ shared_let(:service) { CustomFields::Hierarchy::HierarchicalItemService.new }
+ shared_let(:root) { service.generate_root(custom_field).value! }
+ shared_let(:luke) { service.insert_item(parent: root, label: "luke", short: "LS").value! }
+ shared_let(:mara) { service.insert_item(parent: luke, label: "mara").value! }
+
+ describe "first value being nil and second value a string" do
+ let(:values) { [nil, mara.id.to_s] }
+ let(:formatted_value) { mara.ancestry_path }
+ let(:expected) do
+ I18n.t(:text_journal_set_to,
+ label: "#{custom_field.name}",
+ value: "#{formatted_value}")
+ end
+
+ it { expect(rendered).to be_html_eql(expected) }
+ end
+
+ describe "first value being a string and second value being nil" do
+ let(:values) { [mara.id.to_s, nil] }
+ let(:formatted_value) { mara.ancestry_path }
+ let(:expected) do
+ I18n.t(:text_journal_deleted,
+ label: "#{custom_field.name}",
+ old: "#{formatted_value}")
+ end
+
+ it { expect(rendered).to be_html_eql(expected) }
+ end
+
+ context "with multiple values" do
+ describe "first value being nil and second value a string" do
+ let(:values) { [nil, [luke.id, mara.id].join(",")] }
+ let(:formatted_value) { [luke.ancestry_path, mara.ancestry_path].join(", ") }
+ let(:expected) do
+ I18n.t(:text_journal_set_to,
+ label: "#{custom_field.name}",
+ value: "#{formatted_value}")
+ end
+
+ it { expect(rendered).to be_html_eql(expected) }
+ end
+
+ describe "first value being a string and second value being nil" do
+ let(:values) { [[luke.id, mara.id].join(","), nil] }
+ let(:formatted_value) { [luke.ancestry_path, mara.ancestry_path].join(", ") }
+ let(:expected) do
+ I18n.t(:text_journal_deleted,
+ label: "#{custom_field.name}",
+ old: "#{formatted_value}")
+ end
+
+ it { expect(rendered).to be_html_eql(expected) }
+ end
+
+ describe "both values being strings" do
+ let(:values) { [[luke.id, mara.id].join(","), luke.id.to_s] }
+ let(:original_value) { [luke.ancestry_path, mara.ancestry_path].join(", ") }
+ let(:formatted_value) { luke.ancestry_path }
+
+ let(:expected) do
+ I18n.t(:text_journal_changed_plain,
+ label: "#{custom_field.name}",
+ linebreak: "",
+ old: "#{original_value}",
+ new: "#{formatted_value}")
+ end
+
+ it { expect(rendered).to be_html_eql(expected) }
+ end
+ end
+ end
end