Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
a1562bc
[#69139] Add SprintFilter for work package queries
myabc Mar 10, 2026
24702a9
Add sprint board permission dependencies
myabc Mar 11, 2026
014e419
Implement Agile::Sprint#board_name
myabc Mar 11, 2026
cd0587b
Add polymorphic board linkage
myabc Mar 11, 2026
acc9284
[#69139] Add sprint task board creation
myabc Mar 9, 2026
fe08927
Align sprint and backlog menu helpers
myabc Mar 13, 2026
c22c90b
[#72942] Add Start sprint button
myabc Mar 13, 2026
18c8bef
Disable Start sprint for active sprint overlap
myabc Mar 13, 2026
a83f0e8
Add Finish sprint flow for symmetry
myabc Mar 13, 2026
bf86c27
Simplify sprint component assumptions
myabc Mar 13, 2026
9de770b
Add sprint-specific success notices
myabc Mar 13, 2026
411cc24
Fix sprint filter schema dependency
myabc Mar 13, 2026
40ac7eb
[#73137] Add active sprint constraint
myabc Mar 13, 2026
45ef125
Use enum scope in sprint validation
myabc Mar 13, 2026
71ce954
Move scrum guards into routes
myabc Mar 13, 2026
e3cbf33
Split sprint start/finish feature specs
myabc Mar 17, 2026
772fae7
Flesh out sprint start feature spec
myabc Mar 17, 2026
4858712
Merge pull request #22347 from opf/impl/73137-active-sprint-per-proje…
myabc Mar 18, 2026
570651f
Merge branch 'dev' into feature/69139-sprint-task-boards
myabc Mar 18, 2026
5f1232a
Simplify sprint component spec guards
myabc Mar 19, 2026
64562ca
Merge pull request #22348 from opf/code-maintenance/scrum-projects-ro…
myabc Mar 19, 2026
af72113
Align sprint boards with shared sprints
myabc Mar 19, 2026
66d8c8a
Add missing :aggregate_failures in controller spec
myabc Mar 19, 2026
68d1b2c
Merge branch 'dev' into feature/69139-sprint-task-boards
myabc Mar 20, 2026
28ffe98
Derive sprint board columns dynamically
myabc Mar 22, 2026
0cdc5f9
Merge branch 'dev' into feature/69139-sprint-task-boards
myabc Mar 22, 2026
19d12d3
Aggregate query creation failures
myabc Mar 22, 2026
089bbed
Use POST for sprint start and finish actions
myabc Mar 22, 2026
81f0b66
Fix Rubocop ABC size in before_perform
myabc Mar 22, 2026
8756818
Fix taskboard controller spec expectation
myabc Mar 22, 2026
b198cb0
Add StartContract for sprint start validation
myabc Mar 22, 2026
fcc1793
Add receiving_projects and boards
myabc Mar 22, 2026
c7a5e48
Remove redundant load_project for start/finish
myabc Mar 22, 2026
daf0e11
Remove unused board error locale
myabc Mar 22, 2026
e6a6ba0
Preserve status column order from previous board
myabc Mar 22, 2026
5d012d6
Avoid sprint menu active-sprint N+1
myabc Mar 22, 2026
aa93458
DRY up start/finish failure response handling
myabc Mar 22, 2026
524240c
Merge remote-tracking branch 'origin/dev' into feature/69139-sprint-t…
myabc Mar 23, 2026
600922d
Use SprintPlanning page object in start_finish_spec
myabc Mar 23, 2026
a9d7d52
Move sprint error locales to backlogs module
myabc Mar 23, 2026
4a1441a
Remove project default from SprintHeaderComponent
myabc Mar 23, 2026
2e73acd
Remove .active filter from shared_receiver_projects
myabc Mar 23, 2026
10ca498
Simplify ensure_task_boards with add_dependent!
myabc Mar 23, 2026
2d7780d
Move active_sprint_ids computation to controller
myabc Mar 23, 2026
c6e175d
Replace Task.type fallback with project types
myabc Mar 23, 2026
3ebec53
Authorize shared sprint start and finish
myabc Mar 23, 2026
a7662cd
Use active scope in sprint menu
myabc Mar 23, 2026
c1ef3ac
Use CTE in shared_receiver_projects
myabc Mar 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2002,7 +2002,6 @@ en:
not_editable: "cannot be edited because it is already in effect."
not_current_user: "is not the current user."
system_wide_non_working_day_exists: "conflicts with an existing system-wide non-working day for this date."
only_one_active_sprint_allowed: "only one active sprint is allowed per project."
not_found: "not found."
not_a_date: "is not a valid date."
not_a_datetime: "is not a valid date time."
Expand Down
47 changes: 47 additions & 0 deletions lib/api/v3/queries/schemas/sprint_filter_dependency_representer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

module API
module V3
module Queries
module Schemas
class SprintFilterDependencyRepresenter < FilterDependencyRepresenter
def href_callback; end

private

def type
"[]Integer"
end
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= component_wrapper(tag: :section) do %>
<%= render(Primer::Beta::BorderBox.new(**@system_arguments)) do |border_box| %>
<% border_box.with_header(id: dom_target(sprint, :header)) do %>
<%= render(Backlogs::SprintHeaderComponent.new(sprint:, folded: folded?)) %>
<%= render(Backlogs::SprintHeaderComponent.new(sprint:, project:, folded: folded?, active_sprint_ids:)) %>
<% end %>
<% if stories.empty? %>
<% border_box.with_row(data: { empty_list_item: true }) do %>
Expand Down
8 changes: 4 additions & 4 deletions modules/backlogs/app/components/backlogs/sprint_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ class SprintComponent < ApplicationComponent
include OpTurbo::Streamable
include RbCommonHelper

attr_reader :sprint, :current_user
attr_reader :sprint, :project, :current_user, :active_sprint_ids

delegate :project, to: :sprint

def initialize(sprint:, current_user: User.current, **system_arguments)
def initialize(sprint:, project: sprint.project, current_user: User.current, active_sprint_ids: nil, **system_arguments)
super()

@sprint = sprint
@project = project
@current_user = current_user
@active_sprint_ids = active_sprint_ids

@system_arguments = system_arguments
@system_arguments[:id] = dom_id(sprint)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ See COPYRIGHT and LICENSE files for more details.
<% end %>

<% grid.with_area(:menu) do %>
<%= render(Backlogs::SprintMenuComponent.new(sprint:, project:)) %>
<%= render(Backlogs::SprintMenuComponent.new(sprint:, project:, active_sprint_ids:)) %>
<% end %>
<% end %>
<% end %>
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,24 @@ class SprintHeaderComponent < ApplicationComponent
include Redmine::I18n
include RbCommonHelper

attr_reader :sprint, :collapsed, :current_user
attr_reader :sprint, :project, :collapsed, :current_user, :active_sprint_ids

delegate :project, to: :sprint
delegate :name, to: :sprint, prefix: :sprint

def initialize(
sprint:,
project:,
folded: false,
current_user: User.current
current_user: User.current,
active_sprint_ids: nil
)
super()

@sprint = sprint
@project = project
@collapsed = folded
@current_user = current_user
@active_sprint_ids = active_sprint_ids
end

def wrapper_uniq_by
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,36 @@ See COPYRIGHT and LICENSE files for more details.
tooltip_direction: :se
)

if show_start_sprint_action?
menu.with_item(
label: t(".action_menu.start_sprint"),
tag: :button,
href: start_project_sprint_path(project, sprint),
disabled: disable_start_sprint_action?,
form_arguments: {
method: :post,
data: { turbo: false }
}
) do |item|
item.with_leading_visual_icon(icon: :play)
item.with_description(start_sprint_action_description) if start_sprint_action_description.present?
end
end

if show_finish_sprint_action?
menu.with_item(
label: t(".action_menu.finish_sprint"),
tag: :button,
href: finish_project_sprint_path(project, sprint),
form_arguments: {
method: :post,
data: { turbo: false }
}
) do |item|
item.with_leading_visual_icon(icon: :check)
end
end

if user_allowed?(:create_sprints)
menu.with_item(
id: dom_target(sprint, :menu, :edit_sprint),
Expand Down Expand Up @@ -66,15 +96,16 @@ See COPYRIGHT and LICENSE files for more details.
# menu.with_divider
# end

# menu.with_item(
# # TODO: what to do with the task board?
# label: t(".action_menu.task_board"),
# tag: :a,
# href: backlogs_project_sprint_taskboard_path(project, sprint)
# ) do |item|
# item.with_leading_visual_icon(icon: :"op-view-cards")
# end
#
if show_task_board_link?
menu.with_item(
label: t(".action_menu.task_board"),
tag: :a,
href: backlogs_project_sprint_taskboard_path(project, sprint)
) do |item|
item.with_leading_visual_icon(icon: :"op-view-cards")
end
end

# menu.with_item(
# # TODO: what to do with the burndown chart?
# label: t(".action_menu.burndown_chart"),
Expand Down
35 changes: 33 additions & 2 deletions modules/backlogs/app/components/backlogs/sprint_menu_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ module Backlogs
class SprintMenuComponent < ApplicationComponent
include RbCommonHelper

attr_reader :sprint, :project, :current_user
attr_reader :sprint, :project, :current_user, :active_sprint_ids

def initialize(sprint:, project:, current_user: User.current, **system_arguments)
def initialize(sprint:, project:, current_user: User.current, active_sprint_ids: nil, **system_arguments)
super()

@sprint = sprint
@project = project
@current_user = current_user
@active_sprint_ids = active_sprint_ids

@system_arguments = system_arguments
@system_arguments[:menu_id] = dom_target(sprint, :menu)
Expand All @@ -56,12 +57,42 @@ def stories

private

def show_task_board_link?
sprint.task_board_for(project).present?
end

def show_start_sprint_action?
sprint.in_planning? && ::Sprints::StartContract.can_start?(user: current_user, sprint:, project:)
end

def show_finish_sprint_action?
sprint.active? && ::Sprints::StartContract.can_start_or_finish?(user: current_user, sprint:)
end

def disable_start_sprint_action?
sprint.in_planning? && project_has_another_active_sprint?
end

def start_sprint_action_description
return unless disable_start_sprint_action?

t(".action_menu.start_sprint_disabled_description")
end

def user_allowed?(permission)
current_user.allowed_in_project?(permission, project)
end

def available_story_types
@available_story_types ||= story_types & project.types
end

def project_has_another_active_sprint?
(resolved_active_sprint_ids - [sprint.id]).any?
end

def resolved_active_sprint_ids
active_sprint_ids || Agile::Sprint.for_project(sprint.project).active.pluck(:id)
end
end
end
67 changes: 67 additions & 0 deletions modules/backlogs/app/contracts/sprints/start_contract.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

module Sprints
class StartContract < ::BaseContract
validate :validate_permission
validate :validate_status_in_planning
validate :validate_no_other_active_sprint

def self.can_start_or_finish?(user:, sprint:)
user.allowed_in_project?(:start_complete_sprint, sprint.project)
end

def self.can_start?(user:, sprint:, project:)
can_start_or_finish?(user:, sprint:) &&
user.allowed_in_project?(:show_board_views, project)
end

private

def validate_permission
return if self.class.can_start_or_finish?(user:, sprint: model)

errors.add :base, :error_unauthorized
end

def validate_status_in_planning
return if model.in_planning?

errors.add :status, :must_be_in_planning
end

def validate_no_other_active_sprint
return unless model.in_planning?
return unless Agile::Sprint.where(project: model.project).active.where.not(id: model.id).exists?

errors.add :status, :only_one_active_sprint_allowed
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@
class Projects::Settings::BacklogSharingsController < Projects::SettingsController
menu_item :settings_backlogs

before_action :check_scrum_projects_feature_flag

def show; end

def update
Expand All @@ -51,10 +49,6 @@ def update

private

def check_scrum_projects_feature_flag
render_404 unless OpenProject::FeatureDecisions.scrum_projects_active?
end

def backlog_settings_params
params.expect(project: %i[sprint_sharing])
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def load_backlogs

if OpenProject::FeatureDecisions.scrum_projects_active?
@sprints = Agile::Sprint.for_project(@project).not_completed.order_by_date
@active_sprint_ids = @sprints.select(&:active?).map(&:id)
else
@sprint_backlogs = Backlog.sprint_backlogs(@project)
end
Expand Down
Loading
Loading