Skip to content

Commit d457547

Browse files
authored
Merge pull request #22373 from opf/implementation/71251-migrate-versions-to-sprints
[#71251] Migrate Versions to Sprints
2 parents 7014e13 + 63f81bb commit d457547

File tree

15 files changed

+609
-38
lines changed

15 files changed

+609
-38
lines changed

app/models/journal/caused_by_system_update.rb

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@
2929
#++
3030
#
3131
class Journal::CausedBySystemUpdate < CauseOfChange::Base
32-
def initialize(feature:)
33-
additional = {
34-
"feature" => feature
35-
}
36-
super("system_update", additional)
32+
def initialize(feature:, **additional_attributes)
33+
system_update_attributes =
34+
{ "feature" => feature }.merge(additional_attributes.deep_stringify_keys)
35+
super("system_update", system_update_attributes)
3736
end
3837
end

config/locales/en.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3501,6 +3501,7 @@ en:
35013501
totals_removed_from_childless_work_packages: >-
35023502
Work and progress totals automatically removed for non-parent work packages with <a href="%{href}" target="_blank">version update</a>.
35033503
This is a maintenance task and can be safely ignored.
3504+
sprint_migration: "Version '%{version_name}' has been copied as a sprint."
35043505
total_percent_complete_mode_changed_to_work_weighted_average: >-
35053506
Child work packages without Work are ignored.
35063507
total_percent_complete_mode_changed_to_simple_average: >-

lib/open_project/journal_formatter/cause.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ def system_update_message
101101
{ href: OpenProject::Static::Links.url_for(:blog_article_progress_changes) }
102102
when "totals_removed_from_childless_work_packages"
103103
{ href: OpenProject::Static::Links.url_for(:release_notes_14_0_1) }
104+
when "sprint_migration"
105+
{ version_name: ERB::Util.html_escape(cause["version_name"]) }
104106
else
105107
{}
106108
end

modules/backlogs/app/forms/backlogs/sprints/dates_form.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@
3131
module Backlogs
3232
module Sprints
3333
class DatesForm < ApplicationForm
34+
delegate :active?, to: :model
35+
3436
form do |f|
3537
f.group(layout: :horizontal) do |dates|
3638
dates.text_field(
3739
name: :start_date,
3840
type: :date,
3941
label: attribute_name(:start_date),
4042
placeholder: attribute_name(:start_date),
41-
required: true,
43+
required: active?,
4244
input_width: :small,
4345
data: {
4446
action: "change->refresh-on-form-changes#triggerTurboStream"
@@ -49,7 +51,7 @@ class DatesForm < ApplicationForm
4951
type: :date,
5052
label: attribute_name(:finish_date),
5153
placeholder: attribute_name(:finish_date),
52-
required: true,
54+
required: active?,
5355
input_width: :small,
5456
data: {
5557
action: "change->refresh-on-form-changes#triggerTurboStream"

modules/backlogs/app/models/agile/sprint.rb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,11 @@ class Sprint < ApplicationRecord
6060
default: "in_planning",
6161
validate: true
6262

63-
validates :name, presence: true
64-
validates :project, presence: true
65-
validates :start_date, presence: true
66-
validates :finish_date, presence: true
63+
validates :name, :project, presence: true
64+
validates :start_date, :finish_date, presence: true, if: :active?
6765
validates :finish_date,
6866
comparison: { greater_than_or_equal_to: :start_date },
69-
if: :start_date?
67+
if: -> { start_date? && finish_date? }
7068
validates :status,
7169
uniqueness: {
7270
scope: :project_id,
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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+
# Writes a journal entry on each work package that was associated with a sprint
32+
# during the version-to-sprint migration. Runs asynchronously after the migration
33+
# so that the migration itself does not block on journal creation.
34+
module Backlogs
35+
class MigrateVersionSprintJournalsJob < ApplicationJob
36+
def perform
37+
system_user = User.system
38+
39+
Journal::NotificationConfiguration.with(false) do
40+
WorkPackage.joins(:sprint, :version)
41+
.select("work_packages.*, versions.name AS version_name")
42+
.find_each do |work_package|
43+
cause = Journal::CausedBySystemUpdate.new(
44+
feature: "sprint_migration",
45+
version_name: work_package.version_name
46+
)
47+
Journals::CreateService
48+
.new(work_package, system_user)
49+
.call(cause:)
50+
end
51+
end
52+
end
53+
end
54+
end
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 MakeSprintDatesNullable < ActiveRecord::Migration[8.0]
32+
def change
33+
change_column_null :sprints, :start_date, true
34+
change_column_null :sprints, :finish_date, true
35+
end
36+
end
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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 MigrateVersionsToSprints < ActiveRecord::Migration[8.0]
32+
def up
33+
return if sprint_versions_with_work_package_ids.none?
34+
35+
sprint_versions_with_work_package_ids.find_each do |version|
36+
sprint = create_sprint(version)
37+
migrate_work_packages_to_sprint(sprint, version.wp_ids)
38+
end
39+
40+
Backlogs::MigrateVersionSprintJournalsJob.perform_later
41+
end
42+
43+
def down
44+
raise ActiveRecord::IrreversibleMigration
45+
end
46+
47+
private
48+
49+
def sprint_versions_with_work_package_ids
50+
# Load versions used as sprints including work package ids associated with the version.
51+
# Since the same version can be used as a sprint in one project but not in another,
52+
# the work package ids are filtered by projects where the version is used as a sprint.
53+
Version.joins(:version_settings, :work_packages)
54+
.where(version_settings: { display: VersionSetting::DISPLAY_LEFT })
55+
.where("work_packages.project_id = version_settings.project_id")
56+
.group("versions.id")
57+
.select("versions.*, array_agg(DISTINCT work_packages.id) AS wp_ids")
58+
end
59+
60+
def create_sprint(version)
61+
Agile::Sprint.create!(
62+
name: version.name,
63+
project_id: version.project_id,
64+
status: version.status == "open" ? "in_planning" : "completed",
65+
start_date: version.start_date,
66+
finish_date: version.effective_date
67+
)
68+
end
69+
70+
def migrate_work_packages_to_sprint(sprint, wp_ids)
71+
WorkPackage.where(id: wp_ids).update_all(sprint_id: sprint.id)
72+
end
73+
end

modules/backlogs/spec/contracts/sprints/create_contract_spec.rb

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,13 @@
8989
context "when start_date is blank" do
9090
let(:sprint_start_date) { nil }
9191

92-
it_behaves_like "contract is invalid", start_date: :blank
92+
it_behaves_like "contract is valid"
9393
end
9494

9595
context "when finish_date is blank" do
9696
let(:sprint_finish_date) { nil }
9797

98-
it_behaves_like "contract is invalid", finish_date: %i[blank blank]
98+
it_behaves_like "contract is valid"
9999
end
100100

101101
context "when finish_date is before start_date" do
@@ -105,6 +105,29 @@
105105
it_behaves_like "contract is invalid", finish_date: %i[greater_than_or_equal_to]
106106
end
107107

108+
context "when the sprint is active" do
109+
let(:sprint_status) { "active" }
110+
111+
context "when start_date is blank" do
112+
let(:sprint_start_date) { nil }
113+
114+
it_behaves_like "contract is invalid", start_date: :blank
115+
end
116+
117+
context "when finish_date is blank" do
118+
let(:sprint_finish_date) { nil }
119+
120+
it_behaves_like "contract is invalid", finish_date: :blank
121+
end
122+
123+
context "when finish_date is before start_date" do
124+
let(:sprint_start_date) { Time.zone.today }
125+
let(:sprint_finish_date) { Time.zone.today - 1.day }
126+
127+
it_behaves_like "contract is invalid", finish_date: %i[greater_than_or_equal_to]
128+
end
129+
end
130+
108131
context "when user is admin without project permission" do
109132
let(:user) { build_stubbed(:admin) }
110133
let(:permissions) { [] }

modules/backlogs/spec/features/sprints/create_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,8 @@
123123
click_on "Create"
124124

125125
expect(page).to have_field "Sprint name", validation_error: "can't be blank"
126-
expect(page).to have_field "Start date", validation_error: "can't be blank"
127-
expect(page).to have_field "Finish date", validation_error: "can't be blank"
126+
expect(page).to have_field "Start date", validation_error: false
127+
expect(page).to have_field "Finish date", validation_error: false
128128
end
129129
end
130130

0 commit comments

Comments
 (0)