Skip to content

Commit 601636f

Browse files
committed
[#72942] Add explicit sprint task board creation
Split sprint task board access into explicit create and read paths. - add POST taskboard route and controller action - stop lazy board creation from taskboard show - restore sprint menu task board actions with start sprint state - update focused controller, routing, component, and service specs https://community.openproject.org/wp/72942
1 parent d4ab5b6 commit 601636f

File tree

12 files changed

+302
-103
lines changed

12 files changed

+302
-103
lines changed

modules/backlogs/app/components/backlogs/sprint_menu_component.html.erb

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,17 @@ See COPYRIGHT and LICENSE files for more details.
3636
tooltip_direction: :se
3737
)
3838

39+
if show_start_sprint_action?
40+
menu.with_item(
41+
label: t(".action_menu.start_sprint"),
42+
tag: :button,
43+
href: backlogs_project_sprint_taskboard_path(project, sprint),
44+
form_arguments: { method: :post }
45+
) do |item|
46+
item.with_leading_visual_icon(icon: :play)
47+
end
48+
end
49+
3950
if user_allowed?(:create_sprints)
4051
menu.with_item(
4152
label: t(".action_menu.edit_sprint"),
@@ -74,15 +85,16 @@ See COPYRIGHT and LICENSE files for more details.
7485
item.with_leading_visual_icon(icon: :"op-view-list")
7586
end
7687

77-
# menu.with_item(
78-
# # TODO: what to do with the task board?
79-
# label: t(".action_menu.task_board"),
80-
# tag: :a,
81-
# href: backlogs_project_sprint_taskboard_path(project, sprint)
82-
# ) do |item|
83-
# item.with_leading_visual_icon(icon: :"op-view-cards")
84-
# end
85-
#
88+
if show_task_board_link?
89+
menu.with_item(
90+
label: t(".action_menu.task_board"),
91+
tag: :a,
92+
href: backlogs_project_sprint_taskboard_path(project, sprint)
93+
) do |item|
94+
item.with_leading_visual_icon(icon: :"op-view-cards")
95+
end
96+
end
97+
8698
# menu.with_item(
8799
# # TODO: what to do with the burndown chart?
88100
# label: t(".action_menu.burndown_chart"),

modules/backlogs/app/components/backlogs/sprint_menu_component.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,22 @@ def stories
4848

4949
private
5050

51+
def scrum_projects_active?
52+
OpenProject::FeatureDecisions.scrum_projects_active?
53+
end
54+
55+
def task_board
56+
@task_board ||= Boards::SprintTaskBoardCreateService.find(project:, sprint:)
57+
end
58+
59+
def show_task_board_link?
60+
!scrum_projects_active? || task_board.present?
61+
end
62+
63+
def show_start_sprint_action?
64+
scrum_projects_active? && task_board.blank? && user_allowed?(:start_complete_sprint)
65+
end
66+
5167
def user_allowed?(permission)
5268
current_user.allowed_in_project?(permission, project)
5369
end

modules/backlogs/app/controllers/rb_taskboards_controller.rb

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,15 @@ class RbTaskboardsController < RbApplicationController
3333

3434
helper :taskboards
3535

36-
before_action :load_or_create_board, if: -> { OpenProject::FeatureDecisions.scrum_projects_active? }
36+
before_action :not_authorized_on_feature_flag_inactive, only: :create
3737

3838
def show
3939
if OpenProject::FeatureDecisions.scrum_projects_active?
40-
redirect_to project_work_package_board_path(@project, @board)
40+
@board = Boards::SprintTaskBoardCreateService.find(project: @project, sprint: @sprint)
41+
42+
return redirect_to(project_work_package_board_path(@project, @board)) if @board
43+
44+
render_404
4145
else
4246
@statuses = Type.find(Task.type).statuses
4347
@story_ids = @sprint.stories(@project).map(&:id)
@@ -47,17 +51,37 @@ def show
4751
end
4852
end
4953

50-
private
54+
def create
55+
existing_board = Boards::SprintTaskBoardCreateService.find(project: @project, sprint: @sprint)
56+
return redirect_to(project_work_package_board_path(@project, existing_board)) if existing_board
5157

52-
def load_or_create_board
5358
result = Boards::SprintTaskBoardCreateService
54-
.ensure(user: current_user, project: @project, sprint: @sprint, name: @sprint.board_name)
59+
.new(user: current_user)
60+
.call(project: @project, sprint: @sprint, name: @sprint.board_name)
5561

5662
if result.success?
57-
@board = result.result
63+
redirect_to project_work_package_board_path(@project, result.result)
5864
else
5965
flash[:error] = t(:error_task_board_creation_failed)
60-
return redirect_back_or_to(backlogs_project_backlogs_path(@project)) # rubocop:disable Style/RedundantReturn
66+
redirect_back_or_to(backlogs_project_backlogs_path(@project))
6167
end
6268
end
69+
70+
private
71+
72+
def load_sprint_and_project
73+
@project = Project.visible.find(params[:project_id])
74+
75+
return unless (@sprint_id = params.delete(:sprint_id))
76+
77+
@sprint = if OpenProject::FeatureDecisions.scrum_projects_active?
78+
Agile::Sprint.for_project(@project).visible.find(@sprint_id)
79+
else
80+
Sprint.visible.apply_to(@project).find(@sprint_id)
81+
end
82+
end
83+
84+
def not_authorized_on_feature_flag_inactive
85+
render_404 unless OpenProject::FeatureDecisions.scrum_projects_active?
86+
end
6387
end

modules/backlogs/config/locales/en.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ en:
131131
sprint_menu_component:
132132
label_actions: "Sprint actions"
133133
action_menu:
134+
start_sprint: "Start sprint"
134135
edit_sprint: "Edit sprint"
135136
new_story: "New story"
136137
stories_tasks: "Stories/Tasks"

modules/backlogs/config/routes.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# frozen_string_literal: true
2+
13
#-- copyright
24
# OpenProject is an open source project management software.
35
# Copyright (C) the OpenProject GmbH
@@ -59,7 +61,7 @@
5961
resources :sprints, controller: :rb_sprints, only: %i[update] do
6062
resource :query, controller: :rb_queries, only: :show
6163

62-
resource :taskboard, controller: :rb_taskboards, only: :show
64+
resource :taskboard, controller: :rb_taskboards, only: %i[show create]
6365

6466
resource :wiki, controller: :rb_wikis, only: %i[show edit]
6567

modules/backlogs/lib/open_project/backlogs/engine.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def self.settings
9595
dependencies: :view_sprints
9696

9797
permission :start_complete_sprint,
98-
{},
98+
{ rb_taskboards: :create },
9999
permissible_on: :project,
100100
require: :member,
101101
dependencies: %i[view_sprints manage_board_views],

modules/backlogs/spec/components/backlogs/sprint_menu_component_spec.rb

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,27 +36,35 @@
3636

3737
let(:project) { create(:project, types: [type_feature, type_task]) }
3838
let(:sprint) { create(:agile_sprint, project:, name: "Sprint 1", start_date: Date.yesterday, finish_date: Date.tomorrow) }
39-
let(:stories) { [] }
4039
let(:user) { create(:user) }
4140
let(:permissions) { [] }
41+
let(:board) { nil }
4242

4343
before do
4444
allow(Setting)
4545
.to receive(:plugin_openproject_backlogs)
4646
.and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s)
4747

48-
# Set up user with specific permissions
4948
create(:member,
5049
project:,
5150
principal: user,
5251
roles: [create(:project_role, permissions:)])
5352
login_as(user)
53+
54+
allow(Boards::SprintTaskBoardCreateService)
55+
.to receive(:find)
56+
.with(project:, sprint:)
57+
.and_return(board)
5458
end
5559

5660
def render_component
5761
render_inline(described_class.new(sprint:, project:, current_user: user))
5862
end
5963

64+
def menu_items
65+
page.all(:role, :menuitem).map { it.text.squish }
66+
end
67+
6068
describe "permission-based items" do
6169
context "with :manage_sprint_items permission" do
6270
let(:permissions) { %i[view_sprints manage_sprint_items] }
@@ -102,6 +110,53 @@ def render_component
102110
end
103111
end
104112

113+
describe "task board actions" do
114+
let(:permissions) { %i[view_sprints view_work_packages] }
115+
116+
context "with the feature flag inactive", with_flag: { scrum_projects: false } do
117+
it "shows Task board after Stories/Tasks" do
118+
render_component
119+
120+
expect(menu_items).to include("Stories/Tasks", "Task board")
121+
expect(menu_items.index("Task board")).to be > menu_items.index("Stories/Tasks")
122+
end
123+
end
124+
125+
context "with the feature flag active", with_flag: { scrum_projects: true } do
126+
context "when a board exists" do
127+
let(:board) { build_stubbed(:board_grid, project:) }
128+
129+
it "shows Task board after Stories/Tasks" do
130+
render_component
131+
132+
expect(menu_items).to include("Stories/Tasks", "Task board")
133+
expect(menu_items.index("Task board")).to be > menu_items.index("Stories/Tasks")
134+
end
135+
end
136+
137+
context "when no board exists and the user can start the sprint" do
138+
let(:permissions) { %i[view_sprints view_work_packages start_complete_sprint] }
139+
140+
it "shows Start sprint as the first item" do
141+
render_component
142+
143+
expect(menu_items.first).to eq("Start sprint")
144+
expect(page).to have_octicon(:play)
145+
expect(page).to have_no_selector(:menuitem, text: "Task board")
146+
end
147+
end
148+
149+
context "when no board exists and the user cannot start the sprint" do
150+
it "does not show task-board-related items" do
151+
render_component
152+
153+
expect(page).to have_no_selector(:menuitem, text: "Start sprint")
154+
expect(page).to have_no_selector(:menuitem, text: "Task board")
155+
end
156+
end
157+
end
158+
end
159+
105160
describe "always-visible items" do
106161
let(:permissions) { [:view_sprints] }
107162

0 commit comments

Comments
 (0)