Skip to content

Commit 6b70210

Browse files
authored
Merge pull request #18852 from opf/implementation/62983-project-phase-journalized-for-work-packages
have project phase changes show up on wp activity
2 parents b10971e + f84e9c5 commit 6b70210

File tree

7 files changed

+337
-13
lines changed

7 files changed

+337
-13
lines changed

app/models/journal.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class Journal < ApplicationRecord
5858
register_journal_formatter OpenProject::JournalFormatter::MeetingWorkPackageId
5959
register_journal_formatter OpenProject::JournalFormatter::ProjectPhaseActive
6060
register_journal_formatter OpenProject::JournalFormatter::ProjectPhaseDates
61+
register_journal_formatter OpenProject::JournalFormatter::ProjectPhaseDefinition
6162
register_journal_formatter OpenProject::JournalFormatter::ProjectStatusCode
6263
register_journal_formatter OpenProject::JournalFormatter::ScheduleManually
6364
register_journal_formatter OpenProject::JournalFormatter::SubprojectNamedAssociation

app/models/work_package/journalized.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ def self.event_url
100100
register_journal_formatted_fields "ignore_non_working_days", formatter_key: :ignore_non_working_days
101101
register_journal_formatted_fields "cause", formatter_key: :cause
102102
register_journal_formatted_fields /file_links_?\d+/, formatter_key: :file_link
103+
register_journal_formatted_fields "project_phase_definition_id", formatter_key: :project_phase_definition
103104

104105
# Joined
105106
register_journal_formatted_fields :parent_id, :project_id,

config/locales/en.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1673,12 +1673,13 @@ en:
16731673
work_package: "Work packages"
16741674
project_phase:
16751675
activated: "activated"
1676-
deactivated: "deactivated"
16771676
added_date: "set to %{date}"
16781677
changed_date: "changed from %{from} to %{to}"
1679-
removed_date: "date deleted %{date}"
1680-
phase_and_one_gate: "%{phase_message}. %{gate_message}"
1678+
deactivated: "deactivated"
1679+
deleted_project_phase: "Deleted project phase"
16811680
phase_and_both_gates: "%{phase_message}. %{start_gate_message}, and %{finish_gate_message}"
1681+
phase_and_one_gate: "%{phase_message}. %{gate_message}"
1682+
removed_date: "date deleted %{date}"
16821683

16831684
# common attributes of all models
16841685
attributes:
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
class OpenProject::JournalFormatter::ProjectPhaseDefinition < JournalFormatter::NamedAssociation
32+
private
33+
34+
def associated_object_name(object)
35+
if object.nil?
36+
I18n.t(:"activity.project_phase.deleted_project_phase")
37+
elsif phase_active?(object)
38+
super
39+
else
40+
"#{super} (#{I18n.t(:label_inactive)})"
41+
end
42+
end
43+
44+
def phase_active?(definition)
45+
@journal
46+
.journable
47+
.project
48+
.phases
49+
.detect { |phase| phase.definition_id == definition.id }
50+
&.active
51+
end
52+
end

lib_static/plugins/acts_as_journalized/lib/journal_formatter/named_association.rb

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,17 @@ def format_values(values, key, cache:)
5353
klass = class_from_field(key)
5454

5555
values.map do |value|
56-
if klass && value
57-
record = associated_object(klass, value.to_i, cache:)
58-
if record
59-
if record.respond_to? :name
60-
record.name
61-
else
62-
record.subject
63-
end
64-
end
65-
end
56+
next unless klass && value
57+
58+
record = associated_object(klass, value.to_i, cache:)
59+
associated_object_name(record)
6660
end
6761
end
6862

63+
def associated_object_name(object)
64+
object&.name
65+
end
66+
6967
def associated_object(klass, id, cache:)
7068
if cache
7169
cache.fetch(klass, id) do

spec/features/work_packages/project_phases/linking_spec.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
current_user { user }
5151

5252
let(:work_package_page) { Pages::FullWorkPackage.new(work_package) }
53+
let(:activity_tab) { Components::WorkPackages::Activities.new(work_package) }
5354

5455
it "allows seeing and editing linked phases" do
5556
work_package_page.visit!
@@ -58,6 +59,12 @@
5859

5960
work_package_page.set_attributes({ projectPhase: initiating_phase_definition.name })
6061

62+
activity_tab.within_journal_entry(work_package.journals.last) do
63+
activity_tab.expect_journal_changed_attribute(
64+
text: "Project phase changed from #{executing_phase_definition.name} to #{initiating_phase_definition.name}"
65+
)
66+
end
67+
6168
work_package_page.expect_and_dismiss_toaster(message: "Successful update.")
6269

6370
work_package_page.expect_attributes(project_phase: initiating_phase_definition.name)
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
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 OpenProject::JournalFormatter::ProjectPhaseDefinition do
34+
describe "#render" do
35+
let(:project_phase) { build_stubbed(:project_phase) }
36+
let(:other_project_phase) { build_stubbed(:project_phase) }
37+
let(:project_phases) { [project_phase, other_project_phase].compact }
38+
let(:project) { build_stubbed(:project, phases: project_phases) }
39+
let(:work_package) { build_stubbed(:work_package, project:) }
40+
let(:journal) { build_stubbed(:work_package_journal, journable: work_package) }
41+
let(:instance) { described_class.new(journal) }
42+
43+
before do
44+
allow(Project::PhaseDefinition)
45+
.to receive(:find_by)
46+
.and_return(nil)
47+
48+
project_phases.each do |phase|
49+
allow(Project::PhaseDefinition)
50+
.to receive(:find_by)
51+
.with(id: phase.definition_id)
52+
.and_return(phase.definition)
53+
end
54+
end
55+
56+
shared_examples_for "renders project phase definition change" do
57+
it { expect(instance.render(:project_phase_definition, [old_value, new_value])).to eq(expected) }
58+
end
59+
60+
context "when setting an active phase" do
61+
let(:old_value) { nil }
62+
let(:new_value) { project_phase.definition_id.to_s }
63+
let(:expected) do
64+
I18n.t(:text_journal_set_to,
65+
label: "<strong>Project phase</strong>",
66+
value: "<i>#{project_phase.name}</i>")
67+
end
68+
69+
it_behaves_like "renders project phase definition change"
70+
end
71+
72+
context "when changing between two active phases" do
73+
let(:old_value) { other_project_phase.definition_id.to_s }
74+
let(:new_value) { project_phase.definition_id.to_s }
75+
let(:expected) do
76+
I18n.t(:text_journal_changed_plain,
77+
label: "<strong>Project phase</strong>",
78+
linebreak: nil,
79+
old: "<i>#{other_project_phase.name}</i>",
80+
new: "<i>#{project_phase.name}</i>")
81+
end
82+
83+
it_behaves_like "renders project phase definition change"
84+
end
85+
86+
context "when deleting an active phase" do
87+
let(:old_value) { project_phase.definition_id.to_s }
88+
let(:new_value) { nil }
89+
let(:expected) do
90+
I18n.t(:text_journal_deleted,
91+
label: "<strong>Project phase</strong>",
92+
old: "<strike><i>#{project_phase.name}</i></strike>")
93+
end
94+
95+
it_behaves_like "renders project phase definition change"
96+
end
97+
98+
context "when setting an inactive phase" do
99+
let(:project_phase) { build_stubbed(:project_phase, active: false) }
100+
let(:old_value) { nil }
101+
let(:new_value) { project_phase.definition_id.to_s }
102+
let(:expected) do
103+
I18n.t(:text_journal_set_to,
104+
label: "<strong>Project phase</strong>",
105+
value: "<i>#{project_phase.name} (Inactive)</i>")
106+
end
107+
108+
it_behaves_like "renders project phase definition change"
109+
end
110+
111+
context "when changing between two inactive phases" do
112+
let(:project_phase) { build_stubbed(:project_phase, active: false) }
113+
let(:other_project_phase) { build_stubbed(:project_phase, active: false) }
114+
let(:old_value) { other_project_phase.definition_id.to_s }
115+
let(:new_value) { project_phase.definition_id.to_s }
116+
let(:expected) do
117+
I18n.t(:text_journal_changed_plain,
118+
label: "<strong>Project phase</strong>",
119+
linebreak: nil,
120+
old: "<i>#{other_project_phase.name} (Inactive)</i>",
121+
new: "<i>#{project_phase.name} (Inactive)</i>")
122+
end
123+
124+
it_behaves_like "renders project phase definition change"
125+
end
126+
127+
context "when deleting an inactive phase" do
128+
let(:project_phase) { build_stubbed(:project_phase, active: false) }
129+
let(:old_value) { project_phase.definition_id.to_s }
130+
let(:new_value) { nil }
131+
let(:expected) do
132+
I18n.t(:text_journal_deleted,
133+
label: "<strong>Project phase</strong>",
134+
old: "<strike><i>#{project_phase.name} (Inactive)</i></strike>")
135+
end
136+
137+
it_behaves_like "renders project phase definition change"
138+
end
139+
140+
context "when setting a phase not configured for the project" do
141+
let(:project) { build_stubbed(:project, phases: []) }
142+
let(:old_value) { nil }
143+
let(:new_value) { project_phase.definition_id.to_s }
144+
let(:expected) do
145+
I18n.t(:text_journal_set_to,
146+
label: "<strong>Project phase</strong>",
147+
value: "<i>#{project_phase.name} (Inactive)</i>")
148+
end
149+
150+
it_behaves_like "renders project phase definition change"
151+
end
152+
153+
context "when changing between two phases not configured for the project" do
154+
let(:project) { build_stubbed(:project, phases: []) }
155+
let(:old_value) { other_project_phase.definition_id.to_s }
156+
let(:new_value) { project_phase.definition_id.to_s }
157+
let(:expected) do
158+
I18n.t(:text_journal_changed_plain,
159+
label: "<strong>Project phase</strong>",
160+
linebreak: nil,
161+
old: "<i>#{other_project_phase.name} (Inactive)</i>",
162+
new: "<i>#{project_phase.name} (Inactive)</i>")
163+
end
164+
165+
it_behaves_like "renders project phase definition change"
166+
end
167+
168+
context "when deleting a phase not configured for the project" do
169+
let(:project) { build_stubbed(:project, phases: []) }
170+
let(:old_value) { project_phase.definition_id.to_s }
171+
let(:new_value) { nil }
172+
let(:expected) do
173+
I18n.t(:text_journal_deleted,
174+
label: "<strong>Project phase</strong>",
175+
old: "<strike><i>#{project_phase.name} (Inactive)</i></strike>")
176+
end
177+
178+
it_behaves_like "renders project phase definition change"
179+
end
180+
181+
context "when setting a phase whose definition is deleted" do
182+
let(:old_value) { nil }
183+
let(:new_value) { "-1" }
184+
let(:expected) do
185+
I18n.t(:text_journal_set_to,
186+
label: "<strong>Project phase</strong>",
187+
value: "<i>#{I18n.t(:"activity.project_phase.deleted_project_phase")}</i>")
188+
end
189+
190+
it_behaves_like "renders project phase definition change"
191+
end
192+
193+
context "when changing between two phases whose definition is deleted" do
194+
let(:old_value) { "-1" }
195+
let(:new_value) { "-2" }
196+
let(:expected) do
197+
I18n.t(:text_journal_changed_plain,
198+
label: "<strong>Project phase</strong>",
199+
linebreak: nil,
200+
old: "<i>#{I18n.t(:"activity.project_phase.deleted_project_phase")}</i>",
201+
new: "<i>#{I18n.t(:"activity.project_phase.deleted_project_phase")}</i>")
202+
end
203+
204+
it_behaves_like "renders project phase definition change"
205+
end
206+
207+
context "when deleting a phase whose definition is deleted" do
208+
let(:old_value) { "-1" }
209+
let(:new_value) { nil }
210+
let(:expected) do
211+
I18n.t(:text_journal_deleted,
212+
label: "<strong>Project phase</strong>",
213+
old: "<strike><i>#{I18n.t(:"activity.project_phase.deleted_project_phase")}</i></strike>")
214+
end
215+
216+
it_behaves_like "renders project phase definition change"
217+
end
218+
219+
context "when changing between an active and an inactive phase" do
220+
let(:project_phase) { build_stubbed(:project_phase, active: true) }
221+
let(:other_project_phase) { build_stubbed(:project_phase, active: false) }
222+
let(:old_value) { other_project_phase.definition_id.to_s }
223+
let(:new_value) { project_phase.definition_id.to_s }
224+
let(:expected) do
225+
I18n.t(:text_journal_changed_plain,
226+
label: "<strong>Project phase</strong>",
227+
linebreak: nil,
228+
old: "<i>#{other_project_phase.name} (Inactive)</i>",
229+
new: "<i>#{project_phase.name}</i>")
230+
end
231+
232+
it_behaves_like "renders project phase definition change"
233+
end
234+
235+
context "when changing between an active and a phase not configured in the project" do
236+
let(:project) { build_stubbed(:project, phases: [other_project_phase]) }
237+
let(:old_value) { other_project_phase.definition_id.to_s }
238+
let(:new_value) { project_phase.definition_id.to_s }
239+
let(:expected) do
240+
I18n.t(:text_journal_changed_plain,
241+
label: "<strong>Project phase</strong>",
242+
linebreak: nil,
243+
old: "<i>#{other_project_phase.name}</i>",
244+
new: "<i>#{project_phase.name} (Inactive)</i>")
245+
end
246+
247+
it_behaves_like "renders project phase definition change"
248+
end
249+
250+
context "when changing between an active and a deleted phase" do
251+
let(:old_value) { other_project_phase.definition_id.to_s }
252+
let(:new_value) { "-1" }
253+
let(:expected) do
254+
I18n.t(:text_journal_changed_plain,
255+
label: "<strong>Project phase</strong>",
256+
linebreak: nil,
257+
old: "<i>#{other_project_phase.name}</i>",
258+
new: "<i>#{I18n.t(:"activity.project_phase.deleted_project_phase")}</i>")
259+
end
260+
261+
it_behaves_like "renders project phase definition change"
262+
end
263+
end
264+
end

0 commit comments

Comments
 (0)