Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ See COPYRIGHT and LICENSE files for more details.
),
tabindex: 0
) do %>
<%= render(Backlogs::StoryComponent.new(story:, sprint:, max_position:)) %>
<%= render(Backlogs::StoryComponent.new(story:, project:, sprint:, max_position:)) %>
<% end %>
<% end %>
<% end %>
Expand Down
15 changes: 4 additions & 11 deletions modules/backlogs/app/components/backlogs/sprint_component.html.erb
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?)) %>
<% end %>
<% if stories.empty? %>
<% border_box.with_row(data: { empty_list_item: true }) do %>
Expand All @@ -45,18 +45,11 @@ See COPYRIGHT and LICENSE files for more details.
<% stories.each do |story| %>
<% border_box.with_row(
id: dom_id(story),
classes: "Box-row--hover-blue Box-row--focus-gray Box-row--clickable Box-row--draggable",
data: draggable_item_config(story).merge(
story: true,
controller: "backlogs--story",
backlogs__story_id_value: story.id,
backlogs__story_split_url_value: details_backlogs_project_backlogs_path(project, story),
backlogs__story_full_url_value: work_package_path(story),
backlogs__story_selected_class: "Box-row--blue"
),
classes: story_classes_attribute,
data: story_data_attribute(story),
tabindex: 0
) do %>
<%= render(Backlogs::StoryComponent.new(story:, sprint:, max_position:)) %>
<%= render(Backlogs::StoryComponent.new(story:, sprint:, project:, max_position:)) %>
<% end %>
<% end %>
<% end %>
Expand Down
44 changes: 38 additions & 6 deletions modules/backlogs/app/components/backlogs/sprint_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,13 @@ class SprintComponent < ApplicationComponent
include OpTurbo::Streamable
include RbCommonHelper

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

delegate :project, to: :sprint

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

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

@system_arguments = system_arguments
Expand All @@ -50,12 +49,13 @@ def initialize(sprint:, current_user: User.current, **system_arguments)
@system_arguments[:padding] = :condensed
@system_arguments[:data] = merge_data(
@system_arguments,
{ data: drop_target_config }
{ data: drop_target_config },
{ data: { test_selector: "sprint-#{sprint.id}" } }
)
end

def stories
sprint.work_packages
sprint.work_packages.where(project:).order(:position)
end

def wrapper_uniq_by
Expand All @@ -81,12 +81,44 @@ def drop_target_config
}
end

def story_classes_attribute
classes = "Box-row--hover-blue Box-row--focus-gray Box-row--clickable"

if work_package_draggable?
classes += " Box-row--draggable"
end

classes
end

def story_data_attribute(story)
draggable_item_config(story).merge(
story: true,
controller: "backlogs--story",
backlogs__story_id_value: story.id,
backlogs__story_split_url_value: details_backlogs_project_backlogs_path(project, story),
backlogs__story_full_url_value: work_package_path(story),
backlogs__story_selected_class: "Box-row--blue",
test_selector: card_test_selector(story)
)
end

def draggable_item_config(story)
return {} unless work_package_draggable?

{
draggable_id: story.id,
draggable_type: "story",
drop_url: move_project_sprint_story_path(project, sprint, story)
}
end

def card_test_selector(story)
"work-package-#{story.id}"
end

def work_package_draggable?
current_user.allowed_in_project?(:manage_sprint_items, project)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,20 @@ class SprintHeaderComponent < ApplicationComponent
include Redmine::I18n
include RbCommonHelper

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

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

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

@sprint = sprint
@project = project
@collapsed = folded
@current_user = current_user
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ See COPYRIGHT and LICENSE files for more details.

<%= grid_layout("op-backlogs-story", tag: :article) do |grid| %>
<% grid.with_area(:drag_handle, classes: "hide-when-print") do %>
<%=
render(
Primer::OpenProject::DragHandle.new(
classes: "op-backlogs-story--drag_handle_button",
label: t(".label_drag_story", name: story.subject)
)
)
%>
<%= if draggable?
render(
Primer::OpenProject::DragHandle.new(
classes: "op-backlogs-story--drag_handle_button",
label: t(".label_drag_story", name: story.subject)
)
)
end %>
<% end %>

<% grid.with_area(:info_line) do %>
Expand Down
9 changes: 7 additions & 2 deletions modules/backlogs/app/components/backlogs/story_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ module Backlogs
class StoryComponent < ApplicationComponent
include OpPrimer::ComponentHelpers

attr_reader :story, :sprint, :max_position, :current_user
attr_reader :story, :sprint, :project, :max_position, :current_user

def initialize(story:, sprint:, max_position:, current_user: User.current)
def initialize(story:, sprint:, project:, max_position:, current_user: User.current)
super()

@story = story
@sprint = sprint
@project = project
@max_position = max_position
@current_user = current_user
end
Expand All @@ -48,5 +49,9 @@ def initialize(story:, sprint:, max_position:, current_user: User.current)
def story_points
story.story_points || 0
end

def draggable?
current_user.allowed_in_project?(:manage_sprint_items, project)
end
Comment on lines +53 to +55
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Well spotted 👍

end
end
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ def update
end

def rebuild_positions
@project.rebuild_positions
if OpenProject::FeatureDecisions.scrum_projects_active?
WorkPackages::RebuildPositionsService.new(project: @project).call
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have a return value here that indicates success or failure. Right now, we always show the flash notice - even if this service call would fail.

This would additionally allow us to write a more unit-test-style spec for the service. But I don't see a way to do this easily, so this will remain a wish for now. Since this action is not expected to be called often, investing all this time seems to be not worth it.

else
@project.rebuild_positions
end
flash[:notice] = I18n.t("backlogs.positions_rebuilt_successfully")

redirect_to_backlogs_settings
Expand Down
3 changes: 2 additions & 1 deletion modules/backlogs/app/controllers/rb_sprints_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ def update_header_component_via_turbo_stream(state: :show)

def update_sprint_header_component_via_turbo_stream(sprint:)
update_via_turbo_stream(
component: Backlogs::SprintHeaderComponent.new(sprint:),
component: Backlogs::SprintHeaderComponent.new(sprint:,
project: @project),
method: :morph
)
end
Expand Down
6 changes: 3 additions & 3 deletions modules/backlogs/app/controllers/rb_stories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ class RbStoriesController < RbApplicationController

NEW_SPRINT_ACTIONS = %i[move].freeze

skip_before_action :load_sprint_and_project, only: NEW_SPRINT_ACTIONS
skip_before_action :load_sprint_and_project, :authorize, only: NEW_SPRINT_ACTIONS

before_action :legacy_load_story, except: NEW_SPRINT_ACTIONS
prepend_before_action :load_sprint, :load_project, :load_story, only: NEW_SPRINT_ACTIONS
before_action :load_project, :authorize, :load_sprint, :load_story, only: NEW_SPRINT_ACTIONS

# Move a story from a Sprint to another Sprint or an Agile::Sprint.
def move_legacy
Expand Down Expand Up @@ -184,7 +184,7 @@ def replace_backlog_component_via_turbo_stream(sprint:)
end

def replace_sprint_component_via_turbo_stream(sprint:)
replace_via_turbo_stream(component: Backlogs::SprintComponent.new(sprint: sprint))
replace_via_turbo_stream(component: Backlogs::SprintComponent.new(sprint: sprint, project: @project))
end

def legacy_load_story
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# 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.
# ++

class WorkPackages::RebuildPositionsService
def initialize(project: nil)
@project = project
end

def call
condition = if @project
::OpenProject::SqlSanitization.sanitize " AND work_packages.project_id = :project_id",
project_id: @project.id
end

WorkPackage.connection.execute <<~SQL.squish
UPDATE work_packages
SET position = mapping.new_position
FROM (
SELECT
id,
ROW_NUMBER() OVER (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL about ROW_NUMBER, very handy for this use case 💡

PARTITION BY project_id, sprint_id
ORDER BY position, created_at
) AS new_position
FROM work_packages
) AS mapping
WHERE work_packages.id = mapping.id
#{condition}
SQL
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ See COPYRIGHT and LICENSE files for more details.
<% else %>
<div class="op-backlogs-container" data-controller="generic-drag-and-drop">
<div id="sprint_backlogs_container" class="op-backlogs-lists">
<%= render(Backlogs::SprintComponent.with_collection(@sprints)) %>
<%= render(Backlogs::SprintComponent.with_collection(@sprints, project: @project)) %>
</div>
<div id="owner_backlogs_container" class="op-backlogs-lists">
<%= render(Backlogs::BacklogComponent.with_collection(@owner_backlogs, project: @project)) %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ See COPYRIGHT and LICENSE files for more details.
end
end %>

<%= render(Backlogs::SprintComponent.with_collection(@sprints)) %>
<%= render(Backlogs::SprintComponent.with_collection(@sprints, project: @project)) %>
</div>
</div>
<% end %>
Expand Down
Loading
Loading